随着最近Rust的更新,是时候重新考虑如何将 Rust 和 React结合起来使用了,我对 Rust 以及它作为一种语言所能做的一切有了新的热爱。它更令人印象深刻的特性之一是它能够在没有太多开销的情况下编写 WebAssembly(Wasm),我之前探索过如何使用 Rust 编写 Wasm。但我想看看将它集成到标准 React 工作流程中是多么的容易。

WASM简介

WebAssembly(缩写为 Wasm)是一种用于基于堆栈的虚拟机的二进制指令格式。Wasm 被设计为编程语言的可移植编译目标,支持在 Web 上部署客户端和服务器应用程序。

WebAssembly 是一种类似于汇编的低级编程语言,可以在大多数现代浏览器中运行。它具有紧凑的二进制格式,可为我们提供近乎原生的网络性能。随着它变得越来越流行,许多语言都编写了绑定来编译成 Web 程序集。这是一个我非常喜欢的工具,很高兴与大家分享我们如何在 React 工作流程中使用它。

react三大属性详解(WebAssemblyRust应用于React组件)(1)

在 Wasm 中从头开始开发程序并不理想,几乎是不可能的。如果您在大学期间曾有幸在 Assembly 中进行编码,您就会明白为什么。幸运的是,有些语言可以毫不费力地编译成 Wasm。这在很多语言(C、Go、C#、Kotlin)中都是可能的,但在这个例子中,我们将使用 Rust。

搭建React项目

第一步是设置一个 React 应用程序。有很多工具可以帮助我们解决这个问题,像Vite或create-react-app。但在本教程中,我们将定制我们的 React 构建。使用Webpack从零开始配置个简单项目,也让大家回顾一下定制构建的步骤。让我们通过运行以下命令来初始化我们的package.json:

$ npm init -y

上面的命令会给我们一个默认的package.json准备安装我们需要的NPM包。我将使用在撰写本文时可用的最新的npm包,但请注意,这些包经常更新,也许我下面使用的 API 中可能会有一些变化。要遵循本教程,请尝试安装我使用的相同版本的NPM包。

接下来我们需要安装 React、Babel、Webpack 一些NPM包。这样我们才能开始编码!

$ npm i react react-dom $ npm i -D webpack webpack-cli webpack-dev-server Html-webpack-plugin $ npm i -D babel-core babel-loader @babel/preset-env @babel/preset-react

然后为 React 应用程序构建我们项目的文件目录结构,创建的文件夹src,public,build,和dist。创建这些文件夹后,让我们介绍我们的第一段 React 代码。打开文件夹src创建index.jsx并添加以下代码:

import React from "react"; import ReactDOM from "react-dom"; ReactDOM.render(<h1>Hello, world!</h1>, document.getElementById("root"));

现在,为了让它作为 Web 应用程序运行,我们需要配置我们的 babel 和 webpack 。为此,请创建两个文件:.babelrcwebpack.config.js 并分别为每个文件添加代码:

{ "presets": ["@babel/preset-env", "@babel/preset-react"] }

const HtmlWebpackPlugin = require('html-webpack-plugin'); const path = require("path"); module.exports = { entry: "./src/index.jsx", output: { path: path.resolve(__dirname, "dist"), filename: "bundle.[hash].js" }, devServer: { compress: true, port: 8080, hot: true, static: './dist', historyApiFallback: true, open: true }, module: { rules: [ { test: /.(js|jsx)$/, exclude: /node_modules/, use: { loader: "babel-loader" } } ] }, plugins: [ new HtmlWebpackPlugin({ template: __dirname "/public/index.html", filename: "index.html" }), ], mode: "development", devtool: 'inline-source-map', };

我们还需要添加我们的默认 HTML,创建public/index.html并添加以下代码:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Rust React</title> </head> <body> <div id="root"></div> </body> </html>

更新我们的package.json,

{ "name": "react-for-wasm", "version": "1.0.0", "description": "", "main": "src/index.jsx", "scripts": { "dev": "webpack server" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "react": "^17.0.2", "react-dom": "^17.0.2" }, "devDependencies": { "@babel/preset-env": "^7.16.4", "@babel/preset-react": "^7.16.0", "@babel/core": "^7.16.0", "babel-loader": "^8.2.3", "html-webpack-plugin": "^5.5.0", "webpack": "^5.64.4", "webpack-cli": "^4.9.1", "webpack-dev-server": "^4.6.0" } }

执行npm run dev,程序正常启动,一个简单的React程序运行起来,实际项目中还是利用工具(vite、create-react-app)来构建项目比较方便。

Rust前置知识

Node.js

前提需要安装Node,安装地址:https://nodejs.org/zh-cn/

cargo

react三大属性详解(WebAssemblyRust应用于React组件)(2)

cargorust 的代码组织和包管理工具,你可以将它类比为 node.js 中的 npmcargo 提供了一系列强大的功能,从项目的建立、构建到测试、运行直至部署,为 rust 项目的管理提供尽可能完整的手段。同时,它也与 rust 语言及其编译器 rustc 本身的各种特性紧密结合。

rustup

rustupRust 的安装和工具链管理工具,并且官网推荐使用 rustup 安装 Rustrustuprustc(rust编译器) 和 cargo 等工具安装在 Cargobin 目录,但这些工具只是 Rust 工具链中组件的代理,真正工作的是工具链中的组件。通过 rustup 的命令可以指定使用不同版本的工具链。

安装地址:https://www.rust-lang.org/zh-CN/tools/install

wasm-bindgen

react三大属性详解(WebAssemblyRust应用于React组件)(3)

wasm-bindgen 提供了 JSRust 类型之间的桥梁,它允许 JS 使用字符串调用 Rust API,或者使用 Rust 函数来捕获 JS 异常。

wasm-bindgen 的核心是促进 javascriptRust 之间使用 wasm 进行通信。它允许开发者直接使用 Rust 的结构体、javascript的类、字符串等类型,而不仅仅是 wasm 支持的整数或浮点数类型。

wasm-pack

react三大属性详解(WebAssemblyRust应用于React组件)(4)

wasm-packRust / Wasm 工作组开发维护,是现在最为活跃的 WebAssembly 应用开发工具。

wasm-pack 支持将代码打包成 npm 模块,并且附带 Webpack 插件(wasm-pack-plugin),借助它,我们可以轻松的将 Rust 与已有的 JavaScript 应用结合。

wasm32-unknown-unknown

通过 rustuptarget 命令可以指定编译的目标平台,也就是编译后的程序在哪种操作系统上运行。

wasm-pack 使用 wasm32-unknown-unknown 目标编译代码。

创建Rust程序

现在我们已经搭建了 React 应用程序,接下来我们进入该教程的 Rust 组件编写。第一步是我们需要创建 Rust 应用程序。我们可以通过运行cargo init --lib .不要忘记该命令末尾的句点,这很重要!)这将创建一个Cargo.toml和一个src/lib.rc文件。为了让 Rust 应用程序准备好将它们的代码转换为 Wasm,我们需要一个名为wasm-bindgen的重要包。我们还需要告诉编译器这个包是一个 cdylib,为此,我们需要修改我们的Cargo.toml文件:

