webpack3的CommonsChunkPlugin插件详解
webpack打出來的包在不做處理的情況下是非常大的,所有依賴都被塞進一個文件中,文件中有業務代碼,有業務代碼依賴的第三方庫代碼,還有webpack生成的運行時代碼等。這樣的一個文件不方便靜態資源緩存,并且初始化頁面的時候下載了所有的JS這是沒必要的,拖慢了頁面速度。所以對于webpack打包的資源文件進行分割按需加載是很重要的一件事情。
webpack4都出來了為啥要寫一篇關于webpack3的文章。
目前webpack3應用的還是很多,并且學習相關知識協查找過相關資料很多遍,所以這次總結一下通過webpack3分割代碼的方法,方便后期需要的時候方便查閱。
在webpack3中使用的分割thunk方法主要是使用webpack自帶的插件(webpack.optimize.CommonsChunkPlugin)實現的。
首先通過webpack來構建項目
目錄結構:
|— src
? |— index.html
? |— indexa.js
? |— indexb.js
|— webpack.config.js
|— node_modules
? |— jquery/
indexa.js
import $ from 'jquery'$() // 調用一下console.log('我是indexa.js')indexb.js
import $ from 'jquery'$() // 調用一下console.log('我是indexb.js')webpack.config.js
// output中的path需要絕對路徑 let path = require('path') // 用于將打包的js文件注入到html文件中 let HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = {entry: {indexa: './src/indexa.js',indexb: './src/indexb.js'},output: {path: path.resolve('./dist/'),filename: '[name].[chunkHash].js'},plugins: [new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})] }上面的示例中有兩個入口,一個是pagea.js,另一個是pageb.js。這兩個入口都引入了jquery.js,并且打包結果中我們看到jquery.js被同時打到了兩個入口文件中。
提取公共第三方庫jquery
這樣的結果顯然不是我們期望的,我們期望兩個入口都引入的jquery被打到單獨的包中,然后在兩個入口引入這個包即可。這就需要借助webpack.optimize.CommonsChunkPlugin插件,下面修改webpack.config.js文件為:
// output中的path需要絕對路徑 let path = require('path') // 用于將打包的js文件注入到html文件中 let HtmlWebpackPlugin = require('html-webpack-plugin') let webpack = require('webpack') let CommonsChunkPlugin = webpack.optimize.CommonsChunkPluginmodule.exports = {entry: {indexa: './src/indexa.js',indexb: './src/indexb.js',jquery: ['jquery'] // 依賴的第三方庫node_modules中},output: {path: path.resolve('./dist/'),filename: '[name].[chunkHash].js'},plugins: [new CommonsChunkPlugin({name: 'jquery', // 如果有該名稱的chunk則選擇這個chunk提取公共文件,這里是jquery,如果沒有則生成的文件是這個名稱的chunk}),new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})] }通過CommonsChunkPlugin插件我們提取了在indexa和indexb中都引入的jquery庫這樣打包結果中pagea.js和pageb.js就大幅減小了。
提取自定義公共模塊
通常我們不止有第三方的公共模塊,我們自己也會寫一些公用的工具方法。現加入公用工具方法文件utils.js。
utils.js
function common () {console.log('我是工具方法') }export {common }修改pagea.js文件
import $ from 'jquery' import {common} from './utils.js'common() // 新加的$() // 調用一下console.log('我是indexa.js')修改pageb.js文件
import $ from 'jquery' import {common} from './utils.js'common() // 新加的$() // 調用一下console.log('我是indexb.js')打包后發現自己的公共方法文件被打包到了jquery……js文件中了,我們并不希望這樣,因為第三方庫一般是不會修改的,我們希望每次打包第三方庫的名稱不變,這樣有助于客戶端緩存。所以我們需要從當前的jquery…js中提取出自己的公共方法文件。
分離utils.js文件和jquery等第三方庫文件
通過打包結果發現我們自定義的模塊確實從jquery中提取了出來,但是卻打到了每個引入的頁面中,這也是我們接受不了的。
修改webpack.config.js文件如下:
// output中的path需要絕對路徑 let path = require('path') // 用于將打包的js文件注入到html文件中 let HtmlWebpackPlugin = require('html-webpack-plugin') let webpack = require('webpack') let CommonsChunkPlugin = webpack.optimize.CommonsChunkPluginmodule.exports = {entry: {indexa: './src/indexa.js',indexb: './src/indexb.js',jquery: ['jquery'] // 依賴的第三方庫node_modules中},output: {path: path.resolve('./dist/'),filename: '[name].[chunkHash].js'},plugins: [new CommonsChunkPlugin({name: 'jquery', // 如果有該名稱的chunk則選擇這個chunk提取公共文件,這里是jquery,如果沒有則生成的文件是這個名稱的chunkminChunks: Infinity // 這樣就只會打包出自身chunk和 webpack生成的一些文件}),new CommonsChunkPlugin({name: 'utils',chunks: ['indexa', 'indexb']}),new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})] }打包結果如下,可以發現utils被提取了出來,這樣就我們的目的就達到了。
minChunks的函數值
我們發現第三方庫我們是通過entry字段手動添加的,這樣比較麻煩,不能以后添加一個第三方庫我們就手動修改一下entry的jquery數組。
我們可以通過minChunks的值傳入一個函數來做,函數返回true則會被打包。修改webpack.config.js文件如下:
// output中的path需要絕對路徑 let path = require('path') // 用于將打包的js文件注入到html文件中 let HtmlWebpackPlugin = require('html-webpack-plugin') let webpack = require('webpack') let CommonsChunkPlugin = webpack.optimize.CommonsChunkPluginmodule.exports = {entry: {indexa: './src/indexa.js',indexb: './src/indexb.js',// 已經不需要了 jquery: ['jquery'] // 依賴的第三方庫node_modules中},output: {path: path.resolve('./dist/'),filename: '[name].[chunkHash].js'},plugins: [new CommonsChunkPlugin({name: "vendor", // 修改jquery名稱為vendor,第三方庫集合minChunks: function (module, ) {// node_modules中出來的都打到這個文件中return module.context && module.context.includes("node_modules");}}),new CommonsChunkPlugin({name: 'utils',chunks: ['indexa', 'indexb']}),new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})] }打包結果如下,只是將jquery…js的名稱換成了vendor…js其他沒有任何變化。
讓moduleId固定下來
通過上面兩張截圖的觀察我們可以發現indexb…js的chunkHash不一樣了,但是我們并沒有修改文件內容。這是因為webpack生成模塊的moduleId在變化。讓moduleId停止變化的插件有兩個,一個是HashedModuleIdsPlugin,還有一個是NamedModulesPlugin。
HashedModuleIdsPlugin: 該插件會根據模塊的相對路徑生成一個四位數的hash作為模塊id, 建議用于生產環境。
NamedModulesPlugin: 當開啟 HMR 的時候使用該插件會顯示模塊的相對路徑,建議用于開發環境。
我們就選擇生產環境用的插件,修改webpack.config.js文件如下:
// output中的path需要絕對路徑 let path = require('path') // 用于將打包的js文件注入到html文件中 let HtmlWebpackPlugin = require('html-webpack-plugin') let webpack = require('webpack') let CommonsChunkPlugin = webpack.optimize.CommonsChunkPluginmodule.exports = {entry: {indexa: './src/indexa.js',indexb: './src/indexb.js',// 已經不需要了 jquery: ['jquery'] // 依賴的第三方庫node_modules中},output: {path: path.resolve('./dist/'),filename: '[name].[chunkHash].js'},plugins: [new CommonsChunkPlugin({name: "vendor", // 修改jquery名稱為vendor,第三方庫集合minChunks: function (module) {// node_modules中出來的都打到這個文件中return module.context && module.context.includes("node_modules");}}),new CommonsChunkPlugin({name: 'utils',chunks: ['indexa', 'indexb']}),// 固定下來模塊的moduleIdnew HashedModuleIdsPlugin(),new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})] }runtime和manifest
其實vender中不止有node_module文件夾中的包,還包括
runtime: 指在瀏覽器運行時,webpack 用來連接模塊化的應用程序的所有代碼。其中包含:在模塊交互時,連接模塊所需的加載和解析邏輯。包括瀏覽器中的已加載模塊的連接,以及懶加載模塊的執行邏輯。
manifest: 當編譯器(compiler)開始執行、解析和映射應用程序時,它會保留所有模塊的詳細要點。這個數據集合稱為 “Manifest”,當完成打包并發送到瀏覽器時,會在運行時通過 Manifest 來解析和加載模塊。無論你選擇哪種模塊語法,那些 import 或 require 語句現在都已經轉換為 __webpack_require__ 方法,此方法指向模塊標識符(module identifier)。通過使用 manifest 中的數據,runtime 將能夠查詢模塊標識符,檢索出背后對應的模塊。
當模塊做出改變的時候manifest也會改變,同時也會導致vender改變,最后導致vender的緩存失效,這種失效并不是因為vender本身內容的改變導致的,所以我們需要分離runtime和manifest。
提取runtime和manifest
修改webpack.config.js文件如下:
// output中的path需要絕對路徑 let path = require('path') // 用于將打包的js文件注入到html文件中 let HtmlWebpackPlugin = require('html-webpack-plugin') let webpack = require('webpack') let CommonsChunkPlugin = webpack.optimize.CommonsChunkPluginmodule.exports = {entry: {indexa: './src/indexa.js',indexb: './src/indexb.js'},output: {path: path.resolve('./'),filename: '[name].[chunkHash].js'},plugins: [new CommonsChunkPlugin({name: "vendor", // 修改jquery名稱為vendor,第三方庫集合minChunks: function (module) {// node_modules中出來的都打到這個文件中return module.context && module.context.includes("node_modules");}}),new CommonsChunkPlugin({name: 'utils',chunks: ['indexa', 'indexb']}),new CommonsChunkPlugin({name: 'manifest',minChunks: Infinity}),// 固定下來模塊的moduleIdnew HashedModuleIdsPlugin(),new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})] }children字段和async字段的作用
children和async作用于動態加載模塊。如果沒有設置children,那么在動態引入的多個腳本中公用的部分并不會被提取出來。如果設置了childrend: true,則公共部分會被提取到主腳本中。進一步設置async字段,那么提取出來的公共部分不會在主腳本中,而會生成一個單獨文件異步引入。
如果動態引入腳本和主腳本有公共的部分,那么及時沒有設置children和async字段也會被提取。
為了去除上面的干擾重建目錄。
|— src|— index.html|— index.js|— child1.js|— child2.js|— webpack.config.js|— node_modules|— jquery/index.js
改文件為主腳本,會動態引入兩個腳本child1.js和child2.js
require.ensure(['./child1.js'], function () { })require.ensure(['./child2.js'], function () { })child1.js
import $ from 'jquery' import {common} from './utils.js'$() common()console.log('我是child1.js')child2.js(和child1.js內容相同)
import $ from 'jquery' import {common} from './utils.js'$() common()console.log('我是child2.js')webpack.config.js
先正常打包,觀察結果。
// output中的path需要絕對路徑 let path = require('path') // 用于將打包的js文件注入到html文件中 let HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = {entry: {index: './src/index.js'},output: {path: path.resolve('./'),filename: '[name].[chunkHash].js',chunkFilename: '[name].[chunkHash].js'},plugins: [new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})] }下圖我們可以看出除了index.js還多了兩個js,這兩個就是通過動態加載引入的js被單獨打包了,并且這兩個js中公共的部分并沒有被提取。還可以注意到這時候index.js是很小的。
提取異步加載的js中公共部分
// output中的path需要絕對路徑 let path = require('path') // 用于將打包的js文件注入到html文件中 let HtmlWebpackPlugin = require('html-webpack-plugin') let CommonsChunkPlugin = webpack.optimize.CommonsChunkPluginmodule.exports = {entry: {index: './src/index.js'},output: {path: path.resolve('./'),filename: '[name].[chunkHash].js',chunkFilename: '[name].[chunkHash].js'},plugins: [new CommonsChunkPlugin({children: true}),new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})] }添加children選項之后:
動態引入進來的文件的公共部分被提取到主塊中了。兩個動態引入文件的尺寸減小并且主腳本的尺寸變大了。
將動態引入的部分單獨打包
// output中的path需要絕對路徑 let path = require('path') // 用于將打包的js文件注入到html文件中 let HtmlWebpackPlugin = require('html-webpack-plugin') let CommonsChunkPlugin = webpack.optimize.CommonsChunkPluginmodule.exports = {entry: {index: './src/index.js'},output: {path: path.resolve('./'),filename: '[name].[chunkHash].js',chunkFilename: '[name].[chunkHash].js'},plugins: [new CommonsChunkPlugin({children: true,async: true}),new HtmlWebpackPlugin({template: './src/index.html',filename: 'index.html'})] }我們可以看到index.js文件又減小了并且多了一個文件。
以上就是我理解的CommonsChunkPlugin插件中children和async的用法。
關于自定義動態引入腳本打包的名字可參考webpack中實現按需加載
注:
hash:一個隨機值,每次打包都會改變,建議用于開發。
chunkHash: 根據文件內容生成一個隨機值,建議用于生產便于緩存。
Infinity:創建一個公共chunk,但是不包含任何模塊,內部是一些webpack生成的runtime代碼和chunk自身包含的模塊(如果chunk存在的話)。
多CommonsChunkPlugin:第二次使用CommonsChunkPlugin插件的時候如果不指定chunks默認針對前一個CommonsChunkPlugin插件生成的chunk做提取。
children部分主腳本:引入異步腳本的腳本。
總結:
參考
webpack4:連奏中的進化
Webpack4之SplitChunksPlugin規則
詳解CommonsChunkPlugin的配置和用法
CommonsChunkPlugin中children和async屬性詳解
Webpack2中的NamedModulesPlugin與HashedModuleIdsPlugin
runtime和manifest
hashed-module-ids-plugin
NamedModulesPlugin
webpack中ensure方法和CommonsChunkPlugin中的children選項
總結
以上是生活随笔為你收集整理的webpack3的CommonsChunkPlugin插件详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: webpack中实现按需加载
- 下一篇: nth-child(n)和nth-of-