DEV Community

Cover image for How to create a react app with Go support using WebAssembly in under 60 seconds
Roy Hadad
Roy Hadad

Posted on • Originally published at Medium

How to create a react app with Go support using WebAssembly in under 60 seconds

Run Go in the browser with react and Typescript using WebAssembly

TL;DR

$ npx create-react-app my-app --template typescript-golang
Enter fullscreen mode Exit fullscreen mode

Why create-react-app?

create-react-app allows us to quickly generate the boilerplate required to bootstrap a react application, providing a level of abstraction above the nitty-gritty infrastructure required to create a modern react app (Webpack, Babel, ESlint, etc.)

Why include Go?

Go is a statically typed, compiled programming language designed at Google, it is syntactically similar to C, but with memory safety, garbage collection, structural typing, and CSP-style concurrency.
In my case, I needed to run Go for JSON schema validations, in other cases, you might want to perform a CPU-intensive task or use a CLI tool written in Go.

But WebAssembly is not supported in all browsers!

I thought so too. In reality, since November 2017, WebAssembly is supported in all major browsers. So as long as you don’t need to support Internet Explorer, there is nothing to worry about.

Go logo

Let’s get down to business 😎

First, initialize a brand new create-react-app project, assuming you’re not an absolute savage, make sure to use the Typescript template 😇

$ npx create-react-app my-app --template typescript
Enter fullscreen mode Exit fullscreen mode

Next, create a folder under /src/LoadWasm

$ cd my-app
$ mkdir ./src/LoadWasm`
Enter fullscreen mode Exit fullscreen mode

create a file to extend the Window type declaration, we’ll use it soon.
/src/LoadWasm/wasmTypes.d.ts

declare global {
  export interface Window {
    Go: any;
    myGolangFunction: (num1: number, num2: number) => number
  }
}

export {};
Enter fullscreen mode Exit fullscreen mode

Copy a file used to load the WebAssembly code into the browser, it adds the Go property on the global window object and will act as a bridge between Javascript and WebAssembly.

This file is similar to the one from the official Go repository, with a few minor tweaks, we’ll use it in the next section.

$ curl https://raw.githubusercontent.com/royhadad/cra-template-typescript-golang/main/template/src/LoadWasm/wasm_exec.js > ./src/LoadWasm/wasm_exec.js`
Enter fullscreen mode Exit fullscreen mode

Next, we’ll create a wrapper component that will wrap our entire application and wait for WebAssembly to be loaded. This can be optimized for performance purposes, but it’s good enough for now, for simplicity’s sake.
/src/LoadWasm/index.tsx

import './wasm_exec.js';
import './wasmTypes.d.ts';

import React, { useEffect } from 'react';

async function loadWasm(): Promise<void> {
  const goWasm = new window.Go();
  const result = await WebAssembly.instantiateStreaming(fetch('main.wasm'), goWasm.importObject);
  goWasm.run(result.instance);
}

export const LoadWasm: React.FC<React.PropsWithChildren<{}>> = (props) => {
  const [isLoading, setIsLoading] = React.useState(true);

  useEffect(() => {
    loadWasm().then(() => {
      setIsLoading(false);
    });
  }, []);

  if (isLoading) {
    return (
      <div>
        loading WebAssembly...
      </div>
    );
  } else {
    return <React.Fragment>{props.children}</React.Fragment>;
  }
};
Enter fullscreen mode Exit fullscreen mode

Finally, we’ll wrap our entire application with the LoadWasm component, This will make sure no other components will load before WebAssembly is loaded
/src/index.tsx

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <LoadWasm>
      <App />
    </LoadWasm>
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

But wait, where is the Go code?

Start by initializing a Go module

$ mkdir ./wasm
$ cd ./wasm
$ go mod init wasm
$ go mod tidy
$ touch ./main.go
$ cd ..
Enter fullscreen mode Exit fullscreen mode

Now we’ll use the syscall/js package to access the javascript Global scope, and set a function on it.
Secondly, we’ll implement a little hack to keep the Go code from terminating: opening a channel and waiting for it to finish, without ever using it 😈
This will allow us to continuously communicate with the Go code without needing to re-instantiate it every time.

/wasm/main.go

package main

import (
   "syscall/js"
)

func myGolangFunction() js.Func {
   return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
      return args[0].Int() + args[1].Int()
   })
}

func main() {
   ch := make(chan struct{}, 0)
   js.Global().Set("myGolangFunction", myGolangFunction())
   <-ch
}
Enter fullscreen mode Exit fullscreen mode

Note: you may need to configure your IDE to support WebAssembly, see VS Code guide, Intellij guide

Now we can add this button somewhere in the application, perhaps in App.tsx

<button onClick={() => { alert(window.myGolangFunction(2, 3)); }}>
  Click here to invoke WebAssembly!
</button>
Enter fullscreen mode Exit fullscreen mode

Putting it all together

Finally, we’ll alter the package.json scripts to support build and hot-reload for WebAssembly.

build:

"build": "npm run build:wasm && npm run build:ts",
"build:ts": "react-scripts build",
"build:wasm": "cd wasm && GOOS=js GOARCH=wasm go build -o ../public/main.wasm && cd .. && echo \"compiled wasm successfully!\""
Enter fullscreen mode Exit fullscreen mode

hot reload

We’ll need a few dependencies

$ npm install watch concurrently --save-dev
Enter fullscreen mode Exit fullscreen mode

And use them in the start script

"start": "concurrently \"npm run watch:ts\" \"npm run watch:wasm\"",
"watch:ts": "react-scripts start",
"watch:wasm": "watch \"npm run build:wasm\" ./wasm",
Enter fullscreen mode Exit fullscreen mode

Finally, we’ll run npm start and access the app on localhost:3000
A full example can be found in this GitHub repository

Didn’t you say it’ll take 60 seconds? Liar!

Pinocchio

Ok, maybe this does take a while, but fear no more! I have a cure for your laziness!
I have created a custom create-react-app template for typescript-golang, all you have to do is run the following command in your working directory

$ npx create-react-app my-app --template typescript-golang
Enter fullscreen mode Exit fullscreen mode

And… boom! A working react app with Typescript & Go support, you can get right into coding 🥳

Feel free to follow & connect via Github & Linkedin

Latest comments (0)