[package] name = "react-for-wasm" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "0.2"

https://rustwasm.github.io/wasm-bindgen/introduction.html

wasm-bindgen教程

我们应该能够在没有任何错误的情况下构建我们的 Rust 应用程序,让我们测试一下(前提下需要安装 Visual Studio以使用 MSVC 或安装 MinGW GCC 编译环境,不然会报错):

https://visualstudio.microsoft.com/zh-hans/visual-cpp-build-tools

Visual Studio下载地址

react三大属性详解(WebAssemblyRust应用于React组件)(5)

这个构建本身对我们没有多大帮助,我们需要为我们的构建添加一个有用的目标。要为 Rust 添加新目标,我们可以运行以下命令:

$ rustup target add wasm32-unknown-unknown

这将为我们编译的 Rust 代码提供合适的目标,允许我们将其添加到我们的 React 应用程序中。让我们修改src/lib.rs,更新内容如下:

use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { fn alert(s: &str); } #[wasm_bindgen] pub fn big_computation() { alert("Big computation in Rust"); } #[wasm_bindgen] pub fn welcome(name: &str) { alert(&format!("Hello {}, from Rust!", name)); }

这将为我们提供两个可以在 React 应用程序中使用的函数,一个是“大计算”函数,另一个是需要名称变量的“欢迎”函数。

