【建議收藏】你不得不知道的webpack性能優化

前言

在上一篇文章從0到1手把手帶你捋一套webpack+vue項目模板中,主要講解了一個單頁項目從npm init開始如何手動搭建前端單頁腳手架以後,這篇文章就繼續跟你們一塊兒分享一下如何從webpack上來作出構建的優化。css

爲何要作webpack配置的優化?

  • 筆者在去年作了一個服務於國家醫保局信息化建設平臺的大型項目中,項目頁面達到800+,隨之帶來的挑戰就是項目中打包出來的js體積愈來愈大,構建速度愈來愈緩慢,無疑,從webpack構建配置上就須要做出一系列的優化了。如下配置的優化,均在真實項目中有過實戰,這裏就在上篇文章demo項目基礎上來一一講解。

如何做出具體的優化?

聲明:本文基於webpack版本號以下html

"webpack": "^4.42.0",
"webpack-cli": "^3.3.11"
複製代碼

下載 (1).jpeg

本文的項目地址:github.com/Paulinho89/…前端

在做出webpack配置優化以前,首先咱們須要藉助一些webpack插件來分析咱們當前的構建日誌,以及構建速度構建體積等。vue

初級分析:使用 webpack 內置的 stats

經過設置stats來統計咱們的構建的信息node

咱們在package.json中添加以下配置react

"scripts": {
    "build:stats": "webpack --config build/webpack.config.prod.js --json > stats.json"
}
複製代碼

運行npm run build:stats後,再執行npm run prod後,在咱們項目的根目錄下會生成一個stats.json文件,這個文件會記錄咱們項目構建的各類信息,同時也能夠stats後看到控制檯打印出對應的構建信息。webpack

image.png

速度分析:使用 speed-measure-webpack-plugin

剛纔提到的stats來分析構建日誌,可是stats的分析仍是比較有限,若是咱們想知道咱們使用的哪一個lodaer,或者是哪一個plugin的具體耗時該怎麼辦呢?speed-measure-webpack-plugin就是一個不錯的分析插件。git

安裝es6

npm i speed-measure-webpack-plugin -D
複製代碼

配置github

const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');

const smp = new SpeedMeasureWebpackPlugin();

module.exports = smp.wrap(WebpackMerge(WebpackConfig, {
    mode: "production",
    devtool: "hidden-source-map",
    entry: {
        app: resolve(__dirname, "../src/main")
    }
}));
複製代碼

運行npm run prod,能夠很清楚的知道咱們每個loader以及plugin運行的耗時以及咱們的總打包的耗時。

image.png

分析體積:webpack-bundle-analyzer

安裝

npm i webpack-bundle-analyze -D
複製代碼

配置

const WebpackBundleAnalyzer = require('webpack-bundle-analyzer');


const { ANALYZE } = process.env;
const { BundleAnalyzerPlugin } = WebpackBundleAnalyzer;

if (ANALYZE === 'true') {
    PluginConfig.push(new BundleAnalyzerPlugin());
}
複製代碼

咱們在package.json中添加以下配置

"analyz": "cross-env NODE_ENV=production ANALYZE=true npm_config_report=true npm run prod",
複製代碼

運行npm run analyz,瀏覽器會自動打開http://127.0.0.1:8888/,此時咱們就能夠很清晰的看到每個打包後的js文件體積Gzip前跟Gzip後的大小對比,以及一些基礎包體積大小的對比。

image.png

上述,咱們藉助了一些插件來幫助咱們分析項目中打包的體積耗時等,那接下來咱們就要從構建的速度上來進行進一步的分析並優化。

多進程/多實例構建:資源並行解析可選方案

image.png

使用 HappyPack 解析資源

原理:每次 webapck 解析一個模塊,HappyPack 會將它及它的依賴分配給 worker 線程中。

安裝

npm i happypack -D
複製代碼

配置

const HappyPack = require('happypack');

plugins: [
    new HappyPack({
        // id 標識符,要和 rules 中指定的 id 對應起來
        id: 'babel',
        // 須要使用的 loader,用法和 rules 中 Loader 配置同樣
        // 能夠直接是字符串,也能夠是對象形式
        loaders: ['babel-loader']
    })
],
複製代碼

運行npm run prod後對比可見,構建的時間縮短了2秒鐘

image.png

並行壓縮 terser-webpack-plugin

使用 terser-webpack-plugin 插件

安裝

npm i terser-webpack-plugin@1.3.0 -D
複製代碼

配置

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
    optimization: {
        minimizer: [
            new TerserPlugin({
                parallel: true,
                cache: true
            })
        ]
    }
}
複製代碼

運行npm run prod後對比可見,構建的時間縮短了500ms

image.png

分包:設置 Externals

