Based on big-react,I am going to implement React v18 core features from scratch using WASM and Rust.
Code Repository:https://github.com/ParadeTo/big-react-wasm
The tag related to this article:v1
Tools
Rust: A secure, efficient, and modern programming language (omitting ten thousand words). You can simply follow the installation instructions provided on the official website.
wasm-pack: A one-stop tool for building, testing, and publishing Rust WebAssembly.
cargo-generate: Quickly create Rust projects by using existing Git repositories as templates.
For more information, you can refer to the Rust and WebAssembly.
Project Structure
First, let's set up the following project structure:
.
├── Cargo.toml
├── package.json
├── examples
│ └── hello-world // React project initialized by vite
├── packages
│ ├── react // WASM project created by cargo generate --git https://github.com/rustwasm/wasm-pack-template
│ ├── react-dom // WASM project created by cargo generate --git https://github.com/rustwasm/wasm-pack-template
│ ├── react-reconciler // Rust project created by cargo new
│ └── shared // Rust project created by cargo new
The Cargo.toml
file is shown below, similar to the commonly mentioned monorepo architecture in frontend development.
[workspace]
members = [
"packages/react",
"packages/react-dom",
"packages/react-reconciler",
"packages/shared"
]
Because react
and react-dom
will export methods for JavaScript to call, we need to create a WASM project using cargo generate --git https://github.com/rustwasm/wasm-pack-template
for those two. The other two can be created as regular Rust projects using cargo new
.
Setting up the Debugging Environment
Let's delete the code in hello-world/src/main.tsx
and write a very simple example:
import {createRoot} from 'react-dom'
const comp = <div>hello world</div>
console.log(comp)
console.log(createRoot(document.getElementById('root')))
When running in the development environment, you can see the compiled code in the browser's debug window as follows:
Now, our goal is to make the hello-world use the React version we are currently developing. To get the above code to run successfully, we need to do the following steps:
- Modify the
package.json
in the hello-world project:
"react": "file://../../packages/react/pkg/react",
"react-dom": "file://../../packages/react-dom/pkg/react-dom",
- Add a packaging command to the
package.json
in the root directory, usingwasm-pack
to package react and react-dom into WASM:
"scripts": {
"build:react": "wasm-pack build packages/react --out-dir pkg/react --out-name jsx-dev-runtime",
"build:react-dom": "wasm-pack build packages/react-dom --out-dir pkg/react-dom --out-name index",
"build": "npm run build:react && npm run build:react-dom"
},
- Add the following code to the
lib.rs
in both react and react-dom:
// react/src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn jsxDEV() -> String {
"hello world".to_string()
}
// react-dom/src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn createRoot(container: &JsValue) -> JsValue {
JsValue::null()
}
Since the naming convention in Rust is generally snake_case, it's better to change it to this:
// react/src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen(js_name = jsxDev)]
pub fn jsx_dev() -> String {
"hello world".to_string()
}
// react-dom/src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen(js_name = createRoot)]
pub fn create_root(container: &JsValue) -> JsValue {
JsValue::null()
}
- Run
npm run build
in the root directory, then runpnpm install
andnpm run dev
in the hello-world directory, open the page in the browser, and you will see the following output:
This indicates that the method exported by WASM has been successfully called on the JS side, and the debugging environment is set up. However, the slightly troublesome part is that if the code is modified, step 4 needs to be executed again.
In the next article, we will implement the jsx_dev function, so stay tuned.