分割webpack配置文件的多種方法
(一)
將你的配置信息寫到多個分散的文件中去坛悉,然后在執(zhí)行webpack的時候利用--config
參數(shù)指定要加載的配置文件志膀,配置文件利用moduleimports
導(dǎo)出裤翩。你可以在webpack/react-starter 看到是使用這種發(fā)方法的。
// webpack 配置文件
|-- webpack-dev-server.config.js
|-- webpack-hot-dev-server.config.js
|-- webpack-production.config.js
|-- webpack.config.js
// npm 命令
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev-server": "webpack-dev-server --config webpack-dev-server.config.js --progress --colors --port 2992 --inline",
"hot-dev-server": "webpack-dev-server --config webpack-hot-dev-server.config.js --hot --progress --colors --port 2992 --inline",
"build": "webpack --config webpack-production.config.js --progress --profile --colors"
},
(二)
調(diào)用第三方的webpack工具住诸,使用其集成的api耿导,方便進行webpack配置晦攒。HenrikJoreteg/hjs-webpack 這個repo就是這么做的。
var getConfig = require('hjs-webpack')
module.exports = getConfig({
// entry point for the app
in: 'src/app.js',
// Name or full path of output directory
// commonly named `www` or `public`. This
// is where your fully static site should
// end up for simple deployment.
out: 'public',
// This will destroy and re-create your
// `out` folder before building so you always
// get a fresh folder. Usually you want this
// but since it's destructive we make it
// false by default
clearBeforeBuild: true
})
(三) Scalable webpack configurations
ones that can be reused and combined with other partial configurations
在單個配置文件中維護配置端辱,但是區(qū)分好條件分支梁剔。調(diào)用不同的npm命令時候設(shè)置不同的環(huán)境變量,然后在分支中匹配舞蔽,返回我們需要的配置文件荣病。
這樣做的好處可以在一個文件中管理不同npm操作的邏輯,并且可以共用相同的配置渗柿。webpack-merge這個模塊可以起到合并配置的作用个盆。
const parts = require('./webpack-config/parts');
switch(process.env.npm_lifecycle_event) {
case 'build':
config = merge(common,
parts.clean(PATHS.build),
parts.setupSourceMapForBuild(),
parts.setupCSS(PATHS.app),
parts.extractBundle({
name: 'vendor',
entries: ['react', 'vue', 'vuex']
}),
parts.setFreeVariable('process.env.NODE_ENV', 'production'),
parts.minify()
);
break;
default:
config = merge(common,
parts.setupSourceMapForDev(),
parts.devServer(),
parts.setupCSS(PATHS.app));
}
// minify example
exports.minify = function () {
return {
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
drop_console: true
},
comments: false,
beautify: false
})
]
}
}
開發(fā)環(huán)境下的自動刷新
webpack-dev-server
webpack-dev-server
在webpack的watch
基礎(chǔ)上開啟服務(wù)器。
webpack-dev-server
是運行在內(nèi)存中的開發(fā)服務(wù)器朵栖,支持高級webpack特性hot module replacement
颊亮。這對于react vue這種組件化開發(fā)是很方便的。
使用webpack-dev-server命令開啟服務(wù)器陨溅,配合HMR及可以實現(xiàn)代碼更改瀏覽器局部刷新的能力终惑。
hot module replacement
Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running without a page reload.
當(dāng)應(yīng)用在運行期間hmr機制能夠修改、添加门扇、或者移除相應(yīng)的模塊雹有,而不使整個頁面刷新。
hmr機制適用于單頁應(yīng)用臼寄。
要實現(xiàn)hmr機制件舵,需要配合webpack-dev-server
服務(wù)器,這個服務(wù)器本身就實現(xiàn)了監(jiān)察watch
文件改動的能力脯厨,再開啟HMR選項铅祸,就添加了watch模塊變化的能力。這是HMR機制能生效的基礎(chǔ)。
從webpack編譯器角度
每次修改一個模塊的時候临梗,webpack會生成兩部分涡扼,一個是manifest.json
,另一部分是關(guān)于這次模塊更新編譯完成的chunks盟庞。manifest.json中存著的是chunk更改前后的hash值吃沪。
從編譯器webpack的角度來講提供了hmr的原材料。供后續(xù)使用什猖。
從模塊的角度
模塊發(fā)生變化時票彪,webpack會生成之前講過的兩部分基礎(chǔ)文件,但是何時將變化后的模塊應(yīng)用到app中去不狮?這里就需要在應(yīng)用代碼中編寫handler去接受到模塊變化信息降铸。但是不能在所有模塊中編寫handler吧?這里就用到了消息冒泡機制摇零。
如圖A.js推掸、C.js沒有相關(guān)hmr代碼,B.js有相關(guān)hmr代碼驻仅,如果c模塊發(fā)生了變化谅畅,c模塊沒有hmr,那么就會冒泡到a噪服、b模塊毡泻。b模塊捕捉到了消息,hmr運行時會相應(yīng)的執(zhí)行一些操作粘优,而a.js捕捉不到信息牙捉,會冒泡到entry.js,而一旦有消息冒泡的入口塊敬飒,這就代表本次hmr失敗了邪铲,hmr會降級進行整個頁面的reload。
從HMR運行時的角度
HMR運行時是一些相關(guān)的操作api无拗,運行時支持兩個方法: check
带到、apply
。
check
發(fā)起 HTTP 請求去獲取更新的 manifest英染,以及一些更新過后的chunk揽惹。
環(huán)境變量的設(shè)置
var env = {
'process.env.NODE_ENV': '"production"'
}
new webpack.DefinePlugin(env)
注意這里單引號間多了個雙引號 why?
以及webpack.DefinePlugin插件的原理四康?
開發(fā)的時候會想寫很多只在開發(fā)環(huán)境出現(xiàn)的代碼搪搏,比如接口mock等,在build命令后這些代碼不會存在闪金。
這對框架或者插件疯溺、組件的開發(fā)是很有幫助的论颅。vue,react等都會這么做囱嫩∈逊瑁可以在這些框架的dev模式提供很多有用的提示信息。
打包文件分割
為何要進行打包文件分割墨闲?
對于一個單頁應(yīng)用項目來說今妄,有分為業(yè)務(wù)代碼和第三方代碼,業(yè)務(wù)代碼會頻繁改動鸳碧,而第三方代碼一般來講變動的次數(shù)較少盾鳞,如果每次修改業(yè)務(wù)代碼都需要用戶將整個js文件都重新下載一遍,對于加載性能來講是不可取的瞻离,所以一般而言我們會將代碼分為業(yè)務(wù)代碼和第三方代碼分別進行打包腾仅,雖然多了一個請求的文件,增加了一些網(wǎng)絡(luò)開銷琐脏,但是相比于瀏覽器能將文件進行緩存而言,這些開銷是微不足道的缸兔。
我們在entry中定義了app
入口日裙,相應(yīng)的業(yè)務(wù)邏輯都封裝在這個入口文件里,如果我們想要第三方代碼獨立出來惰蜜,就要再增加一個入口昂拂,我們習(xí)慣使用vendor
這個命名。
// app.js
require('vue');
require('vuex');
// webpack.config.js
entry: {
app: 'app/app.js',
vendor: ['vue', 'vuex'],
},
vendor入口的傳參是以一個數(shù)組的形式傳遞的抛猖,這是一種非常方便的注入多個依賴的方式格侯,并且能把多個依賴一起打包到一個chunk中。而且不用手動的創(chuàng)建真實存在的入口文件财著。
這相當(dāng)于:
// vendor.js
require('vue');
require('vuex');
// app.js
require('vue');
require('vuex');
// webpack.config.js
entry: {
app: 'app/app.js',
vendor: 'app/vendor.js',
},
但是這樣做只是聲明了一個vendor
入口而已联四,對于app這個入口來說,打包完成的文件還是會有vue和vuex依賴撑教,而新增的入口vendor
打包完成的文件也有了vue和vuex兩個依賴朝墩。模塊依賴關(guān)系如下圖所示。
這里的A可以代表vue
依賴伟姐,最后生成的打包文件是兩個平行關(guān)系的文件收苏,且都包含vue的依賴。
此時需要引入CommonsChunkPlugin
插件
This is a pretty complex plugin. It fundamentally allows us to extract all the common modules from different bundles and add them to the common bundle. If a common bundle does not exist, then it creates a new one.
這是個相當(dāng)復(fù)雜的插件愤兵,他的基礎(chǔ)功能是允許我們從不同的打包文件中抽離出相同的模塊鹿霸,然后將這些模塊加到公共打包文件中。如果公共打包文件不存在秆乳,則新增一個懦鼠。同時這個插件也會將運行時(runtime)轉(zhuǎn)移到公共chunk打包文件中。
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor', 'manifest']
})
]
這里的name可以選擇已經(jīng)存在的塊芥永,這里就選擇了vendor塊芥颈,因為我們本來就是將vendor塊當(dāng)做管理第三方代碼的入口的。
而names傳入一個數(shù)組止状,數(shù)組里包含兩個trunk name淑趾,表示CommonsChunkPlugin
插件會執(zhí)行兩次這個方法阳仔,第一次將公共的第三方代碼抽離移到vendor的塊中,這個過程之前也講過會將運行時runtime也轉(zhuǎn)移到vendor塊中扣泊,第二次執(zhí)行則是將運行時runtime抽離出來轉(zhuǎn)移到manifest塊中近范。這步操作解決了緩存問題。
這樣處理延蟹,最后會生成3個打包文件chunk评矩,app.js是業(yè)務(wù)代碼,vendor則是公共的第三方代碼阱飘,manifest.js則是運行時斥杜。
chunk type 塊的類型大揭秘
webpack1.0官網(wǎng)介紹中的chunk類型讀起來及其拗口chunk type, 所以我這里解讀一下沥匈。
chunk
是webpack中最基本的概念之一蔗喂,且chunk
常常會和entry
弄混淆。在「打包文件分割部分」我們定義了兩個入口entry point -- app和vendor高帖,而通過一些配置缰儿,webpack會生成最后的一些打包文件,在這個例子中最后生成的文件有app.js 散址、 vendor.js 乖阵、 manifest.js
。這些文件便被稱為塊chunk
预麸。
entry & chunk 可以簡單的理解為一個入口瞪浸、一個出口
在官方1.0文檔中webpack的chunk類型分為三種:
- entry chunk 入口塊
- normal chunk 普通塊
- initial chunk 初始塊
entry chunk 入口塊
entry chunk 入口塊
不能由字面意思理解為由入口文件編譯得到的文件,由官網(wǎng)介紹
An entry chunk contains the runtime plus a bunch of modules
可以理解為包含runtime運行時的塊可以稱為entry chunk吏祸,一旦原本存在運行時(runtime)的entry chunk
失去了運行時默终,這個塊就會轉(zhuǎn)而變成initial chunk
。
normal chunk 普通塊
A normal chunk contains no runtime. It only contains a bunch of modules.
普通塊不包含運行時runtime犁罩,只包含一系列模塊齐蔽。但是在應(yīng)用運行時,普通塊可以動態(tài)的進行加載床估。通常會以jsonp的包裝方式進行加載含滴。而code splitting
主要使用的就是普通塊。
initial chunk 初始塊
An initial chunk is a normal chunk.
官方對initial chunk的定義非常簡單丐巫,初始塊就是普通塊谈况,跟普通塊相同的是同樣不包含運行時runtime勺美,不同的是初始塊是計算在初始加載過程時間內(nèi)的。在介紹入口塊entry chunk的時候也介紹過碑韵,一旦入口塊失去了運行時赡茸,就會變成初始塊。這個轉(zhuǎn)變經(jīng)常由CommonsChunkPlugin
插件實現(xiàn)祝闻。
例子解釋
還是拿「打包文件分割」的代碼做例子占卧,
// app.js
require('vue');
require('vuex');
// webpack.config.js
entry: {
app: 'app/app.js',
vendor: ['vue', 'vuex'],
},
沒有使用CommonsChunkPlugin
插件之前,兩個entry分別被打包成兩個chunk联喘,而這兩個chunk每個都包含了運行時华蜒,此時被稱為entry chunk
入口塊。
而一旦使用了CommonsChunkPlugin
插件豁遭,運行時runtime最終被轉(zhuǎn)移到了manifest.js
文件叭喜,此時最終打包生成的三個chunkapp.js 、 vendor.js 蓖谢、 manifest.js
捂蕴,app.js、vendor.js失去了runtime就由入口塊變成初始塊闪幽。
code splitting
前文有講到將依賴分割開來有助于瀏覽器緩存啥辨,提高用戶加載速度,但是當(dāng)業(yè)務(wù)復(fù)雜度增加沟使,代碼量大始終是一個問題委可。這時候就需要normal chunk
普通塊的動態(tài)加載能力了渊跋。
It allows you to split your code into various bundles which you can then load on demand — like when a user navigates to a matching route, or on an event from the user.
code splitting 允許我們將代碼分割到可以按需加載的不同的打包文件中腊嗡,當(dāng)用戶導(dǎo)航到對應(yīng)的路由上時,或者是用戶觸發(fā)一個事件時拾酝,異步加載相應(yīng)的代碼燕少。
我們需要在業(yè)務(wù)邏輯中手動添加一些分割點,標(biāo)明此處事件邏輯之后進行代碼塊的異步加載蒿囤。
// test
window.addEventListener('click', function () {
require.ensure(['vue', 'vuex'], function (require) {
})
})
這段代碼表明當(dāng)用戶點擊時客们,異步請求一個js文件,這個文件中包含該有vue vuex
的依賴材诽。
打包后會根據(jù)手動分割點的信息生成一個打包文件底挫,就是圖中第一行0
開頭的文件。這個文件也就是異步加載的文件脸侥。
下面是之前的一個vue項目建邓,采用code splitting
將幾個路由抽離出來異步加載之后,文件由212kb減少到了137kb睁枕,同樣樣式文件也由58kb減少到了7kb官边。對于首屏渲染來說沸手,性能是會增加不少的。