webpack优化

541 阅读2分钟

统计分析

要会优化必须先学会统计分析。

初级分析

使用 webpack 内置的 stats构建统计信息。

package.json 中使用 stats,在scripts选项中配置:

"build:stats": "webpack --config webpack.prod.js --json > stats.json"

stats.json中的内容:

速度分析

使用 speed-measure-webpack-plugin可以看到每个 loader 和插件执行耗时。

安装:

 yarn add speed-measure-webpack-plugin --dev

配置

// 引入和实例化
const SpeedMeasureWebpackPlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasureWebpackPlugin();

// 使用smp包裹module.exports
module.exports = smp.wrap({
	...loader,
    ...plugins
});

loader 和插件速度分析:

文件大小分析

webpack-bundle-analyzer 分析体积 安装:

 yarn add webpack-bundle-analyzer --dev

配置:

 const WebpackBundleAnalyzer = require("webpack-bundle-analyzer")
  .BundleAnalyzerPlugin;

// 在插件里面配置
plugins:[
	new WebpackBundleAnalyzer(),
]

构建完成后会在 8888 端口展示大小:

可以分析的问题:

  • 依赖的第三方模块文件大小;
  • 业务里面的组件代码大小。

使用高版本的 webpack

优化原因:

  1. V8 带来的优化(for of 替代 forEach、Map 和 Set 替代 Object、includes 替代 indexOf);
  2. 默认使用更快的 md4 hash 算法;
  3. webpacks AST 可以直接从 loader 传递给 AST,减少解析时间;
  4. 使用字符串方法替代正则表达式。

使用 thread-loader 多线程解析资源:

原理:每次 webpack 解析一个模块,thread-loader 会将它及它的依赖分配给 worker 线程中。 安装:

yarn add thread-loader --dev

以解析js为例,配置:

{
  test: /\.js$/,
  exclude: /node_modules/,
  // "eslint-loader"
  use: [{ loader: "thread-loader", options: { workers: 3 } }],
  use: ["babel-loader"],
},

并行压缩

使用terser-webpack-plugin并开启 parallel 参数。
安装:

const TerserWebpackPlugin = require("terser-webpack-plugin");

在optimization里面配置:

optimization: {
  minimizer: [
    new TerserWebpackPlugin({
      parallel: 4, // 或者true
      cache:true
    }),
  ],
},

使用 DLLPlugin 进行分包

将 react、react-dom、redux、react-redux等等的 基础包和业务基础包打包成一个文件。

方法:使用 DLLPlugin 进行分包,DllReferencePlugin 对 manifest.json 引用。

配置webpack.dll.js:

const path = require("path");
const webpack = require("webpack");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
  // 要抽离成dll文件的模块
  entry: { library: ["react", "react-dom"] },
  output: {
    // 文件输出的目录
    path: path.join(__dirname, "build/library"),
    // 生成的dll文件
    filename: "[name].dll.js",
    // 暴露出的全局变量
    output: "[name]_[chunkhash]",
  },
  plugins: [
    new CleanWebpackPlugin(),
    new webpack.DllPlugin({
      // json文件的存放位置
      path: path.join(__dirname, "build/library/[name].json"),
      // dll文件的全局变量名,要跟output.output保持一致
      name: "[name]_[chunkhash]",
    }),
  ],
};

在package.json中配置webpack.dll.js文件的启动命令:

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

运行Webpack,会输出两个文件:

library.json文件内容: Webpack将每个库都进行了编号索引,之后的dll user可以读取这个文件,直接用id来引用。

webpack.prod.js中plugins的配置:

new webpack.DllReferencePlugin({
  manifest: require("./build/library/library.json"),
}),

启动之后index文件明显变小:

在html模板文件中设置引入dll文件:

缓存

目的:提升二次构建速度

缓存思路: 目的:提升二次构建速度

  • babel-loader 开启缓存
  • terser-webpack-plugin 开启缓存
  • 使用 cache-loader 或者 hard-source-webpack-plugin

我建议使用hard-source-webpack-plugin,因为方便,直接在插件引入就行。 安装

