Webpack + React + WebAssembly + Rust 初体验

1,615 阅读2分钟

WebAssembly 是什么?

WebAssembly(wasm)是一种简单的机器模型和具有广泛规范的可执行格式。它被设计成可移植的、紧凑的,并且以或接近本机速度执行。

WebAssembly 有两种表示相同结构的格式:

  1. .wat 文本格式(WebAssembly Text)使用 S 表达式;
  2. .wasm 二进制格式,级别较低,直接供 wasm 虚拟机使用。

安装 Rust 的相关环境

安装 Rust

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

安装 wasm-pack

cargo install wasm-pack

创建 Rust 项目

cargo new --lib wasm-react-demo
  • 在 Carto.toml 添加 wasm_bindgen 依赖
[dependencies]
+ wasm-bindgen = "0.2"
  • 在 src/lib.rs 中添加如下代码
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern {
    fn alert(s: &str);
}

#[wasm_bindgen]
pub fn big_computation() {
    alert("超级耗时的复杂计算逻辑尽量在 rust 实现");
}

#[wasm_bindgen]
pub fn welcome(name: &str) {
    alert(&format!("我是 {}!", name));
}
  • 编译
wasm-pack build --target web

会提示需要配置

Error: crate-type must be cdylib to compile to wasm32-unknown-unknown. Add the following to your Cargo.toml file:

[lib]
crate-type = ["cdylib", "rlib"]

于是在 Cargo.toml 中配置

+ [lib]
+ crate-type = ["cdylib", "rlib"]

再次执行 wasm-pack build --target web 编译

  • 此时在项目根目录下生成 pkg 的文件夹

├── package.json
├── wasm_react_demo_bg.wasm
├── wasm_react_demo_bg.wasm.d.ts
├── wasm_react_demo.d.ts
└── wasm_react_demo.js

创建其React的项目

配置 webpack.config.js 的开发环境

  • 安装相关的包
npm i webpack webpack-cli webpack-dev-server html-webpack-plugin clean-webpack-plugin -D

npm i babel-loader @babel/plugin-transform-runtime @babel/preset-env @babel/preset-react -D

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

npm i react react-dom core-js -S

PS:
i => install
-D => --save-dev
-S => --save

  • 配置 webpack.config.js
const { ProgressPlugin } = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");

module.exports = {
  devtool: "inline-source-map",
  entry: "./app/index.jsx",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].[contenthash:8].bundle.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 ProgressPlugin(),
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: `${__dirname}/public/index.html`,
      filename: "index.html",
    }),
    new WasmPackPlugin({
      crateDirectory: path.resolve(__dirname, "."),
    }),
  ],
  experiments: {
    asyncWebAssembly: true,
  },
};

配置 babel 编译环境

  • 配置 babel.config.js
module.exports = {
  presets: [
    [
      "@babel/preset-env", // 编译基本的es6语法
      {
        useBuiltIns: "usage", // 按需引入兼容的实现的es6的一些浏览器不支持的方法,这样就不需要在入口文件引入 import "babel-polyfill"; 造成文件体积增大
        corejs: 3, // 配合使用一些不兼容的 es6 的函数实现, 3 版本已经不需要依赖 babel-polyfill
        targets: { // 这个配置放到 package.json 中配置了 "browserslist":{}
          chrome: "58",
          ie: "11"
        }
      }
    ],
    "@babel/preset-react"
  ],
  plugins: [
    "@babel/plugin-transform-runtime" // 编译 es7 的一些高级语法编译, 比如 async/await
  ]
}
  • 配置 package.json 的执行脚本
 "scripts": {
    "dev": "webpack serve --mode=development",
    "build": "webpack --mode=production",
    "build:wasm": "cargo build --target wasm32-unknown-unknown",
    "build:bindgen": "wasm-bindgen target/wasm32-unknown-unknown/debug/wasm_react.wasm --out-dir build",
    "build:all": "npm run build:wasm && npm run build:bindgen && webpack --mode=production"
  },
  • React 中调用 WebAssembly 中的函数
import React, { useState } from "react";

const App = () => {
  const rustApp = import("../pkg");

  const [name, setName] = useState("");

  const handleChange = (e) => {
    setName(e.target.value);
  };

  const big_computation = async () => {
    const r = await rustApp;
    r.big_computation();
  };

  const handleClick = async () => {
    const r = await rustApp;
    r.welcome(name);
  };

  return (
    <>
      <div>
        <h1>Hello React Rust WebAssembly</h1>
        <button onClick={big_computation}>Run Computation</button>
      </div>
      <div>
        <input type="text" onChange={handleChange} />
        <button onClick={handleClick}>Click me</button>
      </div>
    </>
  );
};

export default App;

传送门