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
-
WebAssembly.org: https://webassembly.org
-
WebAssembly specification: https://github.com/WebAssembly/spec
-
Go sentences library: https://github.com/neurosnap/sentences
-
Syscall/js package: https://pkg.go.dev/syscall/js
-
Golangbot Webassembly using Go: https://golangbot.com/webassembly-using-go
Photo by Venti Views on Unsplash
Published
29 Jun 2024