記一次真實的webpack優化經歷


前言

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

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

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

webpack打包優化

關於webpack的打包優化一般會從兩個方面考慮:縮短打包時長降低打包后的文件體積,這兩個方面也剛好是前面我需要解決的問題。

所以我們先來了解一下這兩個方面各自有什么具體的實現方式。

縮短打包時長

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

1.減少loader搜索文件范圍

我們可以通過配置loaderexclude選項,告訴對應的loader可以忽略某個目錄;或者通過配置loaderinclude選項,告訴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-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)初探

文末

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

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

筆芯❤️~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM