三種常用的代碼分離方法:
1、入口起點
這是迄今為止最簡單、最直觀的分離代碼的方式。不過,這種方式手動配置較多
webpack.config.js
這將生成如下構建結果
正如前面提到的,這種方法存在一些問題:
2、防止重復
CommonsChunkPlugin 插件可以將公共的依賴模塊提取到已有的入口 chunk 中,或者提取到一個新生成的 chunk。讓我們使用這個插件,將之前的示例中重復的 lodash 模塊去除:
webpack.config.js
這里我們使用 CommonsChunkPlugin 之后,現在應該可以看出,index.bundle.js 中已經移除了重復的依賴模塊。需要注意的是,CommonsChunkPlugin 插件將 lodash 分離到單獨的 chunk,并且將其從 main bundle 中移除,減輕了大小。執行 npm run build 查看效果:
以下是由社區提供的,一些對于代碼分離很有幫助的插件和 loaders:
CommonsChunkPlugin 插件還可以通過使用顯式的 vendor chunks 功能,從應用程序代碼中分離 vendor 模塊。
3、動態導入
當涉及到動態代碼拆分時, 一般使用使用webpack提供的符合 ECMAScript 提案 的 import() 語法。
new Vue({
實施代碼分割并不難,難在搞清楚在什么時候、什么地方進行。
Vue.js 單頁應用進行代碼分割有三種思路:
1. 按頁面
按頁面來進行代碼分割,是最明顯的一種方式。
如果能確保每個單文件組件代表一個頁面,如 Home.vue, About.vue 以及 Contact.vue,那么我們就可以使用 Webpack 的 "動態導入" 函數 (import) 來將它們分割至單獨的構建文件中。之后后,當用戶訪問一個新頁面的時候,Webpack 將異步加載該請求的頁面文件。
如果用到了 vue-router,由于頁面已經分割成了單獨的組件,實施起來會非常方便。
代碼編譯完成后,通過查看生成的統計數據得知:每個頁面都有自己單獨的文件,同時有多出來一個名為 build_main.js 的打包文件。里面包含一些公共的代碼以及邏輯,用來異步加載其它文件,因此它需要在用戶訪問路由之前加載完成。
2. 折疊
“折疊” 是指頁面初次加載時,視圖的不可見部分。用戶通常會花費 1~2 秒來瀏覽可視區域,特別是第一次訪問網站的時候(可能更久),之后才開始向下滑動頁面。
這個時候,可以異步加載剩余的內容。
3. 條件展示內容
代碼分割另一種比較好的備選方式,是按條件展示。比如:模態框、標簽頁、下拉菜單之類。
如果我們需要一個模態框,給模態框設置 v-if 屬性,綁定了 show 變量。一方面用來控制模態框是否顯示,同時也決定了是否應該渲染模態框組件。當頁面加載的時候,它的值為 false,模態框的代碼只有當它顯示的時候才會被加載。如果用戶永遠不打開這個模態框,這部分代碼就永遠不會被下載。缺點是,可能會增加很小的用戶體驗成本:用戶點擊按鈕后,需要等待代碼文件下載完成。
代碼分離是webpack最關注的特性之一,此特性可以將代碼分離到不同的bundle,以及控制加載資源優先級別,代碼分離后可以壓縮代碼文件的大小,如果使用合理可以大幅提高資源文件的加載速度。
有三種常用的分離方法:
入口分離:使用entry手動配置分離代碼。
防止重復:使用CommonsChunkPlugin去除重復和分離chunk.
動態導入:通過模塊的內聯函數調用來分離代碼。
入口起點是最簡單最直觀的分離代碼的方式。不過這種方式手動配置比較多,會有點坑。但是這些坑注定還是要解決的。
先建個demo項目,項目結構如下:
another-module.js:
index.js:
webpack.config.js:
可以看到index.js和another-module.js同時都有引入了lodash,這樣做必然會產生重復引入loadsh。
構建項目結果:
這種方式雖然可以實現代碼,但是造成代碼重復引用。還好webpack提供了CommonsChunkPlugin插件去除重復,將重復的一部分抽取到單獨一個公共文件里面。
現在修改一下webpack.config.js文件
new webpack.optimize.CommonsChunkPlugin({
name:'common'
})
添加抽取公共代碼實例對象,common公共文件名稱的前綴。
執行構建命令,在dist文件夾下多了common.bundle.js。這個就是公共文件
打開index.html,在控制臺還是可以看到有index.js和another-module.js打印的信息。
在webpack中還有一種方式是動態導入的方式,這個可以去搜一下,這里就不演示了。
碼分離是 webpack 中最引人注目的特性之一。此特性能夠把代碼分離到不同的 bundle 中,然后可以按需加載或并行加載這些文件。代碼分離可以用于獲取更小的 bundle,以及控制資源加載優先級,如果使用合理,會極大影響加載時間。
常用的代碼分離方法有三種:
這是迄今為止最簡單直觀的分離代碼的方式。不過,這種方式手動配置較多,并有一些隱患,我們將會解決這些問題。先來看看如何從 main bundle 中分離 another module(另一個模塊):
在 src目錄下創建 another-module.js文件:
09-code-splitting/src/another-module.js
import _ from 'lodash'
console.log(_.join(['Another', 'module', 'loaded!'], ' '))
這個模塊依賴了 lodash,需要安裝一下:
[felix] webpack5 $ npm install lodash --save-dev
修改配置文件:
module.exports={
entry: {
index: './src/index.js',
another: './src/another-module.js',
},
output: {
filename: '[name].bundle.js'
},
}
09-code-splitting/webpack.config.js
//...
module.exports={
entry: {
index: './src/index.js',
another: './src/another-module.js',
},
output: {
filename: '[name].bundle.js'
//...
},
//...
}
執行編譯:
[felix] 09-code-splitting $ npx webpack
assets by status 744 KiB [cached] 4 assets
assets by status 1.44 MiB [emitted]
asset another.bundle.js 1.38 MiB [emitted] (name: another)
asset index.bundle.js 65.1 KiB [emitted] (name: index)
asset app.html 441 bytes [emitted]
Entrypoint index 68.9 KiB (740 KiB)=styles/4a9cff551c7a105e1554.css 3.81 KiB index.bundle.js 65.1 KiB 3 auxiliary assets
Entrypoint another 1.38 MiB=another.bundle.js
runtime modules 3.23 KiB 12 modules
cacheable modules 549 KiB (javascript) 738 KiB (asset) 2.65 KiB (css/mini-extract)
javascript modules 546 KiB
modules by path ../node_modules/ 540 KiB 9 modules
modules by path ./src/ 5.48 KiB 8 modules
asset modules 3.1 KiB (javascript) 738 KiB (asset)
./src/assets/img-1.png 42 bytes (javascript) 101 KiB (asset) [built] [code generated]
./src/assets/webpack-logo.svg 2.99 KiB [built] [code generated]
./src/assets/example.txt 25 bytes [built] [code generated]
./src/assets/qianfeng-sem.jpg 42 bytes (javascript) 637 KiB (asset) [built] [code generated]
json modules 565 bytes
./src/assets/json/data.toml 188 bytes [built] [code generated]
./src/assets/json/data.yaml 188 bytes [built] [code generated]
./src/assets/json/data.json5 189 bytes [built] [code generated]
css ../node_modules/css-loader/dist/cjs.js!./src/style.css 2.65 KiB [built] [code generated]
webpack 5.54.0 compiled successfully in 854 ms
asset another.bundle.js 1.38 MiB [emitted] (name: another) , 我們發現 lodash.js也被打包到 another.bundle.js 中。
查看 app.html:
09-code-splitting/dist/app
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>千鋒大前端教研院-Webpack5學習指南</title>
<link href="styles/4a9cff551c7a105e1554.css" rel="stylesheet">
</head>
<body>
<script defer src="index.bundle.js"></script>
<script defer src="another.bundle.js"></script>
</body>
</html>
兩個入口的 bundle文件都被鏈接到了 app.html中。
我們再來修改一下 index.js文件:
import _ from 'lodash'
console.log(_.join(['index', 'module', 'loaded!'], ' '))
09-code-splitting/src/index.js
// 導入模塊
//...
import _ from 'lodash'
//...
console.log(_.join(['index', 'module', 'loaded!'], ' '))
執行編譯:
[felix] 09-code-splitting $ npx webpack
assets by status 744 KiB [cached] 4 assets
assets by path . 2.82 MiB
asset index.bundle.js 1.44 MiB [emitted] (name: index)
asset another.bundle.js 1.38 MiB [compared for emit] (name: another)
asset app.html 441 bytes [compared for emit]
Entrypoint index 1.44 MiB (740 KiB)=styles/4a9cff551c7a105e1554.css 3.81 KiB index.bundle.js 1.44 MiB 3 auxiliary assets
Entrypoint another 1.38 MiB=another.bundle.js
runtime modules 3.35 KiB 13 modules
cacheable modules 549 KiB (javascript) 738 KiB (asset) 2.65 KiB (css/mini-extract)
javascript modules 546 KiB
modules by path ../node_modules/ 540 KiB 9 modules
modules by path ./src/ 5.57 KiB 8 modules
asset modules 3.1 KiB (javascript) 738 KiB (asset)
./src/assets/img-1.png 42 bytes (javascript) 101 KiB (asset) [built] [code generated]
./src/assets/webpack-logo.svg 2.99 KiB [built] [code generated]
./src/assets/example.txt 25 bytes [built] [code generated]
./src/assets/qianfeng-sem.jpg 42 bytes (javascript) 637 KiB (asset) [built] [code generated]
json modules 565 bytes
./src/assets/json/data.toml 188 bytes [built] [code generated]
./src/assets/json/data.yaml 188 bytes [built] [code generated]
./src/assets/json/data.json5 189 bytes [built] [code generated]
css ../node_modules/css-loader/dist/cjs.js!./src/style.css 2.65 KiB [built] [code generated]
webpack 5.54.0 compiled successfully in 898 ms
觀察一下:
assets by path . 2.82 MiB
asset index.bundle.js 1.44 MiB [emitted] (name: index)
asset another.bundle.js 1.38 MiB [compared for emit] (name: another)
asset app.html 441 bytes [compared for emit]
我們發現:index.bundle.js 文件大小也驟然增大了,可以 lodash.js也被打包到了 index.bundle.js中了。
正如前面提到的,這種方式的確存在一些隱患:
以上兩點中,第一點對我們的示例來說無疑是個問題,因為之前我們在 ./src/index.js 中也引入過 lodash,這樣就在兩個 bundle 中造成重復引用。
module.exports={
entry: {
index: {
import: './src/index.js',
dependOn: 'shared',
},
another: {
import: './src/another-module.js',
dependOn: 'shared',
},
shared: 'lodash',
}
}
09-code-splitting/webpack.config.js
//...
module.exports={
entry: {
index: {
import: './src/index.js',
dependOn: 'shared',
},
another: {
import: './src/another-module.js',
dependOn: 'shared',
},
shared: 'lodash',
},
//...
}
執行編譯
[felix] 09-code-splitting $ npx webpack
assets by status 744 KiB [cached] 4 assets
assets by status 1.45 MiB [emitted]
asset shared.bundle.js 1.39 MiB [emitted] (name: shared)
asset index.bundle.js 57.1 KiB [emitted] (name: index)
asset another.bundle.js 1.53 KiB [emitted] (name: another)
asset app.html 487 bytes [emitted]
Entrypoint index 60.9 KiB (740 KiB)=styles/4a9cff551c7a105e1554.css 3.81 KiB index.bundle.js 57.1 KiB 3 auxiliary assets
Entrypoint another 1.53 KiB=another.bundle.js
Entrypoint shared 1.39 MiB=shared.bundle.js
runtime modules 4.47 KiB 9 modules
cacheable modules 549 KiB (javascript) 738 KiB (asset) 2.65 KiB (css/mini-extract)
javascript modules 546 KiB
modules by path ../node_modules/ 540 KiB 9 modules
modules by path ./src/ 5.57 KiB 8 modules
asset modules 3.1 KiB (javascript) 738 KiB (asset)
./src/assets/img-1.png 42 bytes (javascript) 101 KiB (asset) [built] [code generated]
./src/assets/webpack-logo.svg 2.99 KiB [built] [code generated]
./src/assets/example.txt 25 bytes [built] [code generated]
./src/assets/qianfeng-sem.jpg 42 bytes (javascript) 637 KiB (asset) [built] [code generated]
json modules 565 bytes
./src/assets/json/data.toml 188 bytes [built] [code generated]
./src/assets/json/data.yaml 188 bytes [built] [code generated]
./src/assets/json/data.json5 189 bytes [built] [code generated]
css ../node_modules/css-loader/dist/cjs.js!./src/style.css 2.65 KiB [built] [code generated]
webpack 5.54.0 compiled successfully in 1237 ms
觀察一下:
assets by status 1.45 MiB [emitted]
asset shared.bundle.js 1.39 MiB [emitted] (name: shared)
asset index.bundle.js 57.1 KiB [emitted] (name: index)
asset another.bundle.js 1.53 KiB [emitted] (name: another)
asset app.html 487 bytes [emitted]
index.bundle.js與another.bundle.js共享的模塊lodash.js被打包到一個單獨的文件shared.bundle.js中。
SplitChunksPlugin
SplitChunksPlugin 插件可以將公共的依賴模塊提取到已有的入口 chunk 中,或者提取到一個新生成的 chunk。讓我們使用這個插件,將之前的示例中重復的 lodash 模塊去除:
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
optimization: {
splitChunks: {
chunks: 'all',
},
},
09-code-splitting/webpack.config.js
//...
module.exports={
// entry: {
// index: {
// import: './src/index.js',
// dependOn: 'shared',
// },
// another: {
// import: './src/another-module.js',
// dependOn: 'shared',
// },
// shared: 'lodash',
// },
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
//...
optimization: {
//...
splitChunks: {
chunks: 'all',
}
},
}
執行編譯
執行編譯:
[felix] 09-code-splitting $ npx webpack
assets by status 744 KiB [cached] 4 assets
assets by status 1.46 MiB [emitted]
asset vendors-node_modules_lodash_lodash_js.bundle.js 1.37 MiB [emitted] (id hint: vendors)
asset index.bundle.js 75.3 KiB [emitted] (name: index)
asset another.bundle.js 17.2 KiB [emitted] (name: another)
asset app.html 518 bytes [emitted]
Entrypoint index 1.45 MiB (740 KiB)=vendors-node_modules_lodash_lodash_js.bundle.js 1.37 MiB styles/4a9cff551c7a105e1554.css 3.81 KiB index.bundle.js 75.3 KiB 3 auxiliary assets
Entrypoint another 1.39 MiB=vendors-node_modules_lodash_lodash_js.bundle.js 1.37 MiB another.bundle.js 17.2 KiB
runtime modules 8.1 KiB 17 modules
cacheable modules 549 KiB (javascript) 738 KiB (asset) 2.65 KiB (css/mini-extract)
javascript modules 546 KiB
modules by path ../node_modules/ 540 KiB 9 modules
modules by path ./src/ 5.57 KiB 8 modules
asset modules 3.1 KiB (javascript) 738 KiB (asset)
./src/assets/img-1.png 42 bytes (javascript) 101 KiB (asset) [built] [code generated]
./src/assets/webpack-logo.svg 2.99 KiB [built] [code generated]
./src/assets/example.txt 25 bytes [built] [code generated]
./src/assets/qianfeng-sem.jpg 42 bytes (javascript) 637 KiB (asset) [built] [code generated]
json modules 565 bytes
./src/assets/json/data.toml 188 bytes [built] [code generated]
./src/assets/json/data.yaml 188 bytes [built] [code generated]
./src/assets/json/data.json5 189 bytes [built] [code generated]
css ../node_modules/css-loader/dist/cjs.js!./src/style.css 2.65 KiB [built] [code generated]
webpack 5.54.0 compiled successfully in 914 ms
觀察一下
assets by status 1.46 MiB [emitted]
asset vendors-node_modules_lodash_lodash_js.bundle.js 1.37 MiB [emitted] (id hint: vendors)
asset index.bundle.js 75.3 KiB [emitted] (name: index)
asset another.bundle.js 17.2 KiB [emitted] (name: another)
asset app.html 518 bytes [emitted]
使用 optimization.splitChunks 配置選項之后,現在應該可以看出,index.bundle.js 和 another.bundle.js 中已經移除了重復的依賴模塊。需要注意的是,插件將 lodash 分離到單獨的 chunk,并且將其從 main bundle 中移除,減輕了大小。
當涉及到動態代碼拆分時,webpack 提供了兩個類似的技術。第一種,也是推薦選擇的方式是,使用符合 ECMAScript 提案 的 "https://webpack.docschina.org/api/module-methods/#import-1">import() 語法 來實現動態導入。第二種,則是 webpack 的遺留功能,使用 webpack 特定的 require.ensure。讓我們先嘗試使用第一種……
創建 async-module.js文件:
內容如下:
09-code-splitting/src/async-module.js
function getComponent() {
return import('lodash')
.then(({
default: _
})=> {
const element=document.createElement('div')
element.innerHTML=_.join(['Hello', 'webpack'], ' ')
return element
})
.catch((error)=> 'An error occurred while loading the component')
}
getComponent().then((component)=> {
document.body.appendChild(component)
})
在入口文件中導入:
import './async-module'
09-code-splitting/src/index.js
// 導入模塊
//...
import './async-module'
//...
執行編譯:
[felix] 09-code-splitting $ npx webpack
assets by status 744 KiB [cached] 4 assets
assets by status 1.53 MiB [compared for emit]
assets by chunk 1.46 MiB (id hint: vendors)
asset vendors-node_modules_lodash_lodash_js.bundle.js 1.37 MiB [compared for emit] (id hint: vendors)
asset vendors-node_modules_babel_runtime_regenerator_index_js-node_modules_css-loader_dist_runtime_-86adfe.bundle.js 93.8 KiB [compared for emit] (id hint: vendors)
asset index.bundle.js 54.3 KiB [compared for emit] (name: index)
asset another.bundle.js 17.2 KiB [compared for emit] (name: another)
asset app.html 658 bytes [compared for emit]
Entrypoint index 1.52 MiB (740 KiB)=vendors-node_modules_lodash_lodash_js.bundle.js 1.37 MiB vendors-node_modules_babel_runtime_regenerator_index_js-node_modules_css-loader_dist_runtime_-86adfe.bundle.js 93.8 KiB styles/4a9cff551c7a105e1554.css 3.81 KiB index.bundle.js 54.3 KiB 3 auxiliary assets
Entrypoint another 1.39 MiB=vendors-node_modules_lodash_lodash_js.bundle.js 1.37 MiB another.bundle.js 17.2 KiB
runtime modules 9.21 KiB 18 modules
....
從打印的結果看,除了公共的 lodash 代碼被單獨打包到一個文件外,還生成了一個 vendors-node_modules_babel_runtime_regenerator_index_js-node_modules_css-loader_dist_runtime_-86adfe.bundle.js 文件。
我們看到,靜態和動態載入的模塊都正常工作了。
懶加載或者按需加載,是一種很好的優化網頁或應用的方式。這種方式實際上是先把你的代碼在一些邏輯斷點處分離開,然后在一些代碼塊中完成某些操作后,立即引用或即將引用另外一些新的代碼塊。這樣加快了應用的初始加載速度,減輕了它的總體體積,因為某些代碼塊可能永遠不會被加載。
創建一個 math.js 文件,在主頁面中通過點擊按鈕調用其中的函數:
09-code-splitting/src/math.js
export const add=()=> {
return x + y
}
export const minus=()=> {
return x - y
}
編輯 index.js文件:
const button=document.createElement('button')
button.textContent='點擊執行加法運算'
button.addEventListener('click', ()=> {
import(/* webpackChunkName: 'math' */ './math.js').then(({ add })=> {
console.log(add(4, 5))
})
})
document.body.appendChild(button)
這里有句注釋,我們把它稱為 webpack 魔法注釋:webpackChunkName: 'math', 告訴webpack打包生成的文件名為 math。
啟動服務,在瀏覽器上查看:
?
第一次加載完頁面,math.bundle.js不會加載,當點擊按鈕后,才加載 math.bundle.js文件。
Webpack v4.6.0+ 增加了對預獲取和預加載的支持。
在聲明 import 時,使用下面這些內置指令,可以讓 webpack 輸出 "resource hint(資源提示)",來告知瀏覽器:
下面這個 prefetch 的簡單示例中,編輯 index.js文件:
const button=document.createElement('button')
button.textContent='點擊執行加法運算'
button.addEventListener('click', ()=> {
import(/* webpackChunkName: 'math', webpackPrefetch: true */ './math.js').then(({ add })=> {
console.log(add(4, 5))
})
})
document.body.appendChild(button)
添加第二句魔法注釋:webpackPrefetch: true
告訴 webpack 執行預獲取。這會生成 <link rel="prefetch" href="math.js"> 并追加到頁面頭部,指示著瀏覽器在閑置時間預取 math.js 文件。
09-code-splitting/src/index.js
// 導入模塊
//...
import './async-module'
//...
const button=document.createElement('button')
button.textContent='點擊執行加法運算'
button.addEventListener('click', ()=> {
import( /* webpackChunkName: 'math', webpackPrefetch: true */ './math.js').then(({
add
})=> {
console.log(add(4, 5))
})
})
document.body.appendChild(button)
啟動服務,在瀏覽器上查看:
我們發現,在還沒有點擊按鈕時,math.bundle.js就已經下載下來了。同時,在 app.html里webpack自動添加了一句:
?
點擊按鈕,執行 4+5的加法運算。
與 prefetch 指令相比,preload 指令有許多不同之處:
創建一個 print.js文件:
export const print=()=> {
console.log('preload chunk.')
}
修改 index.js文件:
const button2=document.createElement('button')
button2.textContent='點擊執行字符串打印'
button2.addEventListener('click', ()=> {
import(/* webpackChunkName: 'print', webpackPreload: true */ './print.js').then(({ print })=> {
print(4, 5)
})
})
document.body.appendChild(button2)
09-code-splitting/src/index.js
// 導入模塊
//...
import './async-module'
//...
const button2=document.createElement('button')
button2.textContent='點擊執行字符串打印'
button2.addEventListener('click', ()=> {
import( /* webpackChunkName: 'print', webpackPreload: true */ './print.js').then(({
print
})=> {
print()
})
})
document.body.appendChild(button2)
啟動服務,打開瀏覽器:
?仔細觀察,發現 print.bundle.js未被下載,因為我們配置的是 webpackPreload, 是在父 chunk 加載時,以并行方式開始加載。點擊按鈕才加載的模塊不會事先加載的。
我們修改一下引入方式:
09-code-splitting/src/index.js
//...
import( /* webpackChunkName: 'print', webpackPreload: true */ './print.js').then(({
print
})=> {
print()
})
再次刷新瀏覽器頁面:
print.bundle.js被加載下來,是和當前index.bundle.js并行加載的。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。