webpack4在2月底的時候發布,這次webpack4有了一個名字"Legato",也就是"連奏"的意思,寓意webpack在不斷進化,而且是無縫(no-gaps)的進化。webpack的進化點是通過捐贈者和用戶投票來決定的,之前在介紹webpack3的時候,曾給出過投票數在前幾名的優化點,集中在用戶體驗、構建性能(速度和產出大小)、通用和適配性(es module、typescript、web assemble)等。webpack4發布了,下面將結合文檔和實踐,驗證一下webpack是否兌現了當初的諾言。
webpack4中的新特性
webpack3官方發布的時候,提到了下個版本可能的改動點,翻譯過來如下所示:
- 高性能的構建緩存
- 提升初始化速度和增量構建效率
- 更好的支持Type Script
- 修訂長期緩存
- 支持WASM 模塊支持
- 提升用戶體驗
webpack4官方發布的文檔之中主要提及了以下新特性:
支持零配置(Zero Configuration)
該特性主要用於解決webpack的門檻高問題,webpack是一個配置聲明式的操作模式,npm、gulp是指令式的,需要描述每一步是干什么的,而webpack的配置項凌亂且無序,讓很多開發者頭疼。
webpack4提供了零配置方案,默認入口屬性為./src
,默認輸出路徑為./dist
,不再需要配置文件,實現了開箱即用的封裝能力,更通俗的講,webpack會自動查找項目中src目錄下的index.js文件,然后選擇的模式進行相應的打包操作,最后新建dist目錄並生成一個main.js文件。此外針對開發環境和線上環境提供了兩種打包模式:"production"
和"development"
,"production"
模式內置了項目產出時的基本配置項,"development"
模式基本滿足了快速構建和開發體驗。使用的方法是在運行webapck命令的時候,設置好mode參數的值即可,默認是production屬性。
"scripts": {
"dev": "webpack --mode development",
"build": "webpack --mode production"
}
具體的案例可以前往github進行下載。
下面根據官方的文檔介紹一下兩種模式。
- Production模式
提供了發布程序時的優化配置項,旨在更小的產出文件、更快的運行速度、不暴露源碼和路徑。會默認采用代碼壓縮(minification),作用域提升(scope hoisting),tree-shaking,NoEmitOnErrorsPlugin,無副作用模塊修剪(side-effect-free module pruning)等。
- Development模式
旨在提升開發調試過程中的體驗,如更快的構建速度、調試時的代碼易讀性、暴露運行時的錯誤信息等。會默認采用bundle的輸出包含路徑名和eval-source-map
等,提升代碼的可讀性和構建速度。
兩種模式就是兩個箱子,箱子里面就是各種插件工具,只是有些是開啟的,有些是關閉的,具體有哪些工具可以參考這篇文章。
廢棄CommonsChunkPlugin
webpack4廢棄了CommonsChunkPlugin,引入了optimization.splitChunks
和optimization.runtimeChunk
,旨在優化chunk的拆分。先介紹一下code splitting下的CommonsChunkPlugin有什么缺點,然后仔介紹一下chunk split是怎么進行優化的。
- CommonsChunkPlugin的問題
CommmonsChunkPlugin的思路是Create this chunk and move all modules matching minChunks into the new chunk
,即將滿足minChunks
配置想所設置的條件的模塊移到一個新的chunk文件中去,這個思路是基於父子關系
的,也就是這個新產出的new chunk是所有chunk的父親,在加載孩子chunk的時候,父親chunk是必須要提前加載的。舉個例子:
example:
entryA: vue vuex someComponents
entryB: vue axios someComponents
entryC: vue vux axios someComponents
minchunks: 2
產出后的chunk:
vendor-chunk:vue vuex axios
chunkA~chunkC: only the components
帶來的問題是:對entryA和entryB來說,vendor-chunk都包含了多余的module。
CommonsChunkPlugin另外一個問題是:對異步的模塊不友好。下面也舉例說明:
example:
entryA: vue vuex someComponents
asyncB:vue axios someComponents
entryC: vue vux axios someComponents
minchunks: 2
產出后的chunk:
vendor-chunk:vue vuex
chunkA: only the components
chunkB: vue axios someComponents
chunkC: axios someComponents
帶來的問題是:如果asyncB在entryA中動態引入,則會引入多余的module。
總的來說CommonsChunkPlugin有以下三個問題:
- 產出的chunk在引入的時候,會包含重復的代碼;
- 無法優化異步chunk;
- 高優的chunk產出需要的minchunks配置比較復雜。
- SplitChunksPlugin
與CommonsChunkPlugin的父子關系
思路不同的是,SplitChunksPlugin引入了chunkGroup
的概念,在入口chunk和異步chunk中發現被重復使用的模塊,將重疊的模塊以vendor-chunk的形式分離出來,也就是vendor-chunk可能有多個,不再受限於是所有chunk中都共同存在的模塊,原理的示意如下圖所示:
其中,可以發現SplitChunksPlugin產出的vendor-chunk有多個,對於入口A來說,引入的代碼只有chunkA、vendor-chunkA-B、vendor-chunkA-C、vendor-chunkA-B-C;這時候chunkA、vendor-chunkA-B、vendor-chunkA-C、vendor-chunkA-B-C形成了一個chunkGroup。下面舉個列子:
example:
entryA: vue vuex someComponents
entryB:vue axios someComponents
entryC: vue vux axios someComponents
產出后的chunk:
vendor-chunkA-C:vuex
vendor-chunkB-C:axios
vendor-chunkA-B-C:vue
chunkA: only the components
chunkB: only the components
chunkC: only the components
SplitChunksPlugin能夠解決掉CommonsChunkPlugin中提到的三個問題,SplitChunksPlugin在production
模式下是默認開啟的,但是它默認只作用於異步chunk,如果要作用於入口chunk的話,需要配置optimization.splitChunks.chunks: "all"
,同時webpack自動split chunks是有幾個限制條件的:
- 新產出的vendor-chunk是要被共享的,或者模塊來自npm包;
- 新產出的vendor-chunk的大小得大於30kb;
- 並行請求vendor-chunk的數量不能超出5個;
- 對於entry-chunk而言,並行加載的vendor-chunk不能超出3個。
這些限制可以在SplitChunks的默認配置項中可以一一對應的看到。
splitChunks: {
chunks: "async",
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
name: true,
cacheGroups: {
default: {
minChunks: 2,
priority: -20
reuseExistingChunk: true,
},
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
}
}
}
其實不難理解這些限制,因為SplitChunksPlugin產生的結果就是原來chunk被拆分了,引入的文件數量會變多,因此需要在文件數量上進行限制。
- runtimeChunkPlugin
在使用CommonsChunkPlugin的時候,我們通常會把webpack runtime的基礎函數提取出來,單獨作為一個chunk,畢竟code splitting想把不變的代碼單獨抽離出來,方便瀏覽器緩存,提升加載速度。webpack4廢棄了CommonsChunkPlugin,采用了runtimeChunkPlugin可以將每個entry chunk中的runtime部分的函數分離出來,只需要一個簡單的配置:optimization.runtimeChunk: true
。
sideEffects
在webapck2開始支持ESModule后,webpack提出了tree-shaking進行無用模塊的消除,主要依賴ES Module的靜態結構。在webapck4之前,主要通過在.babelrc文件中設置"modules": false
來開啟無用的模塊檢測,該方法顯然比較粗暴。webapck4靈活擴展了如何對某模塊開展無用代碼檢測,主要通過在package.json
文件中設置sideEffects: false
來告訴編譯器該項目或模塊是pure的,可以進行無用模塊刪除。
官方的github上提供了一個sideEffects的demo示例供參考,如果對tree-shaking的概念不是太了解,可去官方的文檔中tree-shaking部分詳細了解。下面是官方提供的一個減包效果示例,僅供欣賞。
支持壓縮ES6+代碼
在webapck4之前,webpack.prod.conf.js中關於UglifyJsPlugin的注釋會有這么一段話:
// UglifyJs do not support ES6+, you can also use babel-minify for better treeshaking: https://github.com/babel/minify
意思就是UglifyJs無法對ES6+的代碼進行壓縮,需使用babel-minify獲取更好的treeshaking效果。webapck4目前已經支持壓縮ES6+的代碼。
// 源代碼
import {sayHello} from './libs/js/util.js'
let output = sayHello('hwm');
console.log(output);
// 產出
...let r=(e=>`Hello ${e}!`)("hwm");console.log(r)}...
支持更多的模塊類型
webpack4支持以import
和export
形式加載和導出本地的WebAssembly模塊,這一塊本人實際項目並未使用到,暫不做介紹;此外,webpack4支持json模塊和tree-shaking,之前json文件的加載需要json-loader
的支持,webpack4已經能夠支持json模塊(JSON Module),不需要額外的配置;此外,當json文件用ESModule的語法import引入的時候,webpack4還能支持對json模塊進行tree-shaking處理,把用不到的字段過濾掉,起到減包的作用。下面是個示例:
// 引用數據的三種方法
let jsonData = require('./data/test.json');
import jsonData from './data/test.json'
// 打包時只會提取test.json文件中onePart部分。
import { onePart } from './data/test.json'
如何遷移升級到webpack4
0配置的局限性
webpack4聲稱能夠0配置,但是0配置有很多局限性,比如只能是單入口的項目,入口和產出的文件名是固定的,entry是src目錄下的index.js,產出是dist目錄下的main.js,很明顯不能滿足實際項目應用。於是,開發者還是得自己配置webpack.config.js文件。
webpack4配置文件的變化點
如何配置webpack4下的配置文件,需要大致了解webapck4的配置項的改動點。
mode:開發模式 development
- 開啟dev-tool,方便瀏覽器調試
- 提供詳細的錯誤提示
- 利用緩存機制,實現快速構建
- 開啟output.pathinfo,在產出的bundle中顯示模塊路徑信息
- 開啟NamedModulesPlugin
- 開啟NoEmitOnErrorsPlugin
mode:生產模式 production
- 啟動各種優化插件(ModuleConcatenationPlugin、optimization.minimize、ModuleConcatenationPlugin、Tree-shaking),壓縮、合並、拆分,產出更小體積的chunk
- 關閉內存緩存
- 開啟NoEmitOnErrorsPlugin
plugin
- 內置optimization.minimize來壓縮代碼,不用再顯示引入UglifyJsPlugin;
- 廢棄CommonsChunkPlugin插件,使用optimization.splitChunks和optimization.runtimeChunk來代替;
- 使用optimization.noEmitOnErrors來替換NoEmitOnErrorsPlugin插件
- 使用optimization.namedModules來替換NamedModulesPlugin插件
loader
- 廢棄json-loader,友好支持json模塊,以ESMoudle的語法引入,還可以對json模塊進行tree-shaking處理;
其他的改動信息建議查看webpack的中文升級日志。
vue-cli項目如何改造
介紹完了webpack4中核心配置項的變化,接下來結合vue-cli示例項目介紹一下,如何配置webpack.conf.js文件。
1.升級npm包版本
建議升級webpack4,同時升級涉及到的loaders和plugins。webpack4 中 cli 工具分離成了 webpack 核心庫 與 webpack-cli 命令行工具兩個模塊,需要使用 CLI ,必安裝 webpack-cli 至項目中。
npm i webpack webpack-cli webpack-dev-server -D
涉及到的插件有:
"vue-loader": "^15.0.10",
"vue-style-loader": "^4.1.0",
"vue-template-compiler": "^2.5.16",
"copy-webpack-plugin": "^4.0.1",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"html-webpack-plugin": "^3.1.0",
"optimize-css-assets-webpack-plugin": "^4.0.0",
"webpack-bundle-analyzer": "^2.11.1",
"webpack-dev-middleware": "^3.1.2",
"webpack-dev-server": "^3.1.3",
"webpack-merge": "^4.1.0"
2.修改webpack.base.conf.js
webpack4推薦使用了最新版本的vue-loader("vue-loader": "^15.0.10"),但是最新的vue-loader需要在webapck config文件中設置VueLoaderPlugin
插件,否則會報以下錯誤:
vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.
webpack.base.conf.js文件中的改動主要是添加VueLoaderPlugin插件。
const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
...
plugins: [
// 添加VueLoaderPlugin,以響應vue-loader
new VueLoaderPlugin()
],
...
}
3.修改webpack.dev.conf.js
添加mode屬性,並設置為development模式;然后注釋掉 webpack4開發模式已經內置的插件,如webpack.NamedModulesPlugin
和webpack.NoEmitOnErrorsPlugin
插件。
4.修改webpack.prod.conf.js
添加mode屬性,並設置為production模式;然后注釋掉 webpack4生產模式已經內置的插件,如CommonsChunkPlugin
、uglifyjs-webpack-plugin
、ModuleConcatenationPlugin
插件;最后根據webpack4提供的文檔配置optimization,使用splitChunks
和runtimeChunk
完成chunk的提取和優化。
const webpackConfig = merge(baseWebpackConfig, {
...
optimization: {
// 采用splitChunks提取出entry chunk的chunk Group
splitChunks: {
cacheGroups: {
// 處理入口chunk
vendors: {
test: /[\\/]node_modules[\\/]/,
chunks: 'initial',
name: 'vendors',
},
// 處理異步chunk
'async-vendors': {
test: /[\\/]node_modules[\\/]/,
minChunks: 2,
chunks: 'async',
name: 'async-vendors'
}
}
},
// 為每個入口提取出webpack runtime模塊
runtimeChunk: { name: 'manifest' }
}
...
})
經過以上操作,我們已經基本完成了vue-cli項目的改造。運行項目的時候,注意看控制台的報錯,是哪個插件報的錯就去升級那個插件,如果存在找不到模塊之類的錯誤就去升級對應的loader。vue-cli項目webapck4下配置demo已經上傳到github,歡迎下載。
實例說話—webpack4的性能如何
webapck4旨在開發模式下提升構建速度、提升用戶體驗,在生產模式下減小產出包的大小,提升加載和運行速度,下面是webapck3和webapck4下vue-cli的webapck模板項目的實際效果截圖:
開發者模式下:
由上圖可以知道:webapck4下的構建速度是3703ms,明顯由於webapck3下的5617ms;
生產模式下:
由上圖可以知道:webapck4下的app-chunk的大小是933byte,明顯小於webapck3下的11.6K;webapck4下vendor-chunk的大小是109K,小於webapck3下的112K。
兩種模式下,webapck4性能上的確是精進不少,雖然有各種坑,還是建議把坑填了,升級到webpack4。
webpack的未來
想了解webpack的未來,建議先過一下webpack的歷史。
webpack1支持CMD和AMD,同時擁有豐富的plugin和loader,webpack逐漸得到廣泛應用。
webpack2相對於webpack最大的改進就是支持ES Module,可以直接分析ES Module之間的依賴關系,而webpack1必須將ES Module轉換成CommonJS模塊之后,才能使用webpack進行下一步處理。除此之外webpack2支持tree sharking,與ES Module的設計思路高度契合。
webpack3相對於webpack2,過渡相對平穩,但是新的特性大都圍繞ES Module提出,如Scope Hoisting和Magic Comment。
webpack4集中發力在用戶體驗、構建性能(速度和產出大小)、通用和適配性(es module、typescript、web assemble)。
展望未來,webpack的未來肯定持續提升用戶體驗、降低使用門檻,進一步提升構建速度和產出代碼的性能,同時webpack的團隊已經承諾會根據社區的投票來決定新特性開發優先權。以下是公告中給出的未來的重點關注點:
- 繼續修訂長期緩存
- webapck任務多線程化,提升初始化速度和增量構建效率
- 提升CSS到一等公民,引入CSS Module Type ,廢棄ExtractTextWebpackPlugin
- 提升HTML到一等公民,引入HTML Module Type
- 繼續擴展0CJS(0配置文件),加入更多擴展
- 優化支持WASM 模塊,從實驗階段過渡到穩定階段
- 持續提升用戶體驗
參考文獻
webapck4官方medium pr稿
webpack4中文升級日志
webpack4升級指南以及從webpack3.x遷移
Webpack4 新特性 及 Vue-cli項目升級
Webpack4官方指導教程
webpack4.0打包優化策略整理
webapck3新特性