CD's blog CD's blog
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

CD_wOw

内卷的行情,到不了的梦
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Webpack优化知识点
    • webpack 优化分析
    • webpack 优化
      • 1. 缩小文件的搜索范围
      • 2. 减少编译次数及并发打包
      • exclude/include
      • cache-loader
      • happypack
    • vue-webpack5.0 搭建项目
  • Webpack的plugin和loader开发
  • webpack笔记
CD
2020-07-18
目录

Webpack优化知识点

# webpack 优化分析

Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:

  • 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数
  • 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
  • 确定入口:根据配置中的 entry 找出所有的入口文件
  • 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
  • 完成模块编译:在经过上一步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
  • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  • 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统;
  1. 开始打包,我们需要获取所有的依赖模块(开始编译及确定入口) .搜索所有的依赖项,这需要占用一定的时间,即搜索时间,那么我们就确定了:我们需要优化的第一个时间就是搜索时间(Webpack 优化构建速度-缩小文件搜索范围)。
  2. 解析所有的依赖模块(解析成浏览器可运行的代码) .webpack 根据我们配置的 loader 解析相应的文件。日常开发中我们需要使用 loader 对 js ,css ,图片,字体等文件做转换操作,并且转换的文件数据量也是非常大。由于 js 单线程的特性使得这些转换操作不能并发处理文件,而是需要一个个文件进行处理。 我们需要优化的第二个时间就是解析时间,(Webpack 优化构建速度-减少编译次数及并发打包)。
  3. 将所有的依赖模块打包到一个文件. 将所有解析完成的代码,打包到一个文件中,为了使浏览器加载的包更新(减小白屏时间),所以 webpack 会对代码进行优化。

JS 压缩是发布编译的最后阶段,通常 webpack 需要卡好一会,这是因为压缩 JS 需要先将代码解析成 AST 语法树,然后需要根据复杂的规则去分析和处理 AST,最后将 AST 还原成 JS,这个过程涉及到大量计算,因此比较耗时,打包就容易卡住。我们需要优化的第三个时间就是压缩时间。(优化输出质量-压缩文件体积)

  1. 二次打包 当更改项目中一个小小的文件时,我们需要重新打包,所有的文件都必须要重新打包,需要花费同初次打包相同的时间,但项目中大部分文件都没有变更,尤其是第三方库。 我们需要优化的第四个时间就是二次打包时间。(缓存)

# webpack 优化

量化

有时,我们以为的优化是负优化,这时,如果有一个量化的指标可以看出前后对比,那将会是再好不过的一件事。 speed-measure-webpack-plugin 插件可以测量各个插件和 loader 所花费的时间,使用之后,构建时,会得到类似下面这样的信息: smp.jpeg 对比前后的信息,来确定优化的效果。 speed-measure-webpack-plugin 的使用很简单,可以直接用其来包裹 Webpack 的配置:

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

const config = {
  //...webpack配置
};

module.exports = smp.wrap(config);
1
2
3
4
5
6
7
8

# 1. 缩小文件的搜索范围

  1. resolve.modules 配置

    resolve.modules 用于配置 Webpack 去哪些目录下寻找第三方模块。resolve.modules 的默认值是[node modules],含义是先去当前目录的/node modules 目录下去找我们想找的模块,如果没找到,就去上一级目录../node modules 中找,再没有就去../ .. /node modules 中找,以此类推,这和 Node.js 的模块寻找机制很相似。当安装的第三方模块都放在项目根目录的./node modules 目录下时,就没有必要按照默认的方式去一层层地寻找,可以指明存放第三方模块的绝对路径,以减少寻找。

resolve: {
// 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
  'vant': [path.resolve(__dirname,'node_modules')],
  'cd-ui': [path.resolve(__dirname,'./src/plugin')]
},
1
2
3
4
5
  1. resolve.alias 配置

    通过配置 alias 快速到达指定文件的所在地。如果是针对第三方库做简写会影响 Tree-Shaking,适合对整体性比较强的库使用,如果是像 lodash 这类工具类的比较分散的库,比较适合 Tree-Shaking,避免使用这种方式影响到按需引入。

