Webpack的異步加載原理及分包策略(深度好文,建議收藏)

作者:lzg9527

原文鏈接:https://segmentfault.com/a/1190000038180453

webpack 異步加載原理

webpack ensure?有人稱它為異步加載,也有人稱為代碼切割,他其實(shí)就是將 js 模塊給獨(dú)立導(dǎo)出一個(gè).js 文件,然后使用這個(gè)模塊的時(shí)候,再創(chuàng)建一個(gè)?script?對(duì)象壮吩,加入到?document.head?對(duì)象中,瀏覽器會(huì)自動(dòng)幫我們發(fā)起請(qǐng)求加缘,去請(qǐng)求這個(gè) js 文件鸭叙,然后寫個(gè)回調(diào)函數(shù),讓請(qǐng)求到的 js 文件做一些業(yè)務(wù)操作拣宏。

舉個(gè)例子

需求:main.js?依賴兩個(gè) js 文件:A.js?是點(diǎn)擊 aBtn 按鈕后沈贝,才執(zhí)行的邏輯,B.js?是點(diǎn)擊 bBtn 按鈕后勋乾,才執(zhí)行的邏輯宋下。

webpack.config.js嗡善,我們先來寫一下?webpack?打包的配置的代碼

constpath = require('path')// 路徑處理模塊

constHtmlWebpackPlugin = require('html-webpack-plugin')

const{ CleanWebpackPlugin } = require('clean-webpack-plugin')// 引入CleanWebpackPlugin插件

module.exports = {

? entry: {

index: path.join(__dirname,'/src/main.js'),

? },

? output: {

path: path.join(__dirname,'/dist'),

filename:'index.js',

? },

? plugins: [

newHtmlWebpackPlugin({

template: path.join(__dirname,'/index.html'),

? ? }),

newCleanWebpackPlugin(),// 所要清理的文件夾名稱

? ],

}

index.html?代碼如下

<!DOCTYPE html>

? <head>

? ? <title>webpack</title>

? </head>

? <body>

按鈕A

按鈕B

? ? </div>

? </body>

</html>

入口文件?main.js?如下

importA from'./A'

importB from'./B'

document.getElementById('aBtn').onclick = function () {

? alert(A)

}

document.getElementById('bBtn').onclick = function () {

? alert(B)

}

A.js?和?B.js?的代碼分別如下

// A.js

constA ='hello A'

module.exports = A

// B.js

constB ='hello B'

module.exports = B

此時(shí),我們對(duì)項(xiàng)目進(jìn)行?npm run build学歧, 打包出來的只有兩個(gè)文件

index.html

index.js

由此可見罩引,此時(shí)?webpack?把?main.js?依賴的兩個(gè)文件都同時(shí)打包到同一個(gè) js 文件,并在 index.html 中引入枝笨。但是?A.js?和?B.js?都是點(diǎn)擊相應(yīng)按鈕才會(huì)執(zhí)行的邏輯袁铐,如果用戶并沒有點(diǎn)擊相應(yīng)按鈕,而且這兩個(gè)文件又是比較大的話横浑,這樣是不是就導(dǎo)致首頁默認(rèn)加載的 js 文件太大剔桨,從而導(dǎo)致首頁渲染較慢呢?那么有能否實(shí)現(xiàn)當(dāng)用戶點(diǎn)擊按鈕的時(shí)候再加載相應(yīng)的依賴文件呢徙融?

webpack.ensure?就解決了這個(gè)問題洒缀。

require.ensure 異步加載

下面我們將?main.js?改成異步加載的方式

document.getElementById('aBtn').onclick = function () {

//異步加載A

? require.ensure([], function () {

let A = require('./A.js')

? ? alert(A)

? })

}

document.getElementById('bBtn').onclick = function () {

//異步加載b

? require.ensure([], function () {

let B = require('./B.js')

? ? alert(B)

? })

}

此時(shí),我們?cè)龠M(jìn)行一下打包欺冀,發(fā)現(xiàn)多了?1.index.js?和?2.index.js?兩個(gè)文件树绩。而我們打開頁面時(shí)只引入了?index.js?一個(gè)文件,當(dāng)點(diǎn)擊按鈕 A 的時(shí)候才引入?1.index.js?文件隐轩,點(diǎn)擊按鈕 B 的時(shí)候才引入?2.index.js?文件葱峡。這樣就滿足了我們按需加載的需求。

