webpack 升級(jí) 3.x
根據(jù)官方的 release 聲明,webpack 3.x 的一個(gè)顯著特性是 Scope Hoisting
豁陆。之前 webpack 構(gòu)建時(shí)會(huì)把每個(gè)模塊單獨(dú)的放到一個(gè)閉包函數(shù)中像啼,這會(huì)增加 javascript 的執(zhí)行時(shí)間。 3.x 之后埠巨,webpack 則會(huì)將有聯(lián)系的模塊义辕,放到一個(gè)大的閉包函數(shù)里面去虾标,這樣可以減少構(gòu)建出來的文件大小和 javascript 的執(zhí)行時(shí)間。要讓 Scope Hoisting 起作用灌砖,只需要添加下面的插件:
module.exports = {
plugins: [
new webpack.optimize.ModuleConcatenationPlugin()
]
};
這條優(yōu)化主要是針對(duì) javascript 執(zhí)行效率的璧函,自己在項(xiàng)目中使用時(shí),對(duì)于構(gòu)建的時(shí)間并沒有顯著提升基显。
減小文件搜索范圍
這一點(diǎn)的優(yōu)化可以讓 webpack 不必自己遍歷去搜索模塊蘸吓,而可以通過我們定義的路徑,快速定位模塊撩幽。配置代碼如下:
resolve: {
//...
// modules選項(xiàng)配置库继,指定node_modules文件夾所在的位置
//節(jié)省webpack去查找的時(shí)間
modules: [resolve('node_modules')],
alias: {
// vue-cli自帶生成,'vue$' 表示嚴(yán)格匹配 'vue' 這個(gè)字段
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'), // vue-cli自帶生成
'api': resolve('src/api')
//... 其他配置
}
}
resolve.modules
是用來配置模塊庫(通常是 node_modules)所在的位置窜醉。默認(rèn)的配置會(huì)采用向上遞歸搜索的方式去尋找宪萄,為了減少搜索范圍,可以直接寫明 node_modules 的全路徑榨惰。
而alias是指配置別名拜英。通過編寫alias,既能讓webpack查找文件定位更快琅催,在開發(fā)的時(shí)候居凶,也能少些很多相對(duì)路徑的../..
虫给,在引入模塊的時(shí)候也會(huì)很方便。
運(yùn)行時(shí)構(gòu)建
Vue官網(wǎng)的安裝指南里面有這么一段話
當(dāng)使用 vue-loader 或 vueify 的時(shí)候侠碧,*.vue 文件內(nèi)部的模板會(huì)在構(gòu)建時(shí)預(yù)編譯成 JavaScript抹估。你在最終打好的包里實(shí)際上是不需要編譯器的,因?yàn)橹皇怯眠\(yùn)行時(shí)構(gòu)建即可弄兜。
因?yàn)檫\(yùn)行時(shí)構(gòu)建相比完整版縮減了 30% 的體積药蜻,你應(yīng)該盡可能使用這個(gè)版本。
但是在 vue-cli 構(gòu)建出來的項(xiàng)目配置中挨队,我們發(fā)現(xiàn)它還是使用了完整版構(gòu)建谷暮。那把它改正運(yùn)行時(shí)構(gòu)建會(huì)怎么樣呢?
resolve: {
//...
alias: {
/* vue-cli自帶生成盛垦,'vue$' 表示嚴(yán)格匹配 'vue' 這個(gè)字段 */
// 'vue$': 'vue/dist/vue.esm.js', /* 自帶生成完整版 */
'vue$': 'vue/dist/vue.runtime.esm.js', /* 改為運(yùn)行時(shí) */
//...
}
}
npm run dev 之后會(huì)發(fā)現(xiàn)報(bào)了這么一個(gè)錯(cuò)
我們明明使用了 vue-loader湿弦,按照官網(wǎng)的描述,為什么還會(huì)提示需要 vue 包含編譯器的版本呢腾夯?原因在于我們實(shí)例化 vue 的時(shí)候颊埃,還是使用了需要編譯器的寫法,在入口文件 main.js 最下面蝶俱,找到實(shí)例化 vue 的代碼
/* vue-cli 自動(dòng)生成 */
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})
為了能使用運(yùn)行時(shí)構(gòu)建班利,我們需要把代碼改成不需要編譯器的形式
/* 修改之后 */
new Vue({
el: '#app',
router,
store,
// template: '<App/>', /* 如果 Vue 選項(xiàng)中包含渲染函數(shù),該模板將被忽略 */
// components: { App },
render: h => h(App), // 使用render方式引入組件
})
修改之后榨呆,運(yùn)行沒有問題罗标。通過這種修改方式,在項(xiàng)目里就可以用更小的 vue 文件進(jìn)行構(gòu)建了积蜻。
happypack
webpack 的構(gòu)建是單進(jìn)程的闯割。采用 happypack
可以改為多進(jìn)程構(gòu)建,從而提高構(gòu)建速度竿拆。但是我在項(xiàng)目中使用了之后宙拉,構(gòu)建速度并沒有網(wǎng)上描述的提升那么明顯。
首先安裝:npm install happypack --save-dev
然后修改 webpack 配置文件:
const os = require('os');
const HappyPack = require('happypack');
const happThreadPool = HappyPack.ThreadPool({size: os.cpus().length}); // 采用多進(jìn)程丙笋,進(jìn)程數(shù)由CPU核數(shù)決定
//...
module.exports = {
plugins: [
// ...
new HappyPack({
id: 'js',
cache: true,
loaders: ['babel-loader?cacheDirectory=true'], // ?cacheDirectory=true 開啟babel-loader的緩存
threadPool: happThreadPool
})
],
module: {
rules: [
{
// vue-loader 似乎有兼容性問題谢澈,故沒有使用
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig ,
},
{
test: /\.js$/,
loader: ['happypack/loader?id=js'], // 將loader換成happypack
include: [resolve('src')],
},
]
}
}
DllPlugin和DllReferencePlugin
在日常工作中,每當(dāng)修改了業(yè)務(wù)代碼之后御板,一些第三方的锥忿,體積比較大的模塊也會(huì)被重新打包,極大的浪費(fèi)了時(shí)間怠肋。這時(shí)我們就可以使用 DLL
預(yù)先把一些第三方模塊提前打包缎谷,以后修改業(yè)務(wù)代碼再構(gòu)建時(shí)就不會(huì)構(gòu)建這些模塊了。所以,DLL 的作用列林,簡(jiǎn)單的說是把之前一步完成的構(gòu)建分成了兩步:
- 將一些第三方的模塊抽離出來(比如 vue、echarts酪惭、axios等)希痴,預(yù)先構(gòu)建一遍
- 構(gòu)建其他模塊和業(yè)務(wù)代碼
因?yàn)榈谌降哪K變動(dòng)并不會(huì)頻繁,在第一步構(gòu)建之后的 bundle 就可以長(zhǎng)期的放在項(xiàng)目里春感,后續(xù)的構(gòu)建時(shí)只需要進(jìn)行第二步砌创,這樣會(huì)節(jié)省大量的構(gòu)建時(shí)間。
在 build
文件夾里建立一個(gè) webpack.dll.conf.js
鲫懒,我們將一些常用的依賴打包成dll嫩实。
var path = require("path");
var webpack = require("webpack");
module.exports = {
// dll 包含模塊的名字
entry: {
vendor: ['vue/dist/vue.runtime.esm.js', 'lodash', 'vuex', 'axios', 'vue-router', 'element-ui']
},
output: {
path: path.join(__dirname, './static/js'),
filename: '[name].dll.js',
// 給DllPlugin中的name使用,需要和 webpack.DllPlugin 中的 name 字段保持一致
library: '[name]_library'
},
plugins: [
new webpack.DllPlugin({
path: path.join(__dirname, '.', '[name]-manifest.json'),
// 和上面的 library 保持一致
name: '[name]_library',
context: __dirname
}),
]
};
在 package.json 中加入:
"scripts": {
"dll": "webpack --config ./build/webpack.dll.config.js"
},
運(yùn)行 npm run dll
之后窥岩,會(huì)在 static/js
文件夾下生成 vender.dll.js
甲献,會(huì)在 build
文件夾下生成 vender-manifest.json
。這個(gè) vender-manifest.json
會(huì)被 webpack.base.config.js
中加入的新插件 DllReferencePlugin
使用颂翼,以便于業(yè)務(wù)代碼能正確地訪問到構(gòu)建好的第三方模塊的晃洒。
在 webpack.base.config.js
中,添加 DllReferencePlugin
:
module.exports = {
//... 其他配置
plugins: [
// ... 其他插件
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./vendor-mainfest.json') // 指向之前生成的 vendor-mainfest.json
})
]
}
最后朦乏,在項(xiàng)目輸出的index.html里球及,最先引入這個(gè)js:
<body>
<div id="app"></div>
<script src="./static/js/vendor.dll.js"></script>
<script src="/dist/build.js"></script>
</body>
這樣只要第三方模塊不變,我們之后的上線構(gòu)建都不用運(yùn)行 npm run dll
了呻疹。只構(gòu)建業(yè)務(wù)代碼和一些體積比較小的包吃引,構(gòu)建速度是非常快的刽锤。
devtools
// vue-cli 默認(rèn)配置
devtool: config.build.productionSourceMap ? '#source-map' : false,
devtools 決定了 source-map 的生成方式镊尺。source-map的作用這里不深入展開,有興趣可以參考阮一峰老師的?這篇文章姑蓝。下圖是 webpack 官網(wǎng)給出的 devtools 幾種配置選項(xiàng):
vue-cli 在生產(chǎn)環(huán)境默認(rèn)采用的模式是 source-map鹅心,可以發(fā)現(xiàn),這種模式不管對(duì)于 build 還是 rebuild纺荧,都會(huì)?增加時(shí)間旭愧。可以根據(jù)項(xiàng)目需求更改配置宙暇。我在生產(chǎn)環(huán)境使用的是eval模式输枯,因?yàn)槲矣X得生產(chǎn)環(huán)境為了減少請(qǐng)求和混淆代碼可以不用加上source-map,畢竟很少在生產(chǎn)環(huán)境進(jìn)行debug占贫。開發(fā)環(huán)境我采用 cheap-module-eval-source-map
模式桃熄。這樣針對(duì)生產(chǎn)環(huán)境再進(jìn)行構(gòu)建,會(huì)發(fā)現(xiàn)時(shí)間也是大幅度減少了型奥。