新聞中心
????一 引言

成都創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供天元網(wǎng)站建設(shè)、天元做網(wǎng)站、天元網(wǎng)站設(shè)計(jì)、天元網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)與制作、天元企業(yè)網(wǎng)站模板建站服務(wù),10年天元做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。
Webpack 最初是為了解決前端模塊化以及使用 Node.Js 生態(tài)的問題而出現(xiàn),在過去的 8 年時(shí)間里,Webpack 的能力越來越強(qiáng)大。
??
??
但因?yàn)槎嗔舜虬鼧?gòu)建這一層,隨著項(xiàng)目的增長(zhǎng),打包構(gòu)建速度越來越慢,每次啟動(dòng)都要等待幾十秒甚至幾分鐘,然后啟動(dòng)一輪構(gòu)建優(yōu)化,隨著項(xiàng)目的進(jìn)一步增大,構(gòu)建速度又會(huì)降低,陷入不斷優(yōu)化的循環(huán)。
??
??
在項(xiàng)目達(dá)到一定的規(guī)模時(shí),基于 Bundle 的構(gòu)建優(yōu)化的收益變得越來越有限,無法實(shí)現(xiàn)質(zhì)的提升。我們從另一個(gè)角度思考,webpack 之所以慢,主要的原因還是在于他將各個(gè)資源打包整合在一起形成 bundle,如果我們不需要 bundle 打包的過程,直接讓瀏覽器去加載對(duì)應(yīng)的資源,我們將有可能可以跳出這個(gè)循環(huán),實(shí)現(xiàn)質(zhì)的提升。
??
??
在 Bundleless 的架構(gòu)下,我們不再需要構(gòu)建一個(gè)完整的 bundle,同時(shí)在修改文件時(shí),瀏覽器也只需要重新加載單個(gè)文件即可。由于沒有了構(gòu)建這一層我們將能夠?qū)崿F(xiàn)以下的目標(biāo):
- 極快的本地啟動(dòng)速度,只需要啟動(dòng)本地服務(wù)。
- 極快的代碼編譯速度,每次只需要處理單個(gè)文件。
- 項(xiàng)目開發(fā)構(gòu)建的時(shí)間復(fù)雜度始終為 O(1),使得項(xiàng)目能夠持續(xù)保持高效的構(gòu)建。
- 更加簡(jiǎn)單的調(diào)試體驗(yàn),不再強(qiáng)依賴 sourcemaps 即可實(shí)現(xiàn)穩(wěn)定的單文件的 debug。
基于以上的可能性 Bundleless 將重新定義前端的本地開發(fā),讓我們重新找回前端在 10 年前修改單個(gè)文件之后,只需要刷新即可即時(shí)生效的體驗(yàn),同時(shí)疊加上前端的 HotModuleReplace 相關(guān)技術(shù),我們可以把刷新也省去,最終實(shí)現(xiàn)保存即生效。
實(shí)現(xiàn) Bundleless 一個(gè)很重要的基礎(chǔ)能力是模塊的動(dòng)態(tài)加載能力,這一主要的思路會(huì)有兩個(gè):
- System.js 之類的 ES 模塊加載器,好處是具有較高的兼容性。
- 直接利用 Web 標(biāo)準(zhǔn)的 ESModule,面向未來,同時(shí)整體架構(gòu)也更加簡(jiǎn)單。
在本地開發(fā)過程中兼容性的影響不是特別大,同時(shí) ESModule 已經(jīng)覆蓋了超過 90% 的瀏覽器,我們完全可以利用 ESModule 的能力讓瀏覽器自主加載需要的模塊,從而更加低成本同時(shí)面向未來實(shí)現(xiàn) Bundleless。
社區(qū)中在近一兩年也出現(xiàn)了很多基于 ESModule 的開發(fā)工具,如 Vite、Snowpack、es-dev-server 等。本文將主要分享基于瀏覽器的 ESModule 能力實(shí)現(xiàn) Bundless 本地開發(fā)的相關(guān)思路、核心技術(shù)點(diǎn)以及 Vite 的相關(guān)實(shí)現(xiàn)和在供應(yīng)鏈 POS 場(chǎng)景下的落地實(shí)踐。
二 從資源加載看 Bundle 和 Bundleless 的不同
下面以大家最熟悉的 create-react-app 默認(rèn)項(xiàng)目為例,從實(shí)際的頁面渲染資源的加載過程對(duì)比 Bundle 和 Bundleless 的區(qū)別。
??
??
基于 Webpack 的 bundle 開發(fā)模式
??
??
上面的圖具體的模塊加載機(jī)制可以簡(jiǎn)化為下圖:
??
??
在項(xiàng)目啟動(dòng)和有文件變化時(shí)重新進(jìn)行打包,這使得項(xiàng)目的啟動(dòng)和二次構(gòu)建都需要做較多的事情,相應(yīng)的耗時(shí)也會(huì)增長(zhǎng)。
基于 ESModule Bundleless 模式
??
??
從上圖可以看到,已經(jīng)不再有一個(gè)構(gòu)建好的 bundle、chunk 之類的文件,而是直接加載本地對(duì)應(yīng)的文件。
??
??
從上圖可以看到,在 Bundleless 的機(jī)制下,項(xiàng)目的啟動(dòng)只需要啟動(dòng)一個(gè)服務(wù)器承接瀏覽器的請(qǐng)求即可,同時(shí)在文件變更時(shí),也只需要額外處理變更的文件即可,其他文件可直接在緩存中讀取。
對(duì)比總結(jié)
??
??
Bundleless 模式可以充分利用瀏覽器自主加載的特性,跳過打包的過程,使得我們能在項(xiàng)目啟動(dòng)時(shí)獲取到極快的啟動(dòng)速度,在本地更新時(shí)只需要重新編譯單個(gè)文件。下面將分享如何基于瀏覽器 ESModule 的能力實(shí)現(xiàn) Bundleless 的開發(fā)。
三 如何實(shí)現(xiàn) Bundleless
如何使用 ESModule 模塊加載
實(shí)現(xiàn) Bundleless 的第一步是要讓瀏覽器自主加載對(duì)應(yīng)的模塊。
使用 type="module" 開啟 ESModule
利用 import-maps 支持 bare import
分享一個(gè)在 chrome 中已經(jīng)實(shí)現(xiàn)了的 import-maps 的標(biāo)準(zhǔn) ,可以讓我們直接用 import React from 'react' 這樣的寫法,未來我們可以利用此能力實(shí)現(xiàn)線上的 Bundleless 部署。
以上我們介紹了瀏覽器中原生的 ESModule 是如何使用的。面向本地開發(fā)的場(chǎng)景,我們只需要啟動(dòng)一個(gè)本地的 devServer 承載瀏覽器的請(qǐng)求映射到對(duì)應(yīng)的本地文件,同時(shí)動(dòng)態(tài)地將項(xiàng)目中 import 的資源路徑指向我們的本地地址,即可讓瀏覽器直接加載本地的文件,比如可以使用下面的寫法,將入口 JS 文件直接指向本地的路徑,然后 devServer 再攔截相應(yīng)的請(qǐng)求返回對(duì)應(yīng)的文件。
如何加載非 JS 的文件資源
通過 ESModule 我們借助瀏覽器的能力實(shí)現(xiàn)了 JS 的自主加載,但實(shí)際的項(xiàng)目代碼中我們不僅僅會(huì) import JS 文件,也會(huì)有下面的寫法:
// main.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css' // import css 文件
import App from './App' // import jsx 文件
// 使用 JSX 語法
ReactDOM.render(, document.getElementById('root'))
而瀏覽器在處理文件時(shí)是依據(jù) Content-Type 的,不關(guān)心具體的文件類型,所以我們需要在瀏覽器發(fā)起請(qǐng)求時(shí),將對(duì)應(yīng)的資源轉(zhuǎn)化為 ESModule 格式,同時(shí)設(shè)置對(duì)應(yīng)的 Content-Type 為 JS,返回給瀏覽器執(zhí)行,瀏覽器就會(huì)按照 JS 的語法進(jìn)行解析處理,整體的流程可見下圖:
??
??
以下是 Vite 的相關(guān)實(shí)現(xiàn),在請(qǐng)求返回的過程中,對(duì)不同的文件進(jìn)行動(dòng)態(tài)處理:
??
??
如何實(shí)現(xiàn) HotModuleReplace
HotModuleReplace 能夠在我們修改代碼后,不需要刷新頁面,直接在當(dāng)前場(chǎng)景下生效,結(jié)合 Bundleless 極快的生效速度,我們能夠?qū)崿F(xiàn)幾乎沒有延遲的保存即生效的體驗(yàn)。對(duì)于 React,在 Webpack 場(chǎng)景下目前只能通過使用 react-hot-loader 來實(shí)現(xiàn),但這一塊受限于具體的實(shí)現(xiàn),有一些場(chǎng)景會(huì)存在 bug,作者也建議遷移到 React 團(tuán)隊(duì)實(shí)現(xiàn)的 react-refresh,而這一塊在 Webpack 中還沒有相應(yīng)的實(shí)現(xiàn)。在 Bundleless 場(chǎng)景下,因?yàn)槲覀兊拿總€(gè)組件都是獨(dú)立加載的,所以要集成 react-refresh,我們只需要在瀏覽器請(qǐng)求返回時(shí)在文件的頂部和底部加上相應(yīng)的腳本即可完成集成。
??
??
要完整的實(shí)現(xiàn) HotModuleReplace 會(huì)比上面畫得更加復(fù)雜,還需要有一套依賴分析機(jī)制來判斷當(dāng)一個(gè)文件發(fā)生變更之后要替換哪些文件以及是否需要 reload。在 Bundleless 的場(chǎng)景下,因?yàn)椴辉傩枰虬鼮橐粋€(gè)完整的 bundle,同時(shí)我們也能更加靈活地對(duì)單個(gè)文件進(jìn)行修改,這一塊相關(guān)的實(shí)現(xiàn)會(huì)更加容易。
以下是在 Vite 中的相關(guān)實(shí)現(xiàn):
??
??
如何優(yōu)化大量請(qǐng)求導(dǎo)致頁面加載慢
Bundleless 的模式不再打包,提升了啟動(dòng)的速度,但對(duì)于一些有較多外部依賴或者自身文件數(shù)量較多的模塊,需要發(fā)起大量請(qǐng)求才能獲取到全部的資源,這個(gè)會(huì)降低開發(fā)過程中頁面加載的時(shí)間。比如下面是直接在瀏覽器中 import lodash-es 會(huì)并發(fā)出大量的請(qǐng)求:
??
??
在這一塊上我們可以做相應(yīng)的優(yōu)化,將外部的依賴提前打包成單個(gè)文件來減少在開發(fā)過程中由于外部依賴過多而發(fā)起過多的網(wǎng)絡(luò)請(qǐng)求。
在 Vite 的啟動(dòng)流程中有一個(gè) vite optimize 的過程會(huì)自動(dòng)將 package.json 中的 depenencies 借助 Rollup 打包成 ES6 Module。
??
??
提前打包帶來的好處除了能夠提升頁面的加載速度,借助 @rollup/plugin-commonjs 我們能夠?qū)?commonjs 的外部依賴打包為 ESModule 的形式引入,進(jìn)一步擴(kuò)大 Bundleless 的適用范圍。
四 在供應(yīng)鏈 POS 場(chǎng)景下落地實(shí)踐
我們團(tuán)隊(duì)負(fù)責(zé)的供應(yīng)鏈 POS 業(yè)務(wù)主要可分為面向建材家居的家裝行業(yè)和線下小店的零售行業(yè),在技術(shù)架構(gòu)上采用了各個(gè)域 bundle 獨(dú)立開發(fā),然后最終借助底層的 sdk 合并為一個(gè)大的 SPA 的形式。由于項(xiàng)目的復(fù)雜性,在日常開發(fā)過程中,有以下的一些痛點(diǎn):
- 項(xiàng)目的啟動(dòng)和耗時(shí)相對(duì)較長(zhǎng)。
- 改動(dòng)后二次編譯時(shí)間長(zhǎng)。
- 缺少穩(wěn)定的 HMR 能力,開發(fā)過程中需要重復(fù)造場(chǎng)景。
- debug 依賴 sourcemaps 能力,有時(shí)會(huì)出現(xiàn)不穩(wěn)定的情況。
基于以上的問題,借助 Vite 的相關(guān)實(shí)現(xiàn),我們對(duì)本地開發(fā)環(huán)境進(jìn)行了 Bundleless 的嘗試和落地,在實(shí)驗(yàn)的一些項(xiàng)目中對(duì)于本地的開發(fā)體驗(yàn)有了很大的提升。
在啟動(dòng)以及修改生效的速度上帶來極大的提升
目前已實(shí)現(xiàn)單 bundle 維度的開發(fā),打包構(gòu)建速度:
??
??
Webpack
??
??
Vite Bundleless
從上面的可以看出,在啟動(dòng)單個(gè) bundle 時(shí),Webpack 需要 10s 左右的時(shí)間,而基于 Bundleless 的 Vite 只需要 1s 左右,提升 10 倍。
??
??
整體的頁面加載時(shí)間在 4s 左右,仍然比 Webpack 的打包構(gòu)建時(shí)間要短,同時(shí)從上面的視頻中也可以看到 HMR 的速度達(dá)到了毫秒級(jí)的響應(yīng),實(shí)現(xiàn)了基本無感的保存即生效。
不依賴 sourcemap 調(diào)試單個(gè)文件
??
??
落地過程中遇到的問題和解決
在實(shí)際落地過程中,遇到的問題主要是相關(guān)模塊不符合 ESModule 規(guī)范以及一些寫法上的標(biāo)準(zhǔn)化:
- 部分模塊沒有 ESModule 的打包。
- less 依賴 node_modules 的寫法的規(guī)范。
- jsx 文件后綴規(guī)范。
- babel-runtime 的處理。
部分模塊沒有 ESModule 的打包
對(duì)于沒有 ESModule 打包輸出或者輸出的錯(cuò)誤的包,根據(jù)不同的類型使用不同的策略:
- 內(nèi)部的包:通過升級(jí)腳手架,發(fā)布帶有 ESModule 的包的新版本。
- 外部依賴:通過 issue、pull request 等形式,推動(dòng)了 number-precision 等模塊的升級(jí)。
- 同時(shí)有一些由于歷史原因無法打出 ESModule 的包可以借助 @rollup/plugin-commonjs 打包為 ESModule。
less 依賴 node_modules 的寫法的規(guī)范
@import '~@ali/pos-style-mixin/style/lst.less';
// ~ 只在 webpack 中 less-loader 的支持,在原生的 less 中不支持
// 統(tǒng)一遷移為下面的模式
@import '@ali/pos-style-mixin/style/lst.less';
// 同時(shí)在原先的 webpack 構(gòu)建中的 less-laoder 中配置 lessOptions,用于最后的打包
/*
{
loader: 'less-loader',
options: {
lessOptions: {
javascriptEnabled: true,
paths: [path.resolve(cwd, 'node_modules')],
}
}
}
*/
JSX 文件后綴規(guī)范
Vite 在運(yùn)行的過程中會(huì)依據(jù)文件不同的后綴名進(jìn)行對(duì)應(yīng)的編譯處理,而在 Webpack 模式下我們通常會(huì)將 JSX、JS 等文件都丟給 babel-loader 進(jìn)行處理,這使得有一些原本是 JSX 的文件沒有寫JSX 后綴。Vite 只會(huì)對(duì) /\.(tsx?|jsx)$/ 的文件進(jìn)行 esbuild 編譯,對(duì)于純 JS 會(huì)直接跳過 esbuild 的過程。對(duì)于這種情況我們是逐步將錯(cuò)誤的原先沒有寫 JSX 的文件遷移為 JSX 文件。
babel-runtime 的處理
在使用了 babel-plugin-transform-runtime 之后,打包的輸出結(jié)果會(huì)是下面這樣:
??
??
上面所引用的 @babel/runtime/helpers/extends 是 commonjs 的格式無法直接使用,針對(duì)這個(gè)情況,有兩種解法:
1)針對(duì)內(nèi)部自己打包的模塊,可以在進(jìn)行 es6 打包時(shí)添加 useModules 配置,這樣打包出來的代碼就會(huì)是直接引用@babel/runtime/helpers/esm/extends
:
??
??
2)針對(duì)重新打包成本較高的模塊,可以通過 Vite 的插件機(jī)制進(jìn)行轉(zhuǎn)換,將 @babel/runtime/helpers 在運(yùn)行時(shí)替換為 @babel/runtime/helpers/esm 可以通過 alias 配置實(shí)現(xiàn):
??
??
以上是在 Vite 開發(fā)環(huán)境的遷移過程中遇到的一些問題和處理的分享,這一塊的更大范圍的落地還在進(jìn)行中。Bundleless 的落地不僅僅是為了適配 Vite 的開發(fā)模式,同時(shí)也是面向未來規(guī)范各個(gè)模塊代碼的過程,將我們的模塊進(jìn)行標(biāo)準(zhǔn)的 ESModule 化,在有新的工具和思想出現(xiàn)時(shí)可以用更低成本進(jìn)行落地。
五 直接使用 Bundleless 進(jìn)行部署的可行性
受限于網(wǎng)絡(luò)請(qǐng)求和瀏覽器的解析速度,對(duì)于較大型的應(yīng)用,bundle 在加載速度上還是能夠帶來較大的收益。V8 在 2018 年也給出了相關(guān)性能上的建議:在本地開發(fā)和小型的 Web 應(yīng)用中使用。在今天的場(chǎng)景下,隨著瀏覽器和網(wǎng)絡(luò)性能的不斷提升,結(jié)合 ServiceWorker 之類的緩存能力,網(wǎng)絡(luò)加載的影響和越來越小,對(duì)于一些不需要考慮兼容性問題的場(chǎng)景可以進(jìn)行內(nèi)部的嘗試,直接部署通過 ESModule 加載的代碼。
六 總結(jié)
本文主要分享了 Bundleless 架構(gòu)下,如何提升前端的研發(fā)效率、實(shí)現(xiàn)思路以及在具體業(yè)務(wù)場(chǎng)景下落地實(shí)踐。Bundleless 本質(zhì)上是將原先 Webpack 中模塊依賴解析的工作交給瀏覽器去執(zhí)行,使得在開發(fā)過程中代碼的轉(zhuǎn)換變少,極大地提升了開發(fā)過程中的構(gòu)建速度,同時(shí)也可以更好地利用瀏覽器的相關(guān)開發(fā)工具。
站在當(dāng)前的背景下,Web 各個(gè)領(lǐng)域 JavaScript/CSS/HTML 相關(guān)的標(biāo)準(zhǔn)都已成熟,同時(shí)瀏覽器內(nèi)核也趨于統(tǒng)一,前端工程化的核心重點(diǎn)已逐步遷移到研發(fā)提效上,而 Bundleless 的模式能夠帶來長(zhǎng)效的啟動(dòng)和 HMR 的速度,是未來的一大發(fā)展趨勢(shì)。隨著瀏覽器內(nèi)核和 Web 標(biāo)準(zhǔn)的不斷統(tǒng)一,前端的代碼可以不再打包直接運(yùn)行將成為可能,這將進(jìn)一步提高整體的研發(fā)效率。
最后非常感謝 ESModule、Vite、Snowpack 等標(biāo)準(zhǔn)和工具的出現(xiàn),讓前端的開發(fā)體驗(yàn)往前跨了一大步。
名稱欄目:Webpack 打包太慢?來試試 Bundleless
本文路徑:http://fisionsoft.com.cn/article/dpodjpg.html


咨詢
建站咨詢
