Webpack 性能优化

一、优化打包构建速度 - 开发体验和效率

(1)优化 babel-loader

{
    test: /\.js$/,
    use: ['babel-loader?cacheDirectory'], // 开启缓存
    include: path.resolve(__dirname, 'src'), // 明确范围
    // 排除范围, include 和 exclude  两者选一个即可 
    // exclude: path.resolve(__dirname, 'node_modules')
}

(2)happyPack -  多进程打包工具

JS 单线程,开启多进程打包;提高构建速度(特别是多核 CPU)。HappyPack 可以将 Loader 的同步执⾏转换为并⾏的。

build-optimization/webpack.prod.js
const HappyPack = require('happypack')
module.exports = {
    mode: 'production',
    ......
    module: {
        rules: [
            {
                test: /\.js$/,
                // 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
                use: ['happypack/loader?id=babel'],
                include: path.resolve(__dirname, 'src'),
                // exclude: /node_modules/
            }
            ...
        ]
    },
    plugins: [
        ...
        // happyPack 开启多进程打包
        new HappyPack({
            id: 'babel', // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
            loaders: [babel-loader?cacheDirectory], // 如何处理 .js 文件,用法和 loader 配置中一样
        })
    ],
    ...
}

(3)自动刷新

整个网页全部刷新,速度较慢,状态会丢失

配置 devServer,默认开启自动刷新!!!

(4)热更新

新代码生效,网页不刷新,状态不丢失

build-optimization/webpack.dev.js
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin')
module.exports = {
    mode: 'development',
    entry: {
        index: [
            'webpack-dev-server/client?http://localhost:8080',
            'webpack/hot/dev-server',
            path.join(srcPath, 'index.js')
        ]
    },
    ......
    plugins: [
        new webpack.DefinePlugin({
            ENV: JSON.stringify('development')
        }),
        new HotModuleReplacementPlugin()
    ],
    devServer: {
        port: 8080,
        progress: true, // 显示打包的进度条
        contentBase: distPath, // 根目录
        open: true, // 自动打开浏览器
        compress: true, // 启动 gzip 压缩        
        hot: true,  // 热更新开启
        proxy: { ... } // 设置代理
    }
}

(5)DllPlugin - 比较大的库

DllPlugin 可以将特定的类库提前打包,然后引入。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,也实现了将公共代码抽离成单独文件的优化方案。

webpack-dll-demo/build/webpack.dll.js
const path = require('path')
const DllPlugin = require('webpack/lib/DllPlugin')
const { srcPath, distPath } = require('./paths')

module.exports = {
    mode: 'development',
    entry: {
        // 把 React 相关模块的放到一个单独的动态链接库
        react: ['react', 'react-dom']
    },
    output: {
        // 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,也就是 entry 中配置的 react 和 polyfill
        filename: '[name].dll.js',
        // 输出的文件都放到 dist 目录下
        path: distPath,
        // 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react,之所以在前面加上 _dll_ 是为了放在全局变量冲突
        library: '_dll_[name]'
    }, 
    plugins: [
        // 接入 DllPlugin
        new DllPlugin({
            // 动态链接库的全局变量名称,需要和 output.library 中保持一直,该字段的值也就是输出的 manifest.json 文件中 name 字段的值
            // 例如 react.manifest.json 中就有 'name':'_dll_react'
            name: '_dll_[name]',
            // 描述动态链接库的 manifest.json 文件输出时的文件名称
            path: path.join(distPath, '[name].manifest.json')
        })
    ]
}

在 package.json 文件中,配置以下信息,且运行 npm run dll 命令。

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

使⽤ DllReferencePlugin 将依赖⽂件引⼊项⽬中

const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
module.exports = {
    ......
    plugins: [
        new DllReferencePlugin({
            manifest: require(path.join(distPath, 'react.manifest.json'))  // manifest 就是之前打包出来的 json ⽂件
        })
    ]
}

二、优化产出代码 - 产品性能

体积更小;合理分包,不重复加载;速度更快,内存使用更少

(1)小图片 base64 编码

module.exports = {
    mode: 'production',
    module: {
        rules: [
            {
                test: /\.(png|jpg|jpeg|gif)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        limit: 5 * 1024, // 小于 5KB 的图片用 base64 格式产出;否则依然延用 file-loader 的形式,产出 url 格式
                        outputPath: '/img1/', // 打包到 img 目录下
                        publicPath: 'http://cdn.abc.com'  // 设置图片的 CDN 地址
                    }
                }
            }
        ]
    },
    ...
}

(2)bundle 加 hash

如果文件内容没有改变,会命中缓存,浏览器加载会更快些。

module.exports = {
    mode: 'production',
    output: {
        filename: 'bundle.[contentHash:8].js',  -- 重点
        path: distPath,
    },
}

(3)懒加载

通过 import 的语法,先把重要的加载出来,把大的文件异步加载出来

(4)提取公共代码

有些公共代码,我们不需要在多个入口中,重复地打包进去,做一个公共的包,让他们相互引用就可以了。这样可以使我们整个打包出来的体积更小些。

module.exports = {
    ......
    plugins: [
        ...
        // 抽离 CSS 文件
        new MiniCssExtractPlugin({
            filename: 'css/main.[contentHash:8].css'
        })
    ],
    optimization: {
        // 压缩 CSS
        minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],

        // 分割代码块
        splitChunks: {
            
            chunks: 'all',

            // 缓存分组
            cacheGroups: {
                // 第三方模块
                vendor: {
                    name: 'vendor', // chunk 名称
                    priority: 1, // 权限更高,优先抽离,重要!
                    test: /node_modules/,
                    minSize: 0, // 大小限制
                    minChunks: 1 // 最少复用次数
                }

                // 公共的模块
                common: {
                    name: 'common', // chunk 名称
                    priority: 0, // 优先级
                    minSize: 0, // 大小限制
                    minChunks: 2f // 最少复用次数
                }
            }
        }
    }
}

(5)使用 CDN 加速

module.exports = {
    mode: 'production',
    output: {
        filename: 'bundle.[contentHash:8].js',
        path: distPath,
        publicPath: 'http://cdn.abc.com' // 修改所有静态文件 url 的前缀(如 CDN 域名)
    },
    ......
}

(6)使用 production - 开启自动压缩、开启 Tree-Shaking 

1.自动开启压缩代码 - 代码体积更小些,浏览更快些

2.Tree-Shaking  可以实现删除项目中未被引用的代码,会自动删掉调试代码(如开发环境的 warning)。

注意:必须用 ES6 Module 才能让 tree-shaking 生效,commonjs 就不行。

(7)Scope Hosting -多个函数合成一个函数,减少作用域,执行更快一些

Scope Hosting 会分析出模块之间的依赖关系,尽可能的把打包处理的模块合并到一个函数中去。

const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin')
module.exports = {
    resolve: {
        // 针对 npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件
        mainFields: ['jsnext:main', 'browser', 'main']
    },
    plugins: [
        // 开启 Scope Hosting
        new ModuleConcatenationPlugin()
    ]
}

 

你可能感兴趣的