【建议收藏】你不得不知道的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

相关文章
相关标签/搜索