今天給大家?guī)?lái)的主題是Farm,即一個(gè)快速、強(qiáng)大、本地與生產(chǎn)環(huán)境保持一致的 Web 打包器,目標(biāo)是解決 Vite 面臨的主要問(wèn)題。話不多說(shuō),直接開(kāi)始!
1.Vite 的不足
隨著 web 項(xiàng)目規(guī)模的擴(kuò)大,構(gòu)建性能成為了開(kāi)發(fā)者面臨的主要問(wèn)題。對(duì)于一個(gè)巨大的前端項(xiàng)目而言,使用 webpack 編譯可能需要 10 分鐘甚至更長(zhǎng)時(shí)間,而熱更新可能需要超過(guò) 10s,從而大大降低了前端開(kāi)發(fā)的效率。
隨著 vite 的出現(xiàn),它在開(kāi)發(fā)模式下使用原生的 ESM(即非打包模式) ,同時(shí)使用 esbuild 預(yù)打包依賴項(xiàng),極大提升了應(yīng)用開(kāi)發(fā)服務(wù)器啟動(dòng)和熱更新效率。但是非打包模式(Unbundled)并不完美,對(duì)于大型項(xiàng)目來(lái)說(shuō)還是有很大的問(wèn)題:
- 海量模塊請(qǐng)求:對(duì)于一個(gè)大型項(xiàng)目,可能需要加載的模塊有上千個(gè),使用原生模塊系統(tǒng)加載上千個(gè)模塊會(huì)導(dǎo)致瀏覽器卡死甚至崩潰。
- Dev 和 Production 不一致:在大多數(shù)情況下,本地模塊不能直接用于生產(chǎn)環(huán)境。 因此,非打包工具在生產(chǎn)環(huán)境中依然需要打包流程,從而導(dǎo)致 Dev、Production 不一致。當(dāng)這種不一致導(dǎo)致生產(chǎn)錯(cuò)誤時(shí),調(diào)試起來(lái)非常麻煩、痛苦。 而 vite 選擇在 dev 環(huán)境使用 esbuild,在 production 中使用 rollup,這進(jìn)一步擴(kuò)大了不一致性。
- Vite 在 dev 環(huán)境 之所以這么快,是因?yàn)?esbuild, esbuild 是用 go 編寫(xiě)的。 Go 利用了原生平臺(tái)的優(yōu)勢(shì),比 Js 快得多。
2.Farm 的出現(xiàn)
鑒于 Vite 的現(xiàn)狀和面臨的問(wèn)題,F(xiàn)arm 認(rèn)為,前端開(kāi)發(fā)者需要一個(gè)快速、強(qiáng)大、本地/生產(chǎn)環(huán)境一致的 Web 打包器,從而解決 Vite 面臨的問(wèn)題。
其實(shí),F(xiàn)arm 不僅僅是一個(gè)用 Rust 重寫(xiě)的普通打包器,它還包括很多強(qiáng)大和進(jìn)步的設(shè)計(jì)。在性能方面,Vite 與其他工具的基準(zhǔn)測(cè)試(使用 Turbopack 的基準(zhǔn)測(cè)試,1000 個(gè) React 組件)數(shù)據(jù)如下:

不論是在冷啟動(dòng),熱更新等方面,F(xiàn)arm 比 Webpack(純 JS 打包)、Vite(本質(zhì)也算 Rust 方案)、Turbopack(Rust 方案)、Rspack(Rust 方案,字節(jié)開(kāi)源) 等都有顯著的優(yōu)勢(shì)。
值得一提的是,F(xiàn)arm 的作者是 brightwu(吳明亮),曾就職于字節(jié)跳動(dòng)和騰訊。 Farm 開(kāi)源時(shí)間不到一年,在 Github 上已有 500+的 star,是一個(gè)值得持續(xù)關(guān)注的項(xiàng)目。
3.Farm 的設(shè)計(jì)理念
- 性能至上:能用 Rust 編寫(xiě)的模塊都用 Rust,只有少數(shù)不是性能瓶頸的部分沿用 JS
- dev/production 一致性:確保開(kāi)發(fā)和生產(chǎn)完全一致,即在開(kāi)發(fā)中看到的與在生產(chǎn)中得到的一樣。
- 部分打包:Farm 的打包目標(biāo)不是把所有東西都打包在一起,而是限制資源的請(qǐng)求數(shù)量。 Farm 會(huì)根據(jù)依賴關(guān)系和資源大小,將項(xiàng)目打包成 20-30 個(gè)小資源,在不損失緩存粒度的情況下獲得最佳的資源加載性能。
- Web 資產(chǎn)的一等公民支持:Farm 不需要將所有內(nèi)容轉(zhuǎn)換為 JAVAscript,它將任何資源都視為一等公民,如 html、js/jsx/ts/tsx、css/scss、png/svg 等等都是 Farm 支持的基礎(chǔ)模塊,更多資源可以通過(guò)插件支持。
- 兼容性:Farm 適用于舊版 (ES5) 和現(xiàn)代瀏覽器。
- Rollup 風(fēng)格的插件系統(tǒng):易于創(chuàng)建自己的插件,并且易于從 rollup/vite/webpack 遷移插件/項(xiàng)目。
Farm 的目標(biāo)是成為真正的下一代構(gòu)建工具,快速、強(qiáng)大、多環(huán)境一致,并為 Web 開(kāi)發(fā)人員提供最佳的開(kāi)發(fā)體驗(yàn)。
4.使用 Farm
Farm 需要 Node 16 及以上版本,如果開(kāi)發(fā)者使用的是 linux,請(qǐng)確保操作系統(tǒng)版本為 ubuntu 22 及以上(GLIBC >= 2.32)。
4.1 創(chuàng)建 Farm 項(xiàng)目
@farmfe/cli 包提供創(chuàng)建、啟動(dòng)和構(gòu)建 Farm 項(xiàng)目或 Farm 插件的能力。使用如下創(chuàng)建命令來(lái)初始化一個(gè)新的 Farm 項(xiàng)目。
npx @farmfe/cli@latest create
需要注意的是:create 命令目前只支持初始化一個(gè)簡(jiǎn)單的 React 項(xiàng)目,但是已經(jīng)在逐步增強(qiáng)功能階段。
4.2 開(kāi)始項(xiàng)目
首先執(zhí)行 install 命令 ,可以選擇喜歡的包管理器,npm 或 yarn 或 pnpm:
cd farm-react && npm install
// npm
cd farm-react && yarn
// yarn
cd farm-react && pnpm install
// pnpm
npm start
// 開(kāi)始項(xiàng)目
直接通過(guò)瀏覽器訪問(wèn)地址 http://localhost:9000 即可開(kāi)始。
4.3 配置項(xiàng)目
該項(xiàng)目由項(xiàng)目根目錄下的 farm.config.ts 文件配置,比如下面是 farm.config.ts 的內(nèi)容:
import { defineConfig } from '@farmfe/core/dist/config';
export default defineConfig({
// 與編譯相關(guān)的選項(xiàng)
compilation: {
input: {
//可以是相對(duì)路徑或絕對(duì)路徑
index: './index.html',
},
output: {
path: './build',
publicPath: '/',
},
// ...
},
// 與開(kāi)發(fā)服務(wù)器相關(guān)的選項(xiàng)
server: {
port: 9000,
// ...
},
// 附加插件配置
plugins: [],
});
4.4 Farm RoadMap
目前,F(xiàn)arm 已經(jīng)實(shí)現(xiàn)了 Web 構(gòu)建工具的基本功能, 但是要用于生產(chǎn)環(huán)境部署還有很多事情要做。
- Web 資產(chǎn)(html、css、js/jsx/ts/tsx、靜態(tài)資產(chǎn)等)的解析、加載、轉(zhuǎn)換和資源生成。
- 懶編譯
- Dev Server 和 HMR(支持快速響應(yīng))
- 部分打包
- Rust 和 Js 插件系統(tǒng)
- Source Map 支持
- Resources Minimize(暫未支持)
- Tree Shake(暫未支持)
- Css modules(暫未支持)
- 類 Sass 的官方插件(暫未支持)
- 持久緩存(暫未支持)
如上列表所示,目前 Farm 很多生產(chǎn)部署功能還依然在開(kāi)發(fā)中,值得期待!
5.Farm 高級(jí)特性
5.1 懶編譯
當(dāng)涉及到一個(gè)大項(xiàng)目時(shí),開(kāi)發(fā)者可以拆分成小塊并按需加載,這可以通過(guò)動(dòng)態(tài)導(dǎo)入來(lái)實(shí)現(xiàn)。
const page = React.lazy(() => import('./page'));
// 懶加載
默認(rèn)情況下,F(xiàn)arm 會(huì)延遲編譯這些動(dòng)態(tài)導(dǎo)入,只有在真正需要模塊時(shí)才編譯它們。
5.2 部分打包(Partial Bundling)
回到 webpack,開(kāi)發(fā)者經(jīng)常使用 splitChunks 來(lái)拆分 bundle,試圖優(yōu)化資源加載時(shí)間和提高緩存命中率。 但是配置 splitChunks 很復(fù)雜,有時(shí)達(dá)不到想要的效果。
所以 Farm 引入了 Partial Bundling,根據(jù)依賴關(guān)系和資源大小自動(dòng)將應(yīng)用程序打包到多個(gè)資源中。
5.3 Static Assets 靜態(tài)資源
從 v0.4 Farm 開(kāi)始支持三種資源加載方式: url, inline, raw 。下面是 url 模式:
import rocketUrl from './assets/rocket.svg'; // return the url of this image
export function Main() {
return <img src={rocketUrl} />; // using the url
}
使用 query ?inline 告訴 Farm 想要內(nèi)聯(lián)資產(chǎn),然后資產(chǎn)將轉(zhuǎn)換為 base64,例如:
// importer
import logo from './assets/logo.png?inline';
// logo is a base 64 str
// the image module
export default 'data:image/png,base64,xxxxx==';
例如,使用 ?raw 告訴 Farm 想要讀取資產(chǎn)的原始字符串
// import
import logo from './assets/license.txt?raw';
// return the content string of the assets
// license.txt
export default 'MIT xxxx';
5.4 Script 腳本
Farm 支持開(kāi)箱即用地編譯 Js/Jsx/Ts/Tsx,默認(rèn)將 Jsx/Tsx 編譯為 React。
import Button from './Button';
function ButtonGroup(props: ButtonProps) {
return (
<div>
{props.buttons.map((b) => (
<Button>{b}</Button>
))}
</div>
);
}
默認(rèn)情況下 Farm 使用 swc 來(lái)編譯腳本,可以使用 compilation.script 修改配置。
6.本文總結(jié)
本文主要和大家介紹Farm,即一個(gè)快速、強(qiáng)大、本地與生產(chǎn)環(huán)境保持一致的 Web 打包器,目標(biāo)是解決 Vite 面臨的主要問(wèn)題。當(dāng)然,正如文中所言,F(xiàn)arm的很多能力還在陸續(xù)開(kāi)發(fā)、增強(qiáng)中,但是它目的非常明確,是一個(gè)值得長(zhǎng)期關(guān)注的方案。
因?yàn)槠邢蓿恼虏](méi)有過(guò)多展開(kāi),如果有興趣,文末的參考資料提供了優(yōu)秀文檔以供學(xué)習(xí)。最后,歡迎大家點(diǎn)贊、評(píng)論、轉(zhuǎn)發(fā)、收藏!
參考資料
https://farm-fe.github.io/docs/why-farm
https://github.com/farm-fe/farm
https://github.com/farm-fe/performance-compare
https://farm-fe.github.io/docs/features/static
https://farm-fe.github.io/docs/features/script