Step-by-step guide on compiling C++ codes with Emscripten into wasm and using it with Webpack + Typescript + React setup

Last update: May 10, 2022

emscripten-cplusplus-webpack-example

Demo

https://9oelm.github.io/emscripten-cplusplus-webpack-example/

More practical demo

https://9oelm.github.io/risc-v-web-simulator: this project uses this repository as a template to complile more complicated set of cpp files into wasm, and then run it on the web.

Rationale

Ok. Why this repo? Using Emscripten on C++ to use WASM binary with Webpack + Typescript + React will NOT just work and things about this are very poorly documented sparsely across the web.

So I decided to go through this rabbit hole and find out the solution, and I did.

This is a step-by-step hands-on guide on:

  1. Compiling C++ code (two .cc's and two .h's) into a single Webassembly binary using Emscripten in Docker
  2. Feeding the resultant .wasm into Webpack + Typescript + React project under npm workspaces (monorepo) setup
  3. Seeing the thing working, being happy by then, and going back to work

Now, I will just write in order what needs to be exactly done. PRs welcome.

Init project

I assume you already have installed / know nvm, so no explanation on this one.

Run from project root:

nvm use # use appropriate node & npm version as specified by .nvmrc

npm i # install deps for all packages, and link packages from each other under the monorepo setup

Compile C++ to .wasm (everything done inside packages/example-wasm)

First, you will need to build the dockerfile. You can use your own em++ locally, but I personally find it the simplest to use docker container instead.

$ cd packages/example-wasm 

$ ls
add.cc            dockerutil.sh     fib.wasm          package-lock.json
add.h             fib.cc            index.d.ts        package.json
compile.sh        fib.h             index.js          tsconfig.json
dockerfile        fib.js            node_modules

$ chmod u+x compile.sh dockerutil.sh # grant shell scripts permission

$ ./dockerutil.sh -c build # download & build docker image
[+] Building 0.8s (8/8) FINISHED                                             
 => [internal] load build definition from Dockerfile                    0.0s
 => => transferring dockerfile: 154B                                    0.0s
 => [internal] load .dockerignore                                       0.0s
 => => transferring context: 2B                                         0.0s
 => [internal] load metadata for docker.io/emscripten/emsdk:latest      0.0s
 => CACHED [1/4] FROM docker.io/emscripten/emsdk:latest                 0.0s
 => [2/4] WORKDIR /etc/example-wasm                                     0.0s
 => [3/4] RUN ls -la                                                    0.3s
 => [4/4] RUN which em++                                                0.3s
 => exporting to image                                                  0.0s
 => => exporting layers                                                 0.0s
 => => writing image sha256:c4243b91ba65d543b6ace91d2ca6faf0bed700196f  0.0s
 => => naming to docker.io/library/example-wasm                         0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

Then, review the code in compile.sh (important stuffs are written as comments in this file, so no alternative elaboration here):

#!/bin/bash

# EXPORST_NAME means we can import from fib.js as follows:
# import { fib } from './fib.js'
# and fib will contain the module with webassembly

# EXPORTED_FUNCTIONS is the list of functions
# exported from C. Make sure you put them into extern "C" {} block in your c++ code. 
# Then simply prefix the functions you are going to export with an underscore, and add it to the list below

# uncomment "FILESYSTEM=0" if you don't need to use fs (i.e. use it on web entirely)

# if you create more dependencies of fib.cc, simply add them to the end of the below command, like: -o ./fib.js fib.cc add.cc anotherdep.cc and_so_on.cc
em++ -O3 -s WASM=1 -s EXPORTED_RUNTIME_METHODS='[\"cwrap\"]' -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 -s 'EXPORT_NAME="fib"' -s 'EXPORTED_FUNCTIONS=["_fib"]' -s "ENVIRONMENT='web'" -o ./fib.js fib.cc add.cc
    # -s "FILESYSTEM=0"

Now, compile the C++ code using Emscripten in Docker:

$ ./dockerutil.sh -c run

Then, you must be able to see fib.js and fib.wasm in the same directory.

Glue code

If you are looking at this repo, you probably struggled to integrate the output from the previous step into webpack configurations. As pointed out by @surma, you need some additional setup. And the things @surma pointed out in the past also have changed over time... so there needs to be some kind of glue code anyways. That is packages/example-wasm/index.js:

import { fib } from "./fib.js"
import fibonacciModule from "./fib.wasm"

// Since webpack will change the name and potentially the path of the
// `.wasm` file, we have to provide a `locateFile()` hook to redirect
// to the appropriate URL.
// More details: https://kripken.github.io/emscripten-site/docs/api_reference/module.html
const wasm = fib({
  locateFile(path) {
    if (path.endsWith(`.wasm`)) {
      return fibonacciModule
    }
    return path
  },
})

