本篇講的是 Webpack 對於優化打包構建速度,也就是對於開發體驗和效率的優化。
有如下幾處可以優化:
優化 babel-loader
IgnorePlugin 避免引入無用模塊
noParse 避免重複打包
happyPack //多進程打包工具
ParalleUglifyPlugin //多進程打包壓縮
自動刷新
熱更新
DLLPlugin
1.優化 babel-loader
在babel-loader後加cacheDirectory,開啓緩存,
沒有改動的es6代碼會啓用緩存,不會重新編譯
[webpack.dev.js]:
module: {
rules: [{
test: /\.js$/,
loader: ['babel-loader?cacheDirectory'],
include: srcPath,
exclude: /node_modules/
},
]
},
2.IgnorePlugin 避免引入無用模塊
使用場景:
日期處理類庫moment中,包含很多種語言,默認引入所有語言的JS代碼,代碼體積過大;只需要使用中文,那就需要配置忽略對整個類庫的引入,手動按需引入中文。
當全部引入配置
[index.js]配置
import moment from 'moment' //全部引入
moment.locale('zh-cn') //設置語言爲中文
當按需引入配置
① 忽略 moment 引用的所有語言包,忽略 moment 下的 /locale 目錄
[webpack.prod.js]配置:
plugins: [
// 忽略 moment 下的 /locale 目錄
new webpack.IgnorePlugin(/\.\/locale/, /moment/),
]
② 手動動態引入中文語言
[index.js]:
import moment from 'moment' // moment的引入被忽略
import 'moment/locale/zh-cn' //手動引入中文語言包
綜上,IgnorePlugin 避免引入無用模塊,減少了打包體積,提升了打包速度。
3.noParse 避免重複打包
像vue.min.js已經模塊化處理過了,需要忽略對此類型文件的打包。
webpack.prod.js:
rules: [{
test: /\.vue$/,
loader: ['vue-loader'],
include: srcPath
}, ]
IgnorePlugin vs noParse
- IgnorePlugin 直接不引入,代碼中沒有,減少了產出代碼體積;
- noParse 引入了,但是未打包;
4.happyPack 多進程打包
js是單線程,開啓多線程打包,提高構建速度(特別是多核CPU)。
webpack.prod.js:
① 安裝 HappyPack,並引入:
const HappyPack = require('happypack')
② plugins中引入HappyPack規則
new HappyPack({
// 用唯一的標識符 id 來代表當前的 HappyPack 是用來處理一類特定的文件
id: 'babel',
// 如何處理 .js 文件,用法和 Loader 配置中一樣
loaders: ['babel-loader?cacheDirectory']
}),
③ rules 中 配置js 轉換規則爲happypack:
rules: [{
test: /\.js$/,
// 把對 .js 文件的處理轉交給 id 爲 babel 的 HappyPack 實例
use: ['happypack/loader?id=babel'],
include: srcPath,
// exclude: /node_modules/
}, ]
5.ParalleUglifyPlugin 多進程壓縮JS
JS單線程, 開啓多進程壓縮更快,webpack 內置 Uglify工具壓縮JS
webpack.prod.js:
① 安裝webpack-parallel-uglify-plugin 並引入配置文件中
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
② plugins中引入ParallelUglifyPlugin規則
new ParallelUglifyPlugin({
// 傳遞給 UglifyJS 的參數
// (還是使用 UglifyJS 壓縮,只不過幫助開啓了多進程)
uglifyJS: {
output: {
beautify: false, // 最緊湊的輸出
comments: false, // 刪除所有的註釋
},
compress: {
// 刪除所有的 `console` 語句,可以兼容ie瀏覽器
drop_console: true,
// 內嵌定義了但是隻用到一次的變量
collapse_vars: true,
// 提取出出現多次但是沒有定義成變量去引用的靜態值
reduce_vars: true,
}
}
})
happyPack 放在生產環境下,也可以放在本地環境下;
但是 ParallelUglifyPlugin 的壓縮就只能放在生產環境下,開發環境下沒有必要去做壓縮
項目較大, 打包較慢, 開啓多進程能提高速度
項目較小, 打包很快, 開啓多進程會降低速度( 進程開銷)
6.自動刷新
自動刷新用於開發環境
webpack.dev.js:
module.export = {
watch: true, // 開啓監聽,默認爲 false
watchOptions: {
ignored: /node_modules/, // 忽略哪些
// 監聽到變化發生後會等300ms再去執行動作,防止文件更新太快導致重新編譯頻率太高
// 默認爲 300ms
aggregateTimeout: 300,
// 判斷文件是否發生變化是通過不停的去詢問系統指定文件有沒有變化實現的
// 默認每隔1000毫秒詢問一次
poll: 1000
}
}
若開發環境開啓devserver,自動刷新便默認不開啓
7.熱更新
webpack.dev.js:
① 引用webpack的插件HotModuleReplacementPlugin
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
② 配置入口
entry: {
// index: path.join(srcPath, 'index.js'),
index: [
'webpack-dev-server/client?http://localhost:8080/',
'webpack/hot/dev-server',
path.join(srcPath, 'index.js')
],
other: path.join(srcPath, 'other.js')
},
③ 在 plugins中 new HotModuleReplacementPlugin()
④ 配置 devServer中 的 hot: true
devServer: {
port: 8080,
progress: true, // 顯示打包的進度條
contentBase: distPath, // 根目錄
open: true, // 自動打開瀏覽器
compress: true, // 啓動 gzip 壓縮
hot: true,
// 設置代理
proxy: {
// 將本地 /api/xxx 代理到 localhost:3000/api/xxx
'/api': 'http://localhost:3000',
// 將本地 /api2/xxx 代理到 localhost:3000/xxx
'/api2': {
target: 'http://localhost:3000',
pathRewrite: {
'/api2': ''
}
}
}
},
⑤ 開發環境中註冊哪些模塊能觸發熱更新
index.js:
// 增加,開啓熱更新之後的代碼邏輯
if (module.hot) {
module.hot.accept(['./math'], () => {
const sumRes = sum(10, 30)
console.log('sumRes in hot', sumRes)
})
}
自動刷新 VS 熱更新
自動刷新:整個網頁全部刷新,速度較慢;狀態會丟失
熱更新:新代碼生效,網頁不刷新,狀態不丟失
8.DLLPlugin 動態鏈接庫插件
背景:
前端框架如 vue React ,體積大,構建慢
較穩定,不常升級版本
同一個版本之構建一次就可以,不用每次都重新構建
DLLPlugin 將常用框架打包成dll文件,然後再引用
webpack 已內置DLLPlugin支持:
- DLLPlugin 打包出 dll 文件
- DllReferencePlugin -使用 dll 文件
Stage1. 打包生成 dll 文件
① 單獨配置 webpack.dll.js,用於生成dll文件(DllPlugin插件)
const path = require('path')
const DllPlugin = require('webpack/lib/DllPlugin')
const { srcPath, distPath } = require('./paths')
module.exports = {
mode: 'development',
// JS 執行入口文件
entry: {
// 把 React 相關模塊的放到一個單獨的動態鏈接庫
react: ['react', 'react-dom']
},
output: {
// 輸出的動態鏈接庫的文件名稱,[name] 代表當前動態鏈接庫的名稱,
// 也就是 entry 中配置的 react 和 polyfill
filename: '[name].dll.js',
// 輸出的文件都放到 dist 目錄下
path: distPath,
// 存放動態鏈接庫的全局變量名稱,例如對應 react 來說就是 _dll_react
// 之所以在前面加上 _dll_ 是爲了防止全局變量衝突
library: '_dll_[name]',
},
plugins: [
// 接入 DllPlugin
new DllPlugin({
// 動態鏈接庫的全局變量名稱,需要和 output.library 中保持一致
// 該字段的值也就是輸出的 manifest.json 文件 中 name 字段的值
// 例如 react.manifest.json 中就有 "name": "_dll_react"
name: '_dll_[name]',
// 描述動態鏈接庫的 manifest.json 文件輸出時的文件名稱
path: path.join(distPath, '[name].manifest.json'),
}),
],
}
② 在package.json裏配置dll,並運行:npm run dll
package.json:
script :{
"dll":"webpack --config build/webpack.dll.js"
}
產出文件:
react.dll.js文件、//react打包後的dll文件
react.manifast.json.js文件//索引 找到對應關係
Stage2. 使用 dll 文件
webpack.dev.js配置引入:
① 引入插件 DllReferencePlugin
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
② 忽略 /node_modules/ 代碼的loader
module: {
rules: [
{
test: /\.js$/,
loader: ['babel-loader'],
include: srcPath,
exclude: /node_modules/ // 第二,不要再轉換 node_modules 的代碼
},
]
},
③ plugin配置 DllReferencePlugin 插件,告訴 Webpack 使用了哪些動態鏈接庫
plugins: [
// 第三,告訴 Webpack 使用了哪些動態鏈接庫
new DllReferencePlugin({
// 描述 react 動態鏈接庫的文件內容
manifest: require(path.join(distPath, 'react.manifest.json')),
}),
],
④ index.html 裏引用 react.dll.js:
<script src="./react.dll.js"></script>
webpack優化構建速度總結:
可用於生產環境:
- 優化 babel-loader 緩存(主要用於開發)
- IgnorePlugin // 避免引入無用模塊
- noParse // 避免重複打包
- happyPack // 多進程打包
- ParalleUglifyPlugin(主要用於生產環境) // 多進程壓縮JS
不能用於生產環境:
- 自動刷新
- 熱更新
- DLLPlugin(開發)