思路:將 vuevue-router 基礎包經過 cdn 引入,不打入 bundle

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>從0到1手把手帶你捋一套webpack+vue項目模板</title>
</head>
<body>
    <div id="app">
       <router-view></router-view>
    </div>
   <!-- 正常的引入 cdn 資源便可 -->
    <script src="https://cdn.bootcss.com/vue/2.5.16/vue.min.js"></script>
    <script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script>
</body>
</html>

複製代碼

配置

module.exports = {
    module: {
        ...
    },
    externals: {
        'vue': 'Vue',
        'vue-router': 'VueRouter'
    }
}
複製代碼

若是在項目中繼續使用的話,咱們依然可使用import的方式引入。

import Vue from 'vue'
import VueRouter from 'vue-router'
複製代碼

這樣配置的話 webpackdev 運行或 build 打包時,就不會去本地組件包中查找這些在 externals 中註冊的組件了(天然也不會將他們打包到一個 app.js 中去),而是會去 window 域下直接調用 Vue, VueRouter等對象。

進一步分包:預編譯資源模塊 DLLPlugin

思路:將 vue、vue-router等 基礎包打包成一個文件。

方法:使用 webapck內置的插件DLLPlugin 進行分包,DllReferencePlugin 對 manifest.json 引用。

配置

build目錄下新建webpack.config.dll.js

const path = require('path');
const webpack = require('webpack');

module.exports = {
    entry: {
        library: [
            'vue',
            'vue-router'
        ]
    },
    output: {
        filename: '[name]_[hash].dll.js',
        path: path.join(__dirname, '../library'),
        library: '[name]'
    },
    plugins: [
        new webpack.DllPlugin({
            name: '[name]_[hash]',
            path: path.join(__dirname, '../library/[name].json')
        })
    ]
}

複製代碼

咱們在package.json中添加以下配置

"dll": "webpack --config build/webpack.config.dll"
複製代碼

運行npm run dll後,項目根目錄下會自動生成一個library文件夾,其中library.json文件就是咱們接下來要在webpack.config.prod.js中進行的映射。

webpack.config.prod.js配置

const Webpack = require('webpack');

module.exports = {
    plugins: [
        new Webpack.DllReferencePlugin({
            manifest: require('../library/library.json')
        })
    ],
}
複製代碼

再次執行npm run prod後對比發現,分包後的app.js體積比分包前app.js體積小了30kb,構建速度上也有微弱的減小,固然咱們這裏只是把vuevue-router抽離了出來作個演示,那當咱們項目比較大的時候,能夠把更多的業務基礎包抽離出來,效果會更加明顯。

image.png

開啓緩存

babel-loader 開啓緩存,在babel-loader後邊加上參數cacheDirectory=true

配置

plugins: [
    ...BasePlugins,
    new HappyPack({
        // id 標識符,要和 rules 中指定的 id 對應起來
        id: 'babel',
        // 須要使用的 loader,用法和 rules 中 Loader 配置同樣
        // 能夠直接是字符串,也能夠是對象形式
        loaders: ['babel-loader?cacheDirectory=true']
    }),
]
複製代碼

執行npm run prod後,對比發現緩存開啓後比開啓前快了600ms

image.png

使用 cache-loader 或者 hard-source-webpack-plugin

安裝

npm i hard-source-webpack-plugin -D
複製代碼

配置

const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');

module.exports = {
    plugins: [
        new HardSourceWebpackPlugin()
    ],
}
複製代碼

執行npm run prod後,會發如今咱們的node_modules目錄下會自動幫助咱們生成一個.cache目錄,裏邊存放的就是每次構建緩存的文件,運行後對比發現緩存開啓後比開啓前快了1800ms,時間大大縮短,固然咱們這裏也只是爲了演示,縮短的時間不是很明顯,一旦在項目體積大的時候,開啓緩存構建,速度會有巨大的提高。

image.png

縮小構建目標

目的:儘量的少構建模塊,好比 babel-loader 不解析 node_modules

配置

module.exports = {
    module: {
        {
            test: /\.js$/,
            use: [
                {
                    loader: 'thread-loader',
                    options: {
                        workers: 3
                    }
                },
                "babel-loader",
            ],
            exclude: /node_modules/
        }
    }
}
複製代碼

減小文件搜索範圍

  • 優化 resolve.modules 配置(減小模塊搜索層級)
  • 優化 resolve.extensions 配置
  • 合理使用 alias

配置

module.exports = {
    resolve: {
        extensions: [".js", ".json", ".css", ".less", ".vue"],
        alias: {
            vue$: "vue/dist/vue.common.js",
            "@": resolve(__dirname, "../src")
        }
    }
}
複製代碼

tree shaking

概念:1個模塊可能有多個方法,只要其中的某個方法使用到了,則整個文件都會被打到 bundle 裏面去,tree shaking 就是隻把用到的方法打入 bundle ,沒用到的方法會在 uglify 階段被擦除掉。

使用

webpack4中咱們把mode設置爲production 狀況下默認開啓tree-shaking

jstree-shaking這裏就再也不細描述了,有興趣的小夥伴們能夠本身動手試試,那關於csstree-shaking咱們該如何進行配置呢?

在沒有進行開啓csstree-shaking前,咱們先來測試一下,在index.vue中寫一行沒有使用的css,看一下會不會被打包進去。

image.png

執行npm run prod後發現,確實被打包到js文件中了。

image.png

使用purgecss-webpack-plugin,前提是須要配置mini-css-extract-plugin 配合使用開啓csstree-shaking

安裝

npm i mini-css-extract-plugin purgecss-webpack-plugin  -D
複製代碼

配置

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 = {
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name]_[contenthash:8].css'
        }),
        // 開啓css的tree-shaking
        new PurgecssPlugin({
            paths: glob.sync(`${PATHS.src}/**/*`,  { nodir: true }),
        })
    ]
}
複製代碼

運行完npm run prod後發現,在index.vue中寫一行沒有使用到的unused-css這個樣式被擦出掉了,沒有被打包進去。

圖片壓縮

一般一個項目咱們會引入不少各類格式的圖片,多張圖片被打包之後,若是不作壓縮的話,體積仍是至關大的,因此生產環境對圖片體積的壓縮就顯得格外重要了。

方式

  • 使用tinypng手動壓縮,比較零碎,也不夠自動化
  • imagemin
  • image-webpack-loader來進行自動壓縮

這裏咱們就採用image-webpack-loader來實現對圖片的自動壓縮。

安裝

npm i image-webpack-loader -D
複製代碼

配置

module.exports = {
    module: {
        {
            test: /\.(jpg|jpeg|png|gif)$/,
            use: [
                {
                    loader: 'url-loader',
                    options: {
                        limit: 8192,
                        outputPath: "img/",
                        name: "[name]-[hash:6].[ext]"
                    }
                },
                {
                    loader: 'image-webpack-loader',
                    options: {
                      mozjpeg: {
                        progressive: true,
                        quality: 65
                      },
                      // optipng.enabled: false will disable optipng
                      optipng: {
                        enabled: false,
                      },
                      pngquant: {
                        quality: '65-90',
                        speed: 4
                      },
                      gifsicle: {
                        interlaced: false,
                      },
                      // the webp option will enable WEBP
                      webp: {
                        quality: 75
                      }
                    }
                }
            ]
        }
    }
}
複製代碼
<template>
  <div class="container">
    {{ msg }}
    <img :src="require('@/images/bg.jpg')">
  </div>
</template>
複製代碼

運行npm run prod後對比發現,壓縮後的圖片的體積大大縮小。

壓縮前:

image.png

壓縮後:

image.png

構建體積優化:動態 Polyfill

一般咱們在項目中會使用babel來將不少es6中的API進行轉換成es5,可是仍是有不少新特性無法進行徹底轉換,好比promise、async await、map、set等語法,那麼咱們就須要經過額外的polyfill(墊片)來實現語法編譯上的支持。

方案 優勢 缺點
babel-polyfill vue、react官方支持 包的體積比較大,很難單獨抽離async await、map、set等語法
babel-plugin-transform-runtime 只對須要使用到async/await 時,纔會自動引入polyfill,減少庫與工具包的體積 不能polyfill原型上的一些方法
polyfill-service 只返回用戶須要用到的polyfill,並且由社區來維護,好比polyfill.io 部分瀏覽可能不能識別

這裏咱們仍是推薦使用第三種方式,由polyfill.io 官方爲咱們提供的服務。

咱們能夠先來使用polyfill.io 驗證一下,在不一樣的User Agent,是否是會下發不一樣的polyfill

iphone5

image.png

iphone6/7/8

image.png

iphoneX

image.png

咱們對比能夠發現,不一樣的手機機型,咱們去訪問polyfill.io/v3/polyfill…的時候,資源的體積大小是不同的。

項目中使用

<script src='https://polyfill.io/v3/polyfill.min.js'></script>
複製代碼

總結

  1. 雖然,webpack5已經在2020年的10月10號完成了發佈,可是目前基於項目架構在生產環境下的穩定性、可維護性來說,咱們這裏依然採用的是webpack4來分析構建的優化策略。
  2. 固然,webpack5在項目打包優化上會更具備優點,如持久化的緩存、對nodepolyfill的移除、更優的tree-shaking、以及使人興奮的Module Federation,這些新特性仍是很值得你們去升級探索的。接下來,我會在其中的一篇文章中給你們分享webpack5項目升級實戰,敬請期待。

若是您以爲這篇文章對您有一點點幫助,歡迎看完後給我點贊,您的點贊是我前進的動力,謝謝~~

images.jpeg

相關文章
相關標籤/搜索