DEV Community

Cover image for React with web assembly setup without CRA
Dhairya Nadapara
Dhairya Nadapara

Posted on • Updated on

React with web assembly setup without CRA

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
Enter fullscreen mode Exit fullscreen mode

Init npm

I have used npm as package manager

npm init
Enter fullscreen mode Exit fullscreen mode

Init git

I have used git for version control.

git init
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'));
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'],
      },
    ],
  },
};
Enter fullscreen mode Exit fullscreen mode

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"]]
}
Enter fullscreen mode Exit fullscreen mode

Update package.json script to run the project

Add script for running the webpack with npm script

"scripts": {
    "start": "webpack serve --mode development --hot",
}
Enter fullscreen mode Exit fullscreen mode

Add eslint and prettier dependencies

Install and Configure Prettier

npm install --save-dev --save-exact prettier
Enter fullscreen mode Exit fullscreen mode

Create .prettierrc

{
  "semi": true,
  "singleQuote": true,
  "trailingComma": "es5"
}
Enter fullscreen mode Exit fullscreen mode

Add script in package.json

"scripts": {
 "format": "prettier --write \"src/**/*.js\""
},
Enter fullscreen mode Exit fullscreen mode

Add source map for debugging

// webpack.config.js
module.exports = {
  devtool: 'inline-source-map',
  // … the rest of the config
};
Enter fullscreen mode Exit fullscreen mode

Setting ESLint

npm --save-dev install eslint eslint-loader babel-eslint eslint-config-react eslint-plugin-react

Enter fullscreen mode Exit fullscreen mode

Update webpack

module.exports = {
  // modify the module
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ['babel-loader', 'eslint-loader'], // include eslint-loader
      },
    ],
  },
};
Enter fullscreen mode Exit fullscreen mode

Create .eslintrc

{
  "parser": "babel-eslint",
  "extends": "react",
  "env": {
    "browser": true,
    "node": true
  },
  "settings": {
    "react": {
      "version": "detect"
    }
  },
  "rules": {
    "space-before-function-paren": ["off", "always"]
  }
}
Enter fullscreen mode Exit fullscreen mode

Update package.json scripts

"scripts": {
  "eslint-fix": "eslint --fix \"src/**/*.js\"",
  "build": "webpack --mode production",
  "watch": "webpack --watch --mode development",
},
Enter fullscreen mode Exit fullscreen mode

Add html-webpack-plugin

npm install html-webpack-plugin --save-dev
Enter fullscreen mode Exit fullscreen mode
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'),
    }),
  ]
};
Enter fullscreen mode Exit fullscreen mode

Configure css

npm install --save-dev css-loader style-loader
Enter fullscreen mode Exit fullscreen mode

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'
                ]
            }
        ]
    },
    ...
};
Enter fullscreen mode Exit fullscreen mode

create and import css to file

touch src/main.css
Enter fullscreen mode Exit fullscreen mode
body {
    background: red;
}
Enter fullscreen mode Exit fullscreen mode
import React from 'react';
import ReactDOM from 'react-dom';
import './main.css';

...
Enter fullscreen mode Exit fullscreen mode

Run build

npm run build
Enter fullscreen mode Exit fullscreen mode

For hot reloading run 2 command in different terminals

npm run start
Enter fullscreen mode Exit fullscreen mode
npm watch
Enter fullscreen mode Exit fullscreen mode

Create Rust library

cargo new --lib wasm-lib --vcs none --edition 2018
cd wasm-lib
Enter fullscreen mode Exit fullscreen mode

You will find some tests in lib.rs

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}
Enter fullscreen mode Exit fullscreen mode

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::*;
Enter fullscreen mode Exit fullscreen mode

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));
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
  },
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Enable experimental features in webpack

module.exports = {
    ...
    experiments: {
        executeModule: true,
        outputModule: true,
        syncWebAssembly: true,
        topLevelAwait: true,
        asyncWebAssembly: true,
        layers: true,
        lazyCompilation: true
    }
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

Discussion (4)

Collapse
fallenstedt profile image
Alex Fallenstedt

I made something very similar a while ago. Glad to see the momentum going :)

twitter.com/Fallenstedt/status/133...

Collapse
dhairyanadapara profile image
Dhairya Nadapara Author

Yes @fallenstedt , I believe this is the next big thing as apps are moving from native to browser. I have seen you repo when I was looking for basic boilerplate. I suggest if you can add more details in README.md, it will be very helpful to people as they can understand and customize the setup based on their requirements.

Collapse
harshmakadia profile image
Harsh Makadia

Nice article Dhairya!

Collapse
dhairyanadapara profile image
Dhairya Nadapara Author

Thanks @harshmakadia :)