從三個插件開始
1、CommonsChunkPlugin
commonsChunkPlugin 是webpack中的代碼提取插件,可以分析代碼中的引用關系然后根據所需的配置進行代碼的提取到指定的文件中,常用的用法可以歸為四類:(1)、提取node_modules中的模塊到一個文件中;(2)、提取 webpack 的runtime代碼到指定文件中;(3)、提取入口文件所引用的公共模塊到指定文件中;(4)、提取異步加載文件中的公共模塊到指定文件中。下面就具體說明以上四種用法。
1.1 提取node_modules中的模塊
貼一段vue-cli 生成的代碼
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks(module) {
// any required modules inside node_modules are extracted to vendor
// 所有在node_modules中被引用的模塊將被提取到vendor中
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
})
通過查詢 commonsChunkPlugin 的相關文檔可以知道 options 的配置項中name用於 output 中的 filename的 [name] 占位符,也就是通過name可以指定提取出的文件的文件名或包含name的文件夾名稱。minChunks 的取值比較多樣
minChunks: number|Infinity|function(module, count) => boolean
這里我們用的是函數的形式,函數接受兩個參數,且返回值是boolean類型。函數的作用是遍歷所有被引用的模塊(module.resource表示的是被引用模塊的絕對路徑),由此我們可以通過module.resource進行判斷,滿足條件的我們返回true,反之返回false。返回值為true的模塊將被提取到vender中。通過這一步我們可以完成node_modules中模塊的提取。
1.2 提取webpack的runtime代碼
通過給minChunks傳入Infinity這個參數,就可以實現提取webpack的運行時代碼。按照官方文檔的說法是:
// Passing `Infinity` just creates the commons chunk, but moves no modules into it.
// with more entries, this ensures that no other module goes into the vendor chunk
我個人理解:傳入Infinity會生成一個沒有包含任何模塊的文件,生成這個文件的目的是為了保證vendor和其他公共模塊的純潔性,貼一段vue-cli中的webpack.prod.conf.js文件中的配置代碼,並稍作解釋:
// extract webpack runtime and module manifest[module manifest => 模塊清單] to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
})
如
[]
中注釋,manifest是一個模塊清單,如果在沒有manifest的情況下,我們在業務代碼中新引入了一個自定義模塊(不是在node_modules中的模塊),我們會發現打包的vendeor會發生變化,變化就是因為在vendor中需要指明webpack的模塊清單及模塊的引用關系。這樣我們就無法保證vendor的純潔性,所以這就是我們提取manifest的必要性。下面會有兩張對比圖片:
有manifest
無manifest
1.3 提取入口文件所引用的公共模塊
如果是在多入口的打包項目中,提取出公共文件可以減少冗余的打包代碼。如果是在單入口的應用中,這一步驟可以省略,下面直接貼出代碼:
/**
* 該配置用於在多入口打包中,對 entry chunk中的公共代碼進行提取
*/
new webpack.optimize.CommonsChunkPlugin({
name: 'common', // 如果filename不存在,則使用該配置項的value進行對提取的公共代碼進行命名
filename: 'common/common-[hash].js', // filename 配置項可以指定index.html中的文件引用路徑,及提取出來的代碼放置路徑
chunks: [...utils.bundleModules], // 需要指定entry chunk 及 menifest
minChunks: 2 // 指定最小引用次數
})
使用情況如注釋中所言,不再贅述。
1.4 提取異步入口中的公共模塊到單獨文件
如果我們在項目中使用了異步加載模塊的形式進行代碼打包,異步的文件會作為主入口下的子入口。比較典型的例子就是vue中的異步路由形式,每一個異步路由頁面可以作為子入口,而這些入口中的公共代碼我們可以將其提取出來到一個公共文件中,從而實現代碼的精簡。下面看一段路由代碼:
routes: [
{
path: '/',
name: 'Home',
// 此處采用異步路由的形式,第一個參數是路由所對應的組件路徑,最后一個參數是指定[name]占位符
component: resolve => require.ensure(['@/modules/moduleA/Home'], resolve, 'moduleA/js/home')
},
{
path: '/add',
name: 'Add',
component: resolve => require.ensure(['@/components/Add'], resolve, 'moduleA/js/add')
}
]
再貼一段webpack配置代碼:
// This instance extracts shared chunks from code splitted chunks and bundles them
// in a separate chunk, similar to the vendor chunk
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
new webpack.optimize.CommonsChunkPlugin({
names: [...utils.bundleModules],
async: 'vendor-async',
children: true,
minChunks: 2
})
以上兩段代碼會根據commonsChunkPlugin中names所指定的入口chunk名稱進行分別提取,例如names中指定的值為['moduleA', 'moduleB']
,webpack會分別找到moduleA和moduleB兩個主入口,然后分別在兩個主入口中查找異步模塊,如果他們各自的異步模塊中有共通引用的模塊,則這些公共模塊會被提取到一個名為vendr-async-moduleA和vendr-async-moduleB的兩個文件夾中。
通過以上四個步驟我們基本上可以將常用的公共模塊提取到指定的文件中,並且通過commonsChunkPlugin,webpack會自動將依賴文件注入到index.html文件中。完成代碼的分割操作。
2、HTMLWebpackPlugin
htmlWebpackPlugin
這個插件是用來配置主入口html文件的,具體功能可以通過官方文件了解,這里不再贅述。此處需要說明的是在多入口中htmlWebpackPlugin
的配置。下面貼上vue-cli中的配置:
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
})
上面的配置中指定了template和filename,filename是打包后輸出的文件名,filename值是一個字符串,所以可以配置輸出的路徑和文件名,template用於指定模板文件,同樣可以通過字符串的形式指定template的路徑和文件名。所以我們可以根據不同的入口配置多個htmlWebpackPlugin,並且指定不同的輸出路徑,這樣就可以將多入口的打包的index.html文件進行區分了。
在webpack的配置中
plugins
是一個數組,此處我們可以通過循環的方式生成多個htmlWebpackPlugin數組,然后將生成的數組與plugins數組進行合並。多入口配置如下:
// 生成htmlWebpackPlugin數組
const htmlOutput = (function () {
// utils.bundleModules 是打包的模塊列表
return utils.bundleModules.map(item => {
// 指定 template 路徑
let template = './src/modules/' + item + '/index.html'
return new HtmlWebpackPlugin({
// 指定每個index.html 的生成路徑,現在的規則下是生成到dist目錄下與入口chunk name相同的文件夾下
filename: path.resolve(__dirname, '../dist/' + item + '/index.html'),
template,
// 此處需要指定 chunks 的值,如果不指定該值,則會默認將所有生成js文件都注入到index.html文件中。實際上我們只希望得到跟當前入口相關的js文件
chunks: ['manifest', 'common', 'vendor', item],
// 對文件實行壓縮混淆等操作
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
})
})
}())
// ...
// 將生成的htmlOutput 數組拼接到 plugins中
plugins: [
// 其他插件 ...
...htmlOutput
]
通過上面的配置我們就可以順利將index.html 文件分別打包到我們指定的目錄當中。
3、ExtractTextPlugin
通過以上兩個配置我們完成了js文件和html文件的分離,並且將他們打包到了指定的目錄下。最終我們還剩下css文件沒有進行處理。css文件我們通過
extractTextPlugin
這個插件進行處理,相較於vue-cli中的默認配置,我們改動較小。只需要根據入口來指定打包的位置,見代碼:
// vue-cli默認配置,
// extract css into its own file
new ExtractTextPlugin({
// 所有的文件都統一打包到 dist目錄下的css文件夾下
filename: utils.assetsPath('css/[name].[contenthash].css'),
// Setting the following option to `false` will not extract CSS from codesplit chunks.
// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
// It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
allChunks: true
})
// 多入口配置
new ExtractTextPlugin({
// 將每個入口的css文件提取到各自獨立的文件夾下
// 這里的[name]就是占位符,表示的是 entry中的入口名稱
filename: utils.assetsPath('[name]/css/[name].[contenthash].css'),
// Setting the following option to `false` will not extract CSS from codesplit chunks.
// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
// It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
allChunks: true
})
小結
通過以上三個插件我們基本可以將我們需要的代碼打包的指定的路徑下,並且完成index.html中的文件引用正常,下面我們要做的就是將這三個插件串起來,完成多入口項目的打包。