【万字解析】Webpack 优化构建性能(分析->优化)

Webpack 优化构建性能

1. 分析构建性能

分析构建体积

  1. 全局安装 webpack-bundle-analyzer 插件

    bash 复制代码
    npm i -g webpack-bundle-analyzer
  2. 运行 webpack-bundle-analyzer

    bash 复制代码
    webpack-bundle-analyzer

分析构建速度

js 复制代码
// webpack.config.js

const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();

module.exports = smp.wrap(prodWebpackConfig)

2. 分析构建过程

  1. 开始打包,需要获取所有的依赖模块

    搜索所有的依赖项,这需要占用一定的时间,即搜索时间,那么就确定了:

    需要优化的第一个时间就是搜索时间

  2. 解析所有的依赖模块(解析成浏览器可运行的代码)

    Webpack 根据配置的 loader 解析相应的文件。日常开发中需要使用 loader 对 JS、CSS、图片、字体等文件做转换操作,并且转换的文件数据量也是非常大。由于 JS 单线程的特性使得这些转换操作不能并发处理文件,而是需要一个个文件进行处理。

    需要优化的第二个时间就是解析时间

  3. 将所有的依赖模块打包到一个文件

    将所有解析完成的代码,打包到一个文件中,为了使浏览器加载的包更新(减小白屏时间),所以 Webpack 会对代码进行优化。 JS 压缩是发布编译的最后阶段,通常 Webpack 需要卡好一会,这是因为压缩 JS 需要先将代码解析成 AST 语法树,然后需要根据复杂的规则去分析和处理 AST,最后将 AST 还原成 JS,这个过程涉及到大量计算,因此比较耗时,打包就容易卡住。

    需要优化的第三个时间就是压缩时间

  4. 二次打包

    当更改项目中一个小小的文件时,需要重新打包,所有的文件都必须要重新打包,需要花费同初次打包相同的时间,但项目中大部分文件都没有变更,尤其是第三方库。

    需要优化的第四个时间就是二次打包时间

3. 优化构建性能

升级版本

使用最新版本的 Webpack 和 Node.js,因为高版本的 Webpack、Node.js 在内置的 API、算法上都更优。


优化搜索时间

缩小文件搜索范围

Webpack 打包时,会从配置的 entry 出发,解析入口文件的导入语句,再递归的解析,在遇到导入语句时 Webpack 会做两件事情:

  1. 根据导入语句去寻找对应的要导入的文件
  2. 根据找到的要导入文件的后缀,使用配置中的 loader 去处理文件

优化 Loader 配置

可以通过 testincludeexclude 来限制 Loader 要应用的文件.

  • test:配置要解析的文件类型
  • include:配置要解析的文件
  • exclude:配置不解析的文件
javascript 复制代码
// webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        // 匹配 js、mjs 文件
        test: /\.m?js$/,
        // 排除 node_modules、bower_components 目录下的文件搜索
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
}

优化 resolve.modules 配置

resolve.modules 用于配置 webpack 解析模块时应该搜索的目录。

resolve.modules 的默认值是 ['node_modules'] ,含义是先去当前目录下的 ./node_modules 目录下去找想找的模块,如果没找到就去上一级目录 ../node_modules 中找,再没有就去 ../../node_modules 中找,以此类推。

  • 相对路径 :如果当前目录下没有 node_modules,则递归到父目录下查找,直到找到
  • 绝对路径:只查找当前目录,不递归
javascript 复制代码
// webpack.config.js

module.exports = {
  resolve: {
    // 将 node_modules 目录下的文件视为 module
    modules: [path.resolve(__dirname, 'node_modules')],
  },
};

优化 resolve.alias 配置

resolve.alias 配置项通过别名来把原导入路径映射成一个新的导入路径,减少耗时的递归解析操作。

javascript 复制代码
// webpack.config.js

const path = require('path');

module.exports = {
  resolve: {
    alias: {
      // 将当前目录下的 src 目录配置别名为 @
      '@': path.resolve(__dirname, 'src'),
    },
  },
};

减少不必要的编译工作

