Rust 在前端領域的應用
什麼是 Rust?
Rust 語言原本是 Mozilla 員工 Graydon Hoare[1] 的私人項目,Mozilla 於 2009 年開始贊助這個項目,在 2010 年官方首次透露並於 2015 年發佈 1.0 版本。在過去的十年中,編程語言 Rust 一直是一項突破性的技術,Rust 始終站在獨特的學術研究和行業實用性結合的挑戰視角。但是如果說 Rust 的影響僅僅是技術性的,那就錯過了精髓,正如社區在 2016 年討論的系列博客一樣:"Rust 不僅是編程語言或編譯器 [2]"和"Rust 讓一切觸手可及 [3]",同年 Rust 宣佈了其官方口號:“一種讓每個人都能夠構建可靠和高效軟件的語言”。
2021 年 2 月 8 日,Rust 基金會宣佈成立,其基金會董事成員有:AWS、Google、華爲、微軟、Mozilla 。Rust 基金會誕生自 Rust 核心團隊,並且得到了五位全球行業領先公司的財務承諾,這標誌着 Rust 向成熟化邁出了堅實的一步。
爲什麼是 Rust?
性能
各種編程語言內存管理的方式不同,但通常有以下兩種方式:
-
開發者自己分配和銷燬: 比如 C、C++ 等,這種方式相當於把所有權力開放給開發者,管理不當容易內存泄漏。
-
編程語言提供自動垃圾回收機制: 比如 JavaScript、Java、Python 等,這種方式會產生運行時開銷,對性能可能產生影響(注意這裏是 “可能”,沒有辦法證明性能一定比開發者自己管理要差)。
Rust 則另闢蹊徑採用所有權、借用、生命週期機制在編譯期自動插入內存釋放邏輯來實現內存管理,由於沒有了垃圾回收產生的運行時開銷,Rust 整體表現的速度驚人且內存利用率極高。
fn main() {
let a = String::from("hello rust");
let b = a; // 所有權被轉移
println!("{}", a); // 編譯失敗!a 已經被釋放,無法再使用
}
在一項比較 REST API 性能的基準測試中(Rust 使用 Rocket[4],Node.js 使用 Restify[5]),Rust 每秒處理 72,000 個請求,而 Node.js 爲 8,000 個,空閒時使用的內存大約爲 1MB,而 Node.js 爲 19MB。在另一個測試中(Rust 使用 Nickel[6],Node.js 使用 Restana[7]),Rust 對請求的平均響應速度比 Node.js 快近 100 倍。
具體數據可以參考 Web Frameworks Benchmark[8]。
可靠性
Rust 豐富的類型系統和所有權模型保證了內存安全和線程安全,讓您在編譯期就能夠消除各種各樣的錯誤,關於 “內存安全” 和“線程安全”的解釋如下:
內存安全: 在具有內存安全性的編程語言中,所有內存訪問都是明確定義的,通常內存不安全的情況包含:空指針、野指針、懸空指針、使用未初始化的指針、非法釋放、緩衝區溢出、執行非法函數指針、數據競爭等。
據說微軟 70% 的漏洞是內存安全問題
線程安全: 線程安全是程序設計中的術語,指某個函數、函數庫在多線程環境中被調用時,能夠正確地處理多個線程之間的共享變量,使程序功能正確完成。
生產力
Rust 擁有出色的文檔、友好的編譯器和清晰的錯誤提示信息, 還集成了一流的包管理器和構建工具, 智能地自動補全和類型檢驗的多編輯器支持, 以及自動格式化代碼等等。
Rust 在前端構建工具中的應用
Deno
Github((80.9K star):https://github.com/denoland/deno[9]
Deno 是一個簡單、現代且安全的 JavaScript 和 TypeScript 運行時,它使用 V8 並基於 Rust 構建。Deno 是由 Node.JS 之父 Ryan Dahl[10] 創建,在 2018 JS Conf Berlin 上借演講《Design Mistakes in Node》[11] 首次對外公開。
Deno 誕生之初就是爲了解決 Node 不安全和糟糕包的管理等老生常談的問題,其中不安全時常令 Node.JS 開發者感到頭疼和憤怒,近期也剛剛發生 node-ipc 事件(node-ipc 在所有用戶的桌面上都會創建一個文件來宣傳作者的政治觀點),影響到了衆多開源項目包括 Vue CLI 等。
因此 Deno 很自然地擁有以下特性:
-
默認安全,除非特別啓用它,否則使用 Deno 運行的程序沒有文件、網絡或環境訪問權限
deno run --allow-read mod.ts
-
開箱即用地支持 TypeScript
-
僅編譯單個可執行文件
-
擁抱 Web 生態標準,內置了 fetch、localStorage、location 等 API
localStorage.setItem("myDemo", "Deno App"); // 擁有 10M 的持久化存儲限制
-
內置依賴檢查器
deno info
和代碼格式化工具deno fmt
-
有一組經過審查的標準模塊,可以與 Deno 一起使用:deno.land/std[12]
目前已經有衆多公司正在積極探索 Deno,包括 Amazon、Github、IBM、Vercel、Tencent、Microsoft 等頭部技術公司。
SWC
Github(21K star):https://github.com/swc-project/swc[13]
SWC 是一個可擴展的基於 Rust 的前端構建工具,目前核心功能相當於 Babel,包含以下這些模塊:
官方提供的基準測試數據如下:
SWC 在單線程上比 Babel 快 20 倍,在四核上快 70 倍。
目前 SWC 已經被 Next.js、Parcel 和 Deno 等工具以及 Vercel、字節跳動、騰訊、Shopify 等公司廣泛使用。
Parcel
Github(40k star):https://github.com/parcel-bundler/parcel[14]
支持以 HTML 作爲入口的零配置構建工具,Parcel 支持多種開箱即用的語言和文件類型,從 HTML、CSS 和 JavaScript 等 Web 技術到圖像、字體、視頻等資產。當您使用默認不包含的文件類型時,Parcel 將自動爲您安裝所有必要的插件和開發依賴項。Parcel 的 JavaScript 編譯器和源映射是建立在 SWC[15] 編譯器之上的,在 SWC 之上,Parcel 實現了依賴項收集、捆綁、搖樹優化、熱重載等。
目前 Parcel 已經被廣泛應用在微軟、Atlassian、SourceGraph 等公司。
Rome
Github(17.2 star):https://github.com/rome/tools[16]
Rome[17] 是 Babel 作者做的基於 Node.js 的前端構建全家桶,包含但不限於 JavaScript、TypeScript、JSON、HTML、Markdown 和 CSS,在 2021 年 9 月 21 日 宣佈計劃使用 Rust 重構。
其它工具(WIP)
dprint[18]:使用 Rust 編寫,比 Prettier 快 30x 倍
postcss-rs[19]:使用 Rust 編寫,比 Postcss 快 20x 倍
Rust 在桌面應用開發中的應用
Tauri
Github(34.7 star):https://github.com/tauri-apps/tauri[20]
在很長一段時間裏,包括現在,Electron[21] 都是最流行的跨平臺桌面應用開發框架,目前在 Github 上有 101K 個 star,它允許你使用純粹的前端技術(HTML、CSS、JS、Node.JS)來構建桌面應用,不過它也有兩個比較明顯的缺陷被人詬病:包體積太大和內存佔用高,而造成這兩個問題的根本原因是 Electron 是基於 Chromium 和 Node.JS 構建的。
Tauri 是 Electron 的代替品,現在 Tauri 試圖去除 Chromium 轉而使用 Rust 去和系統內置 Webview 進行綁定,簡單來說就是在 Electon 時代你的應用永遠使用 Chromium 內核,而在 Tauri 時代,你的應用在 Windows 上使用 Edge/Webview2,在 macOS 上使用 WebKit,在 Linux 上使用 WebKitGTK。基於 Rust 和 Webview 的好處很明顯:包體積極小且內存佔用極低。以下是官方提供的數據:
看起來還不錯的樣子,不過別忘了如果你使用 Tauri 開發的話,後端(Electron 中叫主進程)目前只能使用 Rust,這將帶來不小的學習成本,除此之外 Tauri 還有很長的路需要走,不過也算文藝復興式的創新。
Rust 在 WebAssembly 中的應用
WebAssembly 是一種新的編碼方式,具有緊湊的二進制格式,可以在現代的網絡瀏覽器中以接近原生的性能運行,目前 Rust 和 WebAssembly 結合有兩大主要用例:
-
整個 Web 應用都基於 Rust 開發:比如 Yew 等框架
-
在現存的 JavaScript 前端中使用 Rust
Yew
Github(Github 20k star):https://github.com/yewstack/yew[22]
Yew 是一個設計先進的 Rust 框架,目的是使用 WebAssembly[23] 來創建多線程的前端應用,它有幾個特點:
-
基於組件的框架,可以輕鬆地創建交互式 UI。擁有 React[24] 或 Elm[25] 等框架經驗的開發人員在使用 Yew 時會感到得心應手。
-
高性能 ,前端開發者可以輕易地將工作分流至後端來減少 DOM API 的調用,從而達到異常出色的性能。(又是一個 “文藝復興式創新”...😂)
-
支持與 JavaScript 交互 ,允許開發者使用 npm 包,並與現有的 JavaScript 應用程序結合。
一個簡單的 Yew 應用代碼如下所示:
use yew::prelude::*;
enum Msg {
AddOne,
}
struct Model {
value: i64,
}
impl Component for Model {
type Message = Msg;
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self {
value: 0,
}
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::AddOne => {
self.value += 1;
// the value has changed so we need to
// re-render for it to appear on the page
true
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
// This gives us a component's "`Scope`" which allows us to send messages, etc to the component.
let link = ctx.link();
html! {
<div>
<button onclick={link.callback(|_| Msg::AddOne)}>{ "+1" }</button>
<p>{ self.value }</p>
</div>
}
}
}
fn main() {
yew::start_app::<Model>();
}
wasm-bindgen
Github(5.0k star):https://github.com/rustwasm/wasm-bindgen[26]
目前 WebAssembly 類型系統還很小,只有四種數字類型,如果要使用複雜類型(例如字符串、對象、數組、結構體),需要花點心思:
-
將字符串或對象轉換爲 WebAssembly 模塊可以理解的東西
-
將 WebAssembly 模塊的返回值轉換爲 JavaScript 可以理解的字符串或對象
但是每次轉換它們(序列化爲線性內存,並提供它們所在位置的引用)是一項枯燥的工作並且容易出錯,幸運的是,Rust world 想出了 wasm-bindgen
來促進 WebAssembly 模塊和 JavaScript 之間的高級交互,其使用方式也非常簡單:
-
創建一個 Rust 項目
$ cargo new --lib hello_world Created library `hello_world` package
-
打開
Cargo.toml
文件並添加wasm-bindgen
依賴項[package] name = "hello_world" version = "0.1.0" authors = ["Sendil Kumar <sendilkumarn@live.com>"] edition = "2018" [lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "0.2.56"
-
打開
src/lib.rs
文件並將內容替換爲以下內容use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn hello_world() -> String { "Hello World".to_string() }
-
編譯成 wasm 模塊
cargo build --target=wasm32-unknown-unknown
-
安裝
wasm-bindgen-cli
,並將 wasm 文件轉換成 JavaScript 文件cargo install wasm-bindgen-cli wasm-bindgen target/wasm32-unknown-unknown/debug/hello_world.wasm --out-dir . # ls -lrta # 76330 hello_world_bg.wasm # 1218 hello_world.js # 109 hello_world.d.ts # 190 hello_world_bg.d.ts
-
然後你就可以使用
hello_world.js
文件了,它可以幫你加載 wasm 文件。
wasm-pack
Github(4.1k star):https://github.com/rustwasm/wasm-pack[27]
這是一個可以直接將你的 Rust 代碼打包成 npm 包的工具,用法十分簡單,只有 4 個命令:
-
new
:使用模板生成一個新的 Rust Wasm 項目 -
build
: 從 rustwasm crate 生成一個 npm wasm pkg -
test
:運行瀏覽器測試 -
pack
和publish
:創建壓縮包,發佈到鏡像倉庫
值得注意的是,WebAssembly 目前還並不是提高 Web 應用性能的萬金油,就目前來說,在 WebAssembly 中使用 DOM API 仍然比從 JavaScript 中調用要慢。但只是暫時性問題的,WebAssembly Interface Types[28] 計劃將解決這個問題。如果你想要了解更多關於這方面的信息,可以查看 Mozilla 的這篇文章 [29] 。
Rust 和 Node 的綁定
NAPI-RS
Github(2.2k star):https://github.com/napi-rs/napi-rs[30]
NAPI-RS 是一個用於在 Rust 中構建預編譯的 Node.js 插件的框架,SWC 便基於此庫。
詳細信息可以看《用 Rust 和 N-API 開發高性能 Node.js 擴展》[31] 這篇文章。
寫在最後
隨着前端開發複雜度的不斷上升,配套工具鏈的效率將不容被忽視,受限於 Node.js 語言本身的效率問題,近幾年將會有更多工具會被 Rust 重寫,效率有望數倍乃至數十倍的提升,另外,Rust 和 WebAssembly 的結合也令人感到激動,但就目前來看離大規模上生產還有相當一段路需要走。
無論如何,對前端開發者而言現在是學習 Rust 的最佳時機。
參考資料
[1] Graydon Hoare: https://github.com/graydon
[2] Rust 不僅是編程語言或編譯器: https://graydon2.dreamwidth.org/247406.html
[3] Rust 讓一切觸手可及: https://www.thefeedbackloop.xyz/safety-is-rusts-fireflower/
[4] Rocket: https://rocket.rs/
[5] Restify: http://restify.com/
[6] Nickel: https://github.com/nickel-org/nickel.rs
[7] Restana: https://github.com/BackendStack21/restana
[8] Web Frameworks Benchmark: https://web-frameworks-benchmark.netlify.app/compare?f=express,rocket
[9] https://github.com/denoland/deno: https://github.com/denoland/deno
[10] Ryan Dahl: https://github.com/ry
[11] 《Design Mistakes in Node》: https://liguo.run/posts/node-mistakes
[12] deno.land/std: https://deno.land/std
[13] https://github.com/swc-project/swc: https://github.com/swc-project/swc
[14] https://github.com/parcel-bundler/parcel: https://github.com/swc-project/swc
[15] SWC: https://swc.rs/
[16] https://github.com/rome/tools: https://github.com/rome/tools
[17] Rome: https://rome.tools/blog/2020/08/08/introducing-rome
[18] dprint: https://github.com/devongovett/dprint-node
[19] postcss-rs: https://github.com/postcss-rs/postcss-rs
[20] https://github.com/tauri-apps/tauri: https://github.com/tauri-apps/tauri
[21] Electron: https://www.electronjs.org/
[22] https://github.com/yewstack/yew: https://github.com/yewstack/yew
[23] WebAssembly: https://webassembly.org/
[24] React: https://reactjs.org/
[25] Elm: https://elm-lang.org/
[26] https://github.com/rustwasm/wasm-bindgen: https://github.com/rustwasm/wasm-bindgen
[27] https://github.com/rustwasm/wasm-pack: https://github.com/rustwasm/wasm-pack
[28] WebAssembly Interface Types: https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md
[29] 這篇文章: https://hacks.mozilla.org/2019/08/webassembly-interface-types/
[30] https://github.com/napi-rs/napi-rs: https://github.com/napi-rs/napi-rs
[31] 《用 Rust 和 N-API 開發高性能 Node.js 擴展》: https://zhuanlan.zhihu.com/p/234914336
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/v2N08wGoqrevDWlKsnNHGw