同时,让我们确保我们的 Rust 应用程序正常工作,使用我们新增的构建目标wasm32-unknown-unknown

cargo build --target wasm32-unknown-unknown

让我们安装命令行wasm-bindgen-cli应用程序,以便我们可以利用我们创建的 WebAssembly 代码(网络不好可能会出现下载不下来)

cargo install -f wasm-bindgen-cli

安装后,我们可以使用 Rust 生成的 WebAssembly 代码并为我们的 React 代码创建一个可用的包:

$ wasm-bindgen target/wasm32-unknown-unknown/debug/react_for_wasm.wasm --out-dir build

这会将 Javascript 包装和优化的 Wasm 代码转储到我们的build目录中,以供 React 使用。接下来我们来使用。

React与Wasm

我们教程的下一步是在我们的 React 应用程序中使用上述 Wasm 代码。为此,我们需要将上述一些脚本添加到我们的package.json,添加到“script”部分

"build:wasm": "cargo build --target wasm32-unknown-unknown", "build:bindgen": "wasm-bindgen target/wasm32-unknown-unknown/debug/rusty_react.wasm --out-dir build", "build": "npm run build:wasm && npm run build:bindgen && npx webpack",

这样执行npm run build,便可以打包所有内容,很方便!

我们还应该安装另一个 NPM 包,以帮助我们使用 Wasm 进行开发。让我们将其添加到我们的开发依赖项中:

$ npm i -D @wasm-tool/wasm-pack-plugin

安装后,我们将更新我们的webpack.config.js文件,配置插件@wasm-tool/wasm-pack-plugin,将以下代码添加到我们的 webpack 配置中:

const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin"); ... plugins: [ ... new WasmPackPlugin({ crateDirectory: path.resolve(__dirname, ".") }), ], ... experiments: { asyncWebAssembly: true }

让我们测试一下NPM SCRIPT,正常来说我们应该能够运行npm run build:wasm,npm run build:bindgen、npm run build并且没有报错,如果报wasm-pack问题,也许你需要安装wasm-pack-init.exe

https://rustwasm.github.io/wasm-pack/installer/

下一步是将 WebAssembly 代码添加到我们的 React 组件中。让我们更新src/index.jsx,导入我们的 Wasm 代码并执行它!

import React, { useState } from "react"; import ReactDOM from "react-dom"; const wasm = import("../build/react_for_wasm"); wasm.then(m => { const App = () => { const [name, setName] = useState(""); const handleChange = (e) => { setName(e.target.value); } const handleClick = () => { m.welcome(name); } return ( <> <div> <h1>Hi there</h1> <button onClick={m.big_computation}>Run Computation</button> </div> <div> <input type="text" onChange={handleChange} /> <button onClick={handleClick}>Say hello!</button> </div> </> ); }; ReactDOM.render(<App />, document.getElementById("root")); });

执行npm run dev,便能测试我们的具体效果了。

react三大属性详解(WebAssemblyRust应用于React组件)(6)

react三大属性详解(WebAssemblyRust应用于React组件)(7)

总结

就是这样!我们可以在 React 应用程序中运行 WebAssembly(用 Rust 编写)。看完这个简短的教程之后,是不是觉得WASM配合Rust(甚至其他语言C\C )可以写很多有趣的东西?学起来!

,