优化 resolve.extensions 配置

导入语句没带文件后缀 时,webpack 会根据 resolve.extension 自动带上后缀后去尝试询问文件是否存在。

javascript 复制代码
// webpack.config.js

module.exports = {
  resolve: {
    // 在解析未带后缀的文件时,会按照 .js -> .css 的顺序进行查找
    extensions: ['.js', '.css'],
  },
};

优化 resolve.mainFields 配置

有一些第三方模块会针对不同环境提供几份代码。 Webpack 会根据 mainFields 的配置去决定优先采用哪份代码

javascript 复制代码
// webpack.config.js

module.exports = {
  resolve: {
    // 先采用 jsnext:main 的代码,再采用 main 的代码
    mainFields: ['jsnext:main', 'main'],
  },
};

优化 module.noParse 配置

module.noParse 配置项可以让 Webpack 忽略对部分没采用模块化的文件的递归解析处理,这样做的好处是能提高构建性能。

原因是一些库,例如:JQuery 、Lodash, 它们庞大又没有采用模块化标准,让 Webpack 去解析这些文件耗时又没有意义。

javascript 复制代码
// webpack.config.js

module.exports = {
  module: {
    // 忽略 jquery 和 lodash 库
    noParse: /jquery|lodash/,
    // 第二种写法
    noParse: (content) => /jquery|lodash/.test(content),
  },
};

如果不使用 symlinks(例如:npm link 或者 yarn link),可以设置 resolve.symlinks: false

javascript 复制代码
// webpack.config.js

module.exports = {
  resolve: {
    symlinks: false
  },
};

优化 resolve.cacheWithContext 配置

如果使用自定义 resolve plugin 规则,并且没有指定 context 上下文,可以设置 resolve.cacheWithContext: false

javascript 复制代码
// webpack.config.js

module.exports = {
  resolve: {
    cacheWithContext: false
  },
};

优化解析时间

多线程并行解析 - thread-Loader

  1. 安装 thread-loader

    javascript 复制代码
    npm i thread-loader
  2. 配置 webpack.config.js

    javascript 复制代码
    // webpack.config.js
    
    module.exports = {
      module: {
        rules: [
          {
            test: /\.js$/,
            include: path.resolve('src'),
            use: [
              "thread-loader", // 一定要放在其他 loader 后面
            ],
          },
        ],
      },
    };

使用资源模块替换 Loader

使用 Webpack 资源模块(asset module)代替旧的 Assets Loader(例如:file-loader/url-loader/raw-loader 等),减少 Loader 配置数量。


使用 HappyPack

HappyPack 能让 Webpack 多线程解析 Loader,它把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。

  1. 安装 happypack

    javascript 复制代码
    npm i -D happypack
  2. 配置 webpack.config.js

javascript 复制代码
// webpack.config.js

const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HappyPack = require('happypack');

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        // 把对 js 文件的处理转交给 id 为 babel 的 HappyPack 实例
        use: ['happypack/loader?id=babel'],
        // 排除 node_modules 目录下的文件,node_modules 目录下的文件都是采用的 ES5 语法,没必要再通过 Babel 去转换
        exclude: path.resolve(__dirname, 'node_modules'),
      },
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          // 把对 css 文件的处理转交给 id 为 css 的 HappyPack 实例
          use: ['happypack/loader?id=css'],
        }),
      },
    ]
  },
  plugins: [
    new HappyPack({
      // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
      id: 'babel',
      // 如何处理 js 文件,用法和 Loader 配置中一样
      loaders: ['babel-loader?cacheDirectory'],
    }),
    new HappyPack({
      id: 'css',
      loaders: ['css-loader'],
    }),
    new ExtractTextPlugin({
      filename: `[name].css`,
    }),
  ],
};

优化压缩时间

多线程压缩

Webpack v3
  1. 安装 webpack-parallel-uglify-plugin

    javascript 复制代码
    npm i -D webpack-parallel-uglify-plugin
  2. 配置 webpack.config.js

    javascript 复制代码
    // webpack.config.js
    
    const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
    
    module.exports = {
      plugins: [
        // 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
        new ParallelUglifyPlugin({
          // 传递给 UglifyJS 的参数
          uglifyJS: {
            output: {
              // 最紧凑的输出
              beautify: false,
              // 删除所有的注释
              comments: false,
            },
            compress: {
              // 在 UglifyJs 删除没有用到的代码时不输出警告
              warnings: false, 
              // 删除所有的 console 语句,可以兼容 ie 浏览器
              drop_console: true,
              // 内嵌定义了但是只用到一次的变量
              collapse_vars: true,
              // 提取出出现多次但是没有定义成变量去引用的静态值
              reduce_vars: true,
            }
          },
        }),
      ],
    };
Webpack v4
  1. 安装 terser-webpack-plugin

    bash 复制代码
    npm i terser-webpack-plugin
  2. 配置 webpack.config.js

    javascript 复制代码
    // webpack.config.js
    
    const TerserPlugin = require("terser-webpack-plugin");
    
    module.exports = {
      optimization: {
        minimize: true,
        minimizer: [
          new TerserPlugin({
            parallel: 4,
            terserOptions: {
              parse: {
                ecma: 8,
              },
              compress: {
                ecma: 5,
                warnings: false,
                comparisons: false,
                inline: 2,
              },
              mangle: {
                safari10: true,
              },
              output: {
                ecma: 5,
                comments: false,
                ascii_only: true,
              },
            },
          })
        ],
      },
    };

减少压缩体积

压缩 CSS - CssMinimizerWebpackPlugin
javascript 复制代码
// webpack.config.js

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  optimization: {
    minimizer: [
      new CssMinimizerPlugin({ parallel: 4, }),
    ],
  }
}

分包 - SplitChunks
javascript 复制代码
// webpack.config.js

module.exports = {
  optimization: {
    /**
     * 把 JS 文件打包成3中类型:
     * 1. vendor:第三方 lib 库,基本不会改动,除非依赖版本升级
     * 2. common:业务组件代码的公共部分抽取出来,改动较少
     * 3. entry.{page}:不同页面 entry 里业务组件代码的差异部分,会经常改动
     * 这样分的好处是尽量按改动频率来区分,利用好浏览器缓存
     */
     splitChunks: {
       chunks: 'all',
       maxInitialRequests: 4, // 防止切分粒度过细,请求过多,约束为4
       cacheGroups: {
         vendor: { // 第三方
           test: /[\\/]node_modules[\\/]/, // 打包 node_module 中的文件
           name: 'vendor', // 模块名称
           priority: 10, // 优先级 数字越大 优先级越高
           enforce: true, // 强制执行
           reuseExistingChunk: true, // 重用已有模块
         },
         common: { // 公共模块
           name: 'common',
           minChunks: 2, // 被引用两处即被归纳到公共模块
           minSize: 1, // 最小 size
           priority: 5, // 优先级
           reuseExistingChunk: true, // 重用已有模块
         },
       },
     },
     // 将 webpack 运行时生成代码打包到 runtime.js
     runtimeChunk: true,
  },
}

合并公共代码 - CommonsChunkPlugin

Webpack v4 移除,通过 optimization.splitChunks 替代。

javascript 复制代码
// webpack.config.js

const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');

module.exports = {
  plugins: [
    new CommonsChunkPlugin({
      chunks: ['common', 'base'], // 从 common 和 base 两个现成的 Chunk 中提取公共的部分
      name: 'base' // 把公共的部分放到 base 中
    })
  ]
}

Tree Shaking

Tree Shaking 是指打包时移除哪些没有使用(未引入)的代码。


按需加载
  1. 代码中通过 import() 按需加载 Chunk

    javascript 复制代码
    // main.js
    
    window.document.getElementById('btn').addEventListener('click', function () {
      // 当按钮被点击后才去加载 show.js 文件,文件加载成功后执行文件导出的函数
      import(/* webpackChunkName: "show" */ './show').then((show) => {
        show('Webpack');
      })
    });
  2. webpack.config.js 中配置动态 Chunk 的输出

