公司目前現有的一款產品是使用vue v2.0
框架實現的,配套的打包工具爲webpack v3.0
。整個項目大概有80
多個vue
文件,也算不上什麼大型項目。javascript
只不過每次頭疼的就是打包所耗費的時間平均在一分鐘
左右,並且打包後有幾個文件顯示爲【big】
,也就是文件體積過大。css
最近就想着搗鼓一下,看能不能在此前的基礎上作一些優化,順帶記錄下來分享給你們。html
關於webpack
的打包優化通常會從兩個方面考慮:縮短打包時長
和下降打包後的文件體積
,這兩個方面也恰好是前面我須要解決的問題。前端
因此咱們先來了解一下這兩個方面各自有什麼具體的實現方式。vue
咱們都知道webpack
的運行流程就像一條生產線同樣,在這條生產線上會按順序的執行每個流程。那很顯然若是每個流程要乾的事情越少或者每個流程有多我的來共同完成,那webpack
打包的執行效率就會提升。java
1.減小loader搜索文件範圍
咱們能夠經過配置loader
的exclude
選項,告訴對應的loader
能夠忽略某個目錄
;或者經過配置loader
的include
選項,告訴loader
只須要處理指定的目錄
。由於loader
處理的文件越少,執行速度就會更快。node
通常比較常見的就是給babel-loader
配置exclude
選項。webpack
// 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
處理。ios
2.利用緩存
關於webpack
的緩存,官方的大體解釋爲:開啓緩存之後,webpack
構建將嘗試從緩存中讀取數據,以免每次運行時都須要運行代價高昂的從新編譯過程。git
那如何在編譯代碼時開啓緩存呢?
◕ 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)初探
若是這篇文章有幫助到你,❤️關注+點贊+收藏+評論+轉發❤️鼓勵一下做者
文章公衆號首發
,關注 不知名寶藏女孩
第一時間獲取最新的文章
筆芯❤️~