export default wasm

And here's the type definition (Emscripten does not create one for you, so I created it myself, and you will need to too if you want):

/* eslint-disable */
export interface FibWasm {
  _fib(a: number): number;
}

export declare const FibWasmPromise: Promise<FibWasm>;

export default FibWasmPromise

So.. judging from above code, you can simply do something like:

import FibWasmPromise from './index.js'

... somewhere in the code ...

async function loadAndrunWasm() {
  const fibWasm = await FibWasmPromise

  fibWasm._fib(5)
}

Right. But to be able to do this, we need some more work from Webpack side.

Webpack configuration (everything done inside packages/example-web)

Now, webpack natively supports importing wasm, but the problem is that the kind of wasm it supports is NOT the kind produced by emscripten. So what do we do?

We put this in module.rules in webpack config:

      {
        test: /fib\.js$/,
        loader: `exports-loader`,
        options: {
          type: `module`,
          // this MUST be equivalent to EXPORT_NAME in packages/example-wasm/complile.sh
          exports: `fib`,
        },
      },
      // wasm files should not be processed but just be emitted and we want
      // to have their public URL.
      {
        test: /fib\.wasm$/,
        type: `javascript/auto`,
        loader: `file-loader`,
        // options: {
        // if you add this, wasm request path will be https://domain.com/publicpath/[hash].wasm
        //   publicPath: `static/`,
        // },
      },

You need to use exports-loader because it seems that fib.js (output from em++) does not export fib for whatever reason. You will get this error if you comment out exports-loader part:

doc1.png

Now, I mentioned that Webpack supports importing wasm out of the box, and that makes this thing not work. So we are just going to tell Webpack to import wasm without any processing. That is what file-loader is doing below exports-loader.

After you launch webpack dev server, you will be able to see your wasm being requested as https://localhost:8080/[hash].wasm.

And don't forget to include @emscripten-cplusplus-webpack-example/example-wasm as a dependency in packages/example-web/package.json (it is already there, but you will need to do it yourself for your project)

"dependencies": {
    "axios": "^0.24.0",
    "lodash.flow": "^3.5.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "@emscripten-cplusplus-webpack-example/example-wasm": "*"
  }

import it and rock

Now, just import it, await the promise and use it. Ah, and if you are curious about Pure and Impure parts, it's another huge topic... to be explained for effective React. I won't explain it here. Just look at how wasm gets imported and used.

= enhance(() => { const [fibResult, setFibResult] = useState(null) useEffect(() => { async function loadAndRunFibWasm() { const fibWasm = await fibWasmPromise setFibResult(fibWasm._fib(20)) } loadAndRunFibWasm() }, []) return ( ) })(ExampleFallback) // eslint-disable-next-line @typescript-eslint/ban-types export type ExamplePureProps = { fibResult: number | null } export const ExamplePure: FC = enhance( ({ fibResult }) => (
{(() => { switch (fibResult) { case null: { return

fib(20): loading

} default: { return

fib(20): {fibResult}

} } })()}
) )(ExampleFallback)">
import React, { useEffect, useState } from "react"
import { FC } from "react"
import { enhance } from "../../utilities/essentials"
import { ExampleFallback } from "./fallback"
import fibWasmPromise from "@emscripten-cplusplus-webpack-example/example-wasm"

// eslint-disable-next-line @typescript-eslint/ban-types
export type ExampleImpureProps = {}

export const ExampleImpure: FC<ExampleImpureProps> =
  enhance<ExampleImpureProps>(() => {
    const [fibResult, setFibResult] = useState<null | number>(null)

    useEffect(() => {
      async function loadAndRunFibWasm() {
        const fibWasm = await fibWasmPromise
        setFibResult(fibWasm._fib(20))
      }
      loadAndRunFibWasm()
    }, [])

    return (
      <ExamplePure
        {...{
          fibResult,
        }}
      />
    )
  })(ExampleFallback)

// eslint-disable-next-line @typescript-eslint/ban-types
export type ExamplePureProps = {
  fibResult: number | null
}

export const ExamplePure: FC<ExamplePureProps> = enhance<ExamplePureProps>(
  ({ fibResult }) => (
    <div>
      {(() => {
        switch (fibResult) {
          case null: {
            return <p>fib(20): loading</p>
          }
          default: {
            return <p>fib(20): {fibResult}</p>
          }
        }
      })()}
    </div>
  )
)(ExampleFallback)

Then.. IT WORKS!!

doc0.png

References

GitHub

https://github.com/9oelM/emscripten-cplusplus-webpack-example
You might also like...