yarn add hard-source-webpack-plugin --dev

配置:

const HardSourceWebpackPlugin = require("hard-source-webpack-plugin");

plugins:[
	new HardSourceWebpackPlugin(),
]

第一次打包: 第二次打包:

可以看到时间有明显缩短。

缩小构建目标

在打包时,尽可能的少构建模块,比如 babel-loader 不解析 node_modules。

减少文件搜索范围

Resolve

Webpack 在启动后会从配置的入口模块出发找出所有依赖的模块,Resolve 配置 Webpack 如何寻找模块所对应的文件。 Webpack 内置 JavaScript 模块化语法解析功能,默认会采用模块化标准里约定好的规则去寻找,但你也可以根据自己的需要修改默认的规则。

Resolve参数:

  1. alias配置项通过别名来把原导入路径映射成一个新的导入路径。例如使用以下配置:
resolve:{
  alias:{
    components: './src/components/'
  }
}

当你通过 import Button from 'components/button 导入时,实际上被 alias 等价替换成了 import Button from './src/components/button' 。 2. mainFields,Webpack 会根据 mainFields 的配置去决定优先采用那份代码, mainFields 默认如下:

  mainFields: ['browser', 'main']
  1. extensions:在导入语句没带文件后缀时,Webpack 会自动带上后缀后去尝试访问文件是否存在。 resolve.extensions 用于配置在尝试过程中用到的后缀列表,默认是:
  extensions: ['.js', '.json']
  1. modules:resolve.modules用于配置 Webpack 去哪些目录下寻找第三方模块。默认值是['node_modules'],含义是先去当前目录下的./node_modules目录下去找想找的模块,如果没找到就去上一级目录../node_modules中找。

综合配置:

resolve: {
  alias: {
    react: path.resolve(
      __dirname,
      "./node_modules/react/umd/react.production.min.js"
    ),
    "react-dom": path.resolve(
      __dirname,
      "./node_modules/react-dom/umd/react-dom.production.min.js"
    ),
  },
  extensions: [".js"],
  mainFields: ["main"],
},

删除无用的 CSS

使用 PurifyCSS,遍历代码,识别已经用到的 CSS class。使用 purgecss-webpack-plugin和 mini-css-extract-plugin 配合使用。它会删除没有用到的css class

安装:

yarn add purgecss-webpack-plugin --dev

配置:

const PurgecssPlugin = require("purgecss-webpack-plugin");

plugins:[
	// 使用glob.sync遍历指定目录下的文件,只匹配文件,不匹配目录
    new PurgecssPlugin({
      paths: glob.sync(`${path.join(__dirname, "src")}/**/*`, { nodir: true }),
    }),	
]

图片压缩

使用image-webpack-loader,url-loader 和 image-webpack-loader 不能一起使用,否则会导致图片出不来。一定要先写 'file-loader' 才能使用 'image-webpack-loader'有各种配置,可以调整你要压缩后图片的质量。

提示:如果使用了 webp 会大大减少体积,但是ios并不支持这个格式,会导致在ios上看不见图片。

{
  test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
  use: [
    {
    loader: 'file-loader',
    options: {
        name: '[name].[hash:7].[ext]',
        outputPath: 'mobile/img'
      }
    },
    {
      loader: 'image-webpack-loader',
      options: {
        mozjpeg: {
          progressive: true,
          quality: 50
        },
        // optipng.enabled: false will disable optipng
        optipng: {
          enabled: false,
        },
        pngquant: {
          quality: [0.5, 0.65],
          speed: 4
        },
        gifsicle: {
          interlaced: false,
        },
        //ios不支持
        // webp: {
        //   quality: 100
        // }
      }
    }
  ]
}

动态 Polyfill

Polyfill Service原理: 识别 User Agent,下发不同的 Polyfill,只返回用户需要的Polyfill。

在html模板引入动态Polyfill。

<script crossorigin="anonymous" src="https://polyfill.io/v3/polyfill.min.js"></script>

体积优化策略总结

  1. Scope Hoisting

  2. Tree-shaking

  3. 图片压缩

  4. 动态 Polyfill