使用 Rust 和 WebAssembly 來進行 Web 開發
直到一段時間以前,如果要進行 Web 開發,幾乎只有使用 JavaScript 這一個選項。但是隨著 WebAssembly 的發展,使用非 JavaScript 語言來進行 Web 開發似乎也變得越來越可行。本文就簡單介紹一下如何使用 Rust 和 WebAssembly 來進行 Web 開發。
什麼是 WebAssembly

WebAssembly 是一種非常高效的,可跨平台運行的低階二進制形式。 它可以被瀏覽器非常高效的執行。Chrome,Safari 和 Firefox 都支援 WebAssembly。
關於 WebAssembly,可以參考這個網站: https://webassembly.org/
為什麼選用 Rust?

自己選用 Rust 有這麼幾個理由:
- 最終可執行檔案的大小:由於要構建網站,所有東西都需要從網路下載。 於是如何控制大小便成為了一個很重要的課題。如果選擇一個運行時較大的語言 ,就會導致網站的載入速度變慢。
- 速度:使用過 React 的人都知道,有時 React 再次渲染時由於 Component 過多,或是某些運算過於依賴 CPU,這時就會導致卡頓。(當然,可以優化。)所以,既然要使用 WebAssembly,就不如選取一個高效的語言。
- 成熟度:現今在可以使用 WebAssembly 的語言中,Rust 可以說是最成熟的。
- 安全:Rust 一直都以安全著稱。它可以在編譯時發現很多問題。
選用框架

現在如果使用 JavaScript 構建網站,通常不會從頭開始,而是選用諸如 React 或是 Vue 等的框架進行構建。使用 Rust 來構建也是一樣。在 Rust 社群中較成熟的框架有兩個:Yew 和 Seed。
Yew 是採用類 React 架構,而 Seed 是參考的 Elm 架構。
由於自己平時使用 React 較多,所以自己選用的是 Yew。
0. 準備工作
Rust + wasm32 編譯工具鏈
由於本篇並不是介紹如何使用 Rust,所以略過安裝 Rust 的部分。
安裝完 Rust 後還需要使用下列命令來添加 wasm32 工具鏈:
rustup target add wasm32-unknown-unknownTrunk
在 macOS 下,可以使用下列命令進行安裝:
brew install trunk其它平台請參考:https://trunkrs.dev
利用 React 構建網站時,如果使用 Webpack Dev Server 就可以在開發時實時查看變更。即使使用 WebAssembly,雖然也可以使用 Webpack,但是還是需要一定 JavaScript 才能使用。那樣就大大減少使用 WebAssembly 的意義了。如果使用 Trunk,就可以在不使用任何 JavaScript 的情況下查看變更了。
1. 創建倉庫
要創建 Rust 倉庫,最簡單的方法是使用 Cargo。Cargo
是 Rust 的軟體包管理器,類似於 Python 的 pip 或是 JavaScript 的
npm 和 yarn。
在終端中輸入:
cargo new --bin first-yew-app
cargo add yew # 需要 cargo-edit
# 你可以使用 cargo install cargo-edit 來安裝 cargo-edit。
這樣 cargo 就會創建一個叫做 first-yew-app
的檔案夾,並將 Rust 檔案放到裏面。
2. 創建 index.html
要使用 Trunk,在 first-yew-app 檔案夾裡面,還需要一個 index.html。
在 index.html 裏面,不需要定義 <body /> 只需要 <head />。
在此附上一個簡單的 index.html:
<!DOCTYPE HTML>
<html>
<head>
<title>First Yew App</title>
</head>
</html>
關於更詳細的 index.html 的配置方法,請參考 Trunk
文檔。
3. 創建第一個 Component
和 React 一樣,Yew 也是基於 Component 的設計模式。Yew 目前只有 Struct Component(對應 React 的 Class Component),但是在將來的 0.19 版本中也將會引入 Function Component。
這裏,讓我們創建一個最簡單的 Component:
use yew::prelude::*;
pub struct App;
impl Component for App {
type Message = ();
type Properties = ();
fn create(_props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Self
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
false
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
false
}
fn view(&self) -> Html {
html! {
<div>{"Hello, World!"}</div>
}
}
}
在這裏附上一個 React 與 Yew Component 的語意轉換列表:
constructor()->create()render()->view()componentDidMount()->rendered()shouldComponentUpdate()->update()componentWillUpdate()->change()componentWillUnmount()->destroy()
4. 渲染 App
將 main 函數替換為下列內容:
fn main() {
yew::start_app::<App>();
}
然後在 first-yew-app 檔案夾下使用下列命令:
trunk serve --open
不出意外的話,你的瀏覽器將會自動打開一個寫著 Hello World! 的頁面。
5. 實現一個簡單的計數器
要對 Component 的內部狀態實現更新,需要使用 Message。
Message 可以為任意枚舉類型。
首先定義一個簡單的 Message:
#[derive(Debug)]
pub enum AppMsg {
Increment,
}
然後將 App 變更為:
pub struct App {
counter: u64,
link: ComponentLink<Self>,
}
App 類型現在有兩個成員,分別是 counter 和 link。counter
存儲有計數器變量。link 則是一個特殊類型 ComponentLink<Self>。
ComponentLink<Self> 是一個指向當前 Component 的 ComponentLink。
ComponentLink 可以用來將 Message 傳回 Component。
實現部分也需要替換為下列內容:
impl Component for App {
type Message = AppMsg;
type Properties = ();
fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self {
Self { counter: 0, link }
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
AppMsg::Increment => {
self.counter += 1;
}
}
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
false
}
fn view(&self) -> Html {
let increment = self.link.callback(|_| AppMsg::Increment);
html! {
<div>
{format!("Current Counter: {}", self.counter)}
<br />
<button onclick=increment>{"Increment"}</button>
</div>
}
}
}
首先,需要將關聯類型 Message 指定為 AppMsg,這樣 App 的
ComponentLink 才可以接收 AppMsg。每次 ComponentLink 收到一個 Message
都會調用 update 方法,在 update 方法中可以對 App
的成員進行變更。如果返回 true 則表示需要重新渲染。在 view
中可以定義渲染內容,在這裏創建一個輸出計數器的 div
和遞增計數器用的按鈕。
使用 self.link.callback 可以創建一個用於 Event
的回調。將這個回調賦予給按鈕的 onclick 就可以觸發 update
方法中處理 AppMsg::Increment 的邏輯了。
6. 最終結果

完整代碼: https://github.com/futursolo/fl-www-examples/tree/master/first-yew-app
7. 使用 Rust 進行 Web 開發的限制
由於使用 Rust 進行 Web 開發還是處在起步的階段,生態系統還不成熟。 所以可能會遇到各種問題。比如:
- 沒有 SSR(伺服器端渲染)
- 沒有比較可用的類似 Styled Components 的 CSS 庫(所以自己造了一個)
- 沒有 Function Component
- 沒有延遲加載(
React.lazy)
等等。
8. 寫在最後
其實使用 Rust 進行 Web 開發是在某種程度上可行的,但是由於當前
WebAssembly 和 Yew 的成熟度問題,在某些問題上還是需要一番探索。
讀到這裏,你也許已經猜到了,這個部落格也是使用 Rust + Yew + WebAssembly
寫成的。
大家也可以參考本站的原始碼:
