React with web assembly setup without CRA
Dhairya Nadapara
Posted on September 1, 2021
Hey everyone, I was curious about the wasm from the last few years because of rust-lang. I have started to learn rust for a long time and I was looking for using it somewhere for learning purposes. As I have most of the work in javascript and react, I was looking if I can do something in the same domain.
I searched about the use-cases and found out that it can be used as a web binary. I have tried some blogs but usually found it with vanilla js or react using rewire to update the webpack. I want to do something basic with a custom setup.
I have tried to create the steps to create the custom setup for React + Wasm using webpack. Hope it will help you. Happy Coding.
Source code: https://github.com/dhairyanadapara/react-wasm-boilerplate
Directory setup
Let's first start with basic thing which are required. We will create the directory and setup version control and JS package manager.
Create new dir
mkdir react-wasn-tutorial && cd react-wasn-tutorial
Init npm
I have used npm as package manager
npm init
Init git
I have used git for version control.
git init
React and Webpack Setup
Now our directory is setup is completed with package manager and version control. Let's start with React setup first and then we will move to Webpack. We will add basic dependencies for react and webpack
Install react dependencies
npm install react react-dom --save
Setup HTML boilerplate
Create public
directory in root and create index.html
inside. It should have one div with "root" id as default id for react root. If you want you can have other name but you will have to use same name in react root.
<!DOCTYPE html>
<html>
<head>
<title>My React Configuration Setup</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
Create root component
If you have used CRA you might have know that all the files and components are written inside src
directory. We will do the same. Create src directory and create out root file index.jsx
mkdir src && cd src && touch index.js
Create react component
Create react component in root file
import React from 'react';
import ReactDOM from 'react-dom';
class Welcome extends React.Component {
render() {
return <h1>Hello World from React boilerplate</h1>;
}
}
ReactDOM.render(<Welcome />, document.getElementById('root'));
Configure webpack 5
Now we will setup the webpack to create build and run the application. First we will install dependencies for webpack and babel.
npm install --save-dev webpack webpack-dev-server webpack-cli
npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader @babel/runtime @babel/plugin-transform-runtime
Create webpack.config.js
Create webpack.config.json
. We will add few configuration in file.
- entry - It's the entry point to JS files for building the build.
- output - It the output directory for build and build name
- devServer - settings for running dev server
- modules - rules for transcompiling the JS to ES2015 for browser compatibility
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'build'),
publicPath: '/',
filename: 'bundle.js',
},
devServer: {
static: './build',
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader'],
},
],
},
};
Create .babelrc
Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments.
Create configuration for babel in root directory
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": [["@babel/transform-runtime"]]
}
Update package.json
script to run the project
Add script for running the webpack with npm script
"scripts": {
"start": "webpack serve --mode development --hot",
}
Add eslint and prettier dependencies
Install and Configure Prettier
npm install --save-dev --save-exact prettier
Create .prettierrc
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5"
}
Add script in package.json
"scripts": {
"format": "prettier --write \"src/**/*.js\""
},
Add source map for debugging
// webpack.config.js
module.exports = {
devtool: 'inline-source-map',
// … the rest of the config
};
Setting ESLint
npm --save-dev install eslint eslint-loader babel-eslint eslint-config-react eslint-plugin-react
Update webpack
module.exports = {
// modify the module
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader', 'eslint-loader'], // include eslint-loader
},
],
},
};
Create .eslintrc
{
"parser": "babel-eslint",
"extends": "react",
"env": {
"browser": true,
"node": true
},
"settings": {
"react": {
"version": "detect"
}
},
"rules": {
"space-before-function-paren": ["off", "always"]
}
}
Update package.json
scripts
"scripts": {
"eslint-fix": "eslint --fix \"src/**/*.js\"",
"build": "webpack --mode production",
"watch": "webpack --watch --mode development",
},
Add html-webpack-plugin
npm install html-webpack-plugin --save-dev
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: //…
output: {
//…
},
devServer: {
static: "./build",
},
module: {
//…
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve('./public/index.html'),
}),
]
};
Configure css
npm install --save-dev css-loader style-loader
Update webpack configuration
module.exports = {
...
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: [/node_modules/, /build/],
use: ['babel-loader', 'eslint-loader']
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
},
...
};
create and import css to file
touch src/main.css
body {
background: red;
}
import React from 'react';
import ReactDOM from 'react-dom';
import './main.css';
...
Run build
npm run build
For hot reloading run 2 command in different terminals
npm run start
npm watch
Create Rust library
cargo new --lib wasm-lib --vcs none --edition 2018
cd wasm-lib
You will find some tests in lib.rs
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
Let remove the test code and write some code.
First import the wasm-bindgen
. It's required for communication between rust and JS
use wasm_bindgen::prelude::*;
Now we will try to execute the JS alert
from rust library. extern
statement tells Rust that we want to call some externally defined functions.
Add public function named greet
, which is exposed to Javascript. Add alert with Hello world
string.
#[wasm_bindgen]
extern {
pub fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet(name: &str) {
alert(&format!("Hello, {}!", name));
}
We have written the code but adding dependencies to Cargo.toml is still required. Update the Cargo.toml with required keys
[package]
name = "wasm-lib"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
description = "A sample project with wasm-pack"
license = "MIT/Apache-2.0"
repository = "https://github.com/yourgithubusername/wasm-lib"
edition = "2018"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
For more info you can refer this article
https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_wasm
Build the package
wasm-pack build --target bundler --out-dir ../build
Add the command to package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "webpack --watch --mode development",
"start": "webpack serve --mode development --hot",
"format": "prettier --write \"src/**/*.js\"",
"eslint-fix": "eslint --fix \"src/**/*.js\"",
"build": "webpack --mode production",
"build:wasm": "cd wasm-lib && wasm-pack build --target bundler --out-dir ../node_modules"
},
Import and use wasm package
import React from 'react';
import * as wasm from 'wasm_lib';
class Welcome extends React.Component {
componentDidMount() {
wasm.greet('Hello World');
}
render() {
return (
<div className="container">
<h1 className="test">Hello World from React boilerplate</h1>
<h2 className="test1">Dhairya Nadapara</h2>
</div>
);
}
}
export default Welcome;
Enable experimental features in webpack
module.exports = {
...
experiments: {
executeModule: true,
outputModule: true,
syncWebAssembly: true,
topLevelAwait: true,
asyncWebAssembly: true,
layers: true,
lazyCompilation: true
}
};
Restart the server. Popup will be shown on load
To run the app execute:
1. npm run build:wasm(In case you want to build lib again)
2. npm run watch
3. npm run start
Note:
This is not the perfect setup for the production app. There are many changes required. I will try to improve this overtime and will update you with new post :)
Reference:
Posted on September 1, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.