Wasm
在開始之前,我們還是先來回顧下 Wasm
:
WebAssembly
是一種二進制指令格式,簡稱為 Wasm
,它可以運行在適用於堆棧的虛擬機上。
WebAssembly
存在的意義就是成為編程語言的可移植編譯目標,讓在 Web
上部署客戶端和服務端應用成為可能。
Wasm
具有緊湊的二進制格式,可為我們提供近乎原生的網絡性能。隨着它變得越來越流行,許多語言都編寫了編譯成 Web 程序集的綁定工具。
為什么是 Rust
Rust
是一個快速、可靠二期又節約內存的編程語言。在過去六年的 stackoverflow
的最受喜愛的編程語言中,它一直蟬聯榜首的位置,主要還是這個語言本身擁有眾多的優點,比如:
- 內存安全
- 類型安全
- 消除數據競爭
- 使用前編譯
- 建立(並且鼓勵)在零抽象之上
- 最小的運行時(無停止世界的垃圾搜集器,無
JIT
編譯器,無VM
) - 低內存占用(程序可以運行在資源受限的環境,比如小的微控制器)
- 針對裸機(比如,寫一個
OS
內核或者設備驅動,把 Rust 當一個 ‘高層’匯編器使用)”
另外,Rust
在 WebAssembly
領域的貢獻非常大的,使用 Rust
編寫 WebAssembly
非常簡單。
但是,Rust
存在的目的不是為了替代 JavaScript
而是和他形成互補,因為 Rust
語言的學習曲線是非常陡峭的,用它去完全替代 Web
開發幾乎是不可能的。
所以,我們一般會在 Web
開發的工具鏈,或者前端頁面中一些非常大量的數據計算中的操作用到它。
前置知識
在開始開發之前,你需要了解一些前置知識,React
相關的就不多說了,我們來看看 Rust
相關的幾個重要概念。
cargo
cargo
是 rust
的代碼組織和包管理工具,你可以將它類比為 node.js
中的 npm
。
cargo
提供了一系列強大的功能,從項目的建立、構建到測試、運行直至部署,為 rust
項目的管理提供盡可能完整的手段。同時,它也與 rust
語言及其編譯器 rustc
本身的各種特性緊密結合。
rustup
rustup
是 Rust
的安裝和工具鏈管理工具,並且官網推薦使用 rustup
安裝 Rust
。
rustup
將 rustc
(rust編譯器) 和 cargo
等工具安裝在 Cargo
的 bin
目錄,但這些工具只是 Rust
工具鏈中組件的代理,真正工作的是工具鏈中的組件。通過 rustup
的命令可以指定使用不同版本的工具鏈。
wasm-bindgen
wasm-bindgen
提供了 JS
和 Rust
類型之間的橋梁,它允許 JS
使用字符串調用 Rust API
,或者使用 Rust
函數來捕獲 JS
異常。
wasm-bindgen
的核心是促進 javascript
和 Rust
之間使用 wasm
進行通信。它允許開發者直接使用 Rust
的結構體、javascript
的類、字符串等類型,而不僅僅是 wasm
支持的整數或浮點數類型。
wasm-pack
wasm-pack
由 Rust / Wasm
工作組開發維護,是現在最為活躍的 WebAssembly
應用開發工具。
wasm-pack
支持將代碼打包成 npm
模塊,並且附帶 Webpack
插件(wasm-pack-plugin
),借助它,我們可以輕松的將 Rust
與已有的 JavaScript
應用結合。
wasm32-unknown-unknown
通過 rustup
的 target
命令可以指定編譯的目標平台,也就是編譯后的程序在哪種操作系統上運行。
wasm-pack
使用 wasm32-unknown-unknown
目標編譯代碼。
好了,了解了 Rust
相關的一些知識,我們一起來完成這個 Demo
吧。
一起來做個 Demo
在開始之前,要確保你的電腦上已經安裝了 Node
和 Rust
,可以在命令行分別輸入 npm
、rustup
看看能否找到命令,如果沒安裝的話自己先安裝一下。
初始化一個簡單 React 程序
首先,我們來初始化一個 React
項目,命令行執行 npm init
:
然后,我們安裝一些開發項目必備的包:
$ 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
然后,我們在項目中創建一些常用的文件夾:src
、page
、public
、build
、和 dist
。
我們在 page
文件夾中創建一個 index.jsx
,編寫一些測試代碼:
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(<h1>code秘密花園 Hello, world!</h1>, document.getElementById('root'));
然后,我們為 babel
和 webpack
創建兩個配置文件:
.babelrc
:
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
webpack.config.js
:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
entry: './page/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',
};
然后,在 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>code秘密花園</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
下面檢查下你的 package.json
,看看和我的是不是一樣:
{
"name": "react-wasm",
"version": "1.0.0",
"description": "一個 Rust 編寫 React 組件的 Demo",
"main": "src/index.jsx",
"scripts": {
"dev": "webpack server"
},
"keywords": [],
"author": "ConardLi",
"license": "MIT",
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@babel/core": "^7.16.0",
"@babel/preset-env": "^7.16.4",
"@babel/preset-react": "^7.16.0",
"babel-loader": "^8.2.3",
"html-webpack-plugin": "^5.5.0",
"webpack": "^5.64.2",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.5.0"
}
}
下面,執行 npm install
,然后 npm run dev
,你就可以跑起來一個非常簡單的 React 應用:
引入 Rust
好了,下面我們來編寫我們的 Rust
組件(別忘了回顧下上面提到的 Rust
前置知識),首先我們使用 Rust
的包管理工具 cargo
來初始化一個簡單的 Rust 應用程序:
cargo init --lib .
執行完之后,會創建一個 Cargo.toml
和一個 src/lib.rc
文件。
然后,我們在 Cargo.toml
中引入 wasm-bindgen
這個包,另外我們還需要告訴編譯器這個包是一個 cdylib
:
[package]
name = "react-wasm"
version = "1.0.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
現在,你可以先嘗試執行下 cargo build
:
第一次執行可能會比較慢,可以
cargo
配置為國內源。
好了,上面只是測試一下構建,它現在還派不上用場,我們下面還要執行一下編譯目標,執行:
$ rustup target add wasm32-unknown-unknown
指定好 wasm32-unknown-unknown
這個編譯目標,我們才能把它應用到我們的 React
程序中,下面我們給我們的 src/lib.rs
寫兩個簡單的函數:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
fn alert(s: &str);
}
#[wasm_bindgen]
pub fn big_computation() {
alert("這個是一個超級耗時的復雜計算邏輯");
}
#[wasm_bindgen]
pub fn welcome(name: &str) {
alert(&format!("Hi 我是 {} ,我在 code秘密花園 !", name));
}
為了確保我們的 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_wasm.wasm --out-dir build
執行完成后,編譯好的 JavaScript
包和優化好的 Wasm
代碼會保存到我們的 build
目錄中,以供 React
程序使用。
在 React 程序中應用 Wasm
下面,我們嘗試一下在我們的 React
程序中用上這些 Wasm
代碼,我們現在 package.json
中添加一些常用的 npm
腳本:
"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
就可以打包所有代碼啦。
下面,我們還需要安裝一下上面我們提到的 wasm-pack
的 Webpack
插件,它可以幫助我們把 Wasm
代碼打包成 NPM
模塊:
npm i -D @wasm-tool/wasm-pack-plugin
最后更新一下我們的 webpack.config.js
,添加下面的配置:
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
...
plugins: [
...
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, ".")
}),
],
...
experiments: {
asyncWebAssembly: true
}
下面,執行一下這幾個命令:npm run build:wasm、npm run build:bindgen、npm run build
,應該都不會報錯。
最后,我們在我們的 React
組件中調用一下我們剛剛生成的 Wasm
模塊:
import React, { useState } from "react";
import ReactDOM from "react-dom";
const wasm = import("../build/rusty_react");
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"));
});
下面,你就可以在 React
組件中愉快的使用 Rust
了!
參考
- https://www.rust-lang.org/learn
- https://rustwasm.github.io/
- https://www.joshfinnie.com/blog/using-webassembly-created-in-rust-for-fast-react-components/
-
轉自https://mp.weixin.qq.com/s/dknF5W-Quh_o4fqTlptCCA