Rust Web 开发指北
尝试使用 rust 开发一个简单的 web 页面。使用 yew 框架,在 Chrome 运行 WebAssembly,并能在 IE 11 上降级到 JS 运行。
环境准备
- rust、rustup、cargo
cago-generate
, more detail$ cargo install cargo-generate
wasm-pack
, more detail$curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
wasm2js
, more detail$ brew install binaryen
- 速度不行的话,自行切换至 中科大 或 清北 的镜像,推荐中科大
初始化
参考 Rust and WebAssembly 的 HelloWorld 样例,使用以下命令进行初始化:
$ cargo generate --git https://github.com/rustwasm/wasm-pack-template
输入名称:hello-rust-wasm
初始化完成后,目录结构如下:
.
├── Cargo.toml
├── LICENSE_APACHE
├── LICENSE_MIT
├── README.md
├── src
│ ├── lib.rs
│ └── utils.rs
└── tests
└── web.rs
编辑页面
由于 rust 初始化时,是按模块初始化的,所以主要代码写在 lib.rs
中(正常初始化为入口为 main.rs
),再由外部 JS 引入之后,启动执行。
为了更贴近实际开发,我们使用 yew 框架进行开发,整体代码参考如下:
- 添加 yew 依赖
[dependencies]
yew = { version = "0.20.0", features = ["csr"] }
- 页面内容
mod utils;
use wasm_bindgen::prelude::*;
use yew::prelude::*;
#[wasm_bindgen]
extern {
fn alert(s: &str);
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[function_component]
fn App() -> Html {
html! {
<div>
<h1>{"Hello Rust"}</h1>
</div>
}
}
#[wasm_bindgen]
pub fn start() {
log("Hello Rust!");
yew::Renderer::<App>::new().render();
}
- 测试构建,成功之后,会于根路径产生
./pkg/*.js
和./pkg/*.wasm
$ wasm-pack build
[INFO]: 🎯 Checking for the Wasm target...
[INFO]: 🌀 Compiling to Wasm...
[INFO]: ⬇️ Installing wasm-bindgen...
[INFO]: found wasm-opt at "/opt/homebrew/bin/wasm-opt"
[INFO]: Optimizing wasm binaries with `wasm-opt`...
[INFO]: Optional fields missing from Cargo.toml: xxx
[INFO]: ✨ Done in 1.46s
[INFO]: 📦 Your wasm pkg is ready to publish at xxx
$ tree ./pkg
./pkg
├── README.md
├── hello_rust_wasm.d.ts
├── hello_rust_wasm.js
├── hello_rust_wasm_bg.js
├── hello_rust_wasm_bg.wasm
├── hello_rust_wasm_bg.wasm.d.ts
└── package.json
0 directories, 7 files
渲染页面
参考 Rust and WebAssembly ,使用以下命令在项目下初始化 web 应用。
# 初始化
$ npm init wasm-app www
$ cd www
# 使用上层我们刚刚构建的 pkg 产物作为我们的业务依赖
# ⚠️ 此处阿里内部同学不要使用 tnpm 等,必须使用 npm,否则指向会有问题
$ npm install hello-rust-wasm@file:../pkg -S
修改 wwww/index.js
文件
import * as wasm from "hello-rust-wasm";
wasm.start();
启动测试并预览:
$ npm start
$ open http://localhost:8080
WASM 兼容 IE11
参考 wasm2js,对于不支持 WebAssembly 的浏览器,可以通过 wasm2js
工具将 WebAssembly 编译到 JS 进行执行。
# 临时编译文件
$ wasm2js ./pkg/hello_rust_wasm_bg.wasm -o ./pkg/fallback-temp.wasm.js
$ cp ./pkg/hello_rust_wasm_bg.js ./pkg/fallback-temp.js
# 修改入口和引用
$ sed 's/hello_rust_wasm_bg/fallback/g' pkg/fallback-temp.wasm.js > pkg/fallback.wasm.js
$ sed 's/hello_rust_wasm_bg.wasm/fallback.wasm.js/' pkg/fallback-temp.js > pkg/fallback.js
# 于是,可以得到 fallback 在 IE11 的降级逻辑代码
$ tree ./pkg
./pkg
├── README.md
├── fallback-temp.js
├── fallback-temp.wasm.js
├── fallback.js
├── fallback.wasm.js
....
└── package.json
渲染兼容 IE 11
添加依赖
$ cd www
$ npm i @babel/core @babel/preset-env babel-loader@8 -D
$ npm i core-js text-encoding -S
添加 fallback.js
import * as wasm from "hello-rust-wasm/fallback.js";
wasm.start();
添加 polyfill.js
,也可以先使用 https://polyfill.io/v3/url-builder/ 来做简单的 polyfill 测试。
import "core-js/modules/es.promise.js";
import "core-js/modules/es.array.fill.js";
import "core-js/modules/es.math.imul.js";
import "core-js/modules/es.math.clz32.js";
import TextEncodingPolyfill from 'text-encoding';
if (typeof window['TextEncoder'] !== 'function') {
window['TextEncoder'] = TextEncodingPolyfill.TextEncoder;
window['TextDecoder'] = TextEncodingPolyfill.TextDecoder;
}
修改 HTML 模板,添加 polyfill 依赖,并使其适配 fallback 的逻辑
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello wasm-pack!</title>
</head>
<body>
<script src="./polyfill.js"></script>
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
<script>
if (typeof WebAssembly === 'object' && !/wasm=0/.test(location.href)) {
document.write('<script src="./bootstrap.js"><\/script>');
} else {
document.write('<script src="./fallback.js"><\/script>');
}
</script>
</body>
</html>
修改 webpack 打包逻辑
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');
module.exports = {
entry: {
bootstrap: './bootstrap.js',
fallback: './fallback.js',
polyfill: './polyfill.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.m?js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
targets: { chrome: '50', ie: '11' },
},
],
],
},
},
},
],
},
plugins: [new CopyWebpackPlugin(['index.html'])]
};
为了方便上述的命令一次性执行,可以添加一个 build.sh
文件
#!/bin/bash
# build WebAssembly
rm -rf ./pkg
wasm-pack build --release
# temp files
wasm2js ./pkg/hello_rust_wasm_bg.wasm -o ./pkg/fallback-temp.wasm.js
cp ./pkg/hello_rust_wasm_bg.js ./pkg/fallback-temp.js
sed 's/hello_rust_wasm_bg/fallback/g' pkg/fallback-temp.wasm.js > pkg/fallback.wasm.js
sed 's/hello_rust_wasm_bg.wasm/fallback.wasm.js/' pkg/fallback-temp.js > pkg/fallback.js
# build web app
cd www
rm -rf ./dist
npm run build
最终效果
在线预览地址:https://unpkg.com/hello-rust-wasm@0.2.0/dist/index.html
Chrome 下使用 wasm 运行(当然,你可以判断一下环境,来优化一下 polyfill 的加载)
IE 11 下降级到 JS 运行
小结
WebAssembly 可以在业务场景下进行小规模试验,目前初步具备整体的生态工具链,包括 rust 打包、WebAssembly 打包、以及 wasm2js 的降级兼容处理等等。
Rust 作为前端领域最具未来投资价值的语言,将前端的上限做了极大的提升。也希望通过本文,能引起更多前端同学的关注和尝试,引导前端做更多的 Rust 实践。通过实践,一定需要通过实践(落地一个业务场景)来更加强力地驱动 rust 的落地(包括但不限于 操作系统、WebApp、WAVM for non-Browser、WebContainer for Desktop-App、WebVM for Serverless 等等),并实现自身更广阔的职业发展空间。