<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>

          漸進(jìn)式 Unbundled 開發(fā)工具探索之路

          共 14069字,需瀏覽 29分鐘

           ·

          2021-06-06 14:46

          簡短摘要:得益于現(xiàn)代瀏覽器內(nèi)置了模塊處理系統(tǒng)(ESM), 業(yè)界新興開發(fā)工具 Snowpack、WMR、Vite 等直接將模塊解析加載過程直接交給瀏覽器, Dev Server 能夠秒級(jí)啟動(dòng)。和傳統(tǒng)開發(fā)工具編譯時(shí)解析加載模塊最終打包到 JS Bundle 中相比,本地開發(fā)體驗(yàn)提升明顯

          傳統(tǒng) Bundled Development

          復(fù)雜項(xiàng)目構(gòu)建太慢

          業(yè)界主流的開發(fā)工具還是以 Webpack 為主,隨著項(xiàng)目體積增大,開發(fā)階段一次性將源代碼和第三方依賴編譯處理打包到一起的耗時(shí)會(huì)顯著增加。在我們團(tuán)隊(duì)內(nèi)部的 monorepo 倉庫中,應(yīng)用項(xiàng)目開發(fā)時(shí),為了開發(fā)階段調(diào)試方便,通常也會(huì)對(duì)一些公共庫源碼一起打包編譯,成千上萬個(gè)模塊導(dǎo)致首次 dev server 啟動(dòng)耗時(shí)在幾分鐘甚至十幾分鐘,嚴(yán)重影響了開發(fā)效率與體驗(yàn)。

          Webpack 打包慢的問題相信也是大家都會(huì)遇到的問題。隨手一搜,各種 Webpack 配置優(yōu)化以及最佳實(shí)踐之類的文檔數(shù)不勝數(shù)。大部分也都是遵循  Webpack[1] Build Performance Guide[2]。

          粗略一看上面的一些優(yōu)化方式很多, 但是在我們的場景中很多都是不夠通用,比如 thread-loader 結(jié)合 babel-loader 的方式在業(yè)務(wù)項(xiàng)目中經(jīng)常會(huì)遇到報(bào)錯(cuò)的情況,原因是業(yè)務(wù)項(xiàng)目通常會(huì)使用 babel-plugin-import 針對(duì)業(yè)務(wù)內(nèi)部的組件庫按需加載組件以及樣式,配置大致如下:

          [
            "babel-plugin-import",
              {
                "libraryName""custom-ui-components",
                "style": (name: string, file: Object) => {
                  return `${name}/style/2x`;
                }
              }
          ]

          我們知道 JavaScript 中線程間共享的數(shù)據(jù)必須序列化,上述 style 函數(shù)在序列化時(shí)會(huì)直接報(bào)錯(cuò)。

          esbuild 作為一個(gè) bundle 工具性能很不錯(cuò),但是針對(duì)應(yīng)用生產(chǎn)環(huán)境打包還存在一些問題,如降級(jí)到 ES5,Code Split 、 CSS 處理等。社區(qū)內(nèi)有提供替換 babel-loader 的 esbuild-loader, 通過 loader 的方式在 Webpack JS 運(yùn)行時(shí)中編譯單個(gè)文件的方式在速度上也不如單純用 esbuild 一把梭快。對(duì)瀏覽器兼容性有要求的項(xiàng)目,平滑使用也比較困難。

          減少處理的模塊數(shù)量方面,針對(duì) MPA,簡單的做法是在 dev 時(shí)顯式指定需要開發(fā)的頁面,但是比較局限不夠靈活, Webpack 5 提供的實(shí)驗(yàn)性特性 Lazy Compilation 在開發(fā)階段能夠做到真正的按需編譯提升 dev server 啟動(dòng)速度:

          首次 dev server 啟動(dòng)時(shí), 會(huì)代理 Webpack 入口以及 dynamic import 導(dǎo)出的模塊,打開瀏覽器頁面后,代理模塊在運(yùn)行時(shí)通過 Server-Send-Events 與 Lazy Compilation Backend Server 通信決定需要真正編譯處理的資源。但是作為實(shí)驗(yàn)功能,目前還不是很穩(wěn)定,筆者在使用時(shí),遇到過初次打開頁面白屏必須手動(dòng)刷新的問題。而且后續(xù)對(duì)需要編譯的入口通過 babel-loader 或者 ts-loader 重新編譯打包時(shí),仍然會(huì)有慢的問題存在。

          Webpack 4 中通過插件 lazy-compile-webpack-plugin[3] 也可以實(shí)現(xiàn)類似的效果。

          更多的優(yōu)化方式這里不一一列舉, 總結(jié)下來,現(xiàn)有的一些 Webpack 打包優(yōu)化方式或多或少都不夠通用,或者存在一些問題。

          打包工具之上的探索

          沿著減少 Webpack 處理模塊數(shù)量的思路,node_modules 下的第三方依賴如果能夠從打包環(huán)節(jié)剔除,僅對(duì)業(yè)務(wù)代碼打包,對(duì)構(gòu)建速度肯定會(huì)有明顯的提升。

          比較常見的方法是將常用的第三方庫在 Webpack 構(gòu)建時(shí)配置 external, Html 中直接通過 script 標(biāo)簽引入 UMD 產(chǎn)物, 這種方式有以下問題:

          1. 每個(gè)依賴的 UMD 產(chǎn)物會(huì)增加額外的一段兼容代碼,造成冗余。
          2. 瀏覽器中直接使用可能會(huì)污染全局變量,以及容易被修改覆蓋。
          3. 很多公司內(nèi)部包,并沒有提供 UMD 產(chǎn)物。

          既然 UMD 產(chǎn)物不太符合我們的場景,那么直接替換依賴為對(duì)應(yīng)的 ESM 產(chǎn)物呢?業(yè)界也有類似的思路:

          1. Rollup 社區(qū)有 rollup-plugin-cdn[4] 支持代碼中從 unpkg 引入依賴的 ESM 產(chǎn)物:
          import hyper from 'https://unpkg.com/hyperhtml@latest/esm/index.js';
          hyper(document.body)`
            <h1>Hello ESM</h1>`;
          1. Pika(Skypack 前身)提供了 @pika/cdn-webpack-plugin[5] 支持生產(chǎn)環(huán)境構(gòu)建時(shí)將 package.json 中的依賴替換為 Pika CDN 上對(duì)應(yīng)的鏈接,同時(shí) html 中通過 script type=module 加載打包后的 js 產(chǎn)物, 以 React 為例在最終 JS Chunk 中大致如下:
           import __mun2tz2a_default, * as __mun2tz2a_all from "https://cdn.skypack.dev/react";
              window["https://cdn.skypack.dev/react"] = Object.assign((__mun2tz2a_default || {}), __mun2tz2a_all);
          (window["webpackJsonptask_activity"] = window["webpackJsonptask_activity"] || []).push([["vendors"],{
           /***/ "50ab":
          /*!****************************************!*\
            !*** ./node_modules/__pika__/react.js ***!
            \****************************************/
          /*! no static exports found */
          /***/ (function(module, exports) {
          module.exports = window["https://cdn.skypack.dev/react"];
          /***/ }),
          }])

          可以看到利用瀏覽器對(duì) ESM 的原生支持,直接從 Skypack CDN import 導(dǎo)入 React ,之后以全局變量的方式掛到 window 變量上。其他模塊中導(dǎo)入 React ,最終會(huì)是 window 上存在的變量。

          考慮到我們主要是想提升 dev server 啟動(dòng)時(shí)構(gòu)建的速度,并且很多依賴都是公司內(nèi)部包,筆者在 @pika/cdn-webpack-plugin[6] 的基礎(chǔ)上,結(jié)合團(tuán)隊(duì)內(nèi)部的 CJS 轉(zhuǎn) ESM 服務(wù),在 dev 環(huán)節(jié)將用到的第三方依賴改成從線上 import 導(dǎo)入。最終在實(shí)際業(yè)務(wù)項(xiàng)目中測試效果并不理想,主要有以下一些問題:

          1. 第三方依賴以及依賴的依賴都會(huì)直接在瀏覽器中請求,請求數(shù)量成指數(shù)增長,瀏覽器中白屏?xí)r間過長,甚至請求數(shù)量會(huì)直接爆炸,運(yùn)行報(bào)錯(cuò)。
          2. 速度上面,一些項(xiàng)目確實(shí)能夠在 6s 左右 dev 完成,但是在一些比較大的內(nèi)部 monorepo 中,仍然是需要幾十秒的時(shí)間。
          3. CJS 轉(zhuǎn) ESM 中間的坑太多: 大部分依賴都還是只提供了 CJS 版本,在轉(zhuǎn)換過程中不論是用 rollup-plugin-commonjs 還是 esbuild ,一些問題都避免不了。
          4. 同樣會(huì)污染全局變量。

          在 Webpack 生態(tài)基礎(chǔ)上將第三方依賴以 ESM 形式直接加載看起來也不太能滿足我們的場景,那么如何更好地提升 dev server 啟動(dòng)速度呢?

          邁入 Unbundled Development

          業(yè)界現(xiàn)狀

          基于 ESM 的現(xiàn)代 unbundled 開發(fā)工具在社區(qū)如雨后春筍不斷發(fā)展,es-dev-server、Snowpack 、Wmr 、Vite 等工具直接直接拋棄 Webpack,通過內(nèi)部的 Dev Server 接收請求后實(shí)時(shí)對(duì) JS、TS、CSS 等資源實(shí)時(shí)編譯處理。

          圖片來自 Snowpack 官網(wǎng): https://www.snowpack.dev/concepts/how-snowpack-works

          和 Webpack 等打包工具相比,這類的 unbundled 開發(fā)工具有如下優(yōu)點(diǎn):

          1. 飛快的啟動(dòng)速度:dev server 啟動(dòng)時(shí)僅需要對(duì)依賴預(yù)處理編譯成 ESM 格式,一次處理之后,后續(xù)依賴沒有變化時(shí),可以做到秒級(jí)啟動(dòng),
          2. 實(shí)時(shí)按需編譯:運(yùn)行時(shí)瀏覽器第一次請求對(duì)應(yīng)模塊時(shí),dev server 本地實(shí)時(shí)編譯處理返回對(duì)應(yīng)內(nèi)容。
          3. 更快的熱更新:針對(duì)具體修改的文件,根據(jù)模塊的依賴關(guān)系圖, 逐步向上尋找 accept 該模塊 HMR 更新的文件,重新請求文件內(nèi)容。和 Webpack 的熱更新需要重新整體構(gòu)建相比會(huì)更快。

          這種新興 Unbundled Development 模式看著能夠滿足我們對(duì) dev server 啟動(dòng)速度的需求,下面的問題就是針對(duì)使用我們內(nèi)部應(yīng)用開發(fā)工具的業(yè)務(wù)項(xiàng)目來說如何平滑的接入這些工具。

          由于我們的應(yīng)用開發(fā)工具提供了一套現(xiàn)代 Web 項(xiàng)目開發(fā)范式,從應(yīng)用入口和各種資源的處理使用,以及服務(wù)端 API 的一體化調(diào)用上都有內(nèi)部一些標(biāo)準(zhǔn)。直接在項(xiàng)目中使用上述工具會(huì)有很多問題,免不了對(duì)上面的工具進(jìn)行二次開發(fā),這就要求這些工具提供足夠靈活的擴(kuò)展自定義方式,這里就業(yè)界用的比較多的 Snowpack 和 Vite 2.0 簡單對(duì)比如下:

          (點(diǎn)擊查看大圖)

          通過上面的簡單對(duì)比,以及文檔豐富程度上來看, Vite 2.0 擴(kuò)展起來會(huì)更加靈活方便。因此在給我們的應(yīng)用開發(fā)工具提供 Unbundled Development 模式前期,也是果斷選擇了 Vite 2.0 作為底層, 但是最終實(shí)現(xiàn)版本里面我們選擇借鑒 Vite、Wmr 等工具自主開發(fā)實(shí)現(xiàn) dev server 部分,主要有以下幾個(gè)角度的考慮:

          1. 依賴預(yù)處理過程需要深度定制:一些公司內(nèi)部包需要特殊處理,這部分我們最終決定放到內(nèi)部的 CJS 轉(zhuǎn) ESM 服務(wù)上,同一個(gè) package 的具體版本,只會(huì)處理一次,后續(xù)使用時(shí)直接下載轉(zhuǎn)換后的產(chǎn)物即可。
          2. 深度定制需求:在我們的設(shè)計(jì)體系里面,用戶并不會(huì)直接接觸到具體底層工具的配置,比如 babel.config.js、postcss.config.js 等,而是集中在我們提供的配置文件進(jìn)行自定義的需求。Vite 2.0 本身是支持這些文件的加載,另一方面具體資源文件處理的標(biāo)準(zhǔn)也有不同的地方,比如 CSS Modules 處理上,我們默認(rèn)支持文件后綴 .module.css 的形式,也支持關(guān)閉文件后綴的約定。這些有的可以通過配置實(shí)現(xiàn),但是大部分還是要接管 Vite 內(nèi)部的資源的編譯處理過程,如果通過 Vite 提供的插件可以完成,這樣對(duì) Vite 利用不是很充分,也顯得比較冗余。
          3. 后續(xù)維護(hù)與發(fā)展:初期我們打算提供基本的 dev server 解決開發(fā)環(huán)節(jié)慢的問題,后期需要結(jié)合已有 SSR、SSG 等建設(shè)補(bǔ)齊對(duì)應(yīng)的能力。

          綜上,實(shí)現(xiàn) Unbundled Dev Server 對(duì)我們來說更容易維護(hù)以及后續(xù)添加一些能力更方便。

          Unbundled Dev Server 原理

          為了更好的理解 Unbudled 開發(fā)工具的細(xì)節(jié),我們從下面幾個(gè)方面分別介紹:

          依賴預(yù)處理

          上文我們提到,業(yè)務(wù)項(xiàng)目中使用到的依賴很多只提供了 CJS 產(chǎn)物, 首先,我們需要將第三方依賴轉(zhuǎn)換成 ESM 格式。業(yè)界常用的工具主要是 Rollup 和 esbuild 等構(gòu)建工具。

          基本思路是分析項(xiàng)目源碼中使用到的依賴, 這些依賴作為構(gòu)建工具的入口整體打包,好處是整體將依賴打包得到 common chunks,瀏覽器中打開頁面加載第三方依賴的請求數(shù)量會(huì)少很多。缺點(diǎn)是添加依賴或者刪除一些依賴改動(dòng)了 package.json 或者 lock 文件時(shí), 需要重新對(duì)依賴編譯打包,在一些比較大的中后臺(tái)項(xiàng)目中,依賴預(yù)處理耗時(shí)還是存在的。

          這里我們目前采用的方案是,沿用之前內(nèi)部已有的 CJS 轉(zhuǎn) ESM 服務(wù),直接下載線上依賴已經(jīng)轉(zhuǎn)換好的的 ESM 產(chǎn)物。后續(xù)針對(duì)下載的 ESM 文件,用 esbuild 做一次 bundle 減少瀏覽器中運(yùn)行的請求數(shù)量。

          借助 CJS 轉(zhuǎn)ESM 服務(wù)和直接本地編譯轉(zhuǎn)換相比,有以下好處:

          1. 首次對(duì)項(xiàng)目依賴預(yù)處理之后,后續(xù)添加依賴只需要從云端下載新添加的依賴 ESM 產(chǎn)物,之后借助 esbuild 重新 bundle 耗時(shí)也不會(huì)很多。
          2. 一些需要特殊處理的第三方依賴,統(tǒng)一在云端處理修復(fù),業(yè)務(wù)項(xiàng)目不依賴開發(fā)工具的發(fā)版升級(jí)。針對(duì)一些通用的處理場景,我們會(huì)提供 UI 界面引導(dǎo)用戶輸入依賴對(duì)應(yīng)信息,自動(dòng)修復(fù)。
          3. CJS 到 ESM 轉(zhuǎn)換的產(chǎn)物可以在本地全局緩存,跨項(xiàng)目復(fù)用已經(jīng)編譯好的產(chǎn)物, 也就是說,隨著使用項(xiàng)目增加,云端和本地雙重緩存級(jí)聯(lián),能夠大幅度減少 CJS 轉(zhuǎn) ESM 的時(shí)間。

          同時(shí)我們針對(duì)內(nèi)部模塊比較多的依賴,如 antd,在線上 CJS 轉(zhuǎn) ESM 時(shí),會(huì)將內(nèi)部模塊打包到單個(gè)產(chǎn)物中,這樣能減少成百上千的網(wǎng)絡(luò)請求。

          以 React 為例, 直接從云端下載的 ESM 產(chǎn)物內(nèi)容截圖如下:

          可以看到依賴項(xiàng) object-assign 會(huì)額外帶有版本號(hào)信息。在遞歸下載第三方依賴 ESM 文件后,能夠得到如下的 json 文件,存儲(chǔ)某個(gè)版本依賴實(shí)際 ESM 文件的路徑, 如下:

          {
             "react?16.14.0""/Library/Caches/__web_modules__/[email protected]",
             "react-dom?16.14.0""/Library/Caches/__web_modules__/[email protected]",
             "object-assign?4.1.1""/Library/Caches/__web_modules__/[email protected]",
          }

          版本號(hào)是直接在 node_modules 中解析對(duì)應(yīng)依賴位置獲取的,也就是說,項(xiàng)目下存在某個(gè)依賴多個(gè)版本也是支持的。

          之后通過 esbuild 打包時(shí)借助 onResolve hook 從上面的 json 文件中匹配具體的 ESM 文件路徑, 示例代碼如下:

          const bundleResult = await require('esbuild').build({
              entryPoints: ['react''react-dom'],
              bundle: true,
              splitting: true,
              chunkNames: 'chunks/[name]-[hash]',
              metafile: true,
              outdir: webModulesDir,
              format: 'esm',
              treeShaking: 'ignore-annotations',
              plugins: [
                {
                   name: 'resolve-deps-plugin',
                   setup(build) {
                      build.onResolve({filter: /^/}, async args => {
                         const { kind, path } = args;
                         if (['import-statement''entry-point''dynamic-import'].includes(kind)) {
                           if (kind === 'entrypoint') {
                             //  針對(duì) bundle 入口, 直接本地 resolve 得到版本號(hào)
                             // 在上面的 json 文件中拿到具體路徑返回
                           } else if (path.startsWith('/esm/bv')) {
                             // 針對(duì)依賴的依賴,比如 object-assign^4.1.1
                             // 通過 semver 在文件中匹配獲取實(shí)際的 ESM 文件路徑返回
                           }
                         }
                      })
                   }
                }
              ]
          })

          到這里,依賴預(yù)處理已經(jīng)完成了,最終通過 bundleResult.metafile 能夠得到我們最終的 import-map.json 后續(xù)在請求模塊時(shí)用來解析模塊依賴路徑到轉(zhuǎn)換后的 esm 文件, 目錄結(jié)構(gòu)和內(nèi)容大致如下:

          針對(duì) monorepo 中某些 package 并不發(fā)布,在應(yīng)用中直接使用源碼統(tǒng)一構(gòu)建的場景, 在分析使用到的依賴時(shí),也會(huì)收集這些 package 用到的依賴,統(tǒng)一預(yù)處理成 ESM 格式。某些 package build 后提供產(chǎn)物在應(yīng)用中使用時(shí),會(huì)根據(jù)當(dāng)前 package 最新代碼, 本地實(shí)時(shí)編譯轉(zhuǎn)換成 ESM,這里本地編譯轉(zhuǎn)換和云端會(huì)復(fù)用底層代碼,效果上也類似。

          資源文件處理

          整體 Server 的實(shí)現(xiàn)部分借鑒了 WMR 和 Vite 的 插件系統(tǒng)設(shè)計(jì), Plugin 作為 Rollup Plugin 的超集,通過 Plugin Container 提供統(tǒng)一的插件接口, 和 Snowpack、es-dev-server 在 Server 中間件中處理請求、文件轉(zhuǎn)換不同,WMR 、Vite 的插件體系將文件轉(zhuǎn)換以及 Server 中間件分離開來,概念上比較清晰, 也比較易于維護(hù)。WMR、Vite 這種插件系統(tǒng)也有利于 dev 和 build 時(shí)復(fù)用文件轉(zhuǎn)換相關(guān)的邏輯。

          從瀏覽器發(fā)出請求到 Server 返回對(duì)應(yīng)資源的流程如下圖所示:

          在 resolveId hook 中根據(jù) url 解析出具體文件路徑。load hook 主要加載文件內(nèi)容。transform hook 是編譯轉(zhuǎn)換各種類型資源文件的核心。下面對(duì)幾種資源文件在 Server 內(nèi)部的處理展開描述:

          JSX/TSX 編譯轉(zhuǎn)換

          我們知道 JSX或 TSX 不能直接在瀏覽器中運(yùn)行,這里因?yàn)?dev 環(huán)節(jié)對(duì)瀏覽器兼容性沒有要求以及追求更快的實(shí)時(shí)編譯速度,直接使用 esbuild transform 即可:

          const result = await esbuild.transform(code, {
            loader: 'tsx',
            sourcefile: importer,
            sourcemap: true,
            target: 'chrome63'
          })

          在業(yè)務(wù)項(xiàng)目中實(shí)踐時(shí),遇到的一些問題舉例如下:

          1. esbuild 不支持 React 17 jsx transformer,inject 配置選項(xiàng) transform api 也沒有提供,當(dāng)業(yè)務(wù)項(xiàng)目使用 React 版本為 17 時(shí),并且沒有顯示導(dǎo)入 React 時(shí), 我們在 esbuild transform 的結(jié)果上自動(dòng)注入 import React from 'react'。
          2. 業(yè)務(wù) TS 項(xiàng)目中有很多使用了 const enum 導(dǎo)致 esbuild 無法處理,只能切換到 typescript api 處理。
          3. 老項(xiàng)目業(yè)務(wù)代碼中依賴 babel 生態(tài)的插件, 比如 babel-plugin-macros[7], 這就導(dǎo)致我們額外需要提供 babel 編譯的流程。

          基礎(chǔ)的語法轉(zhuǎn)換完成后,接下來就是 Bare Import 的處理問題,我們的業(yè)務(wù)代碼中直接通過包名導(dǎo)入依賴的方式經(jīng)過打包工具處理能夠正常運(yùn)行, 如下:

          import React from 'react'

          但是在瀏覽器中直接運(yùn)行會(huì)直接報(bào)錯(cuò), import-maps[8] 提案可以解決這個(gè)問題,但是只有最新版本 Chrome 支持。這里我們采用的方案和業(yè)界的做法一致,編譯完成后改寫 import 語句, 以 React 為例, 最終返回的內(nèi)容如下:

          import React from "/node_modules/.web_modules/react.js";

          node_modules/.web_modules 目錄就是我們在依賴預(yù)處理時(shí)生成的第三方依賴 ESM 文件目錄。

          CSS、JSON、圖片等資源處理

          瀏覽器中直接 import 導(dǎo)入資源,要求返回的類型是 application/javascript,因此這些文件在對(duì)應(yīng)的插件中最終都會(huì)被處理成 JavaScript。

          CSS 默認(rèn)會(huì)用 PostCSS 處理之后直接創(chuàng)建 style 標(biāo)簽插入 head 節(jié)點(diǎn)就能生效,以 import ./App.css 為例,返回的內(nèi)容大致如下:

          // src/App.css
          const code = "body {\n  margin: 0;\n}\n"
          const filename = "/Users/songzhenwei/Documents/test/full-demo/src/App.css"
          const styleEl = document.createElement('style');
          const codeEl = document.createTextNode(code);
          styleEl.type = 'text/css';
          styleEl.appendChild(codeEl);
          document.head.appendChild(styleEl);

          這里只是簡單展示返回文件的內(nèi)容,真正可用還需要考慮熱更新時(shí) Style 標(biāo)簽的刪除、CSS 文件中通過 [url function](<https://developer.mozilla.org/en-US/docs/Web/CSS/url( "url function")>) 加載字體圖片等場景。

          JSON 文件比較簡單,這里我們只需要讀取文件 default 導(dǎo)出即可:

          export default { "name""example" }

          業(yè)務(wù)代碼中使用圖片等資源時(shí), 我們會(huì)在 import 語句后面添加 ?assets query 表示為資源請求需要編譯處理, 如下:

          import logo from './logo.png';
          // => rewrite to 
          import logo from '/src/logo.png?assets'

          /src/logo.png?assets 直接返回對(duì)應(yīng)資源的實(shí)際 url 即可:

          export default '/src/logo.png'

          對(duì)于圖片資源,實(shí)際業(yè)務(wù)場景中我們還需要支持根據(jù)文件尺寸決定是否通過 base64 編碼內(nèi)聯(lián)、svgr 等特性。

          到這里通過不同的插件完成了一些文件類型的編譯轉(zhuǎn)換,頁面已經(jīng)可以在瀏覽器中正確渲染。

          熱更新功能

          在 Webpack 等打包工具里面,熱更新相關(guān)代碼通常寫在入口文件內(nèi)如下:

          // src/index.jsx
          import App from './App';

          module.hot.accept('./App', () => {
             renderApp();
          })

          App 根組件中引用到的文件修改時(shí),會(huì)觸發(fā)入口文件中注冊的 accept 回調(diào)函數(shù)重新渲染 App 組件。

          ESM 場景下的 HMR API, 業(yè)界也有一些規(guī)范:Snowpack 聯(lián)合 Vue、Preact 提出了 ESM-HMR Spec[9],類似 module.hot, unbundled 開發(fā)工具需要提供 import.meta.hot 對(duì)象,比如常用的 accept 函數(shù)使用如下:

          import.meta.hot.accept();

          import.meta.hot.accept(['./dep1''./dep2'], ()=> {})

          這部分實(shí)現(xiàn)參考了 Snowpack 和 Vite , 文件更新時(shí),通過內(nèi)部建立好的依賴關(guān)系,上溯至 accept 該文件或自身的文件節(jié)點(diǎn),重新在瀏覽器請求該文件, 如下圖:

          修改 dep-b.js 向上遍歷依賴樹時(shí),找到 accept 的文件節(jié)點(diǎn) App.tsx , 同時(shí)會(huì)依賴路徑上文件節(jié)點(diǎn)的編譯緩存失效,之后通過 HMR client api 重新請求 App.tsx ,為了保證返回的內(nèi)容是最新的,重新請求時(shí)會(huì)加上時(shí)間戳。

          和 Webpack 等打包工具熱更新相比,Unbundled Development 開發(fā)工具熱更新只會(huì)重新編譯加載依賴路徑上的文件, 因此速度也會(huì)更快。同時(shí)也能結(jié)合 React Fast Refresh 做組件級(jí)別的熱更新。

          總結(jié)

          上面通過一些小點(diǎn),介紹了我們內(nèi)部解決 Webpack 打包慢的一些探索以及最終實(shí)現(xiàn) Unbundled Development 模式的一些做法, 最終實(shí)現(xiàn)的版本和我們應(yīng)用開發(fā)的標(biāo)準(zhǔn)范式一一對(duì)齊。在業(yè)務(wù)項(xiàng)目中使用 Unbundled Development 模式后開發(fā)體驗(yàn)有很大提升:

          生產(chǎn)環(huán)境現(xiàn)階段還是通過 Webpack 打包出 JS Bundle ,在一些基礎(chǔ)編譯能力以及使用方式上盡最大努力抹平 Unbundled Development 模式和生產(chǎn)環(huán)境 Webpack 打包的差異。

          通過實(shí)際業(yè)務(wù)項(xiàng)目接入 Unbundled Development 模式實(shí)踐,我們這邊也針對(duì) CJS 轉(zhuǎn) ESM 積累了一定的解決方案。通過云端統(tǒng)一處理的方式,后續(xù)也能發(fā)揮出更大的作用。一些新的方案如免依賴安裝也在持續(xù)探索中,最后,我們也希望能對(duì) Unbundled Development 生態(tài)添磚加瓦,最后反哺生態(tài)。

          參考資料

          [1]

          Webpack: https://webpack.js.org/guides/build-performance/

          [2]

          Build Performance Guide: https://webpack.js.org/guides/build-performance/

          [3]

          lazy-compile-webpack-plugin: https://github.com/liximomo/lazy-compile-webpack-plugin

          [4]

          rollup-plugin-cdn: https://github.com/WebReflection/rollup-plugin-cdn

          [5]

          @pika/cdn-webpack-plugin: https://www.npmjs.com/package/@pika/cdn-webpack-plugin

          [6]

          @pika/cdn-webpack-plugin: https://www.npmjs.com/package/@pika/cdn-webpack-plugin

          [7]

          babel-plugin-macros: https://github.com/kentcdodds/babel-plugin-macros

          [8]

          import-maps: https://github.com/WICG/import-maps#the-basic-idea

          [9]

          ESM-HMR Spec: https://github.com/snowpackjs/esm-hmr


          瀏覽 57
          點(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>
                  AV黄色在线网站 | 国外操逼视频在现 | 欧美成人操B视频 | 中文字幕日韩在线观看视频 | 俺去在线一区 |