cover image
Web

Integrate Go Library into a JavaScript Webpage with WebAssembly

With WebAssembly you can integrate different programming languages together. This opens up new possibilities to use great libraries written in one language in another different platform. In this article I want to show how to integrate a Go library for sentence tokenization in a JavaScript webpage. Normally, you cannot run Go code in a web browser, but with the WebAssembly technology you can.

WebAssembly

WebAssembly is a W3C specification that defines a binary and text format for executable programs that can run in the browser but also stand-alone. WebAssembly programs run in a stack-based virtual machine (VM). Taking its name from the programming language of Assembly, which is very close to the actual hardware, WebAssembly defines a more device-independent language than Assembly, but is still very low-level.

As of the time of writing this article, WebAssembly is supported by 97.11 % of all global browsers (caniuse.com). There exist various extensions to the W3C WebAssembly specification which have different levels of support among browsers.

Sentences Go Library

Sentences is a Go library for sentence tokenization written by Eric Bower and published as open source on GitHub. Since you cannot run Go code in the browser natively, only JavaScript, there is the idea of using WebAssembly to integrate the library into a website.

Expose Sentences Library to JavaScript

You can integrate the sentences library by writing a Go wrapper application that leverages the syscall/js package of Go. It is part of the Go standard library. Note, that at the time of writing this article, this package is tagged as "experimental".

Specifically, we will be using the js.FuncOf function. Here is the full source code of the Go application:

package main

import (
	"fmt"
	"strings"
	"syscall/js"

	"github.com/neurosnap/sentences/english"
)

func sentenceWrapper() js.Func {
	sentenceFunc := js.FuncOf(func(this js.Value, args []js.Value) any {
		if len(args) != 1 {
			return "Invalid no of arguments passed"
		}
		inputSentence := args[0].String()
		fmt.Printf("input %s\n", inputSentence)
		tokenizer, err := english.NewSentenceTokenizer(nil)
		if err != nil {
			panic(err)
		}

		sentences := tokenizer.Tokenize(inputSentence)
		for _, s := range sentences {
			fmt.Println(s.Text)
		}

		var sentenceTexts []string
		for _, s := range sentences {
			trimmedText := strings.TrimSpace(s.Text)
			sentenceTexts = append(sentenceTexts, trimmedText)
		}

		allSentences := strings.Join(sentenceTexts, "\n") // Join all sentences separated by a space
		return allSentences

	})
	return sentenceFunc
}

func main() {
	fmt.Println("Go Web Assembly")
	js.Global().Set("tokenizeSentence", sentenceWrapper())
	<-make(chan struct{})
}

You see that now the sentenceWrapper function is exposed as the tokenizeSentence function in JavaScript. It expects as argument a string to tokenize and returns the tokenized string. The actual integration with the sentences library happens inside the function passed to js.FuncOf.

Compile Wrapper

You compile the application to WebAssembly using the following Go build command:

GOOS=js GOARCH=wasm go build  -o main.wasm

If everything went fine, you should find the main.wasm file as output in your folder. This is the WebAssembly binary that will be loaded into the JavaScript application.

Integrate WebAssembly Go Library in Website

In order to load the main.wasm file into the webpage, you need the following code:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <script src="wasm_exec.js"></script>
    <script>
      const go = new Go();
      WebAssembly.instantiateStreaming(
        fetch("./main.wasm"),
        go.importObject
      ).then((result) => {
        go.run(result.instance);
      });
    </script>
  </head>
  <body>
    <textarea id="sentenceinput" name="jsoninput" cols="80" rows="20"></textarea>
    <input
      id="button"
      type="submit"
      name="button"
      value="tokenize"
      onclick="tokenize(sentenceinput.value)"
    />
    <textarea id="jsonoutput" name="jsonoutput" cols="80" rows="20"></textarea>
  </body>
  <script>
    var tokenize = function (input) {
      jsonoutput.value = tokenizeSentence(input);
    };
  </script>
</html>

When running this webpage in the browser, you also need the utility JavaScript file wasm_exec.js in the current directory. You can get the file from:

cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .

As you can see from the JavaScript code, the exposed tokenizeSentence() function is called from JavaScript when clicking the "tokenize" button. The contents of the input field is used as argument to the function and the return value is then written in the text field to the right.

Note that you need to run a web server in order to run the webpage in your browser. You can use this code to build a webserver in Go:

server/server.go

package main

import (
	"fmt"
	"net/http"
)

func main() {
	err := http.ListenAndServe(":9090", http.FileServer(http.Dir("../")))
	if err != nil {
		fmt.Println("Failed to start server", err)
		return
	}
}

After starting the web server, you can access the page in your browser under: http://localhost:9090.

Thanks to Naveen Ramanathan from golangbot.com for his inspiration to this article.

Conclusion

As always, I hope this article helped you in some way to stay curious. WebAssembly is a technology that has great potential to integrate the many different language eco-systems between each other.

References

Photo by Venti Views on Unsplash

Published

29 Jun 2024

Thomas Derflinger

Written by Thomas Derflinger

I am a visionary entrepreneur and software developer. In this blog I mainly write about web programming and related topics like IoT.