Building Web Apps with React, WebAssembly, and Go
Akshay S
Posted on February 6, 2024
Introduction
Hey folks! Welcome to this blog where we'll delve into the fascinating world of WebAssembly with React.js and Go Lang. But before we jump in, let's quickly demystify WebAssembly—what it is, why it's beneficial, where it finds its applications and how its work.
What is WebAssembly (WASM)?
WebAssembly is like the superhero of web development! It's a special kind of code that can run in modern web browsers. Think of it as a low-level, assembly-like language with a compact binary format. What makes it super cool is its ability to run with almost native performance. It's a fantastic compilation target for languages like C/C++, C#, and Rust, enabling them to work seamlessly on the web. Plus, it plays well with JavaScript, allowing them to team up for powerful web applications.
Why is WebAssembly Useful?
Imagine having the speed and efficiency of low-level languages directly in your web browser. WebAssembly makes this possible! It brings high-performance capabilities to web applications, allowing them to handle complex tasks smoothly and swiftly.
Use Cases of WebAssembly:
-
Performance-Intensive Tasks:
- Ideal for tasks requiring heavy computation, like video editing or 3D graphics rendering.
-
Parallel Processing:
- Well-suited for parallel processing, enhancing overall application speed.
How Web Assembly Works
When you visit a website, your browser has two cool engines working behind the scenes – the JavaScript (JS) engine and the WebAssembly (Wasm) engine. Let's break down what happens with each.
JavaScript Engine Magic:
-
Parsing the Code:
- The JS engine starts by looking at the code you wrote on a website.
- It checks the code's grammar and structure to make sure it's correct.
-
Abstract Syntax Tree (AST):
- If all is good, the engine creates an AST, which is like a tree structure representing your code.
-
Intermediate Representation (IR):
- The AST becomes bytecode, a kind of middle-ground language that's easier for the computer to understand.
-
Machine Code Compilation:
- Finally, the engine compiles this bytecode into machine code that your computer's processor can run.
Wasm Engine Magic:
Now, let's see how WebAssembly brings its own magic to the party.
-
Writing Code with Types:
- With Wasm, you write your code using a statically-typed language, meaning you declare types in advance.
-
Pre-compiled Wasm Module:
- You generate a pre-compiled Wasm module. Think of it like packaging your code in a neat box.
-
Skipping Parsing and IR:
- Because Wasm code already has types, it skips the parsing and AST to IR steps that JS goes through.
-
Direct Compilation:
- The Wasm engine can then directly compile your neatly packaged code into machine code, saving time and making it super fast.
Why Wasm is Speedy:
-
Type Declaration:
- JS checks variable types while running code (dynamic typing), but Wasm knows types in advance (static typing), making it faster.
-
Skip the Line:
- Wasm skips some steps because it's pre-compiled and types are declared, giving it a shortcut to performance.
In a nutshell, Wasm takes a faster route to get your code running in the browser, making web applications snappier and more efficient! 🚀
Let's Get Our Hands Dirty
Alright, let's dive into the practical side of things! Make sure you have Node.js (latest version), Go Lang (version 1.21), and a basic understanding of both Go and React.js. If you're all set, here's a quick rundown of what we'll be doing:
Step 1: Set Up React Project with Vite
-
Install Vite globally (if not already installed):
npm install -g create-vite
-
Create a new React project:
create-vite my-react-wasm-app --template react
-
Navigate to the project:
cd my-react-wasm-app
go mod init my-react-wasm-app
Step 2: Set Up Go Module
-
After creating your React project, let's set up the Go module. Navigate to the project root and create a new folder named
go
.mkdir go
-
Move into the
go
folder:cd go
-
Initialize a new Go module in this directory:
go mod init my-react-wasm-app/go
Now, your Go module is all set up within the go
folder.
Step 3: Write Go Code
- Inside the
go
folder, create a new Go file, for example,main.go
. - Write a simple Go function in
main.go
. For instance, let's create a function that calculate Fibonacci sum:
package main
import (
"syscall/js"
)
func main() {
// Create a channel to keep the Go program alive
done := make(chan struct{}, 0)
// Expose the Go function `fibonacciSum` to JavaScript
js.Global().Set("wasmFibonacciSum", js.FuncOf(fibonacciSum))
// Block the program from exiting
<-done
}
// Define the Fibonacci sum function that will be exposed to JavaScript
func fibonacciSum(this js.Value, p []js.Value) interface{} {
// Retrieve the value of the argument passed from JavaScript
n := p[0].Int()
// Initialize variables for the Fibonacci sequence
a, b, totalSum := 0, 1, 0
// Calculate the Fibonacci sum up to the specified number
for i := 0; i < n; i++ {
totalSum += a
a, b = b, a+b
}
// Return the calculated Fibonacci sum as a JavaScript value
return js.ValueOf(totalSum)
}
Step 4: Convert Go Code to Wasm
- Build the Go code to generate WebAssembly:
This command will create a main.wasm file containing the compiled WebAssembly code.
GOARCH=wasm GOOS=js go build -o main.wasm
- Copy the
wasm_exec.js
file to your project directory:
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
Explanation:
The wasm_exec.js
file is a support file provided by the Go standard library. It contains necessary functions to enable communication between Go and JavaScript in a WebAssembly environment. The cp
command copies this file from the Go installation directory ($(go env GOROOT)/misc/wasm/
) to your current project directory. Including this file in your project is essential for proper interaction between the Go code compiled to WebAssembly and the surrounding JavaScript environment.
Step 5: Use Wasm File in React.js
- After generating the required wasm file (
main.wasm
), move the file to the public folder of your React.js application. This folder is typically located at the root level of your project. - Move the
wasm_exec.js
file to thesrc
folder of your React.js application. This folder is where your source code resides. -
In your React component (
App.tsx
), import the necessary dependencies:import { useEffect, useState } from 'react'; import './wasm_exec.js'; import './wasmTypes.d.ts';
-
Define a WebAssembly function wrapper:
function wasmFibonacciSum(n: number) { return new Promise<number>((resolve) => { const res = window.wasmFibonacciSum(n); resolve(res); }); }
Step 5: Finalize App.tsx
Here is the final version of App.tsx
:
import { useEffect, useState } from 'react';
import './wasm_exec.js'; // Import the wasm_exec.js file for Go-Wasm compatibility
import './wasmTypes.d.ts'; // Import the TypeScript declarations for WebAssembly
// Function to wrap the wasmFibonacciSum function for asynchronous handling
function wasmFibonacciSum(n: number) {
return new Promise<number>((resolve) => {
// Call the wasmFibonacciSum function from Go
const res = window.wasmFibonacciSum(n);
resolve(res);
});
}
const App = () => {
const [isWasmLoaded, setIsWasmLoaded] = useState(false);
const [wasmResult, setWasmResult] = useState<number | null>(null);
// useEffect hook to load WebAssembly when the component mounts
useEffect(() => {
// Function to asynchronously load WebAssembly
async function loadWasm(): Promise<void> {
// Create a new Go object
const goWasm = new window.Go();
const result = await WebAssembly.instantiateStreaming(
// Fetch and instantiate the main.wasm file
fetch('main.wasm'),
// Provide the import object to Go for communication with JavaScript
goWasm.importObject
);
// Run the Go program with the WebAssembly instance
goWasm.run(result.instance);
setIsWasmLoaded(true);
}
loadWasm();
}, []);
// Function to handle button click and initiate WebAssembly calculation
const handleClickButton = async () => {
const n = 10; // Choose a value for n
console.log('Starting WebAssembly calculation...');
const wasmStartTime = performance.now();
try {
// Call the wasmFibonacciSum function asynchronously
const result = await wasmFibonacciSum(n);
setWasmResult(result);
console.log('WebAssembly Result:', result);
} catch (error) {
console.error('WebAssembly Error:', error);
}
const wasmEndTime = performance.now();
console.log(`WebAssembly Calculation Time: ${wasmEndTime - wasmStartTime} ms`);
};
// JSX markup for the React component
return (
<div>
{isWasmLoaded && <p>Wasm Loaded</p>}
{!isWasmLoaded && <p>Wasm not Loaded</p>}
<button onClick={handleClickButton}>Handle Click Wasm</button>
{wasmResult !== null && (
<div>
<p>WebAssembly Result: {wasmResult}</p>
</div>
)}
</div>
);
};
export default App;
Explanation:
- The final
App.tsx
integrates WebAssembly seamlessly into a React application. - It loads the WebAssembly module when the component mounts, displays a message indicating if WebAssembly is loaded or not, and triggers a WebAssembly calculation when the button is clicked.
- The result of the calculation is displayed below the button.
Feel free to customize the values and calculations according to your specific use case. Your React application is now fully equipped to leverage the power of WebAssembly! 🚀
Please check the complete code repository at: https://github.com/akshays-repo/wasm-react
Some Useful Blogs and Resources
Learn techniques to optimize the size of Go WebAssembly binaries for improved web application performance : https://dev.bitolog.com/minimizing-go-webassembly-binary-size/
Explore how Figma utilized WebAssembly to reduce load times significantly, providing insights into real-world performance improvements: https://www.figma.com/blog/webassembly-cut-figmas-load-time-by-3x/
Posted on February 6, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.