alias: {
'@': resolve('src'),
},
1
2
3
  1. exclude/include 配置

    可以通过 exclude/include 配置来确保转译尽可能少的文件。excldue 指定要排除的文件,include 指定要包含的文件。 exclude 的优先级高于 include,在 include 和 exclude 中使用绝对路径数组,尽量避免 exclude,更倾向于使用 include。 建议给 loader 指定 include 或是 exclude,指定其中一个即可,因为 node_modules 目录通常不需要我们去编译,排除后,有效提升编译效率。

const path = require("path");
module.exports = {
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env"],
          },
        },
        include: [path.resolve(__dirname, "src")],
        excludes: ["xxx"], // 减少基础模块编译次数
      },
    ],
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  1. resolve.extensions 配置

合理配置 resolve.extensions,减少文件查找 默认值:extensions:['.js', '.json'],当导入语句没带文件后缀时,Webpack 会根据 extensions 定义的后缀列表进行文件查找,对于书写 extensions,我们尽量遵循以下规则:

  • 列表值尽量少
  • 频率高的文件类型的后缀写在前面
  • 源码中的导入语句尽可能的写上文件后缀,如 require(./data)要写成 require(./data.json)
  1. module.noParse 配置

    告诉 Webpack 不必解析哪些文件,从而提高构建性能。对于一些没有采用模块化(AMD/CommonJS 规范版本)或是本身已经进行了压缩的文件来说,对他们在进行压缩既耗时且没有意义。我们可以使用 noParse 来标识这个模块,这样 Webpack 会引入这些模块,但是不进行转化和解析,从而提升 Webpack 的构建性能 ,例如:jquery 、lodash。用法:

module.exports = {
  //...
  module: {
    // 使用正则表达式
    noParse: /jquery|lodash/;
    // 使用函数,从 Webpack3.0.0开始支持
    noParse: (content) => {
      // 返回true或false
      return /jquery|element-ui/.test(content);
    };
  },
};

1
2
3
4
5
6
7
8
9
10
11
12
13

# 2. 减少编译次数及并发打包

  1. 利用缓存如 cache-loader 做打包缓存

在一些性能开销较大的 loader 之前添加 cache-loader,将结果缓存中磁盘中。默认保存在 node_modueles/.cache/cache-loader 目录下。再下次打包的时候对于重复的代码则会被复用。

  • npm install cache-loader -D
module.exports = {
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: ["cache-loader", "babel-loader"],
      },
    ],
  },
};
1
2
3
4
5
6
7
8
9
10

与其功能类似的还有:

  • HardSourceWebpackPlugin 为模块提供中间缓存,缓存默认的存放路径是: node_modules/.cache/hard-source。HardSourceWebpackPlugin 建议不要和 cache-loader 一起使用,初始化的时候会产生创建缓存等开销,如果两个一起使用,相当于对很多同样的内容做了两次缓存。
  • babel-loader 的 cacheDirectory 标志

如果你跟我一样,只打算给 babel-loader 配置 cache 的话,也可以不使用 cache-loader,给 babel-loader 增加选项 cacheDirectory。 cacheDirectory:默认值为 false。当有设置时,指定的目录将用来缓存 loader 的执行结果。之后的 Webpack 构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel 重新编译过程。设置空值或者 true 的话,使用默认缓存目录:node_modules/.cache/babel-loader。开启 babel-loader 的缓存和配置 cache-loader,我比对了下,构建时间很接近。

  1. DllPlugin 减少基础模块编译次数

DllPlugin 动态链接库插件,其原理是把网页依赖的基础模块抽离出来打包到 dll 文件中,当需要导入的模块存在于某个 dll 中时,这个模块不再被打包,而是去 dll 中获取。 为什么会提升构建速度呢? 原因在于 dll 中大多包含的是常用的第三方模块,如 react、react-dom,所以只要这些模块版本不升级,就只需被编译一次。我认为这样做和配置 resolve.alias 和 module.noParse 的效果有异曲同工的效果。

  1. happypack 或是 thread-loader(webpack4 之后) 多进程解析处理文件

