前端構建新世代,Esbuild 原來還能這么玩!

大家好,我是若川。持續組織了5個月源碼共讀活動,感興趣的可以點此加我微信 ruochuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。

今天分享一篇esbuild的文章~

Hello,我是三元同學。之前停更了一段時間,因為得了流感,一直在家養病,沒來得及更新文章,跟讀者朋友們先說聲抱歉~今天給大家帶來的是我最近寫的原創文章,由于近段時間一直在研究前端構建相關的領域,像 Esbuild、Vite 這些都接觸得比較多了,而且這些工具現在在前端圈也比較熱門,備受業界關注,因此我想我有必要把我研究過的一些東西分享給大家,希望能對你有所幫助。

什么是 Esbuild?

Esbuild 是由 Figma 的 CTO 「Evan Wallace」基于 Golang 開發的一款打包工具,相比傳統的打包工具,主打性能優勢,在構建速度上可以快 10~100 倍。

a6dcdc7972563dd04d00d41f3a73bc96.png

架構優勢

1. Golang 開發

采用 Go 語言開發,相比于 單線程 + JIT 性質的解釋型語言 ,使用 Go 的優勢在于 :

  • 一方面可以充分利用多線程打包,并且線程之間共享內容,而 JS 如果使用多線程還需要有線程通信(postMessage)的開銷;

  • 另一方面直接編譯成機器碼,而不用像 Node 一樣先將 JS 代碼解析為字節碼,然后轉換為機器碼,大大節省了程序運行時間。

2. 多核并行

內部打包算法充分利用多核 CPU 優勢。Esbuild 內部算法設計是經過精心設計的,盡可能充分利用所有的 CPU 內核。所有的步驟盡可能并行,這也是得益于 Go 當中多線程共享內存的優勢,而在 JS 中所有的步驟只能是串行的。

3. 從零造輪子

從零開始造輪子,沒有任何第三方庫的黑盒邏輯,保證極致的代碼性能。

4. 高效利用內存

一般而言,在 JS 開發的傳統打包工具當中一般會頻繁地解析和傳遞 AST 數據,比如 string -> TS -> JS -> string,這其中會涉及復雜的編譯工具鏈,比如 webpack -> babel -> terser,每次接觸到新的工具鏈,都得重新解析 AST,導致大量的內存占用。而 Esbuild 中從頭到尾盡可能地復用一份 AST 節點數據,從而大大提高了內存的利用效率,提升編譯性能。

與 SWC 對比

速度

下面拿純 Esbuild 和 SWC 來編譯代碼,作為 Transformer 來轉換 800+ 個 tsx 文件,不寫任何的 JS 膠水代碼(如 esbuild-register、esbuild-loader、swc-loader 本身為了適配相應的宿主工具,會寫一堆 JS 膠水代碼,影響判斷)。


EsbuildSWCTSC
第一次138 ms217 ms8640 ms
第二次154 ms206 ms8400 ms
第三次142 ms258 ms8480 ms
平均144.7 ms227 ms8507 ms
耗時倍率x1x 1.58x 58.8

從這個例子可以看出,Esbuild 與 SWC 在性能上是在一個量級的,這里通過倉庫的例子 Esbuild 略快,但不排除其他例子里面 SWC 比 Esbuild 略快的場景。

兼容性

Esbuild 本身的限制,包括如下:

  • 沒有 TS 類型檢查

  • 不能操作 AST

  • 不支持裝飾器語法

  • 產物 target 無法降級到 ES5 及以下

意味著需要 ES5 產物的場景只用 Esbuild 無法勝任。

相比之下,SWC 的兼容性更好:

  • 產物支持 ES5 格式

  • 支持裝飾器語法

  • 可以通過寫 JS 插件操作 AST

應用場景

對于 Esbuild 和 SWC,很多時候我們都在對比兩者的性能而忽略了應用場景。對于前端的構建工具來說主要有這樣幾個垂直的功能:

  • Bundler

  • Transformer

  • Minimizer

從上面的速度和兼容性對比可以看出,Esbuild 和 SWC 作為 transformer 性能是差不多的,但 Esbuild 兼容性遠遠不及 SWC。因此,SWC 作為 Transformer 更勝一籌。

