Rust Is The Future of JavaScript Infrastructure 這篇文章講述了 Rust 正在 JS 基建圈流行的事實:Webpack、Babel、Terser、Prettier、ESLint 這些前些年才流行起來的工具都已有了 Rust 替代方案,且效能有著 10~100 倍的提升。
前端基建的迭代浪潮從未停歇,當上面這些工具給 Gulp、js-beautify、tslint 等工具蓋上棺材蓋時,基於 Rust 的新一代構建工具已經悄悄將棺材蓋懸掛在 webpack、babel、prettier、terser、eslint 它們頭上,不知道哪天就會蓋上。
原文已經有了不錯的 中文翻譯,值得一提的是,原文一些英文名詞對應著特定中文解釋,記錄如下:
- low-level programming:
低階程式設計底層程式設計。 - ergonomics:
人體工程學人機工程學。 - opinionated:
自以為是,固執的開箱即用的。 - critical adoption:
批判性採用技術選型臨界點。
精讀
本文不會介紹 Rust 如何使用,而會重點介紹原文提到的 Rust 工具鏈的一些基本用法,如果你感興趣,可以立刻替換現有的工具庫!
swc
swc 是基於 Rust 開發的一系列編譯、打包、壓縮等工具,並且被廣泛應用於更多更上層的 JS 基建,大大推動了 Rust 在 JS 基建的影響力,所以要第一個介紹。
swc 提供了一系列原子能力,涵蓋構建與執行時:
@swc/cli
@swc/cli
可以同時構建 js 與 ts 檔案:
const a = 1
npm i -D @swc/cli
npx swc ./main.ts
# output:
# Successfully compiled 1 file with swc.
# var a = 1;
具體功能與 babel 類似,都可以讓瀏覽器支援先進語法或者 ts,只是 @swc/cli
比 babel 快了至少 20 倍。可以通過 .swcrc
檔案做 自定義配置。
@swc/core
你可以利用 @swc/core
製作更上層的構建工具,所以它是 @swc/cli
的開發者呼叫版本。基本 API 來自官網開發者文件:
const swc = require("@swc/core");
swc
.transform("source code", {
// Some options cannot be specified in .swcrc
filename: "input.js",
sourceMaps: true,
// Input files are treated as module by default.
isModule: false,
// All options below can be configured via .swcrc
jsc: {
parser: {
syntax: "ecmascript",
},
transform: {},
},
})
.then((output) => {
output.code; // transformed code
output.map; // source map (in string)
});
其實就是把 cli 呼叫改成了 node 呼叫。
@swc/wasm-web
@swc/wasm-web
可以在瀏覽器執行時呼叫 wsm 版的 swc,以得到更好的效能。下面是官方的例子:
import { useEffect, useState } from "react";
import initSwc, { transformSync } from "@swc/wasm-web";
export default function App() {
const [initialized, setInitialized] = useState(false);
useEffect(() => {
async function importAndRunSwcOnMount() {
await initSwc();
setInitialized(true);
}
importAndRunSwcOnMount();
}, []);
function compile() {
if (!initialized) {
return;
}
const result = transformSync(`console.log('hello')`, {});
console.log(result);
}
return (
<div className="App">
<button onClick={compile}>Compile</button>
</div>
);
}
這個例子可以在瀏覽器執行時做類似 babel 的事情,無論是低程式碼平臺還是線上 coding 平臺都可以用它做執行時編譯。
@swc/jest
@swc/jest
提供了 Rust 版本的 jest 實現,讓 jest 跑得更快。使用方式也很簡單,首先安裝:
npm i @swc/jest
然後在 jest.config.js
配置檔案中,將 ts 檔案 compile 指向 @swc/jest
即可:
module.exports = {
transform: {
"^.+\\.(t|j)sx?$": ["@swc/jest"],
},
};
swc-loader
swc-loader
是針對 webpack 的 loader 外掛,代替 babel-loader
:
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules)/,
use: {
// `.swcrc` can be used to configure swc
loader: "swc-loader"
}
}
];
}
swcpack
增強了多檔案 bundle 成一個檔案的功能,基本可以認為是 swc 版本的 webpack,當然效能也會比 swc-loader
方案有進一步提升。
截至目前,該功能還在測試階段,只要安裝了 @swc/cli
就可使用,通過建立 spack.config.js
後執行 npx spack
即可執行,和 webpack 的使用方式一樣。
Deno
Deno 的 linter、code formatter、文件生成器採用 swc 構建,因此也算屬於 Rust 陣營。
Deno 是一種新的 js/ts 執行時,所以我們總喜歡與 node 進行類比。quickjs 也一樣,這三個都是一種對 js 語言的執行器,作為開發者,需求永遠是更好的效能、相容性與生態,三者幾乎缺一不可,所以當下雖然不能完全代替 Nodejs,但作為高效能替代方案是很香的,可以基於他們做一些跨端跨平臺的解析器,比如 kraken 就是基於 quickjs + flutter 實現的一種高效能 web 渲染引擎,是 web 瀏覽器的替代方案,作為一種跨端方案。
esbuild
esbuild 是較早被廣泛使用的新一代 JS 基建,是 JS 打包與壓縮工具。雖然採用 Go 編寫,但效能與 Rust 不相上下,可以與 Rust 風潮放在一起看。
esbuild 目前有兩個功能:編譯和壓縮,理論上分別可代替 babel 與 terser。
編譯功能的基本用法:
require('esbuild').transformSync('let x: number = 1', {
loader: 'ts',
})
// 'let x = 1;\n'
壓縮功能的基本用法:
require('esbuild').transformSync('fn = obj => { return obj.x }', {
minify: true,
})
// 'fn=n=>n.x;\n'
壓縮功能比較穩定,適合用在生產環境,而編譯功能要考慮相容 webpack 的地方太多,在成熟穩定後才考慮能在生產環境使用,目前其實已經有不少新專案已經在生產環境使用 esbuild 的編譯功能了。
編譯功能與 @swc
類似,但因為 Rust 支援編譯到 wsm,所以 @swc
提供了 web 執行時編譯能力,而 esbuild 目前還沒有看到這種特性。
Rome
Rome 是 Babel 作者做的基於 Nodejs 的前端基建全家桶,包含但不限於 Babel, ESLint, webpack, Prettier, Jest。目前 計劃使用 Rust 重構,雖然還沒有實現,但我們姑且可以把 Rome 當作 Rust 的一員。
rome
是個全家桶 API,所以你只需要 yarn add rome
就完成了所有環境準備工作。
rome bundle
打包專案。rome compile
編譯單個檔案。rome develop
除錯專案。rome parse
解析檔案抽象語法樹。rome analyzeDependencies
分析依賴。
Rome 還將檔案格式化與 Lint 合併為了 rome check
命令,並提供了友好 UI 終端提示。
其實我並不太看好 Rome,因為它負擔太重了,測試、編譯、Lint、格式化、壓縮、打包的瑣碎事情太多,把每一塊交給社群可能會做得更好,這不現在還在重構中,牽一髮而動全身。
NAPI-RS
NAPI-RS 提供了高效能的 Rust 到 Node 的銜接層,可以將 Rust 程式碼編譯後成為 Node 可呼叫檔案。下面是官網的例子:
#[js_function(1)]
fn fibonacci(ctx: CallContext) -> Result<JsNumber> {
let n = ctx.get::<JsNumber>(0)?.try_into()?;
ctx.env.create_int64(fibonacci_native(n))
}
上面寫了一個斐波那契數列函式,直接呼叫了 fibonacci_native
函式實現。為了讓這個方法被 Node 呼叫,首先安裝 CLI:npm i @napi-rs/cli
。
由於環境比較麻煩,因此需要利用這個腳手架初始化一個工作臺,我們在裡面寫 Rust,然後再利用固定的指令碼釋出 npm 包。執行 napi new
建立一個專案,我們發現入口檔案肯定是個 js,畢竟要被 node 引用,大概長這樣(我建立了一個 myLib
包):
const { loadBinding } = require('@node-rs/helper')
/**
* __dirname means load native addon from current dir
* 'myLib' is the name of native addon
* the second arguments was decided by `napi.name` field in `package.json`
* the third arguments was decided by `name` field in `package.json`
* `loadBinding` helper will load `myLib.[PLATFORM].node` from `__dirname` first
* If failed to load addon, it will fallback to load from `myLib-[PLATFORM]`
*/
module.exports = loadBinding(__dirname, 'myLib', 'myLib')
所以 loadBinding 才是入口,同時專案資料夾下存在三個系統環境包,分別供不同系統環境呼叫:
@cool/core-darwin-x64
macOS x64 平臺。@cool/core-win32-x64
Windows x64 平臺。@cool/core-linux-arm64-gnu
Linux aarch64 平臺。
@node-rs/helper
這個包的作用是引導 node 執行預編譯的二進位制檔案,loadBinding
函式會嘗試載入當前平臺識別的二進位制包。
將 src/lib.rs
的程式碼改成上面斐波那契數列的程式碼後,執行 npm run build
編譯。注意在編譯前需要安裝 rust 開發環境,只要一行指令碼即可安裝,具體看 rustup.rs。然後把當前專案整體當作 node 包釋出即可。
釋出後,就可以在 node 程式碼中引用啦:
import { fibonacci } from 'myLib'
function hello() {
let result = fibonacci(10000)
console.log(result)
return result
}
NAPI-RS 作為 Rust 與 Node 的橋樑,很好的解決了 Rust 漸進式替換現有 JS 工具鏈的問題。
Rust + WebAssembly
Rust + WebAssembly 說明 Rust 具備編譯到 wsm 的能力,雖然編譯後程式碼效能會變得稍慢,但還是比 js 快很多,同時由於 wsm 的可移植性,讓 Rust 也變得可移植了。
其實 Rust 支援編譯到 WebAssembly 也不奇怪,因為本來 WebAssembly 的定位之一就是作為其他語言的目標編譯產物,然後它本身支援跨平臺,這樣它就很好的完成了傳播的使命。
WebAssembly 是一個基於棧的虛擬機器 (stack machine),所以跨平臺能力一流。
想要將 Rust 編譯為 wsm,除了安裝 Rust 開發環境外,還要安裝 wasm-pack。
安裝後編譯只需執行 wasm-pack build
即可。更多用法可以檢視 API 文件。
dprint
dprint 是用 rust 編寫的 js/ts 格式化工具,並提供了 dprint-node 版本,可以直接作為 node 包,通過 npm 安裝使用,從 原始碼 可以看到,使用 NAPI-RS 實現。
dprint-node
可以直接在 Node 中使用:
const dprint = require('dprint-node');
dprint.format(filePath, code, options);
引數文件。
Parcel
Parcel 嚴格來說算是上一代 JS 基建,它出現在 Webpack 之後,Rust 風潮之前。不過由於它已經採用 SWC 重寫,所以姑且算是跟上了時髦。
總結
前端全家桶已經有了一整套 Rust 實現,只是對於存量專案的編譯準確性需要大量驗證,我們還需要時間等待這些庫的成熟度。
但毫無疑問的是,Rust 語言對 JS 基建支援已經較為完備了,剩下的只是工具層邏輯覆蓋率的問題,都可以隨時間而解決。而用 Rust 語言重寫後的邏輯帶來的巨幅效能提升將為社群注入巨大活力,就像原文說的,前端社群可以為了巨大效能提升而引入 Rust 語言,即便這可能導致為社群貢獻門檻的提高。
討論地址是:精讀《Rust 是 JS 基建的未來》· Issue #371 · dt-fe/weekly
如果你想參與討論,請 點選這裡,每週都有新的主題,週末或週一釋出。前端精讀 - 幫你篩選靠譜的內容。
關注 前端精讀微信公眾號
版權宣告:自由轉載-非商用-非衍生-保持署名(創意共享 3.0 許可證)