這個文件是打包過程彙總的核心文件,文件中聲明的對象webpackConfig也是整個打包過程中的核心對象。
1.導入相關對象
const path = require('path')
// 下面是utils工具配置文件,主要用來處理css類文件的loader
const utils = require('./utils')
// 下面引入webpack,來使用webpack內置插件s
const webpack = require('webpack')
// 下面是config目錄下的index.js配置文件,主要用來定義了生產和開發環境的相關基礎配置
const config = require('../config')
// webpack-merge是一個可以合併數組和對象的插件
const merge = require('webpack-merge')
//webpack 基本配置文件
const baseWebpackConfig = require('./webpack.base.conf')
// copy-webpack-plugin,用於將static中的靜態文件複製到產品文件夾dist
const CopyWebpackPlugin = require('copy-webpack-plugin')
// html-webpack-plugin用於將webpack編譯打包後的產品文件注入到html模板中
// 即在index.html裏面加上<link>和<script>標籤引用webpack打包後的文件
const HtmlWebpackPlugin = require('html-webpack-plugin')
// extract-text-webpack-plugin這個插件是用來將bundle中的css等文件產出單獨的bundle文件的,之前也詳細講過
const ExtractTextPlugin = require('extract-text-webpack-plugin')
// optimize-css-assets-webpack-plugin插件的作用是壓縮css代碼的
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
// 醜化壓縮JS代碼
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
// 導入環境變量
const env = require('../config/prod.env')
2.創建webpackConfig對象
本文件中定義了一個webpackConfig對象,這個對象來源於base文件中封裝好的baseWebpackConfig對象和在本文件中封裝的另一個配置對象。webpackConfig對象定義了打包後生成的文件的命名方式、文件的存放路徑、打包代碼時需要用到的一些加載器以及一些插件。
2.1 webpack-merge
build配置文件夾中的文件中除了編譯(dev文件)和打包(prod文件)用的配置文件以外,還有一個兩者共同會用到的base文件。因此在打包的時候需要先整個base文件和prod文件,這裏使用webpack-merge來整合這兩個文件中的配置對象。,返回webpackConfig對象。將webpackConfig對象打印到控制檯中,截取了部分內容如下所示。可以看出來,output和module.rules部分,是將base文件和pros文件整合的結果。
2.2 prodWebpackConfig對象
在源碼中,並沒有這個對象,這裏只是爲了描述時的便利,依據baseWebpackConfig起的名字。
2.2.1 module屬性
module也是一個對象,只有一個rules屬性,rules屬性的值是一個樣式加載器的集合,用於加載各種樣式(不同類型的樣式文件如css/sass/scss,需要使用不同的樣式加載器)。uitls.styleLoaders是一個方法,實參是一個對象,這個對象裏有三個布爾類型的屬性,這些屬性將會作爲條件,選擇性地創建所需的類加載器,並以集合的形式存儲這些樣式加載器。
module: {
//sourceMap:代碼轉換前後的對應關係
/**
* 根據options對象屬性的布爾值作爲條件,生成一個樣式文件加載器的集合。
* */
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
// 此項是自定義項,設置爲true表示,生成獨立的文件
extract: true,
usePostCSS: true
})
},
關於utils.styleLoaders的介紹在這裏。
2.2.2 devtool屬性
devtool: config.build.productionSourceMap ? config.build.devtool : false,
2.2.3 output屬性
定義文件的輸出路徑及命名格式。
output: {
// webpack輸出路徑和命名規則
path: config.build.assetsRoot,
// 文件名稱使用 static/js/[name].[chunkhash].js, chunkhash就是模塊的hash值,用於瀏覽器緩存的,
//當文件中的hash值與模塊中的hash值不一致時,就會重新加載該模塊
filename: utils.assetsPath('js/[name].[chunkhash].js'),
// chunkFilename是非入口模塊文件,也就是說filename文件中引用了chunckFilename
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
2.2.4 plugins屬性
這一部分定義了一些會使用到的插件。
2.2.4.1 DefinePlugin插件
這個插件用於定義當前的運行環境,來區分編譯操作和打包操作。env 是從別的文件中導入的環境變量,這裏的值表示的是打包操作。
/** 定義代碼的運行環境 */
new webpack.DefinePlugin({
// const env = require('../config/prod.env')
/*
module.exports = {
NODE_ENV: '"production"'
}
*/
'process.env': env
}),
2.2.4.2 UglifyJsPlugin插件
這個插件的功能是壓縮JS代碼,自動混淆。詳見UglifyJsPlugin的官方API
// 壓縮JS代碼
new UglifyJsPlugin({
uglifyOptions: {
compress: {
// 不顯示警告信息
warnings: false
}
},
// 壓縮後生成map文件(壓縮前後的代碼對應信息)
sourceMap: config.build.productionSourceMap,
// 並行提高效率,並行數量與CPU的數量(默認爲os.cpus().length - 1,也可以手動指定數量)相關。
parallel: true
}),
2.2.4.2 ExtractTextPlugin插件
這個插件的功能是將css文件提取到單獨的文件中。ExtractTextPlugin插件會將所有的*.css模塊從entry chunk移到一個單獨的css文件中。因此你的樣式將不會再包含在js代碼塊中,而是在一個單獨的css文件中(style.css)中。由於加載css代碼塊和加載js代碼塊是並行操作的,因此這樣做的效率更高,尤其是當你的樣式表很大的時候。官網原文
new ExtractTextPlugin({
/**
* 輸出的樣式文件的路徑及默認格式,使用佔位符。
* */
filename: utils.assetsPath('css/[name].[contenthash].css'),
// 當使用 `CommonsChunkPlugin` 並且在公共 chunk 中有提取的 chunk(來自`ExtractTextPlugin.extract`)時,
// `allChunks` **必須設置爲 `true`。
allChunks: true,
}),
2.2.4.3 OptimizeCSSPlugin插件
使用這個插件來優化、壓縮CSS代碼,解決使用extract-text-plugin可能會造成css重複的問題。解釋原文
new OptimizeCSSPlugin({
cssProcessorOptions: config.build.productionSourceMap
? { safe: true, map: { inline: false } }
: { safe: true }
}),
2.2.4.4 HtmlWebpackPlugin插件
// 將產品文件的引用注入到index.html
/** https://www.jianshu.com/p/08a60756ffda */
new HtmlWebpackPlugin({
//定義生成的html文件的名字,這裏在另外一個配置文件裏面指定了
// index: path.resolve(__dirname, '../dist/index2.html'),
filename: config.build.index,
/** 模板文件,就是根目錄裏的那個文件。如果找不到與模板名相同的文件,則會報錯 */
template: 'index.html',
// 將js文件放到body標籤的結尾(默認值),也可以選擇插入到head中或者不插入
inject: true,
/** https://www.jianshu.com/p/08a60756ffda
*minify 的作用是對 html 文件進行壓縮,minify 的屬性值是一個壓縮選項或者 false 。
* 默認值爲false, 不對生成的 html 文件進行壓縮。
* */
minify: {
// 刪除index.html中的註釋
removeComments: true,
// 刪除index.html中的空格
collapseWhitespace: true,
// 刪除各種html標籤屬性值的雙引號
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
/** 確定script插入的順序,dependency表示按照文件的依賴順序進行處理 */
chunksSortMode: 'dependency'
}),
2.2.4.5 HashedModuleIdsPlugin插件
new webpack.HashedModuleIdsPlugin(),
瀏覽器的緩存機制會自動存儲部分js代碼,例如將第三方插件拆分成一個獨立的chunk代碼(vendor.js)。由於一般我們不會修改第三方的代碼,所以將第三方代碼緩存起來會更高效。但是chunk代碼的名字中除了vendor還有一個哈希值,所以即使我們並沒有修改第三方庫中的代碼,vendor chunk的名字還是會發生變化。一旦發生變化,瀏覽器就會重新加載vendor chunk,這樣的話,瀏覽器的緩存機制就形同虛設。因此使用這個插件來解決這個。
做了個實驗,對於修改了一個程序,然後前後打包的對比結果如下圖所示,可以看出來雖然代碼變化了,但是因爲加了這個,保證了vendor命名沒有變化。
2.2.4.3 ModuleConcatenationPlugin插件
new webpack.optimize.ModuleConcatenationPlugin(),
預編譯提升代碼在瀏覽器中的執行速度。
2.2.4.6 CommonsChunkPlugin插件
CommonsChunkPlugin主要是用來提取第三方庫和公共模塊,避免首屏加載的bundle文件或者按需加載的bundle文件體積過大,從而導致加載時間過長,着實是優化的一把利器。例如,就是兩個js都引用了第三個js文件,不做提取的話,這個js文件就會被重複打包。案例
創建這個對象的時候,傳入了一個options對象,在這個文件中,用到的屬性如下所示。尤其要注意這裏的filename屬性和chunks屬性,雖然沒有實名進行定義,但是卻有約定值。
{
name: string, // or
names: string[],
// chunk的名字,如果這個chunk已經在entry中定義,該chunk會被直接提取;
// 如果沒有定義,則生成一個空的chunk來其他所有chunk的公共代碼
filename: string,
// 可以指定提取出的公共代碼的文件名稱,可以使用output配置項中文件名的佔位符。未定義時使用name作爲文件名。
minChunks: number|Infinity|function(module, count) => boolean,
// 在一個模塊被提取到公共chunk之前,它必須被最少minChunks個chunk所包含。值可以是數字、關鍵字或者是函數
//該數字必須不小於2或者不大於chunks的個數。默認值等於chunks的個數。
//如果指定了Infinity,則創建一個公共chunk,但是不包含任何模塊,內部是一些webpack生成的runtime代碼和chunk自身包含的模塊(如果chunk存在的話)。
//用戶也可以定製自己的邏輯去生成代碼。
chunks: string[],
// 指定從某個(些)chunk中抽取子chunk。如果不定義,則默認從entry chunk中抽取公共代碼。
children: boolean,
// If `true` all children of the commons chunk are selected
deepChildren: boolean,
// If `true` all descendants of the commons chunk are selected
async: boolean|string,
// If `true` a new async commons chunk is created as child of `options.name` and sibling of `options.chunks`.
// It is loaded in parallel with `options.chunks`.
// Instead of using `option.filename`, it is possible to change the name of the output file by providing
// the desired string here instead of `true`.
minSize: number,
// Minimum size of all common module before a commons chunk is created.
}
在源碼中,如果註釋掉所有的CommonsChunkPlugin拆件,則只會生成一個entry chunk,如下圖所示。在這裏,chunk的名字“app”即是入口文件的名字。
app這個值是在base文件裏指定的。
如下代碼所示,這個插件首先被用來提取第三方庫。將所有從將所有從node_modules中引入的js提取到vendor.js,同時便於瀏覽器緩存,提高程序的運行速度。實參裏沒有指定chunks因此是從app.js(entry chunk)裏抽取代碼,同時沒有指定filename,因此文件名取name中的vendor,下同。
//
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
// minChunks 爲函數,則根據返回值的邏輯來確定是否提取爲 commons chunk。
minChunks (module) {
// any required modules inside node_modules are extracted to vendor
//"有正在處理文件" + "這個文件是 .js 後綴" + "這個文件是在 node_modules 中"
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
上面的代碼其實就是生成一個叫做vendor的commons chunk,那麼有哪些模塊會被加入到vendor中呢?就對入口文件及其依賴的模塊進行遍歷,如果該模塊是js文件並且在node_modules中,就會加入到vendor當中,其實這也是一種讓vendor只保留第三方庫的辦法。
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
}),
對於上面的代碼,上面說是防止從app代碼塊變動時導致vendor哈希值變動的問題。但是這裏的Infinity的條件是什麼衆說紛紜,所以我不太清楚。
// 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({
name: 'app',
async: 'vendor-async',
children: true,
// minChunks爲數字,則當模塊被3個chunks公共引用,纔會被抽取出來爲common chunk。
minChunks: 3
}),
當一個模塊被三個chunk公共引用的時候,會被抽取出一個公共塊,但是這裏的name不知道爲什麼是這個。
2.2.4.7 CopyWebpackPlugin插件
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
這個人說,這個插件的功能是過濾掉打包過程中產生的以.開頭的文件。
3.總結
對於後臺碼農來說,這個還是有點棘手。以上是我瀏覽了衆多中英文資料的結果。之後,還會不斷補充。敬請各位大佬不吝賜教。