webpack4支持的一個新特性就是zero配置,不需要config,也可以打包,這對於懶癌患者很有誘惑力,但是這也意味着我們不清楚零配置發生了寫什么,也不知道打包出來的文件是否符合我們的心意,全部都是佛系打包。不過作為項目的親爹親媽,還是要對自己的孩子負責,每個打包過程都是要可控的。本文就是詳解不同mode下,webpack打包都發生了些什么事。
我們來看一下MODE這個參數,他有三個參數production
,development
,none
,前兩個是有預設的插件,而最后一個則是什么都沒有,也就是說設置為none
的話,webpack就是最初的樣子,無任何預設,需要從無到有開始配置。
在webpack的配置中,其他配置都可以沒有!但是mode是必備的,如果不加mode,官方雖然會打包,但同時也會給你一個警告:
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
意思很簡單,就是mode沒有被設置的情況下,系統就會給你一個默認的production
模式。
mode配置很簡單,就只有3個值,任君挑選。none
這個參數,相信大家都能理解,那么我們就研究下其他兩個production
和development
,這為什么要有這兩個狀態,以及兩者在webpack打包中都干了些啥事。
如何在打包中區分production
和development
的狀態
在mode為production
或development
的狀態下,為了兼顧兩個狀態下的程序運行,webpack創建了一個全局變量process.env.NODE_ENV
,等同於在插件plugins中加入了new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development|production") })
,用來區分不同的狀態,同時可以在程序中區分程序狀態。
那么我們該如何在coding的時候進行區分呢?因為process.env.NODE_ENV
是全局變臉給,所以可以這樣來引用值,假設mode:production
:
if ("development" === process.env.NODE_ENV){
....
}else{
....
}
編譯之后:
if ("development" === "production"){
....
}else{
....
}
也就是最后process.env.NODE_ENV會被替換為一個常量。這個小功能可以幫助我們在寫業務JS的時候,區分線上版本與開發版本。
none
模式下的模塊打包
在沒有任何優化處理的情況下,按照webpack默認的情況下打包出來的模塊是怎么樣的呢?下方是一個簡易的例子,我們可以看出,他將模塊打包至數組之中,調用模塊的時候,就是直接調用模塊在此數組中的一個序號。然后沒有壓縮混淆之類的優化,連注釋都幫我們標的好好的,比如導入 /* harmony import / ,/ harmony default export */。
[
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _page2_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
console.log(_page2_js__WEBPACK_IMPORTED_MODULE_0__["default"])
}),
/* 1 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
let str="page1"
/* harmony default export */ __webpack_exports__["default"] = (str);
})
]
但是無論是在開發環境development下,還是在正式壞境production下,這個代碼都是不過關的,對於開發環境,此代碼可讀性太差,對於正式環境,此代碼不夠簡潔,因此,為了減少一些重復操作,webpack4提供的development|production可以很大程度上幫我們做掉一大部分事,我們要做的就是在這些事的基礎上加功能。
development模式下,webpack做了那些打包工作
development
是告訴程序,我現在是開發狀態,也就是打包出來的內容要對開發友好。在此mode下,就做了以下插件的事,其他都沒做,所以這些插件可以省略。
// webpack.development.config.js
module.exports = {
+ mode: 'development'
- devtool: 'eval',
- plugins: [
- new webpack.NamedModulesPlugin(),
- new webpack.NamedChunksPlugin(),
- new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
- ]
}
我們看看NamedModulesPlugin
和NamedChunksPlugin
這兩個插件都做了啥,原本我們的webpack並不會給打包的模塊加上姓名,一般都是按照序號來,從0開始,然后加載第幾個模塊。這個對機器來說無所謂,查找載入很快,但是對於人腦來說就是災難了,所以這個時候給各個模塊加上姓名,便於開發的時候查找。
沒有NamedModulesPlugin
,模塊就是一個數組,引用也是按照在數組中的順序引用,新增減模塊都會導致序號的變化,就是webpack默認打包下的情況,參考上一節。
有了NamedModulesPlugin
,模塊都擁有了姓名,而且都是獨一無二的key,不管新增減多少模塊,模塊的key都是固定的。
{
"./src/index.js": (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
var _page2_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/page2.js");
console.log(_page2_js__WEBPACK_IMPORTED_MODULE_0__["default"])
}),
"./src/page2.js": (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
let str="page1"
__webpack_exports__["default"] = (str);
})
}
除了NamedModulesPlugin
,還有一個NamedChunksPlugin
,這個是給配置的每個chunks命名,原本的chunks也是數組,沒有姓名。
Asset Size Chunks Chunk Names
index.js 4.04 KiB 0 [emitted] index
page2.js 3.75 KiB 1 [emitted] page2
Asset Size Chunks Chunk Names
index.js 4.1 KiB index [emitted] index
page1.js 4.15 KiB page1 [emitted] page1
NamedChunksPlugin
其實就提供了一個功能就是你可以自定義chunks的名字,假如我再不同的包中有相同chunk名,怎么辦?這個時候就要在進行區分了,我么可以用所有的依賴模塊名加本上的模塊名。因為Chunk.modules
已經廢棄了,現在用其他的方法來代替chunk.mapModules
,然后重命名chunk的名字:
new webpack.NamedChunksPlugin((chunk) => {
return chunk.mapModules(m => {
return path.relative(m.context, m.request)
}).join("_")
}),
看一眼做這一行代碼的效果,我們可以看到Chunks這邊已經重命名了,這樣可以很大程度上解決chunks重名的問題:
Asset Size Chunks Chunk Names
index.js 4.1 KiB index.js_page2.js [emitted] index
page2.js 3.78 KiB page2.js [emitted] page2
總結:development也就給我們省略了命名的過程,其他的還是要自己加的。
production
在正式版本中,所省略的插件們,如下所示,我們會一個個分析。
// webpack.production.config.js
module.exports = {
+ mode: 'production',
- plugins: [
- new UglifyJsPlugin(/* ... */),
- new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
- new webpack.optimize.ModuleConcatenationPlugin(),
- new webpack.NoEmitOnErrorsPlugin()
- ]
}
UglifyJsPlugin
我們第一個需要處理的就要混淆&壓縮JS了吧,這個時候就要請出UglifyJs
了,在webpack中他的名字是const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
,這樣就可以使用他了。
不過new UglifyJsPlugin()
,這個插件我們可以在optimize
中配置,效果是一樣的,那么我們是不是就不用再導入一個新的插件了,這樣反而會拖慢webpack的就打包速度。
optimization:{
minimize: true,
},
將插件去除,混淆壓縮放入optimization
,這樣webpack速度快的飛起了。只有第一次打包會慢,之后再打包就快了。
ModuleConcatenationPlugin
webpack.optimize.ModuleConcatenationPlugin()
這個插件的作用是什么呢?官方文檔上是這么描述的:
記住,此插件僅適用於由 webpack 直接處理的 ES6 模塊。在使用轉譯器(transpiler)時,你需要禁用對模塊的處理(例如 Babel 中的 modules 選項)。
NoEmitOnErrorsPlugin
最后一個插件就是webpack.NoEmitOnErrorsPlugin()
,這個就是用於防止程序報錯,就算有錯誤也給我繼續編譯,很暴力的做法呢。
others
還有一些默認的插件配置,也就是可以不在plugins中引用的配置:
flagIncludedChunks
flagIncludedChunks
這個配置的作用是,看結果:
未啟用
Asset Size Chunks Chunk Names
index.js 1.02 KiB 0 [emitted] index
page1.js 970 bytes 1 [emitted] page1
啟用后,如果只有二個文件似乎表現不明顯,於是我增加了三個文件,page1調用page2,index調用page1,那么一目了然,在這里的chunks就是所有引用模塊的id。
Asset Size Chunks Chunk Names
index.js 1.08 KiB 0, 1, 2 [emitted] index
page1.js 1.01 KiB 1, 2 [emitted] page1
page2.js 971 bytes 2 [emitted] page2
OccurrenceOrderPlugin
webpack.optimize.OccurrenceOrderPlugin
這個插件的作用是按照chunk引用次數來安排出現順序,因為這讓經常引用的模塊和chunk擁有更小的id。將上面的例子加上這個配置運行下就是這樣的。
Asset Size Chunks Chunk Names
page2.js 969 bytes 0 [emitted] page2
page1.js 1.01 KiB 1, 0 [emitted] page1
index.js 1.08 KiB 2, 0, 1 [emitted] index
SideEffectsFlagPlugin
webpack.optimize.SideEffectsFlagPlugin()
這個插件如果需要生效的話,需要兩個條件,一個是導入的模塊已經標記了sideEffect,即package.json中的sideEffects這個屬性為false,第二個就是當前模塊引用了次無副作用的模塊,而且沒有使用。那么在打包的時候,就不會將這個模塊打包到文件中。
總結
實際上production mode下,與官方文檔相比,他的配置更加等同於如下配置:
module.exports = {
mode:"none",
optimization:{
flagIncludedChunks:true,
minimize: true,
},
plugins: [
new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.optimize.SideEffectsFlagPlugin()
]
}
production各插件參考文檔
name | effect |
---|---|
FlagDependencyUsagePlugin | 標記沒有用到的依賴,這個插件無法通過webpack獲取,我只能通過強行導入webpack/lib下的class文件來導入。 |
SideEffectsFlagPlugin | 用於處理tree shaking的,tree shaking,sideEffect這個插件的作用就是,如果當前的模塊沒有引用,而且package.json中的sideEffects為false,那么打包的時候就可以將此包剔除。stackoverflow上有用的答案 |
FlagIncludedChunksPlugin | 給當前chunk包含的chunkid加入chunk名之中 |
ModuleConcatenationPlugin | 作用域提升 |
NoEmitOnErrorsPlugin | 阻止任何報錯 |
OccurrenceOrderPlugin | 按照調用次數來給chunks排序 |
UglifyJsPlugin | 混淆壓縮 |