Implement React v18 from Scratch Using WASM and Rust - [1] Build the Project
Tags: react, webassembly, rust
Published: 2024-04-07 14:04:49

> Based on [big-react](https://github.com/BetaSu/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](https://github.com/ParadeTo/big-react-wasm/tree/v1)

# Tools

[**Rust**](https://www.rust-lang.org/): 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**](https://rustwasm.github.io/wasm-pack/installer/): A one-stop tool for building, testing, and publishing Rust WebAssembly.

[**cargo-generate**](https://github.com/cargo-generate/cargo-generate): Quickly create Rust projects by using existing Git repositories as templates.

For more information, you can refer to the [Rust and WebAssembly](https://rustwasm.github.io/docs/book/).

# Project Structure

First, let's set up the following project structure:

```js
.
├── 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:

```ts
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:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vi734roqkpy49ajdk8z4.png)


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:

```json
    "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, using `wasm-pack` to package react and react-dom into WASM:

```json
  "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:

```rust
// react/src/lib.rs
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn jsxDEV() -> String {
    "hello world".to_string()
}
```

```rust
// 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:

```rust
// react/src/lib.rs
use wasm_bindgen::prelude::*;

#[wasm_bindgen(js_name = jsxDev)]
pub fn jsx_dev() -> String {
    "hello world".to_string()
}
```

```rust
// 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 run `pnpm install` and `npm run dev` in the hello-world directory, open the page in the browser, and you will see the following output:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jnpnsr79m4iazgu72b5b.png)

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.