require.ensure?這個(gè)函數(shù)是一個(gè)代碼分離的分割線龙助,表示回調(diào)里面的?require?是我們想要進(jìn)行分割出去的,即?require('./A.js')蛛芥,把 A.js 分割出去提鸟,形成一個(gè)?webpack?打包的單獨(dú) js 文件。它的語法如下

require.ensure(dependencies: String[], callback: function(require), chunkName: String)

我們打開?1.index.js?文件仅淑,發(fā)現(xiàn)它的代碼如下

(window.webpackJsonp = window.webpackJsonp || []).push([

[1],

? [

? ? ,

? ? function (o, n) {

o.exports ='hello A'

? ? },

? ],

])

由上面的代碼可以看出:

異步加載的代碼称勋,會(huì)保存在一個(gè)全局的?webpackJsonp?中。

webpackJsonp.push?的的值涯竟,兩個(gè)參數(shù)分別為異步加載的文件中存放的需要安裝的模塊對(duì)應(yīng)的 id 和異步加載的文件中存放的需要安裝的模塊列表赡鲜。

在滿足某種情況下,會(huì)執(zhí)行具體模塊中的代碼庐船。

import() 按需加載

webpack4 官方文檔提供了模塊按需切割加載银酬,配合 es6 的按需加載?import()?方法,可以做到減少首頁包體積筐钟,加快首頁的請(qǐng)求速度揩瞪,只有其他模塊,只有當(dāng)需要的時(shí)候才會(huì)加載對(duì)應(yīng) js篓冲。

import()的語法十分簡單李破。該函數(shù)只接受一個(gè)參數(shù)宠哄,就是引用包的地址,并且使用了?promise?式的回調(diào)嗤攻,獲取加載的包毛嫉。在代碼中所有被?import()的模塊,都將打成一個(gè)單獨(dú)的包妇菱,放在?chunk?存儲(chǔ)的目錄下承粤。在瀏覽器運(yùn)行到這一行代碼時(shí),就會(huì)自動(dòng)請(qǐng)求這個(gè)資源恶耽,實(shí)現(xiàn)異步加載密任。

下面我們將上述代碼改成?import()方式。

document.getElementById('aBtn').onclick = function () {

//異步加載A

import('./A').then((data) => {

? ? alert(data.A)

? })

}

document.getElementById('bBtn').onclick = function () {

//異步加載b

import('./B').then((data) => {

? ? alert(data.B)

? })

}

此時(shí)打包出來的文件和?webpack.ensure?方法是一樣的偷俭。

路由懶加載

為什么需要懶加載浪讳?

像 vue 這種單頁面應(yīng)用,如果沒有路由懶加載涌萤,運(yùn)用 webpack 打包后的文件將會(huì)很大淹遵,造成進(jìn)入首頁時(shí),需要加載的內(nèi)容過多负溪,出現(xiàn)較長時(shí)間的白屏透揣,運(yùn)用路由懶加載則可以將頁面進(jìn)行劃分,需要的時(shí)候才加載頁面川抡,可以有效的分擔(dān)首頁所承擔(dān)的加載壓力辐真,減少首頁加載用時(shí)。

vue 路由懶加載有以下三種方式

vue 異步組件

ES6 的?import()

webpack 的?require.ensure()

vue 異步組件

這種方法主要是使用了?resolve?的異步機(jī)制崖堤,用?require?代替了?import?實(shí)現(xiàn)按需加載

exportdefaultnewRouter({

? routes: [

? ? {

path:'/home',',

? ? ? component: (resolve) => require(['@/components/home'], resolve),

? ? },

? ? {

? ? ? path: '/about',',

component: (resolve) => require(['@/components/about'], resolve),

? ? },

? ],

})

require.ensure

這種模式可以通過參數(shù)中的?webpackChunkName?將 js 分開打包侍咱。

exportdefaultnewRouter({

? routes: [

? ? {

path:'/home',

component: (resolve) => require.ensure([], () => resolve(require('@/components/home')),'home'),

? ? },

? ? {

path:'/about',

component: (resolve) => require.ensure([], () => resolve(require('@/components/about')),'about'),

? ? },

? ],

})

ES6 的 import()

