使用 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-unknown
Trunk
在 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
寫成的。
大家也可以參考本站的原始碼: