Building an Application
Welcome to the final article in our little series, congratulations, you’ve made it this far!
So far we’ve looked at a lot of little pieces which would eventually make an application and it’s time to tackle that, it’s time to build a web application.
Setting up our Application
To get started we’ll use
create-react-app with TypeScript, I won’t go over doing that setup, the React documentation does a good job for me. You don’t have to use
create-react-app, it’s just a really easy way to bootstrap, but if you’re confident without it, by all means skip this step.
Once you’re created an application though we’ll need to eject
create-react-app because we need to be able to modify the
webpack.config.js file, which can only be done if you eject
Getting all WASM-y
We’ll start by adding the loader created in the last post using
Then editing the
configs/webpack.config.js file to add our loader (follow the instructions in the file for where to put it):
Adding our WASM
I’m going to make a little application that shows at least 2 number input fields and adds all the values together to get a sum, to Go code for it will look like this:
Pretty basic, we use
range to go over the spread of
js.Value, convert each one from a string to a number, sum them up and return boxed in
Next up in our input field, I’ve created a file
NumberInput.tsx for that:
It’s a stateless component that receives two properties, a value for the input field and the callback to execute on change of the input field.
Lastly we’ll make our
Ok, pretty basic, it’s component with state (sorry, no redux or hooks here 😝) where state contains an array of input values and the current sum. The
render will loop over the input values, create our
<NumberInput /> component with the value and give it a function that will call
updateValue when done. State it initialised to have 2 inputs, but you can add more with a button shown on screen.
At the top of the file you’ll see that we’re importing the
main.go file from above and using destructing assignment to get out the
add function, or more accurately, a reference to it from the
Proxy the loader creates for us.
Now it’s time to complete our
updateValue method. But it turns out that using the
add function could be a bit tricky. Sure we can define it as an
any property of the WASM, but what if we wanted to be more intelligent in the way it is represented?
Using Types with our Proxy
How do we make sure that TypeScript knows what type our arguments are that are to be passed into a function that, well, doesn’t exist? Ultimately we want to get away from an
any, instead we want to use TypeScript generics!
We can do this in one of two ways, the first is we just create a definition file that creates an explicit interface for our WASM import:
I’ve created a file called
definitions.d.ts that sits alongside the
App.tsx file, and by declaring the module for
*.go it means that this declaration file works for any imports of Go files. We can also drop the generic arguments, which is nice, but it is a problem it we want to start adding more Go functions, we keep having to edit this file to include them.
So how about going crazy with generic!
Now, stick with me as we break it down:
- We’re saying we have keys of the type (
GoWrapper) that are strings with
- Each key has a type that takes two generic arguments, an input and an output, that’s
<T = any, R = any>
- These go into a function with
paramsarray, denoted by
- The return type is a
Promiseusing the specified return type,
So when we do
add<number, string> it says that were passing in an indeterminate number of arguments that are all numbers and it’ll return a string asynchronously.
This forced type flow down from our state and back, all through the magic of TypeScript types!
If you were working with mixed types in the arguments to the function we could do something like:
| tells TypeScript that the arguments into the function are a string or number type, but not function, boolean, etc.. Pretty crazy right!
Deploying our Application
We’re done! It works locally! Now it’s time to deploy it somewhere.
I’m going to use Azure DevOps Pipelines to build and then deploy it as an Azure Blob Static Website.
To build you’ll need to run the following steps:
- Install our Go dependencies
- Install our npm packages
- Run webpack
- Copy the required files as a build artifact
I’ve created an Azure DevOps YAML build that is in the GitHub repo. It’s modeled on the standard Node.js pipeline but I’ve added the specific Go steps.
The things of note are that you’ll need to install the appropriate Go packages with
go get. To use the
gobridge I created for the loader you’ll need to set the
You’ll also need to make sure that
GOROOT are environment variables available to the loader. By default these aren’t set as environment variables in the agent, I just did it inline:
Alternatively, you can create them for all tasks:
Here’s a completed build! (ignore all the failed ones before it 😆)
At the time of writing we don’t have support for releases in the YAML file for Azure DevOps Pipelines. I use the Azure File Copy task to copy all the files into the storage account I’m running in, followed by the Azure CLI task to set the WASM content type on the WASM file, otherwise it won’t be served correctly:
Remember to change
hello.wasm to whatever your filename is! 😉
Here’s a completed release!
And we are done folks! Starting with no idea what WebAssembly is or how to write Go we’ve gone through a bunch of exploration into how it all works, what makes Go’s approach to WebAssembly a little tricky as a web developer and ultimately how we can introduce Go into the tool chain that we are familiar with these days building web applications.
I do hope you’ve enjoyed this series as we’ve gone along. If you build anything exciting with Go and WASM please let me know!