vue-router?在官網(wǎng)提供了一種方法,可以理解也是為通過?Promise?的?resolve?機(jī)制密幔。因?yàn)?Promise?函數(shù)返回的?Promise?為?resolve?組件本身楔脯,而我們又可以使用?import?來導(dǎo)入組件。

exportdefaultnewRouter({

? routes: [

? ? {

path:'/home',

component: () =>import('@/components/home'),

? ? },

? ? {

path:'/about',

component: () =>import('@/components/home'),

? ? },

? ],

})

webpack 分包策略

在 webpack 打包過程中胯甩,經(jīng)常出現(xiàn)?vendor.js昧廷,?app.js?單個(gè)文件較大的情況,這偏偏又是網(wǎng)頁最先加載的文件偎箫,這就會(huì)使得加載時(shí)間過長木柬,從而使得白屏?xí)r間過長,影響用戶體驗(yàn)淹办。所以我們需要有合理的分包策略弄诲。

CommonsChunkPlugin

在 Webapck4.x 版本之前,我們都是使用?CommonsChunkPlugin?去做分離

plugins: [

newwebpack.optimize.CommonsChunkPlugin({

name:'vendor',

? ? minChunks: function (module, count) {

return(

? ? ? ? module.resource &&

? ? ? ? /.js$/.test(module.resource) &&

module.resource.indexOf(path.join(__dirname,'./node_modules')) ===0

? ? ? )

? ? },

? }),

newwebpack.optimize.CommonsChunkPlugin({

name:'common',

chunks:'initial',

minChunks:2,

? }),

]

我們把以下文件單獨(dú)抽離出來打包

node_modules?文件夾下的,模塊

被 3 個(gè) 入口?chunk?共享的模塊

optimization.splitChunks

webpack 4 最大的改動(dòng)就是廢除了?CommonsChunkPlugin?引入了?optimization.splitChunks齐遵。如果你的?mode?是?production寂玲,那么 webpack4 就會(huì)自動(dòng)開啟?Code Splitting。

它內(nèi)置的代碼分割策略是這樣的:

新的 chunk 是否被共享或者是來自?node_modules?的模塊

新的 chunk 體積在壓縮之前是否大于 30kb

按需加載 chunk 的并發(fā)請(qǐng)求數(shù)量小于等于 5 個(gè)

頁面初始加載時(shí)的并發(fā)請(qǐng)求數(shù)量小于等于 3 個(gè)

雖然在 webpack4 會(huì)自動(dòng)開啟?Code Splitting梗摇,但是隨著項(xiàng)目工程的最大拓哟,這往往不能滿足我們的需求伶授,我們需要再進(jìn)行個(gè)性化的優(yōu)化断序。

應(yīng)用實(shí)例

我們先找到一個(gè)優(yōu)化空間較大的項(xiàng)目來進(jìn)行操作。這是一個(gè)后臺(tái)管理系統(tǒng)項(xiàng)目糜烹,大部分內(nèi)容由 3-4 個(gè)前端開發(fā)违诗,平時(shí)開發(fā)周期較短,且大部分人沒有優(yōu)化意識(shí)疮蹦,只是寫好業(yè)務(wù)代碼完成需求诸迟,日子一長,造成打包出來的文件較大愕乎,大大影響性能阵苇。

我們先用?webpack-bundle-analyzer?分析打包后的模塊依賴及文件大小,確定優(yōu)化的方向在哪感论。

然后我們?cè)倏聪麓虬鰜淼?js 文件

看到這兩張圖的時(shí)候绅项,我內(nèi)心是崩潰的,槽點(diǎn)如下

打包后生成多個(gè)將近 1M 的 js 文件比肄,其中不乏?vendor.js?首頁必須加載的大文件

xlsx.js?這樣的插件沒必要使用快耿,導(dǎo)出 excel 更好的方法應(yīng)該是后端返回文件流格式給前端處理

echart?和?iview?文件太大,應(yīng)該使用 cdn 引入的方法

吐槽完之后我們就要開始做正事了芳绩。正是因?yàn)橛羞@么多槽點(diǎn)掀亥,我們才更好用來驗(yàn)證我們優(yōu)化方法的可行性。

抽離 echart 和 iview

由上面分析可知示括,echart?和?iview?文件太大,此時(shí)我們就用到 webpack4 的?optimization.splitChunks?進(jìn)行代碼分割了痢畜,把他們單獨(dú)抽離打包成文件垛膝。(為了更好地呈現(xiàn)優(yōu)化效果,我們先把 xlsx.js 去掉)

