<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端構(gòu)建新世代,Esbuild 原來還能這么玩!

          共 5085字,需瀏覽 11分鐘

           ·

          2022-01-04 21:35

          Hello,我是三元同學(xué)。之前停更了一段時(shí)間,因?yàn)榈昧肆鞲校恢痹诩茵B(yǎng)病,沒來得及更新文章,跟讀者朋友們先說聲抱歉~今天給大家?guī)淼氖俏易罱鼘懙脑瓌?chuàng)文章,由于近段時(shí)間一直在研究前端構(gòu)建相關(guān)的領(lǐng)域,像 Esbuild、Vite 這些都接觸得比較多了,而且這些工具現(xiàn)在在前端圈也比較熱門,備受業(yè)界關(guān)注,因此我想我有必要把我研究過的一些東西分享給大家,希望能對(duì)你有所幫助。

          什么是 Esbuild?

          Esbuild 是由 Figma 的 CTO 「Evan Wallace」基于 Golang 開發(fā)的一款打包工具,相比傳統(tǒng)的打包工具,主打性能優(yōu)勢(shì),在構(gòu)建速度上可以快 10~100 倍。

          2e3ce1c0b93f353b4dc438b77308adca.webp

          架構(gòu)優(yōu)勢(shì)

          1. Golang 開發(fā)

          采用 Go 語言開發(fā),相比于 單線程 + JIT 性質(zhì)的解釋型語言 ,使用 Go 的優(yōu)勢(shì)在于 :

          • 一方面可以充分利用多線程打包,并且線程之間共享內(nèi)容,而 JS 如果使用多線程還需要有線程通信(postMessage)的開銷;
          • 另一方面直接編譯成機(jī)器碼,而不用像 Node 一樣先將 JS 代碼解析為字節(jié)碼,然后轉(zhuǎn)換為機(jī)器碼,大大節(jié)省了程序運(yùn)行時(shí)間。

          2. 多核并行

          內(nèi)部打包算法充分利用多核 CPU 優(yōu)勢(shì)。Esbuild 內(nèi)部算法設(shè)計(jì)是經(jīng)過精心設(shè)計(jì)的,盡可能充分利用所有的 CPU 內(nèi)核。所有的步驟盡可能并行,這也是得益于 Go 當(dāng)中多線程共享內(nèi)存的優(yōu)勢(shì),而在 JS 中所有的步驟只能是串行的。

          3. 從零造輪子

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

          4. 高效利用內(nèi)存

          一般而言,在 JS 開發(fā)的傳統(tǒng)打包工具當(dāng)中一般會(huì)頻繁地解析和傳遞 AST 數(shù)據(jù),比如 string -> TS -> JS -> string,這其中會(huì)涉及復(fù)雜的編譯工具鏈,比如 webpack -> babel -> terser,每次接觸到新的工具鏈,都得重新解析 AST,導(dǎo)致大量的內(nèi)存占用。而 Esbuild 中從頭到尾盡可能地復(fù)用一份 AST 節(jié)點(diǎn)數(shù)據(jù),從而大大提高了內(nèi)存的利用效率,提升編譯性能。

          與 SWC 對(duì)比

          速度

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


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

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

          兼容性

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

          • 沒有 TS 類型檢查
          • 不能操作 AST
          • 不支持裝飾器語法
          • 產(chǎn)物 target 無法降級(jí)到 ES5 及以下

          意味著需要 ES5 產(chǎn)物的場(chǎng)景只用 Esbuild 無法勝任。

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

          • 產(chǎn)物支持 ES5 格式
          • 支持裝飾器語法
          • 可以通過寫 JS 插件操作 AST

          應(yīng)用場(chǎng)景

          對(duì)于 Esbuild 和 SWC,很多時(shí)候我們都在對(duì)比兩者的性能而忽略了應(yīng)用場(chǎng)景。對(duì)于前端的構(gòu)建工具來說主要有這樣幾個(gè)垂直的功能:

          • Bundler
          • Transformer
          • Minimizer

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

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

          而 Esbuild 作為 Bundler 已經(jīng)被 Vite 作為開發(fā)階段的依賴預(yù)打包工具,同時(shí)也被大量用作線上 esm CDN 服務(wù),比如esm.sh等等;作為 Minimizer ,Esbuild 也已足夠成熟,目前已經(jīng)被 Vite 作為 JS 和 CSS 代碼的壓縮工具用上了生產(chǎn)環(huán)境。

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

          插件機(jī)制

          esbuild 插件就是一個(gè)對(duì)象,里面有namesetup兩個(gè)屬性,name是插件的名稱,setup是一個(gè)函數(shù),其中入?yún)⑹且粋€(gè) build 對(duì)象,這個(gè)對(duì)象上掛載了一些鉤子可供我們自定義一些構(gòu)建邏輯。以下是一個(gè)簡(jiǎn)單的esbuild插件示例:

          let?envPlugin?=?{
          ??name:?'env',
          ??setup(build)?{
          ?????//?文件解析時(shí)觸發(fā)
          ????//?將插件作用域限定于env文件,并為其標(biāo)識(shí)命名空間"env-ns"
          ????build.onResolve({?filter:?/^env$/?},?args?=>?({
          ??????path:?args.path,
          ??????namespace:?'env-ns',
          ????}))

          ????//?加載文件時(shí)觸發(fā)
          ????//?只有命名空間為"env-ns"的文件才會(huì)被處理
          ????//?將process.env對(duì)象反序列化為字符串并交由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',
          ??//?應(yīng)用插件
          ??plugins:?[envPlugin],
          }).catch(()?=>?process.exit(1))

          使用如下:

          *//?應(yīng)用了env插件后,構(gòu)建時(shí)將會(huì)被替換成process.env對(duì)象*

          import?{?PATH?}?from?'env'

          console.log(`PATH?is?${PATH}`)

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

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

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

          3. 實(shí)際的插件應(yīng)該考慮到自定義緩存(減少 load 的重復(fù)開銷)、sourcemap 合并(源代碼正確映射)和錯(cuò)誤處理??梢詤⒖?Svelte plugin。

          虛擬模塊支持

          與 Rollup 對(duì)比

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

          const?wasmHelperId?=?'/__vite-wasm-helper'
          //?helper?函數(shù)實(shí)現(xiàn)
          const?wasmHelper?=?async?(opts?=?{},?url:?string)?=>?{
          ??//?省略具體實(shí)現(xiàn)
          }
          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 的虛擬模塊也有一些限制,為了與真實(shí)模塊區(qū)分開,默認(rèn)約定要在路徑前面拼上一個(gè)'\0'。這樣會(huì)對(duì)路徑產(chǎn)生一定的入侵性,直接放到瀏覽器進(jìn)行 import 會(huì)出問題(Vite 內(nèi)部也將 \0 替換成 __xx 這種形式,以免直接將 帶\0 路徑放到瀏覽器中 import):

          fd4973df3ff1649511e2806994f427f6.webp

          image.png

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

          編譯能力

          使用 Esbuild 的虛擬模塊,可以完成很豐富的功能,除了上述插件實(shí)例中在內(nèi)存中計(jì)算出 env 的值作為模塊內(nèi)容,還可以模塊名當(dāng)做一個(gè)函數(shù)來進(jìn)行編譯,甚至可以在編譯階段實(shí)現(xiàn)函數(shù)遞歸的過程。比如這個(gè) 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?}
          ????})
          ??}
          }

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

          import?fib5?from?'fib(5)'

          console.log(fib5)

          //?13

          所有的模塊都是虛擬模塊,在真實(shí)文件系統(tǒng)中并不存在

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

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

          這也可以在插件當(dāng)中實(shí)現(xiàn),可參考示例。

          落地場(chǎng)景

          1. 代碼壓縮工具

          Esbuild 的代碼壓縮功能非常優(yōu)秀,可以甩開傳統(tǒng)的壓縮工具一個(gè)量級(jí)以上的性能差距。Vite 在 2.6 版本也官宣在生產(chǎn)環(huán)境中直接使用 Esbuild 來壓縮 JS 和 CSS 代碼。


          b7b6317a776b37faff340c12836f41d6.webp

          2. 代替 ts-node

          社區(qū)已經(jīng)有了相應(yīng)的方案 esno: https://github.com/antfu/esno

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

          3. 代替 ts-jest

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

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

          4. 第三方庫 Bundler

          Vite 中在開發(fā)階段使用 Esbuild 來進(jìn)行依賴的預(yù)打包,將所有用到的第三方依賴轉(zhuǎn)成 ESM 格式 Bundle 產(chǎn)物,并且未來有用到生產(chǎn)環(huán)境的打算。

          c87794b65f2d0cc41af66a7b77c2b255.webp

          同時(shí)業(yè)界也有一些平臺(tái)基于純 Esbuild 來做線上 cjs -> esm 的 CDN 服務(wù),比如 esm.sh ?和 skypack:

          5a13d527525e9cea0004cd9816d04549.webpb3514c409400fd45cf2bfd383e2c044a.webp

          5. 打包 Node 庫

          為什么要打包 Node 庫:

          • 減少 node_modules 代碼,避免業(yè)務(wù)安裝一大堆 node_modules 的代碼,減少安裝體積
          • 提高啟動(dòng)速度,所有代碼打到一個(gè)文件,減少了大量的文件 io 操作
          • 更安全。所有代碼打包也是鎖定依賴版本的一種方式,可以避免之前出現(xiàn)的 coa 包導(dǎo)致的大面積 CI 掛掉的問題,可參考云謙的這篇文章。

          這方面 Esbuild 的作用跟現(xiàn)在 vercel 團(tuán)隊(duì)出品的 ncc 差不多,但會(huì)對(duì)代碼的寫法有一些限制,無法分析動(dòng)態(tài) require 或者 import 語句含有變量的情況:

          1da632d17233c334a05fe1ef3b54f76c.webp

          6. 小程序編譯

          對(duì)于小程序的場(chǎng)景,也可以使用 Esbuild 來代替 Webpack,大大提升編譯速度,對(duì)于 AST 的轉(zhuǎn)換則通過 Esbuild 插件嵌入 SWC 來實(shí)現(xiàn),實(shí)現(xiàn)快速編譯。詳見 132 的分享 esbuild 上生產(chǎn)。

          7. Web 構(gòu)建

          Web 場(chǎng)景就顯得比較復(fù)雜了,對(duì)于兼容性和周邊工具生態(tài)的要求比較高,比如低瀏覽器語法降級(jí)、CSS 預(yù)編譯器、HMR 等等,如果要用純 Esbuild 來做,還需要補(bǔ)充很多能力。

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

          如今 Remix 1.0 正式發(fā)布,底層使用 Esbuild 構(gòu)建,帶來了極致的性能體驗(yàn),成為 Next.js 強(qiáng)有力的競(jìng)爭(zhēng)對(duì)手。

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

          瀏覽 68
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国内成人做爱视频播放 | 色播开心五月天 | 亚洲无码高清视频在线观看 | 一级免费视频黄 | 最近中文字幕mv第一季歌词免费 |