作者:華爾街見聞技術(shù)團隊 - 花褲衩
segmentfault.com/a/1190000015919863
前幾天 webpack 作者 Tobias Koppers 發(fā)布了一篇新的文章:webpack 4.0 to 4.16: Did you know?,總結(jié)了一下 webpack4
發(fā)布以來恩够,做了哪些調(diào)整和優(yōu)化卒落,并且說自己正在著手開發(fā) webpack5
。
Oh you are still on webpack 3. I’m sorry, what is blocking you? We already working on webpack 5, so your stack might be outdated soon…
翻譯成中文就是:
正好我也在使用一個文檔生成工具 docz(安利一波) 也最低需要 webpack4+
儡毕,新版 webpack
性能提高了不少,而且 webpack4
都已經(jīng)發(fā)布五個多月了扑媚,想必應(yīng)該已經(jīng)沒什么坑了,應(yīng)該可以安心的按照別人寫的升級攻略升級了附井。之前一直遲遲不升級完全是被去年被 webpack3
坑怕了。它在 code splitting
的情況下 CommonsChunkPlugin
會完全失效两残。過了好一段時間才修復(fù)永毅,欲哭無淚。
所以這次我等了快大半年才準(zhǔn)備升級到 webpack4
但萬萬沒想到還是遇到了不少的問題人弓! 有很多之前遺留的問題還是沒有很好地解決沼死。但最主要的問題還是它的文檔有所欠缺,已經(jīng)廢除了的東西如 commonsChunkPlugin
還在官方文檔中到處出現(xiàn)票从,很多重要的東西卻一筆帶過漫雕,甚至沒寫滨嘱,需要用戶自己去看源碼才能解決。
還比如在 v4.16.0
版本中廢除了 optimization.occurrenceOrder
浸间、 optimization.namedChunks
太雨、 optimization.hashedModuleIds
、 optimization.namedModules
這幾個配置項魁蒜,替換成了 optimization.moduleIds
和 optimization.chunkIds
囊扳,但文檔完中全沒有任何體現(xiàn),所以你在新版本中還按照文檔那樣配置其實是沒有任何效果的兜看。
最新最完整的文檔還是看他項目的配置WebpackOptions.json锥咸,強烈建議遇到不清楚的配置項可以看這個,因為它一定保證是和最新代碼同步的细移。
吐槽了這么多搏予,我們言歸正傳。由于本次手摸手篇幅有些長弧轧,所以拆解成了上下兩篇文章:
上篇 -- 就是普通的在?webpack3
的基礎(chǔ)上升級雪侥,要做哪些操作和遇到了哪些坑
下篇 -- 是在?webpack4
下怎么合理的打包和拆包,并且如何最大化利用?longterm caching
本文章不是手摸手從零教你 webpack 配置精绎,所以并不會講太多很基礎(chǔ)的配置問題速缨。比如如何處理 css 文件,如何配置 webpack-dev-server代乃,講述 file-loader 和 url-loader 之間的區(qū)別等等旬牲,有需求的推薦看 官方文檔 或者 survivejs 出的一個系列教程「橄牛或者推薦看我司的另一篇 wbepack 入門文章原茅,已同步到 webpack4 傳送門。
升級篇
前言
我一直認為模仿和借鑒是學(xué)習(xí)一個新東西最高效的方法堕仔。所以我建議還是通過借鑒一些成熟的 webpack 配置比較好员咽。比如你項目是基于 react 生態(tài)圈的話可以借鑒 create-react-app ,下載之后 npm run eject
就可以看到它詳細的 webpack 配置了贮预。vue 的話由于新版 vue cli
不支持 eject
了贝室,而且改用 webpack-chain來配置,所以借鑒起來可能會不太方便仿吞,主要配置 地址滑频。覺得麻煩的話你可以直接借鑒 vue-element-admin
的 配置』礁裕或者你想自己發(fā)揮峡迷,你可以借鑒 webpack 官方的各種 examples,來組合你的配置。
升級 webpack
首先將 webpack 升級到 4 之后绘搞,直接運行 webpack--xxx
是不行的彤避,因為新版本將命令行相關(guān)的東西單獨拆了出去封裝成了 webpack-cli
。會報如下錯誤:
The CLI moved into a separate package: webpack-cli.Please install
webpack-cli
in addition to webpack itself to use the CLI.
所有你需要安裝 npm install webpack-cli-D-S
夯辖。你也可將它安裝在全局琉预。
同時新版 webpack 需要 Node.js的最低支持版本為6.11.5
不要忘了升級。如果還需要維護老項目可以使用 nvm 來做一下 node 版本管理蒿褂。
升級所有依賴
因為 webpack4
改了 它的 hook
api 圆米,所以所有的 loaders
、 plugins
都需要升級才能夠適配啄栓。
可以使用命令行 npm outdated
娄帖,列出所以可以更新的包。免得再一個個去 npm
找相對于的可用版本了昙楚。
反正把 devDependencies
的依賴都升級一下近速,總歸不會有錯。
帶來的變化
其實這次升級帶來了不少改變堪旧,但大部分其實對于普通用戶來說是不需要關(guān)注的数焊,比如這次升級帶來的功能 SideEffects
、 ModuleType’sIntroduced
崎场、 WebAssemblySupport
,基本平時是用不到的遂蛀。我們主要關(guān)注那些對我們影響比較大的改動如: optimization.splitChunks
代替原有的 CommonsChunkPlugin
(下篇文章會著重介紹)谭跨,和 BetterDefaults-mode
更好的默認配置,這是大家稍微需要關(guān)注一下的李滴。
如果想進一步了解
TreeShaking
和SideEffects
的可見文末拓展閱讀螃宙。上圖參考 Webpack 4 進階
默認配置
webpack 4 引入了 零配置
的概念,被 parcel 刺激到了所坯。 不管效果怎樣谆扎,這改變還是值得稱贊的。
最近又新出了 Fastpack 可以關(guān)注一下芹助。
言歸正題堂湖,我們來看看 webpack 默認幫我們做了些什么?
development
模式下,默認開啟了 NamedChunksPlugin
和 NamedModulesPlugin
方便調(diào)試状土,提供了更完整的錯誤信息无蜂,更快的重新編譯的速度。
module.exports = {
+ mode: 'development'
- devtool: 'eval',
- plugins: [
- ? new webpack.NamedModulesPlugin(),
- ? new webpack.NamedChunksPlugin(),
- ? new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
- ]
}
production
模式下蒙谓,由于提供了 splitChunks
和 minimize
斥季,所以基本零配置,代碼就會自動分割、壓縮酣倾、優(yōu)化舵揭,同時 webpack 也會自動幫你 Scopehoisting
和 Tree-shaking
。
module.exports = {
+ ?mode: 'production',
- ?plugins: [
- ? ?new UglifyJsPlugin(/* ... */),
- ? ?new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
- ? ?new webpack.optimize.ModuleConcatenationPlugin(),
- ? ?new webpack.NoEmitOnErrorsPlugin()
- ?]
}
webpack 一直以來最飽受詬病的就是其配置門檻極高躁锡,配置內(nèi)容極其復(fù)雜和繁瑣午绳,容易讓人從入門到放棄,而它的后起之秀如 rollup稚铣、parcel 等均在配置流程上做了極大的優(yōu)化箱叁,做到開箱即用,所以 webpack4
也從中借鑒了不少經(jīng)驗來提升自身的配置效率惕医。愿世間再也不需要 webpack 配置工程師耕漱。
html-webpack-plugin
用最新版本的的 html-webpack-plugin
你可能還會遇到如下的錯誤:
thrownewError('Cyclic dependency'+nodeRep)
產(chǎn)生這個 bug 的原因是循環(huán)引用依賴,如果你沒有這個問題可以忽略抬伺。
目前解決方案可以使用 Alpha 版本螟够, npm i--save-dev html-webpack-plugin@next
或者加入 chunksSortMode:'none'
就可以了。
但仔細查看文檔發(fā)現(xiàn)設(shè)置成 chunksSortMode:'none'
這樣是會有問題的峡钓。
Allows to control how chunks should be sorted before they are included to the HTML.
這屬性會決定你 chunks 的加載順序妓笙,如果設(shè)置為 none
,你的 chunk 加載在頁面中加載的順序就不能夠保證了能岩,可能會出現(xiàn)樣式被覆蓋的情況寞宫。比如我在 app.css
里面修改了一個第三方庫 element-ui
的樣式,通過加載順序的先后來覆蓋它拉鹃,但由于設(shè)置為了 none
辈赋,打包出來的結(jié)果變成了這樣:
<link href="/app.8945fbfc.css" rel="stylesheet">
<link href="/chunk-elementUI.2db88087.css" rel="stylesheet">
app.css
被先加載了,之前寫的樣式覆蓋就失效了膏燕,除非你使用 important
或者其它 css 權(quán)重的方式覆蓋它钥屈,但這明顯是不太合理的。 vue-cli
正好也有這個相關(guān) issue坝辫,尤雨溪也在不使用 @next
版本的基礎(chǔ)上 hack 了它篷就,有興趣的可以自己研究一下,本人在項目中直接使用了 @next
版本近忙,也沒遇到其它什么問題(除了不兼容 webpack 的 prefetch/preload
相關(guān) issue)竭业。兩種方案都可以,自行選擇及舍。
其它 html-webpack-plugin
的配置和之前使用沒有什么區(qū)別永品。
mini-css-extract-plugin
與 extract-text-webpack-plugin 區(qū)別
由于 webpack4
對 css 模塊支持的完善以及在處理 css 文件提取的方式上也做了些調(diào)整,所以之前我們一直使用的 extract-text-webpack-plugin
也完成了它的歷史使命击纬,將讓位于 mini-css-extract-plugin
鼎姐。
使用方式也很簡單,大家看著 文檔 抄就可以了。
它與 extract-text-webpack-plugin
最大的區(qū)別是:它在 code spliting
的時候會將原先內(nèi)聯(lián)寫在每一個 js chunk bundle
的 css炕桨,單獨拆成了一個個 css 文件饭尝。
將 css 獨立拆包最大的好處就是 js 和 css 的改動,不會影響對方献宫。比如我改了 js 文件并不會導(dǎo)致 css 文件的緩存失效钥平。而且現(xiàn)在它自動會配合 optimization.splitChunks
的配置,可以自定義拆分 css 文件姊途,比如我單獨配置了 element-ui
作為單獨一個 bundle
,它會自動也將它的樣式單獨打包成一個 css 文件涉瘾,不會像以前默認將第三方的 css 全部打包成一個幾十甚至上百 KB 的 app.xxx.css
文件了。
壓縮與優(yōu)化
打包 css 之后查看源碼捷兰,我們發(fā)現(xiàn)它并沒有幫我們做代碼壓縮立叛,這時候需要使用 optimize-css-assets-webpack-plugin 這個插件,它不僅能幫你壓縮 css 還能優(yōu)化你的代碼贡茅。
//配置
optimization: {
?minimizer: [new OptimizeCSSAssetsPlugin()];
}
如上圖測試用例所示秘蛇,由于 optimize-css-assets-webpack-plugin
這個插件默認使用了 cssnano 來作 css 優(yōu)化,所以它不僅壓縮了代碼顶考、刪掉了代碼中無用的注釋赁还、還去除了冗余的 css、優(yōu)化了 css 的書寫順序驹沿,優(yōu)化了你的代碼 margin:10px20px10px20px;
=> margin:10px20px;
艘策。同時大大減小了你 css 的文件大小。更多優(yōu)化的細節(jié)見文檔渊季。
contenthash
但使用 MiniCssExtractPlugin
有一個需求特別注意的地方朋蔫,在默認文檔中它是這樣配置的:
new MiniCssExtractPlugin({
?// Options similar to the same options in webpackOptions.output
?// both options are optional
?filename: devMode ? "[name].css" : "[name].[hash].css",
?chunkFilename: devMode ? "[id].css" : "[id].[hash].css"
});
簡單說明一下:
filename
是指在你入口文件entry
中引入生成出來的文件名,而chunkname
是指那些未被在入口文件entry
引入梭域,但又通過按需加載(異步)模塊的時候引入的文件。
在 copy 如上代碼使用之后發(fā)現(xiàn)情況不對搅轿!每次改動一個 xx.js
文件病涨,它對應(yīng)的 css 雖然沒做任何改動,但它的 文件 hash 還是會發(fā)生變化璧坟。仔細對比發(fā)現(xiàn)原來是 hash
惹的禍既穆。 6.f3bfa3af.css
=> 6.40bc56f6.css
但我這是根據(jù)官方文檔來寫的!為什么還有問題雀鹃!后來在文檔的最最最下面發(fā)下了這么一段話幻工!
For long term caching use filename:
[contenthash].css
. Optionally add [name].
非常的不理解,這么關(guān)鍵的一句話會放在 Maintainers
還后面的地方黎茎,默認寫在配置里面提示大家不是更好囊颅?有熱心群眾已經(jīng)開了一個 pr
,將文檔默認配置為 contenthash
。 chunkhash
=> contenthash
相關(guān) issue踢代。
這個真的蠻過分的盲憎,稍不注意就會讓自己的 css 文件緩存無效。而且很多用戶平時修改代碼的時候都不會在意自己最終打包出來的 dist
文件夾中到底有哪些變化胳挎。所以這個問題可能就一直存在了饼疙。浪費了多少資源!人艱不拆慕爬!大家覺得 webpack 難用不是沒道理的窑眯。
這里再簡單說明一下幾種 hash 的區(qū)別:
hash
hash
和每次 build
有關(guān),沒有任何改變的情況下医窿,每次編譯出來的 hash
都是一樣的磅甩,但當(dāng)你改變了任何一點東西,它的 hash
就會發(fā)生改變留搔。
簡單理解更胖,你改了任何東西, hash
就會和上次不一樣了隔显。
chunkhash
chunkhash
是根據(jù)具體每一個模塊文件自己的的內(nèi)容包括它的依賴計算所得的 hash
却妨,所以某個文件的改動只會影響它本身的 hash
,不會影響其它文件括眠。
contenthash
它的出現(xiàn)主要是為了解決彪标,讓 css
文件不受 js
文件的影響。比如 foo.css
被 foo.js
引用了掷豺,所以它們共用相同的 chunkhash
值捞烟。但這樣子是有問題的,如果 foo.js
修改了代碼当船, css
文件就算內(nèi)容沒有任何改變题画,由于是該模塊的 hash
發(fā)生了改變,其 css
文件的 hash
也會隨之改變德频。
這個時候我們就可以使用 contenthash
了苍息,保證即使 css
文件所處的模塊里有任何內(nèi)容的改變,只要 css 文件內(nèi)容不變壹置,那么它的 hash
就不會發(fā)生變化竞思。
contenthash
你可以簡單理解為是 moduleId
+ content
所生成的 hash
。
熱更新速度
其實相對 webpack 線上打包速度钞护,我更關(guān)心的本地開發(fā)熱更新速度盖喷,畢竟這才是和我們每一個程序員每天真正打交道的東西,打包一般都會扔給 CI
自動執(zhí)行难咕,而且一般項目每天也不會打包很多次课梳。
webpack4
一直說自己更好的利用了 cache
提高了編譯速度距辆,但實測發(fā)現(xiàn)是有一定的提升,但當(dāng)你一個項目惦界,路由懶加載的頁面多了之后挑格,50+之后,熱更新慢的問題會很明顯沾歪,之前的文章中也提到過這個問題漂彤,原以為新版本會解決這個問題,但并沒有灾搏。
不過你首先要排斥你的熱更新慢不是挫望,如:
沒有使用合理的?Devtool?souce map 導(dǎo)致
沒有正確使用?exclude/include?處理了不需要處理的如?node_modules
在開發(fā)環(huán)境不要壓縮代碼?UglifyJs
、提取 css狂窑、babel polyfill媳板、計算文件 hash 等不需要的操作
舊方案
最早的方案是開發(fā)環(huán)境中不是用路由懶加載了,只在線上環(huán)境中使用泉哈。封裝一個 _import
函數(shù)蛉幸,通過環(huán)境變區(qū)分是否需要懶加載。
開發(fā)環(huán)境:
module.exports = file => require("@/views/" + file + ".vue").default;
生成環(huán)境:
module.exports = file => () => import("@/views/" + file + ".vue");
但由于 webpack import
實現(xiàn)機制問題丛晦,會產(chǎn)生一定的副作用奕纫。如上面的寫法就會導(dǎo)致 @/views/
下的 所有 .vue
文件都會被打包。不管你是否被依賴引用了烫沙,會多打包一些可能永遠都用不到 js 代碼匹层。 相關(guān) issue
目前新的解決方案思路還是一樣的,只在生成模式中使用路由懶加載锌蓄,本地開發(fā)不使用懶加載升筏。但換了一種沒副作用的實現(xiàn)方式。
新方案
使用 babel
的 plugins
babel-plugin-dynamic-import-node瘸爽。它只做一件事就是:將所有的 import()
轉(zhuǎn)化為 require()
您访,這樣就可以用這個插件將所有異步組件都用同步的方式引入了,并結(jié)合 BABEL_ENV 這個 bebel
環(huán)境變量剪决,讓它只作用于開發(fā)環(huán)境下恳不。將開發(fā)環(huán)境中所有 import()
轉(zhuǎn)化為 require()
秋度,這種方案解決了之前重復(fù)打包的問題茬腿,同時對代碼的侵入性也很小侍芝,你平時寫路由的時候只需要按照官方文檔路由懶加載的方式就可以了甜攀,其它的都交給 babel
來處理芦倒,當(dāng)你不想用這個方案的時候束倍,也只需要將它從 babel
的 plugins
中移除就可以了迷殿。
具體代碼:
首先在 package.json
中增加 BABEL_ENV
"dev": "BABEL_ENV=development webpack-dev-server XXXX"
接著在 .babelrc
只能加入 babel-plugin-dynamic-import-node
這個 plugins
蔚晨,并讓它只有在 development
模式中才生效乍钻。
{
?"env": {
? ?"development": {
? ? ?"plugins": ["dynamic-import-node"]
? ?}
?}
}
之后就大功告成了肛循,路由只要像平時一樣寫就可以了。文檔
{ path: '/login', component: () => import('@/views/login/index')}
這樣能大大提升你熱更新的速度银择《嗫罚基本兩百加頁面也能在 2000ms
的熱跟新完成,基本做到無感刷新浩考。當(dāng)然你的項目本身就不大頁面也不多夹孔,完全沒必要搞這些。當(dāng)你的頁面變化跟不是你寫代碼速度的時候再考慮也不遲析孽。
打包速度
webpack4
在項目中實際測了下搭伤,普遍能提高 20%~30%的打包速度。
本文不準(zhǔn)備太深入的講解這部分內(nèi)容袜瞬,詳細的打包優(yōu)化速度可以參考 slack 團隊的這篇文章怜俐,掘金還有譯文.
這里有幾個建議來幫你加速 webpack 的打包速度。
首先你需要知道你目前打包慢邓尤,是慢在哪里拍鲤。
我們可以用 speed-measure-webpack-plugin 這個插件,它能監(jiān)控 webpack 每一步操作的耗時汞扎。如下圖:
可以看出其實大部分打包花費的時間是在 Uglifyjs
壓縮代碼季稳。和前面的提升熱更新的切入點差不多,查看 source map
的正確與否佩捞, exclude/include
的正確使用等等绞幌。
使用新版的 UglifyJsPlugin
的時候記住可以加上 cache:true
、 parall:true
一忱,可以提搞代碼打包壓縮速度莲蜘。更多配置可以參考 文檔 或者 vue-cli 的 配置。
編譯的時候還有還有一個很慢的原因是那些第三方庫帘营。比如 echarts
票渠、 element-ui
其實都非常的大,比如 echarts
打包完也還有 775kb芬迄。所以你想大大提高編譯速度问顷,可以將這些第三方庫 externals
出去,使用 script
的方式引入禀梳,或者使用 dll
的方式打包杜窄。經(jīng)測試一般如 echarts
這樣大的包可以節(jié)省十幾秒到幾十秒不等。
還有可以使用一些并行執(zhí)行 webpack 的庫:如parallel-webpack](https://github.com/trivago/parallel-webpack)算途、happypack塞耕。
順便說一下,升級一下 node
可能有驚喜嘴瓤。前不久將 CI
里面的 node 版本依賴從 6.9.2
=> 8.11.3
扫外,打包速度直接提升了一分多鐘莉钙。
總之我覺得打包時間控制在差不多的范圍內(nèi)就可以了,沒必要過分的優(yōu)化筛谚〈庞瘢可能你研究了半天,改了一堆參數(shù)發(fā)現(xiàn)其實也就提升了幾秒驾讲,但維護成本上去了蚊伞,得不償失。還不如升級 node吮铭、升級 webpack厚柳、升級你的編譯環(huán)境的硬件水平來的實在和簡單。
比如我司 CI
使用的是騰訊云普通的的 8 核 16g 的機器沐兵,這個項目也是一個很大的后臺管理項目 200+頁面别垮,引用了很多第三方的庫,但沒有使用什么 happypack
扎谎、 dll
碳想,只是用了最新版的 webpack4
, node@8.11.3
毁靶。編譯速度穩(wěn)定在兩分多鐘胧奔,完全不覺得有什么要優(yōu)化的必要。
Tree-Shaking
這其實并不是 webpack 4 才提出來的概念预吆,最早是 rollup 提出來并實現(xiàn)的龙填,后來在 webpack 2 中就實現(xiàn)了,本次在 webpack 4 只是增加了 JSONTreeShaking
和 sideEffects
能讓你能更好的搖拐叉。
不過這里還是要提一下岩遗,默認 webpack 是支持 Tree-Shaking
的,但在你的項目中可能會因為 babel
的原因?qū)е滤А?/p>
因為 TreeShaking
這個功能是基于 ES6 modules
的靜態(tài)特性檢測凤瘦,來找出未使用的代碼宿礁,所以如果你使用了 babel 插件的時候,如:babel-preset-env蔬芥,它默認會將模塊打包成 commonjs
梆靖,這樣就會讓 TreeShaking
失效了。
其實在 webpack 2 之后它自己就支持模塊化處理笔诵。所以只要讓 babel 不 transform modules
就可以了返吻。配置如下:
// .babelrc
{
?"presets": [
? ?["env", {
? ? ?modules: false,
? ? ?...
? ?}]
?]
}
感興趣的小伙伴,可以關(guān)注公眾號【grain先森】乎婿,回復(fù)關(guān)鍵詞 “小程序”测僵,獲取更多資料,更多關(guān)鍵詞玩法期待你的探索~