Webpack打包慢問(wèn)題的完美解決方法
前言
這幾天寫騰訊實(shí)習(xí)生 Mini 項(xiàng)目的時(shí)候用上了 React 全家桶,當(dāng)然同時(shí)引入了 Webpack 作為打包工具。但是開發(fā)過(guò)程中遇到一個(gè)很棘手的問(wèn)題就是,React 加上 React-Router、superagent、eventproxy 這些第三方輪子一共有好幾百個(gè) module,Webpack 的打包速度極慢。這對(duì)于開發(fā)是非常不好的體驗(yàn),同時(shí)效率也極低。
問(wèn)題分析
我們先來(lái)看一下完全沒有任何優(yōu)化的時(shí)候,Webpack 的打包速度(使用了jsx和babel的loader)。
下面是我們的測(cè)試文件:
//test.js var react = require('react'); var ReactAddonsCssTransitionGroup = require('react-addons-css-transition-group'); var reactDOM = require('react-dom'); var reactRouter = require('react-router'); var superagent = require("superagent"); var eventproxy = require("eventproxy");
運(yùn)行
webpack test.js
在我的2015款RMBP13,i5處理器,全SSD下,性能是這樣的:
沒錯(cuò)你沒有看錯(cuò),這幾個(gè)第三方輪子加起來(lái)有整整668個(gè)模塊,全部打包需要20多秒。
這意味著什么呢?你每次對(duì)業(yè)務(wù)代碼的修改,gulp 或者 Webpack 監(jiān)測(cè)到后都會(huì)重新打包,你要足足等20秒才能看到自己的修改結(jié)果。
但是需要重新打包的只有你的業(yè)務(wù)代碼,這些第三方庫(kù)是完全不用重新打包的,它們的存在只會(huì)拖累打包性能。所以我們要找一些方法來(lái)優(yōu)化這個(gè)過(guò)程。
配置externals
Webpack 可以配置 externals 來(lái)將依賴的庫(kù)指向全局變量,從而不再打包這個(gè)庫(kù),比如對(duì)于這樣一個(gè)文件:
import React from 'react'; console.log(React);
如果你在 Webpack.config.js 中配置了externals:
module.exports = { externals: { 'react': 'window.React' } //其它配置忽略...... };
等于讓 Webpack 知道,對(duì)于 react 這個(gè)模塊就不要打包啦,直接指向 window.React 就好。不過(guò)別忘了加載 react.min.js,讓全局中有 React 這個(gè)變量。
我們來(lái)看看性能,因?yàn)椴挥么虬?React 了所以速度自然超級(jí)快,包也很?。?/p>
配置externals的缺陷
問(wèn)題如果就這么簡(jiǎn)單地解決了的話,那我就沒必要寫這篇文章了,下面我們加一個(gè) react 的動(dòng)畫庫(kù) react-addons-css-transition-group 來(lái)試一試:
import React from 'react'; import ReactAddonsCssTransitionGroup from 'react-addons-css-transition-group'; console.log(React);
對(duì),你沒有看錯(cuò),我也沒有截錯(cuò)圖,新加了一個(gè)很小很小的動(dòng)畫庫(kù)之后,性能又爆炸了。從模塊數(shù)來(lái)看,一定是 Webpack 又把 react 重新打包了一遍。
我們來(lái)看一下為什么一個(gè)很小很小的動(dòng)畫庫(kù)會(huì)導(dǎo)致 Webpack 又傻傻地把 react 重新打包了一遍。找到 react-addons-css-transition-group 這個(gè)模塊,然后看看它是怎么寫的:
// react-addons-css-transition-group模塊 // 入口文件 index.js module.exports = require('react/lib/ReactCSSTransitionGroup');
這個(gè)動(dòng)畫模塊就只有一行代碼,唯一的作用就是指向 react 下面的一個(gè)子模塊,我們?cè)賮?lái)看看這個(gè)子模塊是怎么寫的:
// react模塊 // react/lib/ReactCSSTransitionGroup.js var React = require('./React'); var ReactTransitionGroup = require('./ReactTransitionGroup'); var ReactCSSTransitionGroupChild = require('./ReactCSSTransitionGroupChild'); //....剩余代碼忽略
這個(gè)子模塊又反回去依賴了 react 整個(gè)庫(kù)的入口,這就是拖累 Webpack 的罪魁禍?zhǔn)住?/p>
總而言之,問(wèn)題是這樣產(chǎn)生的:
- Webpack 發(fā)現(xiàn)我們依賴了 react-addons-css-transition-group
- Webpack 去打包 react-addons-css-transition-group 的時(shí)候發(fā)現(xiàn)它依賴了 react 模塊下的一個(gè)叫 ReactTransitionGroup.js 的文件,于是 Webpack 去打包這個(gè)文件。
- ReactTransitionGroup.js 依賴了整個(gè) react 的入口文件 React.js,雖然我們?cè)O(shè)置了 externals ,但是 Webpack 不知道這個(gè)入口文件等效于 react 模塊本身,于是我們可愛又敬業(yè)的 Webpack 就把整個(gè) react 又重新打包了一遍。
讀到這里你可能會(huì)有疑問(wèn),為什么不能把這個(gè)動(dòng)畫庫(kù)也設(shè)置到 externals 里,這樣不是就不用打包了嗎?
問(wèn)題就在于,這個(gè)動(dòng)畫庫(kù)并沒有提供生產(chǎn)環(huán)境的文件,或者說(shuō)這個(gè)庫(kù)根本沒有提供 react-addons-css-transition-group.min.js 這個(gè)文件。
這個(gè)問(wèn)題不只存在于 react-addons-css-transition-group 中,對(duì)于 react 的大多數(shù)現(xiàn)有庫(kù)來(lái)說(shuō)都有這個(gè)依賴關(guān)系復(fù)雜的問(wèn)題。
初級(jí)解決方法
所以對(duì)于這個(gè)問(wèn)題的解決方法就是,手工打包這些 module,然后設(shè)置 externals ,讓 Webpack 不再打包它們。
我們需要這樣一個(gè) lib-bundle.js 文件:
window.__LIB["react"] = require("react"); window.__LIB["react-addons-css-transition-group"] = require("react-addons-css-transition-group"); // ...其它依賴包
我們?cè)谶@里把一些第三方庫(kù)注冊(cè)到了 window.__LIB 下,這些庫(kù)可以作為底層的基礎(chǔ)庫(kù),免于重復(fù)打包。
然后執(zhí)行 webpack lib-bundle.js lib.js,得到打包好的 lib.js。然后去設(shè)置我們的 externals :
var webpack = require('webpack'); module.exports = { externals: { 'react': 'window.__LIB["react"]', 'react-addons-css-transition-group': 'window.__LIB["react-addons-css-transition-group"]', // 其它庫(kù) } //其它配置忽略...... };
這時(shí)由于 externals 的存在,Webpack 打包的時(shí)候就會(huì)避開這些模塊超多,依賴關(guān)系復(fù)雜的庫(kù),把這些第三方 module 的入口指向預(yù)先打包好的 lib.js 的入口 window.__LIB,從而只打包我們的業(yè)務(wù)代碼。
終極解決方法
上面我們提到的方法本質(zhì)上就是一種動(dòng)態(tài)鏈接庫(kù)(dll)”的思想,這在 windows 系統(tǒng)下面是一種很常見的思想。一個(gè)dll包,就是一個(gè)很純凈的依賴庫(kù),它本身不能運(yùn)行,是用來(lái)給你的 app 或者業(yè)務(wù)代碼引用的。
同樣的 Webpack 最近也新加入了這個(gè)功能:webpack.DllPlugin。使用這個(gè)功能需要把打包過(guò)程分成兩步:
- 打包ddl包
- 引用ddl包,打包業(yè)務(wù)代碼
首先我們來(lái)打包ddl包,首先配置一個(gè)這樣的 ddl.config.js:
const webpack = require('webpack'); const vendors = [ 'react', 'react-dom', 'react-router', // ...其它庫(kù) ]; module.exports = { output: { path: 'build', filename: '[name].js', library: '[name]', }, entry: { "lib": vendors, }, plugins: [ new webpack.DllPlugin({ path: 'manifest.json', name: '[name]', context: __dirname, }), ], };
webpack.DllPlugin 的選項(xiàng)中:
- path 是 manifest.json 文件的輸出路徑,這個(gè)文件會(huì)用于后續(xù)的業(yè)務(wù)代碼打包;
- name 是dll暴露的對(duì)象名,要跟 output.library 保持一致;
- context 是解析包路徑的上下文,這個(gè)要跟接下來(lái)配置的 webpack.config.js 一致。
運(yùn)行Webpack,會(huì)輸出兩個(gè)文件一個(gè)是打包好的 lib.js,一個(gè)就是 manifest.json,它里面的內(nèi)容大概是這樣的:
{ "name": "vendor_ac51ba426d4f259b8b18", "content": { "./node_modules/react/react.js": 1, "./node_modules/react/lib/React.js": 2, "./node_modules/react/node_modules/object-assign/index.js": 3, "./node_modules/react/lib/ReactChildren.js": 4, "./node_modules/react/lib/PooledClass.js": 5, "./node_modules/react/lib/reactProdInvariant.js": 6, // ............ } }
接下來(lái)我們就可以快樂地打包業(yè)務(wù)代碼啦,首先寫好打包配置文件 webpack.config.js:
const webpack = require('webpack'); module.exports = { output: { path: 'build', filename: '[name].js', }, entry: { app: './src/index.js', }, plugins: [ new webpack.DllReferencePlugin({ context: __dirname, manifest: require('./manifest.json'), }), ], };
webpack.DllReferencePlugin 的選項(xiàng)中:
- context 需要跟之前保持一致,這個(gè)用來(lái)指導(dǎo) Webpack 匹配 manifest 中庫(kù)的路徑;
- manifest 用來(lái)引入剛才輸出的 manifest.json 文件。
DllPlugin 本質(zhì)上的做法和我們手動(dòng)分離這些第三方庫(kù)是一樣的,但是對(duì)于包極多的應(yīng)用來(lái)說(shuō),自動(dòng)化明顯加快了生產(chǎn)效率。
總結(jié)
以上就是關(guān)于徹底解決Webpack打包慢問(wèn)題的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
showModalDialog 和 showModelessDialog
showModalDialog 和 showModelessDialog...2007-01-01javascript結(jié)合Flexbox簡(jiǎn)單實(shí)現(xiàn)滑動(dòng)拼圖游戲
本文給大家分享的是一則使用javascript結(jié)合Flexbox簡(jiǎn)單實(shí)現(xiàn)滑動(dòng)拼圖游戲的代碼,雖然沒有實(shí)現(xiàn)完整的功能,但是還是推薦給大家,喜歡的朋友可以繼續(xù)做完2016-02-02使用JavaScript獲取掃碼槍掃描得到的條形碼的思路代碼詳解
這篇文章主要介紹了使用JavaScript獲取掃碼槍掃描得到的條形碼的思路代碼詳解,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06TypeScript中interface和type的區(qū)別詳解
本文主要介紹了TypeScript中interface和type的區(qū)別詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07uniapp電商小程序?qū)崿F(xiàn)訂單30分鐘倒計(jì)時(shí)
這篇文章主要為大家詳細(xì)介紹了uniapp電商小程序?qū)崿F(xiàn)訂單30分鐘倒計(jì)時(shí),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11Javascript 變量作用域 兩個(gè)可能會(huì)被忽略的小特性
關(guān)于Javascript,大家肯定都很熟悉啦,對(duì)于有編程經(jīng)驗(yàn)的朋友來(lái)說(shuō),Javascript很快就能上手,不過(guò)關(guān)于JS的變量作用域,還是有一點(diǎn)差別的。2010-03-03