記一次真實的webpack優化經歷

前言

公司目前現有的一款產品是使用vue v2.0框架實現的,配套的打包工具爲webpack v3.0。整個項目大概有80多個vue文件,也算不上什麼大型項目。javascript

只不過每次頭疼的就是打包所耗費的時間平均在一分鐘左右,並且打包後有幾個文件顯示爲【big】,也就是文件體積過大。css

最近就想着搗鼓一下,看能不能在此前的基礎上作一些優化,順帶記錄下來分享給你們。html

webpack打包優化

關於webpack的打包優化通常會從兩個方面考慮:縮短打包時長下降打包後的文件體積,這兩個方面也恰好是前面我須要解決的問題。前端

因此咱們先來了解一下這兩個方面各自有什麼具體的實現方式。vue

縮短打包時長

咱們都知道webpack的運行流程就像一條生產線同樣,在這條生產線上會按順序的執行每個流程。那很顯然若是每個流程要乾的事情越少或者每個流程有多我的來共同完成,那webpack打包的執行效率就會提升。java

1.減小loader搜索文件範圍

咱們能夠經過配置loaderexclude選項,告訴對應的loader能夠忽略某個目錄;或者經過配置loaderinclude選項,告訴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-loadercacheDirectory選項,針對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的整個運行構建過程也是單線程模式的。

因此第一種開啓多線程的方式就是將webpackloader的執行過程從單線程擴展到多線程。這種方式的具體實現依賴的是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源代碼將被傳遞給HappyPackHappyPack將使用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-loaderhappypack相似,也是經過擴展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項目時,會將vuevue-routeraxios等這些插件的代碼跟咱們的代碼打包到一個文件中,而這些插件的代碼除非版本有變化,不然代碼內容基本不會發生變化。因此每次在打包項目時,實際上都在重複打包這些插件的代碼,很顯然浪費了不少時間。

關於動態連接庫這個詞實際上借用的是操做系統中的動態連接庫概念,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出場了:經過配置DllReferencePluginmanifest文件來把依賴的模塊名稱映射到對應的插件。這一步須要在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. 抽離第三方庫

CommonsChunkPluginwebpack官方提供的一個插件,經過配置這個插件,能夠將公共的模塊抽離出來。

webpack v4已經再也不支持該插件,使用SplitChunksPlugin代替。但因爲本項目使用的是webpack v3,所以這裏不對SplitChunksPlugin作介紹。

首先咱們須要在webpackentry選項對咱們須要分離的公共模塊進行配置。

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插件,用於測量打包的速度,並輸出打包過程當中每個loaderplugin的執行時間。

首先是對其進行安裝: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,就能看到該插件輸出的打包時長信息。

從這些信息裏面咱們能很清楚看到每個pluginloader所花費的時長,這樣咱們就能夠針對耗費時間較長的部分進行優化。

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代碼擴展成多線程壓縮之後,在進行打包。

將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.開啓多線程

前面咱們說過多線程是經過將webpackloader的執行過程從單線程擴展到多線程。由於前面咱們開啓了緩存,loader的執行時間已經很是之短,因此在開啓緩存的基礎上在開啓多線程基本是沒有什麼效果的,事實證實也是如此。

所以在這一步我將緩存關掉,使用happypack分別對babel-loadercss-loader開啓了多線程,可是最終打包時長並無太大變化,仍是維持在30s

開啓多線程這個優化方案在本項目中並無很明顯的效果,可能源於項目自己loader處理時間就不長。即便開啓了多線程,線程之間的通訊以及線程最後的彙總耗時和單線程處理耗時是同樣的。

4.動態連接庫

本次我用DLLPluginechartselement這兩個組件進行了分離。

// webpack.dll.config.js
module.exports = {
  // 入口文件
  entry: {
    echarts: ['echarts'],
    element: ['element-ui']
  },
  // 其他代碼省略
}

最後在進行打包,打包時長明顯下降。

配置DLL,打包總體時長由17s->6s

最後關於DLL的配置在實踐時,發現有兩點特別須要注意:

第一個就是webpack.dll.config.js中的resolve配置項,其實在剛開始的時候,參照網上的一些配置對element-ui這個插件進行了分離,最後對整個項目進行打包部署後發現element-ui組件的table沒法渲染。

table沒法渲染

通過一番搜索,發現不少人在element-uigithub上提了不少相關的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.jsvue核心文件vue.esm.js;另外一個是total.js,該模塊是引入了體積較大的echarts文件。

1.壓縮文件

前面咱們介紹了對jsimages進行壓縮能夠減小文件體積,在本項目中已經配置了webpack-parallel-uglify-pluginjs代碼進行壓縮,因此這裏咱們僅僅嘗試對image圖片進行壓縮。

配置image-webpack-loader之後,再次打包會很驚奇的發現並非全部的圖片體積都會減小,有些圖片的體積反正變大了。

對於該異常結果並無在深刻研究,因此暫時斷定該項優化方案對本項目無效。

2.抽離第三方庫

根據前面的分析,若是對應的文件體積減小,最直接的方式就是將vueechartselement-ui這些些體積較大的第三方庫用CommonsChunkPlugin抽離出來。

分離出來之後,main.jstotal.js的文件體積明顯降低:main.js1.5MB -> 755kBtotal.js819kB->29kB

可是分離出來的vendor.js體積達到了1.56MB

3.動態連接庫

動態連接庫在前面實際上歸類到了縮短打包時長,但實際上它除了能有效的縮短打包時長,還能夠將第三方庫分離到不一樣的文件,同時也解決了CommonsChunkPlugin出現的問題。

此次咱們使用DLLPluginvueechartselement這個三個插件進行分離。

// webpack.dll.config.js
module.exports = {
  // 入口文件
  entry: {
    echarts: ['echarts'],
    element: ['element-ui'],
    vue: ["vue"],
  },
  // 其他代碼省略
}

分離出來的三個插件:

以後在進行打包,main.js的大小從1.5MB下降到800kB,其他引用到echarts插件的文件體積也由原來的幾百kB下降到十幾kB

總結

到此,本次關於webpack的打包優化實踐就完成了,總體的打包時間是大大下降;對一些體積較大的文件進行了分離,也有效下降了文件的大小;可是也有一些優化方案在本項目中沒有很明顯的效果,甚至有些拔苗助長,至於緣由當下也沒有仔細去研究。

本篇文章介紹的一些優化方案可能並不全,並且大都適用於webpack v3wekpack v4在不少時候已經默認開啓一些優化方案,因此你們理性參考。後期有機會的話會嘗試將項目的webpack版本進行升級,到時候在來總結分享。

同時,若是是真實的項目優化,全部的優化方案不能只關注打包時長是否下降或者文件體積是否減少,每個優化方案實踐完成之後還須要在開發環境生成環境中對項目進行簡單測試,若是項目運行正常才能說明此項優化方案是成功的。好比前面咱們實踐的DLL優化方案,配置完成之後若是隻關注打包時間文件體積可能會沾沾自喜,但實則將項目部署到服務器之後發現項目根本沒法運行。

最後,若對本篇文章有疑問或者發現錯誤之處,還望指出,共同進步。

近期文章

JavaScript的執行上下文,真沒你想的那麼難
骨架屏(page-skeleton-webpack-plugin)初探

文末

若是這篇文章有幫助到你,❤️關注+點贊+收藏+評論+轉發❤️鼓勵一下做者

文章公衆號首發,關注 不知名寶藏女孩 第一時間獲取最新的文章

筆芯❤️~

相關文章
相關標籤/搜索