webpack5:高级篇(3)

557 阅读9分钟

第十章: 提升构建性能

webpack的性能提升分为两类:
1、提升项目的性能。如减少首屏时间。受益者是用户。
2、提升webpack的构建编译性能。如提高编译速度,减少编译时间。受益者是开发人员。

本章讲的是第二种。

不同版本的webpack 有不同的优化点,建议参考官网进行操作。

10.1 八个通用构建优化

无论你是在 开发环境 还是在 生产环境 下运行构建脚本,以下最佳实践都会有所帮助。

1、更新到最新版本

使用最新的 webpack 版本。我们会经常进行性能优化。

Node.js 更新到最新版本,也有助于提高性能。除此之外,将你的 package 管理工具(例如 npm 或者 yarn )更新到最新版本,也有助于提高性能。较新的版本能够建立更高效的模块树以及提高解析速度。

2、loader

将 loader 应用于最少数量的必要模块。而非如下:

module.exports = {
    //...
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
            },
        ],
    },
};

通过使用 include 字段,仅将 loader 应用在实际需要将其转换的模块:

const path = require('path');

module.exports = {
    //...
    module: {
        rules: [
            {
                test: /\.js$/,
                include: path.resolve(__dirname, 'src'),
                loader: 'babel-loader',
            },
        ],
    },
};

3、引导(bootstrap)

每个额外的 loader/plugin 都有其启动时间。尽量少地使用工具。

4、解析

以下步骤可以提高解析速度:

  • 减少 resolve.modules , resolve.extensions , resolve.mainFiles , resolve.descriptionFiles 中条目数量,因为他们会增加文件系统调用的次数。
  • 如果你不使用 symlinks(例如 npm link 或者 yarn link ),可以设置 resolve.symlinks: false
  • 如果你使用自定义 resolve plugin 规则,并且没有指定 context 上下文,可以设置 resolve.cacheWithContext: false

5、小即是快(smaller = faster)

  • 使用数量更少/体积更小的 library。
  • 在多页面应用程序中使用 SplitChunksPlugin 。
  • 在多页面应用程序中使用 SplitChunksPlugin ,并开启 async 模式。
  • 移除未引用代码。
  • 只编译你当前正在开发的那些代码。

6、持久化缓存

在 webpack 配置中使用 cache 选项。使用 package.json 中的 "postinstall" 清除缓存目录。

cache 类型设置为内存或者文件系统。 memory 选项很简单,它告诉 webpack 在内存中存储缓存,不允许额外的配置:

module.exports = {
    //...
    cache: {
        type: 'memory',
    },
};

7、自定义 plugin/loader

对它们进行概要分析,以免在此处引入性能问题。

8、dll

使用 DllPlugin 为更改不频繁的代码生成单独的编译结果。这可以提高应用程序的编译速度,尽管它增加了构建过程的复杂度。

9、worker 池(worker pool)

thread-loader 可以将非常消耗资源的 loader 分流给一个 worker pool。

不要使用太多的 worker,因为 Node.js 的 runtime 和 loader 都有启动开销。最小化 worker 和 main process(主进程) 之间的模块传输。进程间通讯(IPC,inter process communication)是非常消耗资源的。

10、Progress plugin

将 ProgressPlugin 从 webpack 中删除,可以缩短构建时间。请注意,ProgressPlugin 可能不会为快速构建提供太多价值,因此,请权衡利弊再使用。

10.2 通用构建优化-dll

本节,演示如何使用dll来提高构建速度。

◎ 示例

示例项目中使用了第三方包 lodash,它的代码是不变的,只需要打包一次,而我们自己的代码则因为频繁修改需要打包多次,所以可以对lodash使用dll技术,来避免不必要的重复打包,提高打包速度。

1、搭建webpack基本环境。

npm init -y
npm install --save-dev webpack webpack-cli webpack-dev-server html-webpack-plugin

npm i -S lodash

2、测试代码。

//index.js

import _ from 'lodash' 
console.log(_.join(['hello', 'webpack'], ' '))
//webpack.config.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
 
module.exports = {
    mode: 'production',
    entry: './src/index.js',
    output: {
        clean: true
    },
    plugins: [
        new HtmlWebpackPlugin(),
    ]
}

3、打包。

npx webpack

image.png

4、结论。

在没有使用dll技术时,打包耗时3693ms,main.js的体积为69.5kb。

5、webpack.dll.config.js

新建webpack.dll.config.js文件,写入以下内容:

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

module.exports = {
    mode: 'production',
    entry: {
        lodash: ['lodash']
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dll'),
        library: '[name]_[hash]'
    },
    plugins: [
        new webpack.DllPlugin({
            name: '[name]_[hash]',
            path: path.resolve(__dirname, 'dll/manifest.json')
        })
    ]
}

6、package.json

"scripts": {
    "dll": "webpack --config ./webpack.dll.config.js",
},

7、dll 打包

dll 打包,生成manifest文件。

npm run dll

image.png

image.png

7.2、结论。

可见,打包输出了一个新的目录 dll,里面有:

  • lodash.js 第三方包被单独打包出来了
  • manifest.json 这个文件就是用来进行构建缓存的。
  • xxx.txt

8、webpack.config.js

使用 dll 打包后生成的文件。

...
const webpack = require('webpack')

...
plugins: [
    ...
    new webpack.DllReferencePlugin({
        manifest: path.resolve(__dirname, './dll/manifest.json')
    })
]

9、应用打包。

npx webpack

image.png

10、结论。

可见,使用dll后,打包耗时为608ms,main.js体积为556bytes,构建速度快了6倍。

注意:此时,main.js中并不包含lodash代码,index.html中也只引入了main.js。所以还不能部署上线。

11、安装 AddAssetHtmlWebpackPlugin 插件。

npm i -D add-asset-html-webpack-plugin

12、配置 AddAssetHtmlWebpackPlugin 插件。

//webpack.config.js

...
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')

plugins: [
    ...
    new AddAssetHtmlPlugin({
        filepath: path.resolve(__dirname, './dll/lodash.js'),
        publicPath: './'
    })
]

13、打包,启动开发服务器。

npx webpack
npx webpack serve

image.png

image.png

image.png

image.png

14、结论。

开发时,先对第三方包进行dll打包,可以提高打包速度。最后打包时,需要使用 AddAssetHtmlWebpackPlugin插件 来将dll打包好的第三方包 合并到最终的打包结果中,才能部署上线。

10.3 通用构建优化-worker-pool

worker 池(worker pool):
thread-loader 可以将非常消耗资源的 loader 分流给一个 worker pool 。

thread-loader 的使用方法: 哪个loader需要放在worker pool中运行,就在哪个loader的前面添加 thread-loader 。

◎ 示例

本示例中,使用 babel-loader 对应用中的class语法进行转换。我们想将babel-loader放在一个worker pool中运行,来提高打包速度。

也可以简单理解为:利用电脑多核的特点,将 babel-loader 单独放在一个CPU中运行。

1、搭建webpack基本环境。

npm init -y
npm install --save-dev webpack webpack-cli webpack-dev-server html-webpack-plugin

npm i -D babel-loader @babel/core @babel/preset-env

npm i -D thread-loader

2、webpack.config.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
        clean: true
    },
    devtool: 'cheap-module-source-map',
    devServer: {
        static: path.resolve(__dirname, './dist'),
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'babel-loader',
                        options: {
                            presets: ['@babel/preset-env']
                        }
                    }
                ]
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin(),
    ]
}

3、index.js

class Dog {
    constructor() {
        this.name = '汪汪'
    }

    sayHi(){
        console.log(this.name)
    }
}

const dog = new Dog() 
dog.sayHi() 

4、打包

npx webpack

image.png

4.2 结论

可见,没有使用worker pool时,打包耗时1171ms,main.js体积为1.22kb。

5、配置thread-loader。

module: {
    rules: [
        {
            test: /\.js$/,
            exclude: /node_modules/,
            use: [
                {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                },
                //添加thread-loader
                {
                    loader: 'thread-loader',
                    options:{
                        workers: 2
                    }
                }
            ]
        }
    ]
},

6、打包

npx webpack

image.png

6.2 结论

可见,使用worker pool时,打包耗时1324ms,竟然比没有使用时还长,这是为什么呢?

这是因为启动thread-loader是需要时间的。另外一个原因是:worker pool 只对那些耗时很长的loader才会有明显的提升效果,而本示例中 babel-loader 只是简单转换了class语法,耗时并不长,所以效果不明显。

10.3 开发环境

以下步骤对于 开发环境 特别有帮助。

1、增量编译

使用 webpack 的 watch mode(监听模式)。而不使用其他工具来 watch 文件和调用 webpack 。内置的 watch mode 会记录时间戳并将此信息传递给 compilation 以使缓存失效。

在某些配置环境中,watch mode 会回退到 poll mode(轮询模式)。监听许多文件会导致 CPU 大量负载。在这些情况下,可以使用 watchOptions.poll 来增加轮询的间隔时间。

2、在内存中编译

下面几个工具通过在内存中(而不是写入磁盘)编译和 serve 资源来提高性能:

  • webpack-dev-server
  • webpack-hot-middleware
  • webpack-dev-middleware

3、stats.toJson 加速

webpack 4 默认使用 stats.toJson() 输出大量数据。除非在增量步骤中做必要的统计,否则请避免获取 stats 对象的部分内容。 webpack-dev-server 在 v3.1.3 以后的版本,包含一个重要的性能修复,即最小化每个增量构建步骤中,从 stats 对象获取的数据量。

4、Devtool

需要注意的是不同的 devtool 设置,会导致性能差异。

  • "eval" 具有最好的性能,但并不能帮助你转译代码。
  • 如果你能接受稍差一些的 map 质量,可以使用 cheap-source-map 变体配置来提高性能
  • 使用 eval-source-map 变体配置进行增量编译。

在大多数情况下,最佳选择是 eval-cheap-module-source-map 。

5、避免在生产环境下才会用到的工具

某些 utility, plugin 和 loader 都只用于生产环境。例如,在开发环境下使用 TerserPlugin 来 minify(压缩) 和 mangle(混淆破坏) 代码是没有意义的。通常在开发环境下,应该排除以下这些工具:

  • TerserPlugin
  • [fullhash] / [chunkhash] / [contenthash]
  • AggressiveSplittingPlugin
  • AggressiveMergingPlugin
  • ModuleConcatenationPlugin

6、最小化 entry chunk

Webpack 只会在文件系统中输出已经更新的 chunk。某些配置选项(HMR, output.chunkFilename 的 [name] / [chunkhash]/[contenthash] , [fullhash] )来说,除了对已经更新的 chunk 无效之外,对于 entry chunk 也不会生效。

确保在生成 entry chunk 时,尽量减少其体积以提高性能。下面的配置为运行时代码创建了一个额外的 chunk,所以它的生成代价较低:

module.exports = {
    // ...
    optimization: {
        runtimeChunk: true,
    },
};

7、避免额外的优化步骤

Webpack 通过执行额外的算法任务,来优化输出结果的体积和加载性能。这些优化适用于小型代码库,但是在大型代码库中却非常耗费性能:

module.exports = {
    // ...
    optimization: {
        removeAvailableModules: false,
        removeEmptyChunks: false,
        splitChunks: false,
    },
};

8、输出结果不携带路径信息

Webpack 会在输出的 bundle 中生成路径信息。然而,在打包数千个模块的项目中,这会导致造成垃圾回收性能压力。在 options.output.pathinfo 设置中关闭:

module.exports = {
    // ...
    output: {
        pathinfo: false,
    },
};

9、Node.js 版本 8.9.10-9.11.1

Node.js v8.9.10 - v9.11.1 中的 ES2015 Map 和 Set 实现,存在 性能回退。Webpack 大量地使用这些数据结构,因此这次回退也会影响编译时间。之前和之后的 Node.js 版本不受影响。

10、TypeScript loader

你可以为 loader 传入 transpileOnly 选项,以缩短使用 ts-loader 时的构建时间。使用此选项,会关闭类型检查。如果要再次开启类型检查,请使用 ForkTsCheckerWebpackPlugin 。使用此插件会将检查过程移至单独的进程,可以加快 TypeScript 的类型检查和 ESLint 插入的速度。

module.exports = {
    // ...
    test: /\.tsx?$/,
    use: [
        {
            loader: 'ts-loader',
            options: {
                transpileOnly: true,
            },
        },
    ],
};

10.4 生产环境

以下步骤对于 生产环境 特别有帮助。

不启用SourcMap

source map相当消耗资源,开发环境模式不要设置source map

官方链接

百度脑图

视频教程

千锋最新前端webpack5全套教程,全网最完整的webpack教程(基础+高级)

学习资料

H:\学习课程\ediary日记\学习课程\webpack5_千峰\资料\笔记-webpack5学习指南-V1.0-去水印.pdf

上一篇

webpack5:高级篇(2)