使用 WebAssembly 來增強你的前端應用

語言: CN / TW / HK

準備

此篇文章我們主要使用 Rust 來編寫我們的 WebAssembly 工具庫然後在 React 應用進行使用。所以需要準備好下列的環境

  • Rust 開發環境(rustup、rustc、cargo 這些)
  • 前端開發環境(node、npm 這些)
  • wasm-pack (cargo install wasm-pack)

開始

開始前我們先要準備一個前端應用,這裡我們使用 create-react-app 來建立一個 React 專案。 如果沒有安裝 create-react-app ,先用 pnpm (npm / yarn 也行)全域性安裝一下

bash npm install -g create-react-app

建立專案

使用 typescript 模板來建立專案,以獲得更好的型別提示。

bash create-react-app react-wasm --template typescript

進入到剛建立好的專案目錄中 bash cd react-wasm

建立好 react 專案之後,我們接下來使用 wasm-pack 建立一個 wasm 工具庫

bash wasm-pack new wasm-lib

建立完成後我們的目錄結構是像下面這樣子的,

src 目錄主要編寫前端應用程式碼

wasm-lib/src 目錄主要編寫 rust wasm 程式碼

image.png

wasm-lib/src/lib.rs 目錄下,這裡就是我們可以進行編寫供前端呼叫的 WebAssembly 函式。這裡預設寫好了一個 greet 函式,然後裡面呼叫了 alert 彈框。接下來我們就去看看如何在前端中進行使用這個函式。

```rust mod utils;

use wasm_bindgen::prelude::*;

// 當 wee_alloc 特性開啟的時候, 使用 wee_alloc 作為全域性分配器

[cfg(feature = "wee_alloc")]

[global_allocator]

static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

[wasm_bindgen]

extern "C" { fn alert(s: &str); }

[wasm_bindgen]

pub fn greet() { alert("Hello, wasm-lib!"); }

```

打包 wasm

在 package.json 檔案中新增這一行程式碼,然後我們就可以進行打包了。

image.png

專案根目錄中執行 bash npm run build:wasm 如果在 build 的過程卡住了,或者報錯了。可以先暫時把程式碼優化給關閉掉

在 wasm-lib 目錄下的 Cargo.toml 配置檔案中關掉 wasm-opt

image.png

打包完成後就能看到專案根目錄中出現了一個 pkg 資料夾,這裡面就是我們打包後的產物,前端可以進行呼叫我們用 Rust 編寫的函式。

image.png

前端呼叫

我們有兩種方式去呼叫 pkg 目錄下的 wasm 函式:

一種是將 pkg 作為一個 npm 包釋出 ```bash cd wasm-lib

該命令可以把這個庫作為 npm 包釋出到 npm 中,然後我們前端就正常的方式 npm install 就可以使用了

wasm-pack publish ```

另一種是先在本地進行引用,根目錄下安裝

bash npm install ./pkg 然後 package.json 的依賴中增加了我們的本地庫:

```json { "name": "react-wasm", "version": "0.1.0", "private": true, "dependencies": { "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.3.0", "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.1", "@types/node": "^16.11.36", "@types/react": "^18.0.9", "@types/react-dom": "^18.0.5", "react": "^18.1.0", "react-dom": "^18.1.0", "react-scripts": "5.0.1", "typescript": "^4.7.2", "wasm-lib": "file:pkg", // ++++++++++++++++++++++++++++ "web-vitals": "^2.1.4" },

// ... } ```

接下來就去我們的 App.tsx 中呼叫 greet 方法:

```tsx import React, { useEffect } from "react"; import init, { greet } from "wasm-lib"; import logo from "./logo.svg"; import "./App.css";

function App() { // +++++++++++++++++ useEffect(() => { init().then(() => { greet(); }); }, []);

return (

...
); }

export default App; ```

然後開始執行我們的 react 應用

bash npm run start

開啟 localhost:3000 埠,可以看到我們呼叫 alert 方法成功咯~

image.png

使用 console.log

首先我們要安裝一下 web-sys 這個包,在 Cargo.toml 中加入

toml [dependencies] wasm-bindgen = "0.2.63" web-sys = { version = "0.3.57", features = ['console'] } # ++++++++

在這裡我們開啟了 console 這個 feature, web-sys 還支援很多 feature,比如可以操作 DOM,呼叫前端原生 fetch API , 操作 Canvas , WebGL 等等等。

Rust:

```rust mod utils; use web_sys::console;

use wasm_bindgen::prelude::*;

[cfg(feature = "wee_alloc")]

[global_allocator]

static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

[wasm_bindgen]

pub fn test_log(val: JsValue) { console::log_2(&"從JS端接受到資料".into(), &val); } ```

JS:

```tsx import React, { useEffect } from "react"; import init, { test_log } from "wasm-lib"; import logo from "./logo.svg"; import "./App.css";

function App() { useEffect(() => { init().then(() => { test_log({ a: 1, b: 2, c: 3 }); }); }, []);

return (

...
); }

export default App;

```

列印結果:

image.png

總結

像一些複雜的計算,我們可以使用 WebAssembly 來進行操作,比如上一篇。 WebAssembly 目前論開發效率來說個人感覺是不如原生 JS 的,稍微有點繁瑣,目前感覺它的應用場景更多是在複雜性的計算領域,比如寫遊戲、音視訊解析、加密解密、圖片解析等這種需要複雜計算的。這裡只是提供一個基礎的小demo,讓大家對這個東西有一個感性的瞭解,更深入的東西就得後面用到的時候再去了解了。

最後這裡放上 官方參考文件