Appends a new DOM node to the end of the `document.body` and renders it's child React tree into it.

@react-lit/portal Appends a new DOM node to the end of the document.body and renders it's child React tree into it. Useful to break out of the DOM hie

Dec 9, 2021

Open Source Module to Upload your Media and files into AWS S3 Bucket directly from Front-end React

react-aws-s3 Open Source Module to Upload your Media and files into AWS S3 Bucket directly from Front-end React. Help the Module developer (Amit Mishr

Apr 22, 2022

Converts HTML pages into React components

Converts HTML pages into React components

Extract annotated portions of HTML into React components as separate modules. The structure of HTML is preserved by importing child components and rep

May 8, 2022

In this repo you will find the sample project where we dive deeper into the nuances of how hooks work, particularly in the context of the React render/rerender cycle.

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: ya

Jan 29, 2022

Lift a React component's state into the url

Lift a React component's state into the url

with-url-state Lifts the state out of a react component and into the url Hooks There is a hook based api available on the 3.0.0 branch, published as a

May 11, 2022

Just a small collection of hooks that make it easy to integrate React Query into a REST API

react-query-restful is just a small collection of hooks that make it easy to integrate React Query into a REST API.

May 7, 2022

A javascript microframework to shorten daily use CSS class manipulator methods by adding them all into a single method

CSS Class Builder A small typescript package built to work with ReactJS to short

Jan 7, 2022

NFT Marketplace prototype using Typescript, WalletConnect, Metamask, Web3, Solidity, React and Storybook

ERC721 NFT Marketplace Prototype of a NFT Marketplace based on openZeppelin abstract upgradeable ERC721 contracts and Minting/uploading images to IPFS

Mar 11, 2022

An algebraic effects library for javascript and typescript using generators

Algebraify Algebraic Effects Are Here! (sort of) (If you're unfamiliar with algebraic effects, here's a great article: Algebraic Effects for the Rest

Feb 13, 2022
Ultimate Guide to setup React Context API with our custom hook [Typescript]
 Ultimate Guide to setup React Context API with our custom hook [Typescript]

Ultimate Guide to setup React Context API with our custom hook [Typescript] To start working on this project simply run the following commands git clo

Oct 10, 2021
This tutorial to guide you how to add react.js into shopify normal theme

Integrate-react.js-into-shopify-theme Tutorial to integrate the react.js into shopify theme https://prnt.sc/1w0rgx0 Step note: you have to run theme w

Mar 8, 2022
Code examples for the blog post titled The Complete Guide to Full Stack Solana Development with React, Anchor, Rust, and Phantom
Code examples for the blog post titled The Complete Guide to Full Stack Solana Development with React, Anchor, Rust, and Phantom

The Complete Guide to Full Stack Solana Development with React, Anchor, Rust, and Phantom Code examples to go with the blog post available here Prereq

May 9, 2022
React Clone - How React Works: An In-Depth Guide
React Clone - How React Works: An In-Depth Guide

Как работает React: подробное руководство Привет, друзья! В этой статье я покажу вам, с чего начинается React. Что это означает? Это означает, что мы

Nov 25, 2021
repository to study react advanced guide

repository to study react advanced guide

Nov 22, 2021
TSDX React w/ Storybook User Guide

TSDX React w/ Storybook User Guide Congrats! You just saved yourself hours of work by bootstrapping this project with TSDX. Let’s get you oriented wit

Nov 30, 2021
SFN-Vis parses the output of your AWS Step Functions docker container
SFN-Vis parses the output of your AWS Step Functions docker container

SFN-Vis parses the output of your AWS Step Functions docker container (that runs locally, for testing) and indexes it then serves it along with a frontend for viewing the log streams.

Mar 12, 2022
A simple webpack builder for react

ll-script a simple webpack builder for react feat support mock support customize webpack config install yarn add ll-script -D use Update the scripts s

Dec 14, 2021
基于webpack的一个react应用构建器,集成ts、webpack-dev-server等

介绍 基于webpack4、babel7开发的一款 react项目构建器 特性 基于webpack4、babel7开发 集成了 TypeScript构建、webpack-dev-server支持、热更新、最新的babel配置(支持到es2020) 让你不再关注 bable 和 webpack等乱七八

Dec 23, 2021
A Fastify plugin for serving files emitted by Webpack with Hot Module Replacement (HMR)
A Fastify plugin for serving files emitted by Webpack with Hot Module Replacement (HMR)

fastify-webpack-hot ?? A Fastify plugin for serving files emitted by Webpack with Hot Module Replacement (HMR). (fastify-webpack-hot immediately propa

Apr 5, 2022