javascript 复制代码
// webpack.config.js

module.exports = {
  entry: {
    main: './main.js',
  },
  output: {
    filename: '[name].js',
    // 为动态加载的 Chunk 配置输出文件的名称
    chunkFilename: '[name].js',
  }
};

开启 Scope Hoisting

Scope Hoisting 可以让 Webpack 打包出来的代码文件更小、运行的更快, 它又译作 "作用域提升",是在 Webpack3 中新推出的功能。

javascript 复制代码
// webpack.config.js​
​
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');​
​
module.exports = {​
  resolve: {​
    // 针对 Npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件​
    mainFields: ['jsnext:main', 'browser', 'main']​
  },​
  plugins: [​
    // 开启 Scope Hoisting​
    new ModuleConcatenationPlugin(),​
  ],​
};

优化二次打包时间

缓存

增加初次构建时间,缩短后续构建时间。

  • 利用 cache-loaderHardSourceWebpackPluginbabel-loadercacheDirectory 标志等。
  • cache
javascript 复制代码
// webpack.config.js

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

静态资源不再打包

DllPlugin
javascript 复制代码
// webpack_dll.config.js - 构建出动态链接库文件

const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');

module.exports = {
  entry: {
    // 把 React 相关模块的放到一个单独的动态链接库
    react: ['react', 'react-dom'],
    // 把项目需要所有的 polyfill 放到一个单独的动态链接库
    polyfill: ['core-js/fn/object/assign', 'core-js/fn/promise', 'whatwg-fetch'],
  },
  output: {
    filename: '[name].dll.js',
    path: path.resolve(__dirname, 'dist'),
    // 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
    // 之所以在前面加上 _dll_ 是为了防止全局变量冲突
    library: '_dll_[name]',
  },
  plugins: [
    // 接入 DllPluginnew
    DllPlugin({
      // 动态链接库的全局变量名称,需要和 output.library 中保持一致
      // 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
      // 例如 react.manifest.json 中就有 "name": "_dll_react"
      name: '_dll_[name]',
      // 描述动态链接库的 manifest.json 文件输出时的文件名称
      path: path.join(__dirname, 'dist', '[name].manifest.json'),
    }),
  ],
};
javascript 复制代码
// webpack.config.js

const path = require('path');
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');

module.exports = {
  plugins: [
    // 告诉 Webpack 使用了哪些动态链接库
    new DllReferencePlugin({
      // 描述 react 动态链接库的文件内容
      manifest: require('./dist/react.manifest.json'),
    }),
    new DllReferencePlugin({
      // 描述 polyfill 动态链接库的文件内容
      manifest: require('./dist/polyfill.manifest.json'),
    }),
  ],
};

合理配置输出文件名

javascript 复制代码
// webpack.config.js

module.exports = {
  output: {
    path: path.resolve(__dirname, '../dist'),
    // 给 js 文件加上 contenthash
    filename: 'js/chunk-[contenthash].js',
    clean: true,
  },
}

区分环境

  • 开发 环境中,切忌在开发环境使用生产环境才会用到的工具,例如:在开发环境下,应该排除 [fullhash]/[chunkhash]/[contenthash] 等工具。
  • 生产 环境中,也应该避免使用开发环境才会用到的工具,例如:webpack-dev-server 等插件。

4. 完整优化配置

开发环境的配置文件 webpack.dev.config.js

javascript 复制代码
// webpack.dev.config.js

const path = require('path');
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
const {AutoWebPlugin} = require('web-webpack-plugin');
const HappyPack = require('happypack');

// 自动寻找 pages 目录下的所有目录,把每一个目录看成一个单页应用const autoWebPlugin = new AutoWebPlugin('./src/pages', {
  // HTML 模版文件所在的文件路径
  template: './template.html',
  // 提取出所有页面公共的代码
  commonsChunk: {
    // 提取出公共代码 Chunk 的名称
    name: 'common',
  },
});

module.exports = {
  // AutoWebPlugin 会找为寻找到的所有单页应用,生成对应的入口配置,// autoWebPlugin.entry 方法可以获取到生成入口配置
  entry: autoWebPlugin.entry({
    // 这里可以加入你额外需要的 Chunk 入口
    base: './src/base.js',
  }),
  output: {
    filename: '[name].js',
  },
  resolve: {
    // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤// 其中 __dirname 表示当前工作目录,也就是项目根目录
    modules: [path.resolve(__dirname, 'node_modules')],
    // 针对 Npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件,使用 Tree Shaking 优化// 只采用 main 字段作为入口文件描述字段,以减少搜索步骤
    mainFields: ['jsnext:main', 'main'],
  },
  module: {
    rules: [
      {
        // 如果项目源码中只有 js 文件就不要写成 /\.jsx?$/,提升正则表达式性能
        test: /\.js$/,
        // 使用 HappyPack 加速构建
        use: ['happypack/loader?id=babel'],
        // 只对项目根目录下的 src 目录中的文件采用 babel-loader
        include: path.resolve(__dirname, 'src'),
      },
      {
        test: /\.js$/,
        use: ['happypack/loader?id=ui-component'],
        include: path.resolve(__dirname, 'src'),
      },
      {
        // 增加对 CSS 文件的支持
        test: /\.css$/,
        use: ['happypack/loader?id=css'],
      },
    ]
  },
  plugins: [
    autoWebPlugin,
    // 使用 HappyPack 加速构建new HappyPack({
      id: 'babel',
      // babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
      loaders: ['babel-loader?cacheDirectory'],
    }),
    new HappyPack({
      // UI 组件加载拆分
      id: 'ui-component',
      loaders: [{
        loader: 'ui-component-loader',
        options: {
          lib: 'antd',
          style: 'style/index.css',
          camel2: '-'
        }
      }],
    }),
    new HappyPack({
      id: 'css',
      // 如何处理 .css 文件,用法和 Loader 配置中一样
      loaders: ['style-loader', 'css-loader'],
    }),
    // 提取公共代码new CommonsChunkPlugin({
      // 从 common 和 base 两个现成的 Chunk 中提取公共的部分
      chunks: ['common', 'base'],
      // 把公共的部分放到 base 中
      name: 'base'
    }),
  ],
  watchOptions: {
    // 使用自动刷新:不监听的 node_modules 目录下的文件
    ignored: /node_modules/,
  }
};

生产环境的配置文件 webpack.prod.config.js

javascript 复制代码
// webpack.prod.config.js

const path = require('path');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const { AutoWebPlugin } = require('web-webpack-plugin');
const HappyPack = require('happypack');
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');

// 自动寻找 pages 目录下的所有目录,把每一个目录看成一个单页应用
const autoWebPlugin = new AutoWebPlugin('./src/pages', {
  // HTML 模版文件所在的文件路径
  template: './template.html',
  // 提取出所有页面公共的代码
  commonsChunk: {
    // 提取出公共代码 Chunk 的名称
    name: 'common',
  },
  // 指定存放 CSS 文件的 CDN 目录 URL
  stylePublicPath: '//css.cdn.com/id/',
});

