启动项目几分钟?如何优化webpack配置,启动编译开发环境

1,030 阅读2分钟

版本: webpack@^5.9.0

CSS编译优化

先说css的配置,因为rules中有一个典型的错误配置习惯,在某些视频教程中经常出现。 举个栗子:

{
    test: [/\.scss$/, /\.css$/, /\.less$/],
    exclude: /node_modules/,
    use: [
        'style-loader',
        'css-loader',
        'sass-loader',
    ],
},

首先简单了解下 loader 的功能

  • style-loader 生成style标签,并将css代码注入
  • css-loader 因为当前普遍做法是将css当成模块导入到js里面的,css-loader就是为了识别处理css模块
  • sass-loader 编译sass代码为css

问题:在上述webpack配置代码中,会将后缀名为.css、.scss、.less的文件经历三重loader的编译。这就会导致css、less也会走 sass-loader的编译,这会浪费许多时间,特别是当你在项目中 import '/antd/antd.css'引入了全量的第三方无需编译css代码时。 正确做法应该进行匹配规则拆分:

const config = {
    ...
    rules:[
        {
            test: [/\.css$/i],
            include: path.resolve(__dirname, '../node_modules'),
            use: [
                'style-loader',
                'css-loader',
            ],
        },
        {
            test: [/\.s[ac]ss$/i],
            exclude: path.resolve(__dirname, '../node_modules'),
            use: [
                'style-loader',
                'css-loader',
                'sass-loader',
            ],
        },
    ]
}

如果要独立打包css文件,我们还可以区分一下开发与生产的配置:

const isDev = appConfig.env.isDev
const isPro = appConfig.env.isPro

const config = {
    ...
    rules:[
        {
            test: [/\.css$/i],
            include: path.resolve(__dirname, '../node_modules'),
            use: [
                isPro && MiniCssExtractPlugin.loader,
                isDev && 'style-loader',
                'css-loader',
            ].filter(Boolean),
        },
        {
            test: [/\.s[ac]ss$/i],
            exclude: path.resolve(__dirname, '../node_modules'),
            use: [
                isPro && MiniCssExtractPlugin.loader,
                isDev && 'style-loader',
                'css-loader',
                'sass-loader',
            ].filter(Boolean),
        },
    ]
}

JS编译优化

js的配置参考上述css配置问题,也可能需要类似的调整。这里就不再重复说明。

JS babel编译优化

主要是开启babel的 cacheDirectory 缓存,开启后第一次编译速度没有变化,第二次编译就会读取上次的编译缓存,提升编译效率。

这里有个提醒,如果你也使用了react-refresh/babel,要将babel.config.js中的react-refresh/babel配置转移到webpack中进行配置,否则生产编译后浏览器会报错,配置如下:

const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
const isDev = appConfig.env.isDev
const isPro = appConfig.env.isPro

const config = {
    plugins:[
        ...
        isDev && new ReactRefreshWebpackPlugin()
    ].filter(Boolean),
    module:{
        rules:[
            {
                test: /\.(jsx|js)$/,
                exclude: [path.join(__dirname, '../node_modules')],
                use: [
                    {
                        loader: 'babel-loader',
                        options: {
                            cacheDirectory: true,
                            plugins:[
                                isDev && require.resolve('react-refresh/babel'),
                            ].filter(Boolean),
                        },
                    },
                ],
            },
        ]
    }
}

开启webpack缓存

除了babel提供了 cacheDirectory 缓存配置,webpack也有自身的缓存机制,配置 cache 即可启用

const webpackConfig = {
    cache: true
}

ESLint优化闭坑

eslint可以通过添加 exclude 指定不需要检测的文件。同时eslint启动时会进行一次全量的代码语法检测,如果是较为复杂的项目,文件代码特别多,启动耗时就非常旧。

在我做webpack优化时,发现耗时最长的就是 eslint-webpack-plugin了,我真是觉得这很坑。

看取舍吧,想要提升启动速度,只需添加 lintDirtyModulesOnly 配置。该配置只针对有修改的文件代码进行语法检查

const isDev = appConfig.env.isDev
const isPro = appConfig.env.isPro
const config = {
    plugins:[
        ...
        isDev && new ESLintPlugin({
            exclude: ['node_modules', 'build', 'configs', 'server'],
            lintDirtyModulesOnly: true,
        })
    ].filter(Boolean),
}

生产optimization splitChunks配置

这个不应该放着讲,顺便提一下吧

webpack的optimization配置,能将代码进行归类拆分,根据代码分类编译出多个js文件。 先上配置:

webpackConfig.optimization = {
    ...
    splitChunks: {
        chunks: 'initial',
        cacheGroups: {
            common: {
                test: (module) => {
                    const result = /[\\/]node_modules[\\/]/.test(module.context)
                    return result
                },
                name: 'common',
                priority: 10,
            },
            reactBase: {
                test: (module) => {
                    // const suffix = path.basename(module.context)
                    const curPath = path.parse(module.context)
                    const cwd = process.cwd()
                    const suffix = curPath.dir.replace(cwd, '')
                    const result = /react|redux|prop-types/.test(suffix)
                    return result
                },
                name: 'reactBase',
                priority: 30,
            },
        },
    },
}

入上述配置,根据test进行规则分类匹配划分为公共代码common和react相关的reactBase代码,其余代码和以前一样编译到app.bundle.js中。

这么做的好处: 一般来说,react等包是不进行升级而common公共部分的代码也不是每次更新生产都会有变化,这些模块编译为独立的js代码后。浏览器访问后会进行缓存,生产更新后只需获取最新的 app.bundle.js 资源,而reactBase.js、common.js则直接获取缓存即可。

编译耗时分析工具 speed-measure-webpack-plugin

speed-measure-webpack-plugin 可以对webpack的启动耗时进行分析,有了以下耗时分析数据,就可以根据loader、plugin的耗时有针对性的进行配置优化尝试

image.png

  • 可以看到该项目首次编译启动耗时 1min 32s

image.png

  • 第二次启动获取了缓存,速度明显提升

webpack-bundle-analyzer

如果针对生产编译,还可以使用 webpack-bundle-analyzer 进行编译包分析,针对引用包进行体积优化。

image.png

进行splitChunks后觉得文件体积还是太大,可以开启gzip压缩功能。只需服务端开启gzip的访问资源。

使用 compression-webpack-plugin webpack插件即可编译出压缩代码,下图可以看到 reactBase.js和reactBase.gz文件大小相差将近 10倍

image.png