摘要:在項目架構(gòu)中這兩個東西基本成為了標配,但的模塊必須在使用前經(jīng)過的構(gòu)建后文稱為才能在瀏覽器端使用,而每次修改也都需要重新構(gòu)建后文稱為才能生效,如何提高的構(gòu)建效率成為了提高開發(fā)效率的關(guān)鍵之一。
0. 前言
圖1:ES6 + Webpack + React + Babel
webpack 是個好東西,和 NPM 搭配起來使用管理模塊實在非常方便。而 Babel 更是神一般的存在,讓我們在這個瀏覽器尚未全面普及 ES6 語法的時代可以先一步體驗到新的語法帶來的便利和效率上的提升。在 React 項目架構(gòu)中這兩個東西基本成為了標配,但 commonjs 的模塊必須在使用前經(jīng)過 webpack 的構(gòu)建(后文稱為 build)才能在瀏覽器端使用,而每次修改也都需要重新構(gòu)建(后文稱為 rebuild)才能生效,如何提高 webpack 的構(gòu)建效率成為了提高開發(fā)效率的關(guān)鍵之一。
1. Webpack 的構(gòu)建流程在開始正式的優(yōu)化之前,讓我們先回顧一下 Webpack 的構(gòu)建流程,有哪些關(guān)鍵步驟,只有了解了這些,我們才能分析出哪些地方有優(yōu)化的可能性。
圖2:webpack is a module bundler.
首先,我們來看看官方對于 Webpack 的理念闡釋,webapck 把所有的靜態(tài)資源都看做是一個 module,通過 webpack,將這些 module 組成到一個 bundle 中去,從而實現(xiàn)在頁面上引入一個 bundle.js,來實現(xiàn)所有靜態(tài)資源的加載。所以詳細一點看,webpack 應(yīng)該是這樣的:
圖3:Every static asset should be able to be a module --webpack
通過 loader,webpack 可以把各種非原生 js 的靜態(tài)資源轉(zhuǎn)換成 JavaScript,所以理論上任何一種靜態(tài)資源都可以成為一個 module。
當然 webpack 還有很多其他好玩的特性,但不是本文的重點因此不鋪開進行說明了。了解了上述的過程,我們就可以根據(jù)這些過程的前后處理進行對應(yīng)的優(yōu)化,接下來我們會針對 build 和 rebuild 的過程給與相應(yīng)的意見。
我們先從解析模塊路徑和分析依賴講起,有人可能覺得這無所謂,但當項目應(yīng)用依賴的模塊越來越多,越來越重時,項目越來越大,文件和文件夾越來越多時,這個過程就變得越來越關(guān)乎性能。
2.1 減小 Webpack 覆蓋的范圍build +, rebuild +
webpack 默認會去尋找所有 resolve.root 下的模塊,但是有些目錄我們是可以明確告知 webpack 不要管這里,從而減輕 webpack 的工作量。這時會用到 module.noParse 參數(shù)。
2.2 Resolove.root VS Resolove.moduledirectoriesbuild +, rebuild +
root 和 moduledirectories 如果只從用法上來看,似乎是可以互相替代的。但因為 moduledirectories 從設(shè)計上是取相對路徑,所以比起 root ,所以會多 parse 很多路徑。
resolve: { root: path.resolve("src/node_modules"), extensions: ["", ".js", ".jsx"] }, resolve: { modulesDirectories: ["node_modules", "./src"], extensions: ["", ".js", ".jsx"] },
上面的配置,只會解析
./src/node_modules/a
==== 此處有修改 2016/09/10 感謝 @lili_21 ====
而下面的配置會解析
/some/folder/structure/node_modules/a /some/folder/structure/src/a /some/folder/node_modules/a /some/folder/src/a /some/node_modules/a /some/src/a /node_modules/a /src/a
大部分的情況下使用 root 即可,只有在有很復雜的路徑下,才考慮使用 moduledirectories,這可以明顯提高 webpack 的構(gòu)建性能。這個 issue 也很詳細地討論了這個問題。
3. LOADERSwebpack 官方和社區(qū)為我們提供了各種各樣 loader 來處理各種類型的文件,這些 loader 的配置也直接影響了構(gòu)建的性能。
3.1 Babel-loader: 能者少勞build ++, rebuild ++
以 babel-loader 為例,我們在開發(fā) React 項目時很可能會使用到了 ES6 或者 jsx 的語法,因此使用到 babel-loader 的情況很多,最簡單的情況下我們可以這樣配置,讓所有的 js/jsx 通過 babel-loader:
module: { loaders: [ { test: /.js(x)*$/, loader: "babel-loader", query: { presets: ["react", "es2015-ie", "stage-1"] } } ] }
上面這樣的做法當然是 ok 的,但是對于很多的 npm 包來說,他們完全沒有經(jīng)過 babel 的必要(成熟的 npm 包會在發(fā)布前將自己 es5,甚至 es3 化),讓這些包通過 babel 會帶來巨大的性能負擔,畢竟 babel6 要經(jīng)過幾十個插件的處理,雖然 babel-loader 強大,但能者多勞的這種保守的想法卻使得 babel-loader 成為了整個構(gòu)建的性能瓶頸。所以我們可以使用 exclude,大膽地屏蔽掉 npm 里的包,從而使整包的構(gòu)建效率飛速提高。
module: { loaders: [ { test: /.js(x)*$/, loader: "babel-loader", exclude: function(path) { // 路徑中含有 node_modules 的就不去解析。 var isNpmModule = !!path.match(/node_modules/); return isNpmModule; }, query: { presets: ["react", "es2015-ie", "stage-1"] } } ] }
甚至,在我們十分確信的情況下,使用 include 來限定 babel 的使用范圍,進一步提高效率。
var path = require("path"); module.exports = { module: { loaders: [ { test: /.js(x)*$/, loader: "babel-loader", include: [ // 只去解析運行目錄下的 src 和 demo 文件夾 path.join(process.cwd(), "./src"), path.join(process.cwd(), "./demo") ], query: { presets: ["react", "es2015-ie", "stage-1"] } } ] } }4. PLUGINS
webpack 官方和社區(qū)為我們提供了很多方便的插件,有些插件為我們開發(fā)和生產(chǎn)帶來了很多的便利,但是不合適地使用插件也會拖慢 webpack 的構(gòu)建效率,而有些插件雖然不會為我們的開發(fā)上直接提供便利,但使用他們卻可以幫助我們提高 webpack 的構(gòu)建效率,這也是本文會提到的。
4.1 SourceMapsbuild +
SourceMaps 是一個非常實用的功能,可以讓我們在 chrome debug 時可以不用直接看已經(jīng) bundle 過的 js,而是直接在源代碼上進行查看和調(diào)試,但完美的 SourceMaps 是很慢的,webpack 官方提供了七種 sourceMap 模式共大家選擇,性能對比如下:
devtool | build speed | rebuild speed | production supported | quality |
---|---|---|---|---|
eval | +++ | +++ | no | generated code |
cheap-eval-source-map | + | ++ | no | transformed code (lines only) |
cheap-source-map | + | o | yes | transformed code (lines only) |
cheap-module-eval-source-map | o | ++ | no | original source (lines only) |
cheap-module-source-map | o | - | yes | original source (lines only) |
eval-source-map | -- | + | no | original source |
source-map | -- | -- | yes | original source |
具體各自的區(qū)別請參考 https://github.com/webpack/do... ,我們這里推薦使用 cheap-source-map,也就是去掉了column mapping 和 loader-sourceMap(例如 jsx to js) 的 sourceMap,雖然帶上 eval 參數(shù)的可以快更多,但是這種 sourceMap 只能看,不能調(diào)試,得不償失。
4.2 OPTIMIZATIONbuild ++,rebuild ++
webpack 提供了一些可以優(yōu)化瀏覽器端性能的優(yōu)化插件,如UglifyJsPlugin,OccurrenceOrderPlugin 和 DedupePlugin,都很實用,也都在消耗構(gòu)建性能(UglifyJsPlugin 非常耗性能),如果你是在開發(fā)環(huán)境下,這些插件最好都不要使用,畢竟腳本大一些,跑的慢一些這些比起每次構(gòu)建要耗費更多時間來說,顯然還是后者更會消磨開發(fā)者的耐心,因此,只在正產(chǎn)環(huán)境中使用 OPTIMIZATION。
4.3 CommonsChunkrebuild +
當你的 webpack 構(gòu)建任務(wù)中有多個入口文件,而這些文件都 require 了相同的模塊,如果你不做任何事情,webpack 會為每個入口文件引入一份相同的模塊,顯然這樣做,會使得相同模塊變化時,所有引入的 entry 都需要一次 rebuild,造成了性能的浪費,CommonsChunkPlugin 可以將相同的模塊提取出來多帶帶打包,進而減小 rebuild 時的性能消耗。這里有一篇很通俗易懂的使用方法:http://webpack.toobug.net/zh-... ,感興趣的朋友不妨一試。
4.4 DLL & DllReferencebuild +++, rebuild +++
除了正在開發(fā)的源代碼之外,通常還會引入很多第三方 NPM 包,這些包我們不會進行修改,但是仍然需要在每次 build 的過程中消耗構(gòu)建性能,那有沒有什么辦法可以減少這些消耗呢?DLLPlugin 就是一個解決方案,他通過前置這些依賴包的構(gòu)建,來提高真正的 build 和 rebuild 的構(gòu)建效率。
鑒于現(xiàn)有的資料對于這兩個插件的解釋都不是很清楚,筆者這里翻譯了一篇日本同學的文章,通過一個簡單的例子來說明一下這兩個插件的用法。我們舉例,把 react 和 react-dom 打包成為 dll bundle。
首先,我們來寫一個 DLLPlugin 的 config 文件。
webpack.dll.config.js
const path = require("path"); const webpack = require("webpack"); module.exports = { entry: { vendor: ["react", "react-dom"] }, output: { path: path.join(__dirname, "dist"), filename: "[name].dll.js", /** * output.library * 將會定義為 window.${output.library} * 在這次的例子中,將會定義為`window.vendor_library` */ library: "[name]_library" }, plugins: [ new webpack.DllPlugin({ /** * path * 定義 manifest 文件生成的位置 * [name]的部分由entry的名字替換 */ path: path.join(__dirname, "dist", "[name]-manifest.json"), /** * name * dll bundle 輸出到那個全局變量上 * 和 output.library 一樣即可。 */ name: "[name]_library" }) ] };
執(zhí)行 webpack 后,就會在 dist 目錄下生成 dll bundle 和對應(yīng)的 manifest 文件
$ ./node_modules/.bin/webpack --config webpack.dll.config.js Hash: 36187493b1d9a06b228d Version: webpack 1.13.1 Time: 860ms Asset Size Chunks Chunk Names vendor.dll.js 699 kB 0 [emitted] vendor [0] dll vendor 12 bytes {0} [built] + 167 hidden modules $ ls dist ./ vendor-manifest.json ../ vendor.dll.js
manifest 文件的格式大致如下,由包含的 module 和對應(yīng)的 id 的鍵值對構(gòu)成。
cat dist/vendor-manifest.json { "name": "vendor_library", "content": { "./node_modules/react/react.js": 1, "./node_modules/react/lib/React.js": 2, "./node_modules/process/browser.js": 3, "./node_modules/object-assign/index.js": 4, "./node_modules/react/lib/ReactChildren.js": 5, "./node_modules/react/lib/PooledClass.js": 6, "./node_modules/fbjs/lib/invariant.js": 7, ...
好,接下來我們通過 DLLReferencePlugin 來使用剛才生成的 DLL Bundle。
首先我們寫一個只去 require react,并通過 console.log 吐出的 index.js。
var React = require("react"); var ReactDOM = require("react-dom"); console.log("dll"s React:", React); console.log("dll"s ReactDOM:", ReactDOM);
再寫一個不參考 Dll Bundle 的普通 webpack config 文件。
webpack.conf.js
const path = require("path"); const webpack = require("webpack"); module.exports = { entry: { "dll-user": ["./index.js"] }, output: { path: path.join(__dirname, "dist"), filename: "[name].bundle.js" } };
執(zhí)行 webpack,會在 dist 下生成 dll-user.bundle.js,約 700K,耗時 801ms。
$ ./node_modules/.bin/webpack Hash: d8cab39e58c13b9713a6 Version: webpack 1.13.1 Time: 801ms Asset Size Chunks Chunk Names dll-user.bundle.js 700 kB 0 [emitted] dll-user [0] multi dll-user 28 bytes {0} [built] [1] ./index.js 145 bytes {0} [built] + 167 hidden modules
接下來,我們加入 DLLReferencePlugin
webpack.conf.js
const path = require("path"); const webpack = require("webpack"); module.exports = { entry: { "dll-user": ["./index.js"] }, output: { path: path.join(__dirname, "dist"), filename: "[name].bundle.js" }, // ----在這里追加---- plugins: [ new webpack.DllReferencePlugin({ context: __dirname, /** * 在這里引入 manifest 文件 */ manifest: require("./dist/vendor-manifest.json") }) ] // ----在這里追加---- };
./node_modules/.bin/webpack Hash: 3bc7bf760779b4ca8523 Version: webpack 1.13.1 Time: 70ms Asset Size Chunks Chunk Names dll-user.bundle.js 2.01 kB 0 [emitted] dll-user [0] multi dll-user 28 bytes {0} [built] [1] ./index.js 145 bytes {0} [built] + 3 hidden modules
結(jié)果是非常驚人的,只有2.01K,耗時 70 ms,無疑大大提高了 build 和 rebuild 的效率。實際放到頁面上看下是否可行。
因為 Dll bundle 在依賴安裝完畢后就可以進行了,我們可以在第一次執(zhí)行 dev server 前執(zhí)行一次 dll bundle 的 webapck 任務(wù)。
4.4.1 和 external 的比較有人會說,這個和 用 webpack 的 externals 配置把 require 的 module 指向全局變量有點像啊。
const path = require("path"); const webpack = require("webpack"); module.exports = { entry: { "ex": ["./index.js"] }, output: { path: path.join(__dirname, "dist"), filename: "[name].bundle.js" }, externals: { // require("react")はwindow.Reactを使う "react": "React", // require("react-dom")はwindow.ReactDOMを使う "react-dom": "ReactDOM" } };
這里有兩個主要的區(qū)別:
像是 react 這種已經(jīng)打好了生產(chǎn)包的使用 externals 很方便,但是也有很多 npm 包是沒有提供的,這種情況下 DLLBundle 仍可以使用。
如果只是引入 npm 包一部分的功能,比如 require("react/lib/React") 或者 require("lodash/fp/extend") ,這種情況下 DLLBundle 仍可以使用。
當然如果只是引用了 react 這類的話,externals 因為配置簡單所以也推薦使用。
4.5 HappyPackbuild +, rebuild +
webpack 的長時間構(gòu)建搞的大家都很 unhappy。于是 @amireh 想到了一個點子,既然 loader 默認都是一個進程在跑,那是否可以讓 loader 多進程去處理文件呢?
happyPack 的文檔寫的很易懂,這里就不再贅述,happyPack 不僅利用了多進程,同時還利用緩存來使得 rebuild 更快。下面是插件作者給出的性能數(shù)據(jù):
For the main repository I tested on, which had around 3067 modules, the build time went down from 39 seconds to a whopping ~10 seconds when there was yet no
Successive builds now take between 6 and 7 seconds.
Here"s a rundown of the various states the build was performed in:
Elapsed (ms) | Happy? | Cache enabled? | Cache present? | Using DLLs? | |
---|---|---|---|---|---|
39851 | NO | N/A | N/A | NO | |
37393 | NO | N/A | N/A | YES | |
14605 | YES | NO | N/A | NO | |
13925 | YES | YES | NO | NO | |
11877 | YES | YES | YES | NO | |
9228 | YES | NO | N/A | YES | |
9597 | YES | YES | NO | YES | |
6975 | YES | YES | YES | YES |
5. 其他The builds above were run on Linux over a machine with 12 cores.
上面我們針對 webpack 的 resolve、loader 和 plugin 的過程給出了相應(yīng)的優(yōu)化意見,除了這些哪些優(yōu)化點呢?其實有些優(yōu)化貫穿在這個流程中,比如緩存和文件 IO。
5.1 Cache無論在何種性能優(yōu)化中,緩存總是必不可少的一部分,畢竟每次變動都只影響很小的一部分,如果能夠緩存住那些沒有變動的部分,直接拿來使用,自然會事半功倍,在 webpack 的整個構(gòu)建過程中,有多個地方提供了緩存的機會,如果我們打開了這些緩存,會大大加速我們的構(gòu)建,尤其是 rebuild 的效率。
5.1.1 webpack.cacherebuild +
webpack 自身就有 cache 的配置,并且在 watch 模式下自動開啟,雖然效果不是最明顯的,但卻對所有的 module 都有效。
5.1.2 babel-loader.cacheDirectoryrebuild ++
babel-loader 可以利用系統(tǒng)的臨時文件夾緩存經(jīng)過 babel 處理好的模塊,對于 rebuild js 有著非常大的性能提升。
5.1.3 HappyPack.cachebuild +, rebuild +
上面提到的 happyPack 插件也同樣提供了 cache 功能,默認是以 .happypack/cache--[id].json 的路徑進行緩存。因為是緩存在當前目錄下,所以他也可以輔助下次 build 時的效率。
5.2 FileSystem默認的情況下,構(gòu)建好的目錄一定要輸出到某個目錄下面才能使用,但 webpack 提供了一種很棒的讀寫機制,使得我們可以直接在內(nèi)存中進行讀寫,從而極大地提高 IO 的效率,開啟的方法也很簡單。
var MemoryFS = require("memory-fs"); var webpack = require("webpack"); var fs = new MemoryFS(); var compiler = webpack({ ... }); compiler.outputFileSystem = fs; compiler.run(function(err, stats) { // ... var fileContent = fs.readFileSync("..."); });
當然,我們還可以通過 webpackDevMiddleware 更加無縫地就接入到 dev server 中,例如我們以 express 作為靜態(tài) server 的例子。
var compiler = webpack(webpackCfg); var webpackDevMiddlewareInstance = webpackDevMiddleware(compiler, { // webpackDevMiddleware 默認使用了 memory-fs publicPath: "/dist", aggregateTimeout: 300, // wait so long for more changes poll: true, // use polling instead of native watchers stats: { chunks: false } }); var app = express(); app.use(webpackDevMiddlewareInstance); app.listen(xxxx, function(err) { console.log(colors.info("dev server start: listening at " + xxxx)); if (err) { console.error(err); } }6. 總結(jié)
上面我們從 webpack 構(gòu)建的各個部分,給出了相應(yīng)的優(yōu)化策略,如果你的項目中能夠?qū)⑵渫耆瀼仄饋恚?0 倍提速不是夢想。這些優(yōu)化也同樣應(yīng)用到了我們團隊的 react 項目中,https://github.com/uxcore/uxcore ,歡迎一起來討論 webpack 的效率優(yōu)化方案。
7. 參考文章webpack build performance:http://webpack.github.io/docs...
webpackのDLLバンドルを使ってビルドを速くする:http://qiita.com/pirosikick/i...
How to make your Webpack builds 10x faster:http://www.slideshare.net/tru...
本文作者 eternalsky,始發(fā)于團隊微信公眾號 猿猿相抱 和個人博客 空の屋敷,轉(zhuǎn)載請保留作者信息。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/79738.html
學習的過程中收藏了這些優(yōu)秀教程和的項目,希望對你有幫助。 github地址, 有不錯的就更新 官方文檔 中文指南 初級教程 webpack-howto 作者:Pete Hunt Webpack 入門指迷 作者:題葉 webpack-demos 作者:ruanyf 一小時包教會 —— webpack 入門指南 作者:VaJoy Larn webpack 入門及實踐 作者:...
摘要:發(fā)布一周以來,獲得了,登上了,在上進入了前三。為什么要創(chuàng)造當我在設(shè)計一個新的移動端用戶的網(wǎng)站比如面向微信用戶的網(wǎng)站的時候,調(diào)研了一些現(xiàn)有的框架,應(yīng)用比較廣泛的有等。否則只能有中國人來關(guān)注你的項目了。要知道,外國開發(fā)者比中國開發(fā)者多很多倍的。 受邀寫一篇 Mobi.css 的誕生歷程,請原諒我吸引眼球的標題,我會努力把這篇文章寫得有一些干貨的。 GitHub Repo | Homepag...
摘要:打包分析與性能優(yōu)化背景在去年年末參與的一個項目中,項目技術(shù)棧使用,生產(chǎn)環(huán)境全量構(gòu)建將近三分鐘,項目業(yè)務(wù)模塊多達數(shù)百個,項目依賴數(shù)千個,并且該項目協(xié)同前后端開發(fā)人員較多,提高構(gòu)建效率,成為了改善團隊開發(fā)效率的關(guān)鍵之一。 webpack打包分析與性能優(yōu)化 背景 在去年年末參與的一個項目中,項目技術(shù)棧使用react+es6+ant-design+webpack+babel,生產(chǎn)環(huán)境全量構(gòu)建將...
摘要:前言月份開始出沒社區(qū),現(xiàn)在差不多月了,按照工作的說法,就是差不多過了三個月的試用期,準備轉(zhuǎn)正了一般來說,差不多到了轉(zhuǎn)正的時候,會進行總結(jié)或者分享會議那么今天我就把看過的一些學習資源主要是博客,博文推薦分享給大家。 1.前言 6月份開始出沒社區(qū),現(xiàn)在差不多9月了,按照工作的說法,就是差不多過了三個月的試用期,準備轉(zhuǎn)正了!一般來說,差不多到了轉(zhuǎn)正的時候,會進行總結(jié)或者分享會議!那么今天我就...
閱讀 742·2021-07-25 21:37
閱讀 3654·2019-08-30 15:55
閱讀 2572·2019-08-30 15:54
閱讀 1717·2019-08-30 15:44
閱讀 3123·2019-08-30 15:44
閱讀 859·2019-08-30 15:43
閱讀 1021·2019-08-29 15:36
閱讀 3038·2019-08-29 10:58