構建優化
1、 noParse(無需解析內部依賴的包)
對於我們引入的一些第三方包,比如jQuery
,在這些包內部是肯定不會依賴別的包,所以根本不需要webpack去解析它內部的依賴關係,可以在webpack
配置檔案中的module
屬性下加上noParse
屬性,它的值是一個正規表示式,用來匹配無需解析的模組,這樣可以節約webpack
的打包時間,提高打包效率。
module:{
noParse:/jquery/
}
複製程式碼
需要注意的是必須確保新增的包中沒有依賴別的包,否則會報依賴錯誤。
2、DllPlugin(動態連結庫)
專案中依賴的一些第三方包比如react
、vue
一般情況下包的內容不會發生改變,而每一次打包都要對它們進行構建顯然是不合理的,這會很浪費效能。正確的做法應該是將這些第三方包只打包一次,之後直接引用就可以,直到第三方包需要更新版本時再重新進行構建。這樣我們在打包的時候只需要構建我們的業務程式碼即可。
Dllplugin
外掛可以幫助我們把這些不做修改的包抽取為動態連結庫,並且會生成一個名為manifest.json的檔案,這個檔案是用來讓DLLReferencePlugin
對映到相關的依賴上去的。
以React
為例抽取Dll
- 首先建立一個將
React
抽取為Dll的webpack
配置檔案webpack.react.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
mode: 'development',
// 入口檔案:將要配置成Dll的庫放進來
entry: {
react: [
'react'
]
},
// 輸出檔案
output: {
// 輸出檔名
filename: '[name]_dll.js',
// 輸出檔案路徑
path: path.resolve(__dirname, '../dist'),
// 將打包好的結果暴露在全域性
library: '[name]_dll'
},
plugins: [
new webpack.DllPlugin({
// dll檔名
name: '[name]_dll',
// 清單檔案路徑
path: path.resolve(__dirname, '../dist/mainfest.json')
})
]
}
複製程式碼
- 配置
DllReferencePlugin
這個外掛一般是在webpack
的主配置檔案中配置,在plugins
配置下新增如下配置,manifest
檔案會將對dll檔案的應用對映到模組的id
上,然後在需要的時候來引用Dll
檔案。
new webpack.DllReferencePlugin({
// (絕對路徑) manifest (或者是內容屬性)中請求的上下文
context: __dirname,
// 清單檔案路徑
manifest: path.resolve(__dirname, '../dist/manifest.json')
})
複製程式碼
- 構建
然後執行
webpack.react.js
配置檔案,一般情況下可以在package.json中新增執行指令碼命令
scripts:{
"build:react": "webpack --config ./build/webpack.react.js"
}
複製程式碼
在命令列中執行該命令,這會在dist
目錄下生成react
的Dll
檔案,還會生成一個mainfest.json
檔案,這個檔案包含了import
和require
的request
到模組id
的對映。DLLReferencePlugin
也會引用這個檔案。然後再進行打包,就不會對react
重複打包,而是直接引用生成的react_dll
檔案。
程式碼優化
程式碼優化的最終目的是為了使用者體驗的優化,各種壓縮、拆分操作都應該圍繞最終目標展開。
1、webpack自帶優化
把webpack
的mode
設定為production
,進行生產模式打包的時候webapck
會自動進行以下優化
- tree shaking
webpack
會在打包時移除引入了但是未引用的程式碼,但是隻有通過ES6模組系統中import
語法引入的才會適用tree shaking
,減小生產環境下檔案的體積,自動實現最基本的優化
- scope hoisting
分析模組之間的依賴關係,會盡可能的將打散的模組合併到一個函式中去。需要注意的是這種優化方式也是隻有通過ES6
模組系統中import
語法引入的模組才適用,並且只有那些只被引用的一次的模組才有可能被合併。
- 程式碼壓縮
所有程式碼自動使用UglifyJsPlugin
進行壓縮。
2、CSS優化
- 將CSS拆分為獨立檔案
mini-css-extract-plugin
可以將CSS拆分為單獨的檔案,使CSS檔案可以被非同步載入,加快頁面載入呈現的速度。
使用方法:
- 安裝外掛
$ npm i mini-css-extract-plugin -D
複製程式碼
- 修改主配置檔案,使用該外掛
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
...,
module: {
rules: [
...,
{
test: /\.css$/,
// 將原來多有使用style-loader來處理的地方替換為使用MiniCssExtractPlugin.loader
// use: ['style-loader', 'css-loader'],
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
},
plugins: [
...,
new MiniCssExtractPlugin({
// 生成的檔名
filename: '[name].css'
})
]
}
複製程式碼
- 壓縮CSS
optimize-css-assets-webpack-plugin
外掛可以用來壓縮CSS檔案,但使用該外掛會導致webpack預設的js壓縮配置無法生效,所以還需要手動對js程式碼進行壓縮,js壓縮使用官方推薦的terser-webpack-plugin
使用方法:
- 安裝外掛
$ npm i optimize-css-assets-webpack-plugin terser-webpack-plugin -D
複製程式碼
- 修改主配置檔案,使用該外掛
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
...,
optimization: {
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
}
}
複製程式碼
3、 JS優化
- 拆分公共程式碼
使用方法:只需在主配置檔案中新增如下配置即可
Optimization: {
splitChunks: {
Chunks: 'all'
}
}
複製程式碼
- IgnorePlugin(忽略外掛)
有很多的第三方包內部會做國際化處理,包含很多的語言包,而這些語言包對我們來說時沒有多大用處的,只會增大包的體積,我們完全可以忽略掉這些語言包,從而提高構建效率,減小包的體積。
以moment為例,首先找到moment中語言包所在的資料夾,然後在webpack配置檔案中新增外掛
// 該方法的兩個引數都是正則,第一個參數列示要忽略的路徑,第二個表示該資源所在目錄,在該資料夾下引入的語言包都會被忽略
new webpack.IgnorePlugin(/\.\/locale/, /moment/)
複製程式碼
這時候moment使用預設語言英語,如果要使用別的語言,可以手動引入需要使用的語言包。
import moment from 'moment'
import 'moment/locale/zh-cn'
moment.locale('zh-CN')
複製程式碼
- 懶載入
在頁面中有一些程式碼塊可能並不是在頁面初始化的時候就需要使用的,需要使用者的某些操作出發才會使用,如果使用者不觸發這個操作那這個程式碼塊可能永遠不會被使用。我們可以將這些某個操作觸發才會使用的程式碼塊按需載入進來,而不是在頁面建立的時候直接載入,這樣可以加快初始頁面的載入速度。
// 假設在頁面中有一個id為btn的按鈕,當點選這個按鈕的時候需要對時間進行操作
const btn = document.querySelector('#btn')
btn.onclick = e => import(/* webpackChunkName: "moment" */ 'moment').then(module => {
var moment = module.default;
moment().format('YYYY-MM-DD')
})
複製程式碼
但是使用懶載入會影響使用者體驗,所以在懶載入的同時可以使用魔法註釋:Prefetching
,可以在首頁資源載入完畢後,空閒時間時,將動態匯入的資源載入進來,這樣即可以提高頁面載入速度又保證了使用者體驗。
const btn = document.querySelector('#btn')
btn.onclick = e => import(/* webpackChunkName: "moment" *//* webpackPrefetch: true */ 'moment').then(module => {
var moment = module.default;
moment().format('YYYY-MM-DD')
})
複製程式碼
保持更新版本
webpack每次版本更新都會在效能上有很大提升,官方也推薦保持版本的更新。