Back to blog

Wrapping Node Modules With GopherJS

2017-08-05

GopherJS lets you write Go code for the front end, but it also gives you access to the entire NPM registry.

When you call gopherjs build ., the compiler will look for files in the same directory that end with .inc.js and simply wrap that file in a function and call it at runtime after your func main().

Say you have the following two files in a directory:

1// main.go
2package main
3
4import "fmt"
5
6func main() {
7    fmt.Println("hello")
8}
1// bye.inc.js
2console.log('good bye');

If you run gopherjs build . you will get <dirname>.js along with its source maps. If you run node <dirname>.js then you will see the following output:

$ node gophertest.js
hello
good bye

Okay, we're half way there! Sorta.

A node module out of the box will not work this way. The reason is that a node module has browser-incompatible syntax, specifically imports/exports.

Therefore, you will need a tool such as Browserify or Webpack to take a Node.js module and turn it into a browser-friendly script. In this case, I will use Webpack.

Folder Structure

Let's create our mini app in a directory in your $GOPATH/src called gophermodules This app will install the isPrimitive npm module and run it in the browser.

First, install the npm library in that same directory:

npm i is-primitive

Now let's create a global link ot the npm module that the browser can access.

1// libs.js
2window.isPrimitive = require('is-primitive');

Notice the name of this file is libs.js and not libs.inc.js because if this script ran on the browser it wouldn't know how to deal with require('is-primitive'). Therefore, we will use Webpack to bundle libs.js into libs.inc.js

Add a webpack.config.js file in the same directory:

 1const path = require('path');
 2
 3module.exports = {
 4    entry: [
 5        './libs.js',
 6    ],
 7    output: {
 8        path: path.resolve(__dirname),
 9        filename: 'libs.inc.js',
10    },
11};

Webpack has a very large ecosystem for optimizations, so feel free to go nuts here.

Run webpack -- of course if you don't have it: npm i -g webpack

And voila! Your newly installed npm library is now accessible globally. All you need to do is write a Go wrapper package. Let's create one.

Create a new directory in gophermodules called is-primitive and add the following file

1package isprimitive
2
3func IsPrimitive(val interface{}) bool {
4    return js.Global.Call("isPrimitive", val).Bool()
5}

Now let's go back to our main.go function and use it!

 1package main
 2
 3import (
 4    "fmt"
 5
 6    "gophermodules/is-primitive"
 7)
 8
 9func main() {
10    fmt.Println(
11        isprimitive.IsPrimitive(3),
12        isprimitive.IsPrimitive(map[string]string{}),
13    )
14}

Run gopherjs build .

Include gophermodules.js into your index.html -- I'll assume you know how to do this one.

Now run the file in your browser and you should see the following output:

true
false

And there! We just installed an npm library, wrapped it with Go bindings, bundled it with our Go code, and called it on the browser!

Here's what your folder strucute should look like:

└── gophertest
    ├── gophertest.js
    ├── gophertest.js.map
    ├── index.html
    ├── is-primitive
    │   └── is-primitive.go
    ├── libs.inc.js
    ├── libs.js
    ├── main.go
    ├── node_modules
    │   └── is-primitive
    │       ├── LICENSE
    │       ├── README.md
    │       ├── index.js
    │       └── package.json
    └── webpack.config.js

Things I like:

  1. Access to the entire npm registry.
  2. Wrapping npm modules with Go, so you can use them in Go idiomatically and with defined types.

Things I don't like:

  1. Access to the entire npm registry.
  2. Defining your npm modules globally on window.
  3. Your build process just went from gopherjs build . to...well, JavaScript land.