Using Rust/WASM in a monorepo with create-react-app
2022-08-22
Behold, the buzzwords:
- Rust / WASM / wasm-bindgen
- React
- Monorepo / Yarn workspaces
- Webpack 5 / create-react-app 5
- Typescript
The main goal here: To use Rust + WASM in a react app, inside a monorepo.
TLDR: visit the final product! https://github.com/cmdcolin/rust_react_monorepo_template. It is also deployed live here https://cmdcolin.github.io/rust_react_monorepo_template
#Steps to create this type of integration from scratch
#Create repo
mkdir template
cd template
git init
#Create root package.json
Then put this in the monorepo's root package.json
This sets our repo up as a "monorepo" with two "workspaces". one will be the
wasm code, in hello-wasm
, one will be an instance of create-react-app
#Add a create-react-app
instance inside the monorepo
This will make an app
subfolder inside our monorepo
#Download the hello world rust wasm-bindgen
example and put it in a folder named hello-wasm
Download https://github.com/rustwasm/wasm-bindgen/tree/main/examples/hello_world to the hello-wasm folder
This link can help https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Frustwasm%2Fwasm-bindgen%2Ftree%2Fmain%2Fexamples%2Fhello_world
#Add some extra fields to the package.json
in the hello-wasm
folder
#Modify the hello-wasm
example to return a value instead of making an alert
I changed the rust code to return a String value instead of making an alert box
#Build the hello-wasm
pkg
Go into the hello-wasm
folder and run yarn build
. This creates a directory
named pkg
which has .wasm
files and .js
files. Now, the hello-wasm
folder is effectively a node package. We could publish this to NPM
(see
footnote 1)
#Add the hello-wasm
package to the app
dependencies
Add "hello-wasm":"^1.0.0"
to the dependencies
array in app/package.json
.
This will refer to our local monorepo's rust wasm package!
#Create craco config for create-react-app
As of writing, with webpack
v5/create-react-app
v5, you have to customize
the create-react-app
to add extra webpack
flags.
So, yarn add @craco/craco
in the app folder, then create this
craco.config.js
Note: this thread helped me to create the craco config https://github.com/Emurgo/cardano-serialization-lib/issues/295
Also see footnote 2 for more info
#Final step: Use async import()
to import the hello-wasm
greeting
We use a useEffect
hook to import the code asynchronously, and can call our
rust function, greet
, from javascript!
In order to greet an arbitrary person, I modified this slightly in the live demo. See https://github.com/cmdcolin/rust_react_monorepo_template/blob/master/app/src/App.tsx
#Run the app!
Go into the app
folder, and then run yarn start
#Result!
A screenshot of the app, showing the string "Hello Colin" which is generated via rust and wasm
#Conclusion
My main aim was to demonstrate creating a "simple" monorepo setup showing how you can integrate Rust+WASM and React. Feel free to ask me any questions and go check out the repo!
https://github.com/cmdcolin/rust_react_monorepo_template
#Other resources
This article is quite helpful also, but uses a file:/ reference in their
package.json
while my approach uses a monorepo, it is fundamentally quite
similar though!
https://tkat0.github.io/posts/how-to-create-a-react-app-with-rust-and-wasm
#Footnote 1: The hello-wasm
folder IS a npm package with wasm files
The hello-wasm
folder can be published to NPM by itself. When consumers of the
package import the module, they would receive pkg/index.js
from the main
field in package.json
, and then pkg/index.js
in turn imports the
index.wasm
file. Then it is up to the consumers bundler to package that
correctly.
#Footnote 2: Bundlers and wasm
As of writing, I am using webpack
v5 (part of create-react-app
v5), which
has "native support" for wasm. Still, it is hidden behind a flag called
"experiments" (see first google result for webpack wasm here
https://webpack.js.org/configuration/experiments/) so I use @craco/craco
to
modify the webpack
config of create-react-app
v5 to add this.
Note also: The first time I wrote this, I used webpack
v4, which used a
slightly different workflow (used a special webpack
loader called
wasm-loader
)
You can also likely use similar techniques described in this article to
incorporate into next.js
since it also uses webpack
. If you have info on how
other bundlers use wasm, feel free to leave a comment.
#Footnote 3: Why do I have to use async imports?
Fundamentally, the .wasm
file has to be fetched asynchronously before it can
be run (it is not in my experience e.g. embedded as binary data inside a js
file) which means it would be difficult to use the wasm code as a synchronous
import.
There are hints that this may be possible but it would rely on the bundler embedding the wasm code in the js itself, or maybe top-level-await. If anyone has more info, feel free to leave a comment!
#Footnote 4: Build setup
The hello-wasm
package does not automatically recompile when we are running
e.g. yarn start
in the app
folder. Therefore, changes to the rust requires
you to manually run yarn build
in the hello-wasm
folder. Just something to
be aware of
#Footnote 5: My first experience with trying to make this work was rocky!
I first created an example of rust+wasm+react almost two years ago when creating a fractal viewer https://github.com/cmdcolin/logistic_chaos_map and it has some development notes on the stumbling blocks I faced https://github.com/cmdcolin/logistic_chaos_map/blob/master/NOTES.md
#Footnote 6: I thought you said typescript too
Yep! The hello-wasm
example generates typescript .d.ts
files! Check out the
hello-wasm/pkg/
folder after you build it! This was none of my doing, just a
built-in feature. PS: I highly recommend inspecting the pkg
folder that is
produced in the hello-wasm
build to help understand the details. I also
recommend reading the https://rustwasm.github.io/wasm-bindgen/ docs and if you
are getting started with rust, read the Rust Book along with doing rustlings
https://github.com/rust-lang/rustlings
#Footnote 7: Another resource
This article was posted on reddit and is also a great resource especially about sync vs async webpack loading schemes for wasm
https://canvasapp.com/blog/building-modern-web-apps-with-rust-wasm-and-webpack/