hjr265.me / blog /

Embedding a JavaScript Library (KaTeX) in Go

KaTeX is the fan-favourite way of doing math and equations on the web. The library is simple to use and easy to reason about. But it is JavaScript. How do you build a Go program that renders math and equations like KaTeX?

You embed KaTeX in your Go program.

Several Go packages allow you to run JavaScript from within Go. In this post, we will use github.com/lithdew/quickjs.

Let us first fetch the latest copy of katex.min.js from CDNJS. KaTeX uses the MIT license; make sure to include that in your project.

Next, in your Go program, embed katex.min.js:

1
2
3
4
5
6
7
8
package katex

import (
  _ "embed"
)

//go:embed katex.min.js
var code string

And add a Render function that:

  • Evaluates the embedded katex.min.js code
  • Sets the LaTeX string as a global variable.
  • Calls katex.renderToString on the variable with the LaTeX string.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func Render(src []byte, display bool) (string, error) {
  // Force QuickJS to run on the same thread.
  runtime.LockOSThread()
  defer runtime.UnlockOSThread()

  // Create a new QuickJS runtime. Free it after use.
  runtime := quickjs.NewRuntime()
  defer runtime.Free()

  // Create a new QuickJS context. Free it after use.
  context := runtime.NewContext()
  defer context.Free()

  globals := context.Globals()

  // Evaluate the katex.min.js code.
  result, err := context.Eval(code)
  if err != nil {
    return "", err
  }
  defer result.Free()

  // Set the LaTeX string to a global variable and call katex.renderToString on it.
  globals.Set("latexSrc", context.String(string(src)))
  if display {
    result, err = context.Eval("katex.renderToString(latexSrc, { displayMode: true })")
  } else {
    result, err = context.Eval("katex.renderToString(latexSrc)")
  }
  if err != nil {
    return "", err
  }
  defer result.Free()

  // Return the rendered equation.
  return result.String(), nil
}

The above code gives you the most straightforward implementation. You may want to rewrite it in a way where you evaluate katex.min.js only once and keep using the same context for every invocation of Render.

Using JavaScript libraries from within Go made packages like goldmark-katex possible. Of course, for convenience, you are trading a bit of performance (because you are evaluating JavaScript from within Go). But sometimes, that convenience makes it worth it. For goldmark-katex, it was either this or building a KaTeX clone in Go.


This post is 9th of my #100DaysToOffload challenge. Want to get involved? Find out more at 100daystooffload.com.


comments powered by Disqus