vue.config.js?修改如下:

chainWebpack: config => {

? ? config.optimization.splitChunks({

chunks:'all',

? ? ? cacheGroups: {

? ? ? ? vendors: {

name:'chunk-vendors',

? ? ? ? ? test: /[/]node_modules[/]/,

priority:10,

chunks:'initial'

? ? ? ? },

? ? ? ? iview: {

name:'chunk-iview',

priority:20,

? ? ? ? ? test: /[/]node_modules[/]_?iview(.*)/

? ? ? ? },

? ? ? ? echarts: {

name:'chunk-echarts',

priority:20,

? ? ? ? ? test: /[/]node_modules[/]_?echarts(.*)/

? ? ? ? },

? ? ? ? commons: {

name:'chunk-commons',

minChunks:2,

priority:5,

chunks:'initial',

reuseExistingChunk:true

? ? ? ? }

? ? ? }

? ? })

? },

此時(shí)我們?cè)儆?webpack-bundle-analyzer?分析一下

打包出來的 js 文件

從這里可以看出我們已經(jīng)成功把?echart?和?iview?單獨(dú)抽離出來了丁稀,同時(shí)?vendor.js?也相應(yīng)地減小了體積吼拥。此外,我們還可以繼續(xù)抽離其他更多的第三方模塊线衫。

CDN 方式

雖然第三方模塊是單獨(dú)抽離出來了凿可,但是在首頁或者相應(yīng)路由加載時(shí)還是要加載這樣一個(gè)幾百 kb 的文件,還是不利于性能優(yōu)化的。這時(shí)枯跑,我們可以用 CDN 的方式引入這樣插件或者 UI 組件庫惨驶。

在?index.html?引入相應(yīng) cdn 鏈接

<head>

</head>

<body>

</body>

vue.config.js?配置?externals

configureWebpack: (config) => {

? config.externals = {

vue:'Vue',

xlsx:'XLSX',

iview:'iView',

iView:'ViewUI',

? }

}

刪除之前的引入方式并卸載相應(yīng) npm 依賴包

npm uninstall vue iview echarts xlsx --save

此時(shí)我們?cè)趤砜匆幌麓虬蟮那闆r


打包出來的 js 文件


well done ! 這時(shí)基本沒有打包出大文件了,首頁加載需要的?vendor.js?也只有幾十 kb敛助,而且我們還可以進(jìn)一步優(yōu)化粗卜,就是把 vue 全家桶的一些模塊再通過 cdn 的方法引入,比如?vue-router纳击,vuex续扔,axios?等。這時(shí)頁面特別是首頁加載的性能就得到大大地優(yōu)化了焕数。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末纱昧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子堡赔,更是在濱河造成了極大的恐慌识脆,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件加匈,死亡現(xiàn)場離奇詭異存璃,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)雕拼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門纵东,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人啥寇,你說我怎么就攤上這事偎球。” “怎么了辑甜?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵衰絮,是天一觀的道長。 經(jīng)常有香客問我磷醋,道長猫牡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任邓线,我火速辦了婚禮淌友,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘骇陈。我一直安慰自己震庭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布你雌。 她就那樣靜靜地躺著器联,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拨拓,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天肴颊,我揣著相機(jī)與錄音,去河邊找鬼千元。 笑死苫昌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的幸海。 我是一名探鬼主播祟身,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼物独!你這毒婦竟也來了袜硫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤挡篓,失蹤者是張志新(化名)和其女友劉穎婉陷,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體官研,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡秽澳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了戏羽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片担神。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖始花,靈堂內(nèi)的尸體忽然破棺而出妄讯,到底是詐尸還是另有隱情,我是刑警寧澤酷宵,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布亥贸,位于F島的核電站,受9級(jí)特大地震影響浇垦,放射性物質(zhì)發(fā)生泄漏炕置。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一男韧、第九天 我趴在偏房一處隱蔽的房頂上張望朴摊。 院中可真熱鬧,春花似錦煌抒、人聲如沸仍劈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春况既,著一層夾襖步出監(jiān)牢的瞬間这溅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國打工棒仍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悲靴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓莫其,卻偏偏與公主長得像癞尚,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子乱陡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容