{"id":15654610,"url":"https://github.com/9oelm/emscripten-cplusplus-webpack-example","last_synced_at":"2025-05-01T04:25:36.741Z","repository":{"id":83601792,"uuid":"490114765","full_name":"9oelM/emscripten-cplusplus-webpack-example","owner":"9oelM","description":"Step-by-step guide on compiling C++ codes with Emscripten into wasm and using it with Webpack + Typescript + React setup","archived":false,"fork":false,"pushed_at":"2022-05-17T06:13:47.000Z","size":3303,"stargazers_count":31,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-02-25T12:23:43.069Z","etag":null,"topics":["emscripten","react","typescript","webassembly","webpack"],"latest_commit_sha":null,"homepage":"https://9oelm.github.io/emscripten-cplusplus-webpack-example/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/9oelM.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-09T02:54:10.000Z","updated_at":"2024-10-22T22:57:12.000Z","dependencies_parsed_at":"2023-07-07T22:30:42.616Z","dependency_job_id":null,"html_url":"https://github.com/9oelM/emscripten-cplusplus-webpack-example","commit_stats":null,"previous_names":[],"tags_count":0,"template":true,"template_full_name":"9oelM/react-typescript-monorepo-boilerplate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/9oelM%2Femscripten-cplusplus-webpack-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/9oelM%2Femscripten-cplusplus-webpack-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/9oelM%2Femscripten-cplusplus-webpack-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/9oelM%2Femscripten-cplusplus-webpack-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/9oelM","download_url":"https://codeload.github.com/9oelM/emscripten-cplusplus-webpack-example/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242728412,"owners_count":20175935,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["emscripten","react","typescript","webassembly","webpack"],"created_at":"2024-10-03T12:52:45.077Z","updated_at":"2025-03-09T17:30:46.869Z","avatar_url":"https://github.com/9oelM.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# emscripten-cplusplus-webpack-example\n\n# Demo\n\nhttps://9oelm.github.io/emscripten-cplusplus-webpack-example/\n\n## More practical demo\n\nhttps://riscv.vercel.app: 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.\n\n## Rationale\nOk. 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._** \n\nSo I decided to go through this rabbit hole and find out the solution, and I did.\n\nThis is a step-by-step hands-on guide on: \n1. Compiling C++ code (two .cc's and two .h's) into a single Webassembly binary using Emscripten in Docker\n2. Feeding the resultant .wasm into Webpack + Typescript + React project under npm workspaces (monorepo) setup\n3. Seeing the thing working, being happy by then, and going back to work\n\nNow, I will just write in order what needs to be exactly done. PRs welcome.\n\n## Init project\n\nI assume you already have installed / know `nvm`, so no explanation on this one.\n\nRun from project root:\n```bash\nnvm use # use appropriate node \u0026 npm version as specified by .nvmrc\n\nnpm i # install deps for all packages, and link packages from each other under the monorepo setup\n```\n\n## Compile C++ to .wasm (everything done inside `packages/example-wasm`)\n\nFirst, 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.\n\n```bash\n$ cd packages/example-wasm \n\n$ ls\nadd.cc            dockerutil.sh     fib.wasm          package-lock.json\nadd.h             fib.cc            index.d.ts        package.json\ncompile.sh        fib.h             index.js          tsconfig.json\ndockerfile        fib.js            node_modules\n\n$ chmod u+x compile.sh dockerutil.sh # grant shell scripts permission\n\n$ ./dockerutil.sh -c build # download \u0026 build docker image\n[+] Building 0.8s (8/8) FINISHED                                             \n =\u003e [internal] load build definition from Dockerfile                    0.0s\n =\u003e =\u003e transferring dockerfile: 154B                                    0.0s\n =\u003e [internal] load .dockerignore                                       0.0s\n =\u003e =\u003e transferring context: 2B                                         0.0s\n =\u003e [internal] load metadata for docker.io/emscripten/emsdk:latest      0.0s\n =\u003e CACHED [1/4] FROM docker.io/emscripten/emsdk:latest                 0.0s\n =\u003e [2/4] WORKDIR /etc/example-wasm                                     0.0s\n =\u003e [3/4] RUN ls -la                                                    0.3s\n =\u003e [4/4] RUN which em++                                                0.3s\n =\u003e exporting to image                                                  0.0s\n =\u003e =\u003e exporting layers                                                 0.0s\n =\u003e =\u003e writing image sha256:c4243b91ba65d543b6ace91d2ca6faf0bed700196f  0.0s\n =\u003e =\u003e naming to docker.io/library/example-wasm                         0.0s\n\nUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them\n```\n\nThen, review the code in `compile.sh` (important stuffs are written as comments in this file, so no alternative elaboration here):\n\n```bash\n#!/bin/bash\n\n# EXPORST_NAME means we can import from fib.js as follows:\n# import { fib } from './fib.js'\n# and fib will contain the module with webassembly\n\n# EXPORTED_FUNCTIONS is the list of functions\n# exported from C. Make sure you put them into extern \"C\" {} block in your c++ code. \n# Then simply prefix the functions you are going to export with an underscore, and add it to the list below\n\n# uncomment \"FILESYSTEM=0\" if you don't need to use fs (i.e. use it on web entirely)\n\n# 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\nem++ -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\n    # -s \"FILESYSTEM=0\"\n```\n\nNow, compile the C++ code using Emscripten in Docker:\n\n```\n$ ./dockerutil.sh -c run\n```\n\nThen, you must be able to see `fib.js` and `fib.wasm` in the same directory.\n\n## Glue code\n\nIf 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`:\n\n```js\nimport { fib } from \"./fib.js\"\nimport fibonacciModule from \"./fib.wasm\"\n\n// Since webpack will change the name and potentially the path of the\n// `.wasm` file, we have to provide a `locateFile()` hook to redirect\n// to the appropriate URL.\n// More details: https://kripken.github.io/emscripten-site/docs/api_reference/module.html\nconst wasm = fib({\n  locateFile(path) {\n    if (path.endsWith(`.wasm`)) {\n      return fibonacciModule\n    }\n    return path\n  },\n})\n\nexport default wasm\n```\n\nAnd 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):\n\n```ts\n/* eslint-disable */\nexport interface FibWasm {\n  _fib(a: number): number;\n}\n\nexport declare const FibWasmPromise: Promise\u003cFibWasm\u003e;\n\nexport default FibWasmPromise\n```\n\nSo.. judging from above code, you can simply do something like:\n\n```js\nimport FibWasmPromise from './index.js'\n\n... somewhere in the code ...\n\nasync function loadAndrunWasm() {\n  const fibWasm = await FibWasmPromise\n\n  fibWasm._fib(5)\n}\n```\n\nRight. But to be able to do this, we need some more work from Webpack side.\n\n## Webpack configuration (everything done inside `packages/example-web`)\n\nNow, 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?\n\nWe put this in `module.rules` in webpack config:\n\n```js\n      {\n        test: /fib\\.js$/,\n        loader: `exports-loader`,\n        options: {\n          type: `module`,\n          // this MUST be equivalent to EXPORT_NAME in packages/example-wasm/complile.sh\n          exports: `fib`,\n        },\n      },\n      // wasm files should not be processed but just be emitted and we want\n      // to have their public URL.\n      {\n        test: /fib\\.wasm$/,\n        type: `javascript/auto`,\n        loader: `file-loader`,\n        // options: {\n        // if you add this, wasm request path will be https://domain.com/publicpath/[hash].wasm\n        //   publicPath: `static/`,\n        // },\n      },\n```\n\nYou 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:\n\n![doc1.png](./doc1.png)\n\nNow, 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`.\n\nAfter you launch webpack dev server, you will be able to see your wasm being requested as `https://localhost:8080/[hash].wasm`.\n\nAnd 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)\n\n```\n\"dependencies\": {\n    \"axios\": \"^0.24.0\",\n    \"lodash.flow\": \"^3.5.0\",\n    \"react\": \"^17.0.2\",\n    \"react-dom\": \"^17.0.2\",\n    \"@emscripten-cplusplus-webpack-example/example-wasm\": \"*\"\n  }\n```\n\n## `import` it and rock\n\nNow, 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.\n\n```ts\nimport React, { useEffect, useState } from \"react\"\nimport { FC } from \"react\"\nimport { enhance } from \"../../utilities/essentials\"\nimport { ExampleFallback } from \"./fallback\"\nimport fibWasmPromise from \"@emscripten-cplusplus-webpack-example/example-wasm\"\n\n// eslint-disable-next-line @typescript-eslint/ban-types\nexport type ExampleImpureProps = {}\n\nexport const ExampleImpure: FC\u003cExampleImpureProps\u003e =\n  enhance\u003cExampleImpureProps\u003e(() =\u003e {\n    const [fibResult, setFibResult] = useState\u003cnull | number\u003e(null)\n\n    useEffect(() =\u003e {\n      async function loadAndRunFibWasm() {\n        const fibWasm = await fibWasmPromise\n        setFibResult(fibWasm._fib(20))\n      }\n      loadAndRunFibWasm()\n    }, [])\n\n    return (\n      \u003cExamplePure\n        {...{\n          fibResult,\n        }}\n      /\u003e\n    )\n  })(ExampleFallback)\n\n// eslint-disable-next-line @typescript-eslint/ban-types\nexport type ExamplePureProps = {\n  fibResult: number | null\n}\n\nexport const ExamplePure: FC\u003cExamplePureProps\u003e = enhance\u003cExamplePureProps\u003e(\n  ({ fibResult }) =\u003e (\n    \u003cdiv\u003e\n      {(() =\u003e {\n        switch (fibResult) {\n          case null: {\n            return \u003cp\u003efib(20): loading\u003c/p\u003e\n          }\n          default: {\n            return \u003cp\u003efib(20): {fibResult}\u003c/p\u003e\n          }\n        }\n      })()}\n    \u003c/div\u003e\n  )\n)(ExampleFallback)\n```\n\nThen.. IT WORKS!!\n\n![doc0.png](./doc0.png)\n\n## References\n- https://github.com/PetterS/clang-wasm\n- https://towardsdev.com/lets-export-the-c-library-to-javascript-wasm-library-part-a-4b99c9a7cf2\n- https://emscripten.org/docs/porting/files/file_systems_overview.html#file-system-overview\n- https://github.com/webpack/webpack/issues/7352\n- https://gist.github.com/surma/b2705b6cca29357ebea1c9e6e15684cc\n- https://developer.mozilla.org/en-US/docs/WebAssembly/existing_C_to_wasm\n- https://github.com/emscripten-core/emscripten/issues/10224\n- https://stackoverflow.com/questions/8249945/what-is-object-in-object-file-and-why-is-it-called-this-way\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F9oelm%2Femscripten-cplusplus-webpack-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F9oelm%2Femscripten-cplusplus-webpack-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F9oelm%2Femscripten-cplusplus-webpack-example/lists"}