公司的舊項目仍然在使用Webpack3。提取公共代碼依然使用的是CommonsChunkPlugin插件,因此須要研究一下CommonsChunkPlugin的用法。node
可是官方文檔的對於此插件的解釋,讓我感覺不到這個插件的默認行爲是什麼,只是簡單的知道它要作的事情是分離代碼塊。須要好好研究一番。react
文件目錄以下:webpack
src文件夾下放源代碼,裏面放了兩個模塊index.js和Greeter.js。dist文件夾下放輸出文件。再看看webpack.config.js中的配置:web
var webpack = require('webpack');
module.exports = {
entry: __dirname + "/src/index.js",//已屢次說起的惟一入口文件
output: {
path: __dirname + "/dist",//打包後的文件存放的地方
filename: "bundle.js"//打包後輸出文件的文件名
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({}),
],
}
複製代碼
配置好輸入輸出便可,加上咱們的CommonsChunkPlugin插件。瀏覽器
運行webpack,報錯:You did not specify any valid target chunk settings. (你沒有指定任何有效的目標chunk設置)。緩存
好,那咱們指定一個,我看name字段表明指定的chunk名:bash
var webpack = require('webpack');
module.exports = {
entry: __dirname + "/src/index.js",//已屢次說起的惟一入口文件
output: {
path: __dirname + "/dist",//打包後的文件存放的地方
filename: "bundle.js"//打包後輸出文件的文件名
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'IamChunk',
}),
],
}
複製代碼
仍然報錯:Multiple assets emit to the same filename bundle.js。 這是說多個chunk不能放在同一個bundle.js中,既然產生了多個,那指定一個輸出文件確定是不行了,須要生成多個bundle文件,將filename: "bundle.js"改爲filename: "[name].js"便可。app
我在《Webpack 理解Chunk》解釋過Chunk。理解Chunk是理解Webpack的關鍵。異步
var webpack = require('webpack');
module.exports = {
entry: __dirname + "/src/index.js",//已屢次說起的惟一入口文件
output: {
path: __dirname + "/dist",//打包後的文件存放的地方
filename: "[name].js"//打包後輸出文件的文件名
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'IamChunk',
}),
],
}
複製代碼
成功,輸出以下,生成了兩個chunk,一個main,一個IamChunk:async
咱們來看看這兩個文件下的代碼,看看CommonsChunkPlugin幫咱們作了什麼。
// main.js下的代碼
webpackJsonp([0],[
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
const greeter = __webpack_require__(1);
document.querySelector("#root").appendChild(greeter());
/***/ }),
/* 1 */
/***/ (function(module, exports) {
module.exports = function () {
var greet = document.createElement('div');
greet.textContent = "Hi there and greetings!";
return greet;
};
/***/ })
],[0]);
複製代碼
// IamChunk下的代碼,我作了相應簡化,沒有貼出所有代碼
/******/ (function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ var parentJsonpFunction = window["webpackJsonp"];
/******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
...
/******/ return result;
/******/ };
/******/
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // objects to store loaded and loading chunks
/******/ var installedChunks = {
/******/ 1: 0
/******/ };
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
...
/******/ return module.exports;
/******/ }
/******/ })
/************************************************************************/
/******/ ([]);
複製代碼
呦吼,main.js下只有咱們的源代碼了,CommonsChunkPlugin將runtime的的代碼打包到了IamChunk這個文件中。
runtime的代碼是指瀏覽器運行時,Webpack用來鏈接模塊化應用程序的全部代碼。就是上例中IamChunk中的代碼。
CommonsChunkPlugin確定不止是分離runtime代碼這一個功能啊,我猜測CommonsChunkPlugin是這樣工做的,將多個chunk中公共的代碼,提取到一個chunk中。下面咱們就來作這個實驗。
在src下新建一個index2.js文件,一樣引用Greeter.js。而後配置config,配置兩個入口:
var webpack = require('webpack');
module.exports = {
entry: {
index: __dirname + "/src/index.js",
index2: __dirname + "/src/index2.js"
},
output: {
path: __dirname + "/dist",//打包後的文件存放的地方
filename: "[name].js"//打包後輸出文件的文件名
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor', // 咱們無論它叫IamChunk了,給它一個你們都起的一個名字
}),
],
}
複製代碼
效果以下:
造成了3 個Chunk,咱們分別看一下里面的內容:
// index.js 只有源碼中index.js文件的代碼,沒有了Greeter.js的代碼。
// index2.js文件也是一樣的,沒有了Greeter.js的代碼。
// 不用多想,Greeter.js的代碼確定是跑到vendor.js裏去了。
webpackJsonp([1],{
/***/ 4:
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_react__ = __webpack_require__(1);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_react___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_react__);
const greeter = __webpack_require__(3);
console.log('11', __WEBPACK_IMPORTED_MODULE_0_react___default.a);
document.querySelector("#root").appendChild(greeter());
/***/ })
},[4]);
複製代碼
// vendor.js
// 果真Greeter.js因爲是兩個Chunk共同使用的模塊,因此被抽離到vender中了。
...
/***/ (function(module, exports) {
module.exports = function () {
var greet = document.createElement('div');
greet.textContent = "Hi there and greetings!";
return greet;
};
/***/ })
複製代碼
接下來我又實驗了除了咱們的業務代碼,多個入口同時引用第三方庫。CommonsChunkPlugin對第三方庫並無什麼特殊待遇,如:只有index.js引用了第三方庫,index2.js沒有引用此第三方庫,那麼第三方庫就會打包到index.js的Chunk裏,若是index.js,index2.js都引用了同一個第三方庫,那麼這個第三方庫,就會被打包到vendor.js中。
在咱們只配置了name這一個屬性的時候,也就是指定公共Chunk名稱以後,CommonsChunkPlugin的默認行爲是:生成一個公共Chunk,其餘多個Chunk同時引用同一個module時,將其提取到這個公共Chunk中。額外的,還會將Webpack runtime的代碼提取到這個公共Chunk中。
理解CommonsChunkPlugin的默認行爲,是理解它的第一步,我以爲這是官方文檔沒有好好交代的一個問題。
初步認識CommonsChunkPlugin以後,咱們就要探討它在平常工做中的應用了。
開發單頁應用,通常只有一個入口,代碼分割其中的一個用途是,將業務代碼(常常變更)和第三方庫代碼(不常常變更),打包到不一樣的Chunk中。
目前第三方庫代碼只被咱們的入口Chunk引用,沒有被多個Chunk引用,CommonsChunkPlugin的默認行爲無論用了,但是咱們仍想將第三方庫提取到公共Chunk中
咱們將index2從entry中刪掉,而後在index.js源代碼中,引入react庫。
var webpack = require('webpack');
module.exports = {
entry: {
index: __dirname + "/src/index.js",
},
output: {
path: __dirname + "/dist",//打包後的文件存放的地方
filename: "[name].[chunkhash].js"//打包後輸出文件的文件名
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
}),
],
}
複製代碼
如上的配置,Webpack不會幫咱把react打包到vendor中,畢竟react只被一個chunk引用了。咱們須要使用minChunks字段:
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module) {
return module.context && module.context.includes("node_modules");
}
}),
],
複製代碼
這裏minChunks傳入的是一個函數,結果返回true,就是要提取的代碼模塊,咱們將node_modules下的代碼,打包時都提取到公共Chunk中。
runtime中的manifest代碼,每次打包,都有可能變更,這樣就違背了咱們打包第三方庫代碼的初衷:利用瀏覽器緩存,讓不變的代碼一直能夠利用緩存加載。
因此咱們要將其單獨提取到一個Chunk中,配置以下:
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
minChunks: function(module){
return module.context && module.context.includes("node_modules");
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: "manifest",
minChunks: Infinity
}),
]
複製代碼
生成效果以下:
就這樣,咱們成功將業務代碼,第三方庫代碼,runtime代碼分別打包到三個Chunk中。這樣的配置,能知足咱們通常的打包場景了。
這裏的minChunks使用了Infinity,這個Infinity表明着當即打包,當即打包可不就只能打包到runtime的內容嗎,別的什麼也打包不進來啊。
對,這就是Infinity的效果,CommonsChunkPlugin的name還有一種用法,就是跟entry的key一致,以下:
module.exports = {
entry: {
greeter: ['./src/Greeter.js', './src/GteeterAgain.js'],
},
output: {
path: __dirname + "/dist",//打包後的文件存放的地方
filename: "[name].[chunkhash].js"//打包後輸出文件的文件名
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "greeter",
minChunks: Infinity
}),
],
}
複製代碼
這裏面CommonsChunkPlugin的name和entry中的greeter一致,那麼插件就立馬將Greeter.js,GteeterAgain.js打包起來,不會由於你又搞了什麼公共模塊,繼續往這個greeter的Chunk裏添加新模塊。
這也就是我說的要先理解CommonsChunkPlugin默認行爲。它默認會打包公共模塊,因此就有了minChunks: Infinity來講明我這個打包不想打包公共的,只想打包我在entry中指定的。
咱們看一下minChunks的簽名:
minChunks: number|Infinity|function(module, count) -> boolean,
複製代碼
minChunks還能夠傳遞數字,數字就相對好理解一點,就是要被多個的Chunk同時引用,纔會被打包到公共Chunk中。
如今咱們考慮一個相對複雜的場景,就是項目須要使用懶加載。也就是有了異步Chunk。
咱們在index.js中,這樣引用Greeter.js、GreeterAgain.js,在Greeter.js、GreeterAgain.js中都引用了react庫,咱們仍用上例中的配置,推測應該產生5個Chunk:入口Chunk、兩個異步Chunk、manifest Chunk、vendor Chunk:
// 懶加載引用,這樣就成了異步引用了
import(/* webpackChunkName: "Greeter" */'./Greeter').then(module => {
const greeter = module.default
document.querySelector("#root").appendChild(greeter());
})
import(/* webpackChunkName: "GreeterAgain" */'./GreeterAgain').then(module => {
const greeter = module.default
document.querySelector("#root").appendChild(greeter());
})
複製代碼
咱們仍然使用上例中的配置,看一下打包效果:
輸出了4個Chunk,跟咱們預想的不同,少了一個抽離公共代碼的vendor Chunk。咱們再看一下打包結果分析圖:
確實是4個,並且在Greeter和GreeterAgain這兩個Chunk中,同時引用了react,這顯然不是咱們想看到的,咱們但願公共代碼都抽離出來,哪怕它是異步的Chunk。
配置以下: 這裏用到了async屬性,這個屬性要替代name屬性,告訴Webpack,我這個是針對異步的公共Chunk的名稱。
var webpack = require('webpack');
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
entry: {
index: __dirname + "/src/index.js",
},
output: {
path: __dirname + "/dist", //打包後的文件存放的地方
filename: "[name].[chunkhash:8].js", //打包後輸出文件的文件名
chunkFilename: "[name].[chunkhash:8].js",
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
async: "vendor",
minChunks: function (module) {
return module.context && module.context.includes("node_modules");
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: "manifest",
minChunks: Infinity
}),
new BundleAnalyzerPlugin(), // 用於顯示Bundle分析結果可視化的插件
],
}
複製代碼
再看一下可視化的打包結果:
獲得了咱們預想中5個Chunk。其中vendor.js是異步Chunk中的公共代碼。
一個CommmonsChunkPlugin對象,會讓知足minChunks配置想所設置的條件的模塊移到一個新的chunk文件中去。若是你想針對異步Chunk提取公共代碼,用async屬性替換name。
提取的公共Chunk,是原始Chunk的父親,它們之間有父子級關係。好比上面的vendor.js是index.js、Greeter、GreeterAgain這三個Chunk的父親。咱們在瀏覽器使用它們時,加載孩子Chunk的時候,必須先加載父親Chunk。
今天的研究就先到這裏,代碼分割是Webpack的主要特性,也是相對比較複雜的一個技術點,若是要應對複雜龐大的項目,就須要咱們對代碼分割的配置有更深的理解了。