module.exports = {
  // AutoWebPlugin 会找为寻找到的所有单页应用,生成对应的入口配置,
  // autoWebPlugin.entry 方法可以获取到生成入口配置
  entry: autoWebPlugin.entry({
    // 这里可以加入你额外需要的 Chunk 入口
    base: './src/base.js',
  }),
  output: {
    // 给输出的文件名称加上 Hash 值
    filename: '[name]_[chunkhash:8].js',
    path: path.resolve(__dirname, './dist'),
    // 指定存放 JavaScript 文件的 CDN 目录 URL
    publicPath: '//js.cdn.com/id/',
  },
  resolve: {
    // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
    // 其中 __dirname 表示当前工作目录,也就是项目根目录
    modules: [path.resolve(__dirname, 'node_modules')],
    // 只采用 main 字段作为入口文件描述字段,以减少搜索步骤
    mainFields: ['jsnext:main', 'main'],
  },
  module: {
    rules: [
      {
        // 如果项目源码中只有 js 文件就不要写成 /\.jsx?$/,提升正则表达式性能
        test: /\.js$/,
        // 使用 HappyPack 加速构建
        use: ['happypack/loader?id=babel'],
        // 只对项目根目录下的 src 目录中的文件采用 babel-loader
        include: path.resolve(__dirname, 'src'),
      },
      {
        test: /\.js$/,
        use: ['happypack/loader?id=ui-component'],
        include: path.resolve(__dirname, 'src'),
      },
      {
        // 增加对 CSS 文件的支持
        test: /\.css$/,
        // 提取出 Chunk 中的 CSS 代码到单独的文件中
        use: ExtractTextPlugin.extract({
          use: ['happypack/loader?id=css'],
          // 指定存放 CSS 中导入的资源(例如图片)的 CDN 目录 URL
          publicPath: '//img.cdn.com/id/'
        }),
      },
    ]
  },
  plugins: [
    autoWebPlugin,
    // 开启ScopeHoisting
    new ModuleConcatenationPlugin(),
    // 使用HappyPack
    new HappyPack({
      // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
      id: 'babel',
      // babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
      loaders: ['babel-loader?cacheDirectory'],
    }),
    new HappyPack({
      // UI 组件加载拆分
      id: 'ui-component',
      loaders: [{
        loader: 'ui-component-loader',
        options: {
          lib: 'antd',
          style: 'style/index.css',
          camel2: '-'
        }
      }],
    }),
    new HappyPack({
      id: 'css',
      // 如何处理 .css 文件,用法和 Loader 配置中一样
      // 通过 minimize 选项压缩 CSS 代码
      loaders: ['css-loader?minimize'],
    }),
    new ExtractTextPlugin({
      // 给输出的 CSS 文件名称加上 Hash 值
      filename: `[name]_[contenthash:8].css`,
    }),
    // 提取公共代码
    new CommonsChunkPlugin({
      // 从 common 和 base 两个现成的 Chunk 中提取公共的部分
      chunks: ['common', 'base'],
      // 把公共的部分放到 base 中
      name: 'base'
    }),
    new DefinePlugin({
      // 定义 NODE_ENV 环境变量为 production 去除 react 代码中的开发时才需要的部分
      'process.env': {
        NODE_ENV: JSON.stringify('production')
      }
    }),
    // 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
    new ParallelUglifyPlugin({
      // 传递给 UglifyJS 的参数
      uglifyJS: {
        output: {
          // 最紧凑的输出
          beautify: false,
          // 删除所有的注释
          comments: false,
        },
        compress: {
          // 在 UglifyJs 删除没有用到的代码时不输出警告
          warnings: false,
          // 删除所有的 `console` 语句,可以兼容ie浏览器
          drop_console: true,
          // 内嵌定义了但是只用到一次的变量
          collapse_vars: true,
          // 提取出出现多次但是没有定义成变量去引用的静态值
          reduce_vars: true,
        }
      },
    }),
  ]
};
相关推荐
10年前端老司机1 小时前
什么!纯前端也能识别图片中的文案、还支持100多个国家的语言
前端·javascript·vue.js
摸鱼仙人~1 小时前
React 性能优化实战指南:从理论到实践的完整攻略
前端·react.js·性能优化
程序员阿超的博客2 小时前
React动态渲染:如何用map循环渲染一个列表(List)
前端·react.js·前端框架
magic 2452 小时前
模拟 AJAX 提交 form 表单及请求头设置详解
前端·javascript·ajax
小小小小宇7 小时前
前端 Service Worker
前端
只喜欢赚钱的棉花没有糖7 小时前
http的缓存问题
前端·javascript·http
小小小小宇8 小时前
请求竞态问题统一封装
前端
loriloy8 小时前
前端资源帖
前端
源码超级联盟8 小时前
display的block和inline-block有什么区别
前端
GISer_Jing8 小时前
前端构建工具(Webpack\Vite\esbuild\Rspack)拆包能力深度解析
前端·webpack·node.js