前言
公司目前現有的一款產品是使用vue v2.0
框架實現的,配套的打包工具為webpack v3.0
。整個專案大概有80
多個vue
檔案,也算不上什麼大型專案。
只不過每次頭疼的就是打包所耗費的時間平均在一分鐘
左右,而且打包後有幾個檔案顯示為【big】
,也就是檔案體積過大。
最近就想著搗鼓一下,看能不能在此前的基礎上做一些優化,順帶記錄下來分享給大家。
webpack打包優化
關於webpack
的打包優化一般會從兩個方面考慮:縮短打包時長
和降低打包後的檔案體積
,這兩個方面也剛好是前面我需要解決的問題。
所以我們先來了解一下這兩個方面各自有什麼具體的實現方式。
縮短打包時長
我們都知道webpack
的執行流程就像一條生產線一樣,在這條生產線上會按順序的執行每一個流程。那很顯然如果每一個流程要乾的事情越少或者每一個流程有多個人來共同完成,那webpack
打包的執行效率就會提高。
1.減少loader搜尋檔案範圍
我們可以通過配置loader
的exclude
選項,告訴對應的loader
可以忽略某個目錄
;或者通過配置loader
的include
選項,告訴loader
只需要處理指定的目錄
。因為loader
處理的檔案越少,執行速度就會更快。
一般比較常見的就是給babel-loader
配置exclude
選項。
// webpack.config.js
module.exports = {
entry: {},
output: {},
plugin: [],
module: {
rules:[
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/ // exclude的值是一個正則
}
]
}
}
以上配置即告訴babel-loader
在轉化JS
程式碼的時候忽略node_modules
目錄,這麼配置是因為我們引用node_modules
下的包基本都是編譯過的,所以不需要在通過babel-loader
處理。
2.利用快取
關於webpack
的快取,官方的大致解釋為:開啟快取以後,webpack
構建將嘗試從快取中讀取資料,以避免每次執行時都需要執行代價高昂的重新編譯過程。
那如何在編譯程式碼時開啟快取呢?
◕ cacheDirectory
第一種是配置babel-loader
的cacheDirectory
選項,針對babel-loader
開啟快取。
// webpack.config.js
module.exports = {
entry: {},
output: {},
plugin: [],
module: {
rules:[
{
test: /\.js$/,
loader: 'babel-loader?cacheDirectory',
exclude: /node_modules/
}
]
}
}
◕ cache-loader
第二種是利用cache-loader
。
首先需要對其進行安裝:npm install cache-loader --save-dev
;接著在webpack
中進行配置:
// webpack.config.js
module.exports = {
entry: {},
output: {},
plugin: [],
module: {
rules:[
{
test: /\.js$/,
loader: [
'cache-loader',
'babel-loader'
]
exclude: /node_modules/
},
{
test: /\.ext$/,
use: [
'cache-loader',
// 其他的loader
// ...
],
}
]
}
}
對於cache-loader
官方給出的使用建議為:在一些效能開銷較大的loader之前新增此loader,以將結果快取到磁碟裡;儲存和讀取這些快取檔案會有一些時間開銷,所以請只對效能開銷較大的loader使用此 loader
。
可以簡單粗暴的認為如果一個
loader
在執行過程中處理的任務較多,較為耗時,即判定此loader
效能開銷較大。我們就可以嘗試給該loader
開啟快取,當然如果開啟快取以後實際的打包時長
並沒有降低,則說明開啟快取對該loader
的效能影響不大。
更多有關
cache-loader
的內容可以檢視:https://www.webpackjs.com/loaders/cache-loader/
◕ hard-source-webpack-plugin
第三種是開啟快取的方式是使用hard-source-webpack-plugin
。它是一個webpack
外掛,安裝命令為:npm install --save-dev hard-source-webpack-plugin
;最基礎的配置如下:
// webpack.config.js
// 引入
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
// 只在生產環境下開啟HardSourceWebpackPlugin
if (process.env.NODE_ENV === "production") {
module.exports.plugins = (module.exports.plugins || []).concat([
new HardSourceWebpackPlugin()
])
}
更多有關
hard-source-webpack-plugin
的用法可以檢視:https://github.com/mzgoddard/hard-source-webpack-plugin
以上三種開啟快取
的方式雖然各不相同,但只要做了配置就可以在我們的磁碟中看到它們的快取結果。
3.多執行緒
多執行緒也就是將一件事情交給多個人去做,從理論上來講是可以提高一件事情的完成效率。
◕ happyhack
我們都知道受限於node
的單執行緒
模式,webpack
的整個執行
和構建
過程也是單執行緒
模式的。
所以第一種開啟多執行緒
的方式就是將webpack
中loader
的執行過程從單執行緒
擴充套件到多執行緒
。這種方式的具體實現依賴的是HappyPack
外掛。
使用happypack
的第一步依然是安裝:npm install --save-dev happypack
;最簡單的配置如下:
// webpack.config.js
// 引入
const HappyPack = require('happypack');
module.exports = {
entry: {},
output: {},
plugin: [],
module: {
rules:[
{
test: /\.js$/,
// 使用loader調起happypack
loader: 'happypack/loader',
exclude: /node_modules/
}
]
}
}
// 只有在生產環境下配置對應的happypack
if (process.env.NODE_ENV === "production") {
module.exports.plugins = (module.exports.plugins || []).concat([
new HappyPack({
// re-add the loaders you replaced above in #1:
loaders: 'babel-loader',
})
])
}
這樣的配置表示匹配到的.js
原始碼將被傳遞給HappyPack
,HappyPack
將使用loaders
指定的載入器(本例中是babel-loader
)並行地轉換它們。
這種最基礎的配置,預設是3
個執行緒並行處理。同時我們也可以通過配置thread
選項,自定義執行緒個數。
// webpack.config.js
new HappyPack({
// re-add the loaders you replaced above in #1:
loaders: 'babel-loader',
// 自定義執行緒個數
threads: 2,
})
關於執行緒
的設定,官方推薦使用共享執行緒池
的方式來控制執行緒個數
:However, if you're using more than one HappyPack plugin it can be more optimal to create a thread pool yourself and then configure the plugins to share that pool, minimizing the idle time of threads within it.(但是,如果您使用多個HappyPack外掛,那麼最好自己建立一個執行緒池,然後配置這些外掛來共享該池,從而最大限度地減少其中執行緒的空閒時間。)
執行緒池
的建立也很簡單:
// webpack.config.js
const happyThreadPool = HappyPack.ThreadPool({ size: 4 });
除了可以通過上面的這種方式建立具體的執行緒數,也可以根據CPU
的核數建立:
// webpack.config.js
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length-1 });
執行緒池
建立好了以後,通過threadPool
進行指定共享執行緒池
:
// webpack.config.js
// 此處省略一些程式碼
new HappyPack({
// re-add the loaders you replaced above in #1:
loaders: 'babel-loader',
// 使用共享執行緒池
threadPool: happyThreadPool
})
最後一個實用的配置項是verbose
選項,可以讓happypack
輸出執行日誌:
// webpack.config.js
// 此處省略一些程式碼
new HappyPack({
// re-add the loaders you replaced above in #1:
loaders: 'babel-loader',
// 使用共享執行緒池
threadPool: happyThreadPool,
// 輸出執行日誌
verbose: true
})
更多有關
HappyPack
的內容的可以檢視:https://github.com/amireh/happypack
不過這裡很遺憾的是該外掛的作者在github
上面宣佈他本人不會在對該專案進行更新維護了。
◕ thread-loader
thread-loader
和happypack
類似,也是通過擴充套件loader
的處理執行緒來降低打包時間。安裝命令:npm install thread-loader --save-dev
;最簡單的配置如下:
// webpack.config.js
module.exports = {
entry: {},
output: {},
plugin: [],
module: {
rules:[
{
test: /\.js$/,
loader: [
'thread-loader',
'babel-loader'
]
exclude: /node_modules/
}
]
}
}
即將thread-loader
配置到其他的loader
之前即可。
更多有關
thread-loader
的內容可以檢視:https://www.webpackjs.com/loaders/thread-loader
◕ webpack-parallel-uglify-plugin
一般我們為了減少打包後的檔案體積,會對檔案進行壓縮,比如刪除換行
、刪除中註釋
等。那常見的就是對JS
進行壓縮,最基本的就是使用webpack
官方提供的uglifyjs-webpack-plugin
外掛;不過該外掛是單執行緒壓縮程式碼,效率相對來說比較低。而webpack-parallel-uglify-plugin
就是一款多執行緒
壓縮js
程式碼的外掛;
安裝命令:npm install webpack-parallel-uglify-plugin
;簡單的配置如下:
// webpack.config.js
// 引入 ParallelUglifyPlugin 外掛
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
// 只在生產環境中配置ParallelUglifyPlugin
if (process.env.NODE_ENV === "production") {
new ParallelUglifyPlugin({
workerCount: 4,//開啟幾個子程式去併發的執行壓縮。預設是當前執行電腦的cPU核數減去1
cacheDir: './cache/',
uglifyJs:{
output:{
beautify:false,//不需要格式化
comments:false,//不保留註釋
},
compress:{
warnings:false,// 在Uglify]s除沒有用到的程式碼時不輸出警告
drop_console:true,//刪除所有的console語句,可以相容ie瀏覽器
collapse_vars:true,//內嵌定義了但是隻用到一次的變數
reduce_vars:true,//取出出現多次但是沒有定義成變數去引用的靜態值
}
}
}),
}
之後在進行打包,可以顯著提升JS
程式碼的壓縮效率。
這個外掛的使用一定要注意
版本
的問題,如果配置以後在構建程式碼時出現問題,可以嘗試更換低版本。
本次我的webpack
版本為v3.6
,直接安裝的webpack-parallel-uglify-plugin
版本為v2.0.0
。後面打包出現錯誤,於是將其版本降低為0.4.2
後就可以正常打包。
關於多執行緒
我們特別需要注意,並不是執行緒數量
越多構建時間就越短。因為子執行緒處理完成後需要將把結果傳送到主程式
中,主程式
在進行彙總處理,這個過程也是需要耗費時間的。所以最適合的執行緒數量可以嘗試通過實踐去獲得。
4.動態連結庫
一般我們在打包一個vue
專案時,會將vue
、vue-router
、axios
等這些外掛的程式碼跟我們的程式碼打包到一個檔案中,而這些外掛
的程式碼除非版本有變化,否則程式碼內容基本不會發生變化。所以每次在打包專案時,實際上都在重複打包這些外掛的程式碼,很顯然浪費了很多時間。
關於動態連結庫
這個詞實際上借用的是作業系統中的動態連結庫
概念,webpack
的具體實現也就是把前面我們描述的那些外掛
分別打包成一個獨立的檔案。當有模組需要引用該外掛時會通過生成的json
檔案連結到對應的外掛。這樣不管是我們在開發環境
還是在生成環境
下的打包構建,都不需要在對這些外掛做重複的處理。那接下來我們看看動態連結庫
的配置和使用。
首先我們需要新建一個webpack.dll.config.js
,該檔案本身是一個webpack
配置檔案,主要用於分離第三方外掛。
// webpack.dll.config.js
const path = require("path");
const webpack = require("webpack");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const DllPlugin = require('webpack/lib/DllPlugin');
// 分離出來的第三方庫檔案存放的目錄
const dllPath = "webpackDLL";
module.exports = {
// 入口檔案 入口處配置需要分離的第三方外掛
entry: {
echarts: ['echarts'], // 該配置表示分離echarts外掛
},
// 輸出檔案
output: {
path: path.join(__dirname, dllPath), // 分離出來的第三方外掛儲存位置
filename: "[name]-dll.[hash:8].js", // 分離出來的第三方外掛檔名稱
library: '_dll_[name]' // 第三方外掛的名稱,後續其他模組需要引用該外掛,便用該名稱進行引用
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
}
},
plugins: [
// 清除之前的dll檔案
new CleanWebpackPlugin(),
// 使用DLLPlugin進行分離
new webpack.DllPlugin({
// 生成的 *.manfest.json 檔案的路徑
path: path.join(__dirname, dllPath, "[name]-manifest.json"),
// 這裡的name需要和前面output.library配置一致
// 之後生成的*.manfest.json 中有一個name欄位,值就是我們這裡配置的值
name: '_dll_[name]',
})
]
};
關於上面各個配置項
的含義已在註釋中說明。接下來我們先用這個配置項做一個打包,看一下結果。在這之前我們需要在package.json
中新增一個script
指令碼。
// package.json
{
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --hot --port 4500 --host 192.168.1.10",
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules",
"dll": "webpack -p --progress --config ./webpack.dll.config.js"
}
}
新增的dll
指令碼會使用webpack.dll.config.js
作為配置檔案執行打包任務。
指令碼執行成功以後,本地已經生成了對應的目錄和檔案。
其中echarts.dll.2a6026f8.js
就是我們分離出來的echarts
外掛,echarts-manifest.json
就是前面我們說的json
檔案。
第三方庫檔案分離後,當有模組需要引用echarts
時,該如何引用到對應的echarts.dll.2a6026f8.js
檔案呢?
此時就需要DllReferencePlugin
出場了:通過配置DllReferencePlugin
的manifest
檔案來把依賴的模組名稱對映到對應的外掛。這一步需要在webpack.config.js
中進行配置:
// webpack.config.js
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
module.exports = {
entry: {},
output: {},
module: {},
plugin: [
new DllReferencePlugin({
// manifest 就是之前打包出來的 *.manifest.json 檔案
manifest: path.join(__dirname, 'webpackDll', 'echarts-manifest.json'),
}),
]
}
以上配置完成後,如果我們處於開發環境
,執行npm run dev
開啟瀏覽器
會發現頁面無法正常顯示,且控制檯有報錯資訊:
這裡是因為我們還需要在入口模板檔案index.html
中手動引入分離出來的外掛
檔案。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="app"></div>
<!-- 手動引入 -->
<script type="text/javascript" src="./webpackDLL/echarts-dll.2a6026f9.js"></script>
</body>
</html>
之後在重新整理頁面就沒有問題了。
這裡需要特別注意,開發環境
中手動引入對應外掛的路徑為./webpackDLL/*.2a6026f9.js
,此時如果對專案進行打包部署,打包後index.html
引用的依然是./webpackDLL/*.2a6026f9.js
,很顯然單是在本地環境中該資源的引用路徑就是錯誤的;更甚之專案打包後的輸出路徑
一般都會單獨配置,比如dist
目錄,部署時也只會部署該目錄下的檔案。
所以僅僅是前面的配置,專案部署以後根本無法正常執行。
解決這個問題很顯然有一個簡單粗暴的方式:index.html
中引入的路徑依然不變,打包後的程式碼依然在dist
目錄下,只是打包完成後手動將對應的webpackDLL
外掛目錄以及檔案複製到dist
目錄下,這樣直接將dist
目錄部署到伺服器即可正常執行。
除了這種方式之外,我們完全可以藉助webpack
的一些外掛來完成這個功能,這裡就不演示了,大家可以自己嘗試去完成。
降低打包後的檔案體積
1.壓縮檔案
◕ image-webpack-loader
關於圖片的壓縮可以選擇image-webpack-loader
。正常情況下安裝命令為:npm install image-webpack-loader --save-dev
,只不過我在使用該命令安裝時出現了很多錯誤,在網上收集到一些解決方案,最終發現將npm
換成cnpm
去安裝image-webpack-loader
才能安裝成功:cnpm install image-webpack-loader --save-dev
。
關於image-webpack-loader
的最基礎的配置如下:
// webpack.config.js
module.exports = {
entry: {},
output: {},
plugin: [],
module: {
rules:[
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
exclude: [resolve("src/icons")],
use: [
{
loader: "url-loader",
options: {
limit: 1024*10,
name: path.posix.join("assets", "images/[name].[hash:7].[ext]"),
},
},
{
loader: 'image-webpack-loader',// 壓縮圖片
options: {
bypassOnDebug: true,
}
}
]
}
]
}
}
更多有關
image-webpack-loader
的內容請檢視:https://www.npmjs.com/package/image-webpack-loader
cache-loader4.1.0
要求webpack4.0.0
cache-loader 3.0.1
要求3.0.1
◕ webpack-parallel-uglify-plugin
該外掛用於壓縮JS
程式碼(多執行緒壓縮),用法前面已經介紹過,這裡就不在介紹了。
通過壓縮檔案來減少檔案的體積的同時會導致
webpack
打包時長增加,因為這相當於在做一件事的過程中增加了一些步驟。
2. 抽離第三方庫
CommonsChunkPlugin
是webpack
官方提供的一個外掛,通過配置這個外掛,可以將公共的模組抽離出來。
webpack v4
已經不再支援該外掛,使用SplitChunksPlugin
代替。但由於本專案使用的是webpack v3
,因此這裡不對SplitChunksPlugin
做介紹。
首先我們需要在webpack
的entry
選項對我們需要分離的公共模組
進行配置。
module.exports = {
entry: {
main: ["./src/main.js"], //原有的入口檔案
vender: ['echarts'] // 表示將echarts模組分離出來
},
}
接著需要在plugin
中配置這個公共模組
的輸出:
module.exports = {
plugins:[
new webpack.optimize.CommonsChunkPlugin({
name: 'vender', // 對應entry中配置的入口vender
filename: '[name].js' // 分離出來的模組以name選項作為檔名,也就是vender.js
})
]
}
配置完成後對專案進行打包,就會看到打包結果中多出一個名為vendor.js
的檔案。
此時我們可以去檢視有使用過echarts
的元件被打包後的js
檔案體積明顯減少。
如果我們還需要繼續分離其他的一些公共模組
,可以在entry
中繼續配置:
module.exports = {
entry: {
main: ["./src/main.js"], //原有的入口檔案
vender: ['echarts', 'vue', 'other-lib']
},
}
如果前面配置的plugin
的保持不變,則entry.vendor
配置的公共模組統一會打包到vendor.js
檔案中;那如果配置的公共模組
過多,就會導致抽離出來的vendor.js
檔案體積過大。
解決這個問題可以使用前面我們介紹過的
動態連結庫
對第三方外掛進行分離,後面實踐部分會提到。
3.刪除無用程式碼
一個產品在迭代的過程中不可避免的會產生一些廢棄程式碼
,或者我們在使用一個前端元件庫時,只使用了元件庫中的一小部分元件,而打包時會將整個元件庫的內容進行打包。那不管是廢棄程式碼
或者未使用到的元件程式碼
都可以稱之為無用的程式碼
,那很顯然刪除這些無用的程式碼也可以減少打包後的檔案體積。
◕ purgecss-webpack-plugin
PurgeCSS
是一個用來刪除未使用的CSS程式碼
的工具。首先對其進行安裝:npm install purgecss-webpack-plugin -save-dev
。
該外掛使用是需要和
mini-css-extract-plugin
外掛結合使用,因此還需要安裝mini-css-extract-plugin
。不過特別需要注意mini-css-extract-plugin
要求webpack v4
。
接著在webpack
配置檔案中進行配置:
// webpack.config.js
const path = require('path')
const glob = require('glob')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const PurgecssPlugin = require('purgecss-webpack-plugin')
const PATHS = {
src: path.join(__dirname, 'src')
}
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist')
},
optimization: {
splitChunks: {
cacheGroups: {
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true
}
}
}
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader"
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
}),
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
}),
]
}
更多有關
purgecss-webpack-plugin
的內容可以檢視:https://www.purgecss.cn/
。
◕ tree-shaking
在webpack的官網中,對tree-shaking
的解釋如下:
官方文件有說明在webpack v4
可以通過sideEffects
來實現,同時給我們演示了一些很基礎的示例。
關於這個優化方案
,不管是在一些相關概念的理解還是專案的實踐中均沒有達到我想要的效果,所以在這裡僅僅把這個優化點梳理在這裡。關於該優化方案在專案中的具體配置和效果就不在演示了,以免誤導大家。
最後關於tree-shaking
的一些知識,看到了一些解釋的較為詳細的文章,貼到這裡供大家參考:
1. 【你的Tree-Shaking並沒什麼卵用】(https://segmentfault.com/a/1190000012794598)
2. 【Tree-Shaking效能優化實踐 - 原理篇 】(https://juejin.cn/post/6844903544756109319)
打包分析工具
那除了前面我們介紹的具體的優化方案
之外,還有兩個常用的打包分析工具
可以幫助我們分析構建過程
和打包後的檔案體積
。
1.speed-measure-webpack-plugin
speed-measure-webpack-plugin
它是一個webpack
外掛,用於測量打包的速度,並輸出打包過程中每一個loader
和plugin
的執行時間。
首先是對其進行安裝:npm install speed-measure-webpack-plugin
;接著在webpack.config.js
中進行配置:
// webpack.config.js
// 引入
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
// 建立例項
const smw = new SpeedMeasureWebpackPlugin();
// 呼叫例項的smw.wrap並將webpack的配置作為引數傳入
module.exports = smw.wrap({
entry: {},
output: {}
module: {},
plugins:[],
})
完成以上步驟以後,我們在執行npm run build
,就能看到該外掛輸出的打包時長資訊。
從這些資訊裡面我們能很清楚看到每一個plugin
和loader
所花費的時長,這樣我們就可以針對耗費時間較長的部分進行優化。
2.webpack-bundle-analyzer
webpack-bundle-analyzer
也是一個webpack
外掛,用於建立一個互動式的樹形圖
視覺化所有打包後的檔案
,包括檔案的體積
和檔案裡面包含的內容
。
它的使用也非常簡單,首先是安裝:npm install webpack-budle-analyzer
;安裝完成後,只需要在webpack
的配置檔案中寫入如下內容:
// webpack.config.js
// 引入
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
之後我們執行npm run build
,打包結束以後webpack
會輸出如下日誌。
接著會預設彈出瀏覽器視窗,開啟http://127.0.0.1:8888/
。
若沒有自動開啟,可以手動輸入地址。同時需要注意的是該外掛預設啟動在
8888
埠上,假如出現埠占用情況,可以對預設的埠進行配置,詳情可參考:https://www.npmjs.com/package/webpack-bundle-analyzer
。
從頁面中我們可以清楚的看到每一個檔案的大小
,同時還可以看到該檔案中引入了那些模組
、每一個模組
的檔案大小
。根據這些內容,我們就可以有針對性的處理一些大檔案
和這些大檔案
中一些體積較大的模組
。
總結
到此我們已經列舉了很多具體的webpack
優化方案和每一種優化方案
的簡單配置。接下來我們會將這些方案應用到實際的專案中,在實踐開始之前我們先對前面的內容簡單做一個回顧和總結。
實踐開始
此刻已經是萬事具備,只差實踐了。上面的優化方案在實際的專案中效果如何,一起來看看吧。
縮短打包時長
首先我們利用speed-measure-webpack-plugin
對整個專案做一個打包時長分析。
這圖雖然內容不全,但是重要的部分已經給大家展示出來了。通過這個耗時分析工具輸出的日誌資訊,我們能很清晰的看到整個打包耗時50
秒,其中UglifyJsPlugin
就執行了長達了33
秒的時間,其他相對比較耗時的就是各種loader
的執行。
關於
縮短打包時長
,後一項的優化都是在前面一項優化基礎上進行的,所以整體打包時間會不斷縮短。
1.使用webpack-parallel-uglify-plugin代替uglifyjs-webpack-plugin
根據前面的分析我們急需優化的第一個點就是使用webpack-parallel-uglify-plugin
代替uglifyjs-webpack-plugin
外掛,將js
程式碼的壓縮變成多執行緒。
將js
程式碼擴充套件成多執行緒壓縮以後,在進行打包。
這個效果真的算是非常明顯了,整體的打包時間由50秒 -> 36秒
;JS
程式碼的壓縮也由33秒 -> 15秒
,幾乎節省了50%
的時間。
2.開啟快取
接下來的一個優化點就是開啟快取
,前面介紹了三種
開啟快取的方式,只不過在本專案的實際應用中,只有hard-source-webpack-plugin
這種方式效果比較明顯,其餘兩種幾乎沒有效果。
配置了hard-source-webpack-plugin
以後,第一次打包所耗費的時長基本不會發生變化,還是上一步我們優化後的30s
。
它的作用會在發生在下一次打包時。
配置hard-source-webpack-plugin
後第一次打包時長沒有發生變化是因為此時還沒有快取檔案
,第一次打包完成後才會生成快取檔案
;之後第二次在進行打包,直接讀取快取檔案
,整體時間明顯縮短;而且通過第二次打包的時長分析結果可以看到已經沒有loader
的耗時分析,也說明了本次打包是直接從快取中讀取的結果。
上面測試的第二次打包
是在第一次的打包基礎之上且並且沒有改動程式碼。那實際開發時,我們大多數都是對程式碼做了修改瞭然後再次打包,那這種情況下快取對打包時長的影響又是如何呢?我們來試一試便知。
在此我隨意修改了兩個.vue
檔案,分別給其增加了一行程式碼,然後在進行打包。
檔案修改以後,對應的快取檔案
就會失效,因此我們看到對應loader
重新執行,整體的打包時間也有所增加,不過總體來說,該外掛是可以有效縮短打包時長的。
3.開啟多執行緒
前面我們說過多執行緒是通過將webpack
中loader
的執行過程從單執行緒
擴充套件到多執行緒
。因為前面我們開啟了快取,loader
的執行時間已經非常之短,所以在開啟快取
的基礎上在開啟多執行緒
基本是沒有什麼效果的,事實證明也是如此。
因此在這一步我將快取關掉,使用happypack
分別對babel-loader
和css-loader
開啟了多執行緒,但是最終打包時長並沒有太大變化,還是維持在30s
。
開啟多執行緒
這個優化方案在本專案中並沒有很明顯的效果,可能源於專案本身loader
處理時間就不長。即使開啟了多執行緒,執行緒
之間的通訊以及執行緒
最後的彙總耗時和單執行緒處理耗時是一樣的。
4.動態連結庫
本次我用DLLPlugin
將echarts
和element
這兩個元件進行了分離。
// webpack.dll.config.js
module.exports = {
// 入口檔案
entry: {
echarts: ['echarts'],
element: ['element-ui']
},
// 其餘程式碼省略
}
最後在進行打包,打包時長明顯降低。
最後關於DLL
的配置在實踐時,發現有兩點特別需要注意:
第一個就是webpack.dll.config.js
中的resolve
配置項,其實在剛開始的時候,參照網上的一些配置對element-ui
這個外掛進行了分離,最後對整個專案進行打包部署後發現element-ui
元件的table
無法渲染。
經過一番搜尋,發現很多人在element-ui
的github
上提了很多相關的issue
,說自己使用DLLPlugin
分離了element-ui
以後表格不渲染、tooltip
控制元件失效。不過官方基本上都說不是element-ui
本身的問題並且將issue
至為closed
。
最後翻了翻這些issue
,按照其中的一個辦法新增了resolve
配置後發現問題得以解決。
第二點需要注意其實在前面已經說過了,就是我們需要在index.html
入口模板中手動引入分離出來的第三方外掛
,同時生產環境
下還需要將分離出來的外掛程式碼
複製到webpack
打包輸出目錄
下,專案部署後才能正常執行。
5.總結
到此,關於縮短打包時長這方面的優化基本完成了,我們總共嘗試了4
種方案,最終將打包時長由最初的50s -> 6s
,可見效果還是非常明顯的。
降低打包後的檔案體積
在優化之前我們依然是使用webpack-bundle-analyzer
對打包後的檔案體積
進行分析。
這裡我挑出來兩個具有代表性的結果截圖給大家,一個是入口檔案main.js
,裡面引入的體積較大的模組
是element-ui
的核心檔案element-ui.common.js
和vue
核心檔案vue.esm.js
;另一個是total.js
,該模組是引入了體積較大的echarts
檔案。
1.壓縮檔案
前面我們介紹了對js
和images
進行壓縮可以減少檔案體積,在本專案中已經配置了webpack-parallel-uglify-plugin
對js
程式碼進行壓縮,所以這裡我們僅僅嘗試對image
圖片進行壓縮。
配置image-webpack-loader
以後,再次打包會很驚奇的發現並不是所有的圖片體積都會減少,有些圖片的體積反正變大了。
對於該異常結果並沒有在深入研究,所以暫時判定該項優化方案對本專案無效。
2.抽離第三方庫
根據前面的分析,如果對應的檔案體積減少,最直接的方式就是將vue
、echarts
、element-ui
這些些體積較大的第三方庫用CommonsChunkPlugin
抽離出來。
分離出來以後,main.js
和total.js
的檔案體積明顯下降:main.js
由1.5MB -> 755kB
;total.js
從819kB->29kB
。
但是分離出來的vendor.js
體積達到了1.56MB
。
3.動態連結庫
動態連結庫
在前面實際上歸類到了縮短打包時長
,但實際上它除了能有效的縮短打包時長,還可以將第三方庫分離到不同的檔案,同時也解決了CommonsChunkPlugin
出現的問題。
這次我們使用DLLPlugin
將vue
、echarts
、element
這個三個外掛進行分離。
// webpack.dll.config.js
module.exports = {
// 入口檔案
entry: {
echarts: ['echarts'],
element: ['element-ui'],
vue: ["vue"],
},
// 其餘程式碼省略
}
分離出來的三個外掛:
之後在進行打包,main.js
的大小從1.5MB
降低到800kB
,其餘引用到echarts
外掛的檔案體積也由原來的幾百kB
降低到十幾kB
。
總結
到此,本次關於webpack
的打包優化實踐就完成了,整體的打包時間是大大降低;對一些體積較大的檔案進行了分離,也有效降低了檔案的大小;但是也有一些優化方案在本專案中沒有很明顯的效果,甚至有些適得其反
,至於原因當下也沒有仔細去研究。
本篇文章介紹的一些優化方案
可能並不全,而且大都適用於webpack v3
,wekpack v4
在很多時候已經預設開啟一些優化方案,所以大家理性參考。後期有機會的話會嘗試將專案的webpack
版本進行升級,到時候在來總結分享。
同時,如果是真實的專案優化,所有的優化方案不能只關注打包時長
是否降低或者檔案體積
是否減小,每一個優化方案
實踐完成以後還需要在開發環境
和生成環境
中對專案進行簡單測試,如果專案執行正常才能說明此項優化方案是成功的。比如前面我們實踐的DLL
優化方案,配置完成以後如果只關注打包時間
和檔案體積
可能會沾沾自喜,但實則將專案部署到伺服器以後發現專案根本無法執行。
最後,若對本篇文章有疑問或者發現錯誤之處,還望指出,共同進步。
近期文章
JavaScript的執行上下文,真沒你想的那麼難
骨架屏(page-skeleton-webpack-plugin)初探
文末
如果這篇文章有幫助到你,❤️關注+點贊+收藏+評論+轉發❤️鼓勵一下作者
文章公眾號首發
,關注 不知名寶藏女孩
第一時間獲取最新的文章
筆芯❤️~