但作為 Bundler 以及 Minimizer,SWC 就顯得捉襟見肘了,首先官方的 swcpack 目前基本處于不可用狀態,Minimizer 方面也非常不成熟,很容易碰到兼容性問題。

而 Esbuild 作為 Bundler 已經被 Vite 作為開發階段的依賴預打包工具,同時也被大量用作線上 esm CDN 服務,比如esm.sh等等;作為 Minimizer ,Esbuild 也已足夠成熟,目前已經被 Vite 作為 JS 和 CSS 代碼的壓縮工具用上了生產環境。

綜合來看,SWC 與 Esbuild 的關系類似于當下的 Babel 和 Webpack,前者更適合做兼容性自定義要求高的 Transformer(比如移動端業務場景),而后者適合做 Bundler 和 Minimizer,以及兼容性自定義要求均不高的 Transformer。

插件機制

esbuild 插件就是一個對象,里面有namesetup兩個屬性,name是插件的名稱,setup是一個函數,其中入參是一個 build 對象,這個對象上掛載了一些鉤子可供我們自定義一些構建邏輯。以下是一個簡單的esbuild插件示例:

let?envPlugin?=?{name:?'env',setup(build)?{//?文件解析時觸發//?將插件作用域限定于env文件,并為其標識命名空間"env-ns"build.onResolve({?filter:?/^env$/?},?args?=>?({path:?args.path,namespace:?'env-ns',}))//?加載文件時觸發//?只有命名空間為"env-ns"的文件才會被處理//?將process.env對象反序列化為字符串并交由json-loader處理build.onLoad({?filter:?/.*/,?namespace:?'env-ns'?},?()?=>?({contents:?JSON.stringify(process.env),loader:?'json',}))},
}require('esbuild').build({entryPoints:?['app.js'],bundle:?true,outfile:?'out.js',//?應用插件plugins:?[envPlugin],
}).catch(()?=>?process.exit(1))

使用如下:

*//?應用了env插件后,構建時將會被替換成process.env對象*import?{?PATH?}?from?'env'console.log(`PATH?is?${PATH}`)

不過在編寫插件的時候有一些需要注意的地方:

  1. Esbuild 插件機制只可作用于 build API,而不適用于 transformAPI,這意味著 webpack 當中的 esbuild-loader 這種只使用 Esbuild transform 功能的地方無法利用 Esbuild 的插件機制。

  2. 插件中的 filter 正則是使用 go 原生的正則實現的,用來過濾文件,為了不使性能過于劣化,規則應該盡可能嚴格。同時它本身和 JS 的正則也有所區別,比如前瞻(?<=)、后顧(?=)和反向引用(\1)就不支持。

  3. 實際的插件應該考慮到自定義緩存(減少 load 的重復開銷)、sourcemap 合并(源代碼正確映射)和錯誤處理。可以參考 Svelte plugin。

虛擬模塊支持

與 Rollup 對比

作為打包器,一般需要兩種形式的模塊,一種存在于真實的磁盤文件系統中,另一種并不在磁盤而在內存當中,也就是虛擬模塊。Rollup 本身就天然支持虛擬模塊,Vite 基于它的插件機制,也重度使用了虛擬模塊的功能,以 wasm 文件的處理為例:

const?wasmHelperId?=?'/__vite-wasm-helper'
//?helper?函數實現
const?wasmHelper?=?async?(opts?=?{},?url:?string)?=>?{//?省略具體實現
}
export?const?wasmPlugin?=?(config:?ResolvedConfig):?Plugin?=>?{return?{name:?'vite:wasm',resolveId(id)?{if?(id?===?wasmHelperId)?{return?id}},async?load(id)?{if?(id?===?wasmHelperId)?{return?`export?default?${wasmHelperCode}`}if?(!id.endsWith('.wasm'))?{return}const?url?=?await?fileToUrl(id,?config,?this)//?虛擬模塊return?`
import?initWasm?from?"${wasmHelperId}"
export?default?opts?=>?initWasm(opts,?${JSON.stringify(url)})
`}}
}

但 Rollup 的虛擬模塊也有一些限制,為了與真實模塊區分開,默認約定要在路徑前面拼上一個'\0'。這樣會對路徑產生一定的入侵性,直接放到瀏覽器進行 import 會出問題(Vite 內部也將 \0 替換成 __xx 這種形式,以免直接將 帶\0 路徑放到瀏覽器中 import):

a6f61ae84b6568035fdd417f71214657.png

image.png

Esbuild 中對于虛擬模塊的支持更加友好一些,直接通過 namespace 來區分真實模塊和虛擬模塊,這樣也不會有 \0 這樣 hack 操作。

編譯能力

使用 Esbuild 的虛擬模塊,可以完成很豐富的功能,除了上述插件實例中在內存中計算出 env 的值作為模塊內容,還可以模塊名當做一個函數來進行編譯,甚至可以在編譯階段實現函數遞歸的過程。比如這個 Esbuild 插件:

{name:?'fibo',setup(build)?{build.onResolve({?filter:?/^fib\(\d+\)/?},?args?=>?{return?{?path:?args.path,?namespace:?'fib'?}})build.onLoad({?filter:?/^fib\(\d+\)/,?namespace:?'fib'?},?args?=>?{const?match?=?/^fib\((\d+)\)/.exec(args.path);n?=?Number(match[1]);console.log(n);let?contents?=?n?<?2???`export?default?${n+1}`?:?`import?n1?from?'fib(${n?-?1})'import?n2?from?'fib(${n?-?2})'export?default?n1?+?n2`return?{?contents?}})}
}

引入這個插件,可以解析如下的 import 語句:

import?fib5?from?'fib(5)'console.log(fib5)//?13

所有的模塊都是虛擬模塊,在真實文件系統中并不存在

另外,還能借助虛擬模塊來進行 URL Import,支持如下的 import 代碼:

import?React?from?'https://esm.sh/react@17'

這也可以在插件當中實現,可參考示例。

落地場景

1. 代碼壓縮工具

Esbuild 的代碼壓縮功能非常優秀,可以甩開傳統的壓縮工具一個量級以上的性能差距。Vite 在 2.6 版本也官宣在生產環境中直接使用 Esbuild 來壓縮 JS 和 CSS 代碼。

427d143fc8d5b72de05b76d2db48ab35.png

2. 代替 ts-node

社區已經有了相應的方案 esno: https://github.com/antfu/esno

ts-node?index.ts
//?替換為
esno?hello.ts

3. 代替 ts-jest

使用 esbuild-jest 代替ts-jest,我曾經嘗試在某些大型包中使用 esbuild-jest 來作為 transformer,相比 ts-jest,整體大概提升 3 倍測試效率。

Github 地址:https://github.com/aelbore/esbuild-jest

4. 第三方庫 Bundler

Vite 中在開發階段使用 Esbuild 來進行依賴的預打包,將所有用到的第三方依賴轉成 ESM 格式 Bundle 產物,并且未來有用到生產環境的打算。

3823cf6a727a40425db37c118829f3a5.png

同時業界也有一些平臺基于純 Esbuild 來做線上 cjs -> esm 的 CDN 服務,比如 esm.sh ?和 skypack:

4869f81926dbd78f6ddcad62d2f3afe1.png27ba0df55afab775a31d940ac1ab206c.png

5. 打包 Node 庫

為什么要打包 Node 庫:

  • 減少 node_modules 代碼,避免業務安裝一大堆 node_modules 的代碼,減少安裝體積

  • 提高啟動速度,所有代碼打到一個文件,減少了大量的文件 io 操作

  • 更安全。所有代碼打包也是鎖定依賴版本的一種方式,可以避免之前出現的 coa 包導致的大面積 CI 掛掉的問題,可參考云謙的這篇文章。

這方面 Esbuild 的作用跟現在 vercel 團隊出品的 ncc 差不多,但會對代碼的寫法有一些限制,無法分析動態 require 或者 import 語句含有變量的情況:

2ea35e794b2499265edb8a4396a9efbe.png

6. 小程序編譯

對于小程序的場景,也可以使用 Esbuild 來代替 Webpack,大大提升編譯速度,對于 AST 的轉換則通過 Esbuild 插件嵌入 SWC 來實現,實現快速編譯。詳見 132 的分享 esbuild 上生產。

7. Web 構建

Web 場景就顯得比較復雜了,對于兼容性和周邊工具生態的要求比較高,比如低瀏覽器語法降級、CSS 預編譯器、HMR 等等,如果要用純 Esbuild 來做,還需要補充很多能力。

之前三元同學基于 Esbuild 實現了一套 Web 開發腳手架 ewas,已經在 Github 開源,并且已成功落地到我之前的小冊項目當中,相比 create-react-app 啟動速度提升了 100 倍以上(30s -> 0.3s)。倉庫地址: https://github.com/sanyuan0704/ewas。

如今 Remix 1.0 正式發布,底層使用 Esbuild 構建,帶來了極致的性能體驗,成為 Next.js 強有力的競爭對手。

但總體來說,目前 Esbuild 對于真實的 Web 場景還有很多能力不支持,還有一些硬傷,包括語法不支持降級到ES5,拆包不靈活、不支持 HMR,對于真正能作為 Webpack 一樣的構建工具來講還有很長的路要走。

bebca41683adbb8261673c366b76630e.gif

·················?若川簡介?·················

你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助3000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。

d87985be83193cf88e60781fbb65b7cd.png

識別方二維碼加我微信、拉你進源碼共讀

今日話題

略。分享、收藏、點贊、在看我的文章就是對我最大的支持~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/275036.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/275036.shtml
英文地址,請注明出處:http://en.pswp.cn/news/275036.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

大三下學期十四周總結

在小組的學習方面&#xff0c;這周主要是對微信小程序的學習。對JSON格式請求在Spring boot與小程序之間的交互有了一些了解。對微信的接口wx.request、wx.uploadFile、wx.chooseImage的接口的使用。微信開發后臺傳過來的響應數據如果不是標準的json格式&#xff0c;需要在小程…

平面設計師和ui設計師_平面設計師為什么要享受所有樂趣?

平面設計師和ui設計師Graphic designers are pretty cool. We have to admit that. Be it their dressing style, their attitude and most importantly their enviable gadgets. Large Mac monitor, wacom tablet, drawing sets, swatchbooks , iPad pro with pencil, humungo…

轉:Xcode下的GDB調試命令

Xcode的調試器為用戶提供了一個GDB的圖形化界面&#xff0c;GDB是GNU組織的開放源代碼調試器。您可以在Xcode的圖形界面里做任何事情&#xff1b;但是&#xff0c;如果您需要您可以在命令行里使用GDB的命令&#xff0c;且gdb可以在終端運行&#xff0c;也可以在Xcode下的控制臺…

web表單設計:點石成金_設計復雜的用戶表單:12個UX最佳實踐

web表單設計:點石成金It’s been a few years that I’ve been taking interest in designing complex user forms, where a lot of information is requested from users. Here are a few industries where you regularly find such flows:幾年來&#xff0c;我一直對設計復雜…

跨平臺開發框架到底哪家強?5款主流框架橫向對比!

跨平臺開發框架到底哪家強&#xff1f;目前市場上有多個專業做跨平臺開發的框架&#xff0c;那么對開發者來說究竟哪一個框架更符合自己的需求呢&#xff1f;筆者特地總結對比了一下不同框架的特性。國內外筆者選擇了一共5個主流的測評對象&#xff0c;分別是RN&#xff0c;Flu…

【一句日歷】2019年6月

【2019年6月1日兒童節星期六】 人們在協商&#xff0c;解決和處理各種狀況時&#xff0c;若要獲得圓滿的結果&#xff0c;平靜的心和自我控制能力必不可少。任何人都明白。如果我們不能很好地控制自我&#xff0c;反而讓焦躁和嗔怒干擾了我們&#xff0c;那么我們的工作不再具有…

Android學習摘要一之Android歷史

Google與你998年9月7日創立&#xff0c;經過十幾年在搜索引擎方面的精耕細作&#xff0c;成為全球互聯網巨頭&#xff0c;尤其在地圖搜索的應用更是引人注目。Google與2007年11月5日宣布基于Linux平臺的開源手機操作系統&#xff0c;名稱為Android&#xff0c;中文譯為“機器人…

c#創建web應用程序_創建Web應用程序圖標集的6個步驟

c#創建web應用程序I am not great at creating logos or icons, mainly because of the lack of practice. So when I was tasked to create an unique icon set for our web app, I wasn’t confident that things will turn out right. After researching effective and rele…

基于pnpm + lerna + typescript的最佳項目實踐 - 理論篇

本文來自作者金虹橋程序員 投稿原文鏈接&#xff1a;https://juejin.cn/post/7043998041786810398本系列文章分為兩篇&#xff1a;理論篇和實踐篇 理論篇&#xff1a;介紹pnpm&#xff08;pnpm的特點、解決的問題等&#xff09;、lerna&#xff08;lerna的常用命令&#xff09;…

nginx 多進程 + io多路復用 實現高并發

一、nginx 高并發原理 簡單介紹&#xff1a;nginx 采用的是多進程&#xff08;單線程&#xff09; io多路復用(epoll)模型 實現高并發 二、nginx 多進程 啟動nginx解析初始化配置文件后會 創建&#xff08;fork&#xff09;一個master進程 之后 這個進程會退出 master 進程會…

轉載:程序員從初級到中級10個秘訣

Justin James曾發表過一篇博文《10 tips for advancing from a beginner to an intermediate developer》&#xff0c;為我們分享如何才能完成程序員從初級到中級的蛻變&#xff0c;現將中文譯文轉載于此&#xff0c;供大家借鑒。 在一封與TechRepublic會員交流的郵件當中&…

ux設計工具_UX設計中的工具和實用主義

ux設計工具There’s a zillion tools for User Experience and User Interface Design. Don’t take my word for it: a simple Google search for “what are the best tools for wireframing” (to take just one aspect of UX) leads you to endless pages of “The 20 best…

幕后常駐嘉賓配音小姐姐的2021年度總結

大家好&#xff0c;我是若川。這是公眾號幕后常駐嘉賓配音小姐姐&#xff0c;看完了上一個阿源小姐姐的年度總結《一張圖看程序媛阿源的2021個人年度流水賬》&#xff0c;寫的年度總結投稿。點擊以下音頻可以查看收聽往期更多音頻。以下是正文~Hi&#xff0c;大家好呀~我是若川…

java spring cloud版b2b2c社交電商spring cloud分布式微服務:服務注冊與發現(Eureka、Consul)...

Spring Cloud簡介電子商務社交平臺源碼請加企鵝求求&#xff1a;一零三八七七四六二六。Spring Cloud是一個基于Spring Boot實現的云應用開發工具&#xff0c;它為基于JVM的云應用開發中涉及的配置管理、服務發現、斷路器、智能路由、微代理、控制總線、全局鎖、決策競選、分布…

js 全選

<form name"frm" method"post" action"/add" onsubmit"return check()"><table class"titem" ><tr><td class"field"> * 所屬批次</td><td class"value"><sele…

結果規格化_結果

結果規格化If you’ve seen an Instagram story involving a question and people tilting their heads, you probably were looking at the “Who Is More” Instagram filter. In this article, I will share the creative process and decision making behind this filter.如…

2021 年 JavaScript 大事記

大家好&#xff0c;我是 ConardLi&#xff0c;不知不覺中&#xff0c;2021 年已經接近尾聲了&#xff0c;不知道在 2021 這一年&#xff0c;你收獲了什么&#xff1f;又失去了什么呢&#xff1f;又到了開始做年終總結的時候了&#xff0c;今天&#xff0c;我來給 JavaScript 做…

java版spring cloud+spring boot+redis多租戶社交電子商務平臺 (十三)springboot集成spring cache...

電子商務社交平臺源碼請加企鵝求求&#xff1a;三五三六二四七二五九本文介紹如何在springboot中使用默認的spring cache&#xff0c;聲明式緩存Spring 定義 CacheManager 和 Cache 接口用來統一不同的緩存技術。例如 JCache、 EhCache、 Hazelcast、 Guava、 Redis 等。在使用…

windows符號服務器地址

當調試windows程序的時候&#xff0c;有時候會需要一些符號文件。系統的公有符號文件微軟都是提供的&#xff0c;只需在調試器中設置即可&#xff0c;在下次調試時&#xff0c;調試器會自動從網上下載需要的符號文件。可以使用符號文件的調試器有windbg等等。 符號服務器地址&a…

如何融入到更積極的環境,促進技術提升

眾所周知&#xff0c;關注公眾號可以了解學習掌握技術方向&#xff0c;學習優質好文&#xff0c;落實到自己項目中。還可以結交圈內好友&#xff0c;讓自己融入到積極上進的技術氛圍&#xff0c;促進自己的技術提升。話不多說&#xff0c;推薦這些優質前端公眾號前端之神100w閱…