当有大量的文件需要解析和处理的时候,wbepack 的构建会变得很慢。HappyPack 可以把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。让 webpack 在同一时刻处理多个任务,从而提升构建速度。

const Happypack = require("happypack");
module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.js[x]?$/,
        use: "Happypack/loader?id=js",
        include: [path.resolve(__dirname, "src")],
      },
      {
        test: /\.css$/,
        use: "Happypack/loader?id=css",
        include: [
          path.resolve(__dirname, "src"),
          path.resolve(__dirname, "node_modules", "bootstrap", "dist"),
        ],
      },
    ],
  },
  plugins: [
    new Happypack({
      id: "js", //和rule中的id=js对应
      //将之前 rule 中的 loader 在此配置
      use: ["babel-loader"], //必须是数组
    }),
    new Happypack({
      id: "css", //和rule中的id=css对应
      use: ["style-loader", "css-loader", "postcss-loader"],
    }),
  ],
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

如果使用的是 thread-loader, 把它放置在其他 loader 之前, 放置在 thread-loader 之后的 loader 就会在一个单独的 worker 池(worker pool)中运行。 thread-loader 的一些限制

  • 这些 loader 不能产生新的文件。
  • 这些 loader 不能使用定制的 loader API(也就是说,通过插件)。
  • 这些 loader 无法获取 webpack 的选项设置。
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        // 创建一个 js worker 池
        use: ["thread-loader", "babel-loader"],
      },
      {
        test: /\.s?css$/,
        exclude: /node_modules/,
        // 创建一个 css worker 池
        use: [
          "style-loader",
          "thread-loader",
          {
            loader: "css-loader",
            options: {
              modules: true,
              localIdentName: "[name]__[local]--[hash:base64:5]",
              importLoaders: 1,
            },
          },
          "postcss-loader",
        ],
      },
    ],
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  1. terser-webpack-plugin 默认开启多进程压缩优化 缓存文件在 node-modules/.cache 里。

webpack4 中 webpack.optimize.UglifyJsPlugin 已被废弃。webpack4 默认内置使用 terser-webpack-plugin 插件压缩优化代码,而该插件使用 terser 来缩小 JavaScript。

module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true,
      }),
    ],
  },
};
1
2
3
4
5
6
7
8
9
  1. IgnorePlugin 忽略第三方指定目录 当引入一些第三方包的时候,有的包经常会带有没有压缩的文件、各国语言的文件,比如 Select2 就会带上一排国家语言包 OwO,这个时候我们就可以使用 IgnorePlugin 在打包时忽略 本地化内容。 例如
module.exports = {
    //...
    plugins: [
        //忽略 moment 下的 ./locale 目录
        new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
        new webpack.IgnorePlugin(/^\.\/dist/js/i18n$/, /select2$/)
    ]
}

// 在引入时:
import moment from 'moment';
import 'moment/locale/zh-cn';// 手动引入语言包

import select2 from 'select2';
import 'select2/dist/js/i18n/zh-cn';// 手动引入语言包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. 抽离公共代码 -- 详看打包配置

将公共代码放置 common 目录、ui 框架提取、从不同的入口文件抽取需要的公共代码、抽取 webpack 运行资源.在浏览器运行时,webpack 用来连接模块化的应用程序的所有代码。runtime 包含:在模块交互时,连接模块所需的加载和解析逻辑。包括浏览器中的已加载模块的连接,以及懒加载模块的执行逻辑。

module.exports = {
  optimization: {
    splitChunks: {
      //分割代码块

      chunks: "all", //  (默认是 async) :initial、async 和 all
      minSize: 30000, // (默认是 30000):形成一个新代码块最小的体积
      minChunks: 1, //  (默认是 1)在分割之前,这个代码块最小应该被引用的次数(译注:保证代码块复用性,默认配置的策略是不需要多次引用也可以被分割)
      cacheGroups: {
        vendor: {
          //第三方依赖
          priority: 1, //设置优先级,首先抽离第三方模块
          name: "vendor",
          test: /node_modules/,
          chunks: "initial",
          minSize: 0,
          minChunks: 1, //最少引入了1次
        },
        //缓存组
        common: {
          //公共模块
          chunks: "initial",
          name: "common",
          minSize: 100, //大小超过100个字节
          minChunks: 3, //最少引入了3次
        },
      },
    },
    runtimeChunk: {
      name: "manifest",
    },
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  1. 使用 Tree Shaking 剔除 JS 无用代码

tree-shaking 依赖 ES6 的 import、export 的模块化语法.它正常工作的前提是代码必须采用 ES6 的模块化语法,因为 ES6 模块化语法是静态的(在导入、导出语句中的路径必须是静态字符串,且不能放入其他代码块中。如果采用了 ES5 中的模块化,例如 module.export = {...}、require( x+y )、if (x) { require( './util' ) },则 Webpack 无法分析出可以剔除哪些代码。下面启动 tree-shaking

  • 修改.babelrc 以保留 ES6 模块化语句:
{
  "presets": [
    [
      "env",
      { "module": false } //关闭Babel的模块转换功能,保留ES6模块化语法
    ]
  ]
}
1
2
3
4
5
6
7
8
  • 启动 webpack 时带上 --display-used-exports 可以在 shell 打印出关于代码剔除的提示
  • 在使用第三方库时,需要配置 resolve.mainFields: ['jsnext:main', 'main'] 以指明解析第三方库代码时,采用 ES6 模块化的代码入口

# exclude/include

可以通过 exclude/include 配置来确保转译尽可能少的文件。excldue 指定要排除的文件,include 指定要包含的文件。 exclude 的优先级高于 include,在 include 和 exclude 中使用绝对路径数组,尽量避免 exclude,更倾向于使用 include。 建议给 loader 指定 include 或是 exclude,指定其中一个即可,因为 node_modules 目录通常不需要我们去编译,排除后,有效提升编译效率。

const path = require("path");
module.exports = {
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env"],
          },
        },
        include: [path.resolve(__dirname, "src")],
      },
    ],
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# cache-loader

在一些性能开销较大的 loader 之前添加 cache-loader,将结果缓存中磁盘中。默认保存在 node_modueles/.cache/cache-loader 目录下。

  • npm install cache-loader -D
module.exports = {
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: ["cache-loader", "babel-loader"],
      },
    ],
  },
};
1
2
3
4
5
6
7
8
9
10

如果你跟我一样,只打算给 babel-loader 配置 cache 的话,也可以不使用 cache-loader,给 babel-loader 增加选项 cacheDirectory。 cacheDirectory:默认值为 false。当有设置时,指定的目录将用来缓存 loader 的执行结果。之后的 Webpack 构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel 重新编译过程。设置空值或者 true 的话,使用默认缓存目录:node_modules/.cache/babel-loader。开启 babel-loader 的缓存和配置 cache-loader,我比对了下,构建时间很接近。

# happypack

当有大量的文件需要解析和处理的时候,wbepack 的构建会变得很慢。HappyPack 可以把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。让 webpack 在同一时刻处理多个任务,从而提升构建速度。

const Happypack = require("happypack");
module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.js[x]?$/,
        use: "Happypack/loader?id=js",
        include: [path.resolve(__dirname, "src")],
      },
      {
        test: /\.css$/,
        use: "Happypack/loader?id=css",
        include: [
          path.resolve(__dirname, "src"),
          path.resolve(__dirname, "node_modules", "bootstrap", "dist"),
        ],
      },
    ],
  },
  plugins: [
    new Happypack({
      id: "js", //和rule中的id=js对应
      //将之前 rule 中的 loader 在此配置
      use: ["babel-loader"], //必须是数组
    }),
    new Happypack({
      id: "css", //和rule中的id=css对应
      use: ["style-loader", "css-loader", "postcss-loader"],
    }),
  ],
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# vue-webpack5.0 搭建项目 (opens new window)

编辑 (opens new window)
#Webpack
上次更新: 2023/03/22, 15:28:00
Webpack的plugin和loader开发

Webpack的plugin和loader开发→

最近更新
01
gsap动画库学习笔记 - 持续~
06-05
02
远程组件加载方案笔记
05-03
03
小程序使用笔记
03-29
更多文章>
Theme by Vdoing | Copyright © 2020-2023 CD | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式