# 优化构建速度

# 1.1 构建费时分析

# 1.1 构建费时分析

这里我们需要使用插件 speed-measure-webpack-plugin (opens new window),我们参考文档配置一下 首先安装一下

$ npm i -D speed-measure-webpack-plugin

修改我们的配置文件 webpack.config.js

// 费时分析
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
...

const config = {...}

module.exports = (env, argv) => {
  // 这里可以通过不同的模式修改 config 配置


  return smp.wrap(config);
}

执行打包

报错了🤦🏻‍♂️ 这里就暴露了使用这个插件的一个弊端,就是:

有些 Loader 或者 Plugin 新版本会不兼容,需要进行降级处理

这里我们对 mini-css-extract-plugin 进行一下降级处理: ^2.1.0 -> ^1.3.6

重新安装一下依赖,再次执行打包

降了版本之后,还是报错,根据提示信息,我们给配置加上 publicPath: './'\

output: {
    filename: 'bundle.js', // 输出文件名
    path: path.join(__dirname, 'dist'), // 输出文件目录
    publicPath: './'
  },

在尝试一次 成功了!

注意:在 webpack5.x 中为了使用费时分析去对插件进行降级或者修改配置写法是非常不划算的,这里因为演示需要,我后面会继续使用,但是在平时开发中,建议还是不要使用。

# 1.2 优化 resolve 配置

# 1.2.1 alias

alias 用的创建 import 或 require 的别名,用来简化模块引用,项目中基本都需要进行配置。

const path = require('path')
...
// 路径处理方法
function resolve(dir){
  return path.join(__dirname, dir);
}

 const config  = {
  ...
  resolve:{
    // 配置别名
    alias: {
      '~': resolve('src'),
      '@': resolve('src'),
      'components': resolve('src/components'),
    }
  }
};

配置完成之后,我们在项目中就可以

// 使用 src 别名 ~ 
import '~/fonts/iconfont.css'

// 使用 src 别名 @ 
import '@/fonts/iconfont.css'

// 使用 components 别名
import footer from "components/footer";

# 1.2.2 extensions

webpack 默认配置

const config = {
  //...
  resolve: {
    extensions: ['.js', '.json', '.wasm'],
  },
};

如果用户引入模块时不带扩展名,例如

import file from '../path/to/file';

那么 webpack 就会按照 extensions 配置的数组从左到右的顺序去尝试解析模块

需要注意的是:

高频文件后缀名放前面; 手动配置后,默认配置会被覆盖

如果想保留默认配置,可以用 ... 扩展运算符代表默认配置,例如

const config = {
  //...
  resolve: {
    extensions: ['.ts', '...'], 
  },
};

# 1.2.3 modules

告诉 webpack 解析模块时应该搜索的目录,常见配置如下

const path = require('path');

// 路径处理方法
function resolve(dir){
  return path.join(__dirname, dir);
}

const config = {
  //...
  resolve: {
     modules: [resolve('src'), 'node_modules'],
  },
};

告诉 webpack 优先 src 目录下查找需要解析的文件,会大大节省查找时间

# 1.2.4 resolveLoader

resolveLoader 与上面的 resolve 对象的属性集合相同, 但仅用于解析 webpack 的 loader 包。 一般情况下保持默认配置就可以了,但如果你有自定义的 Loader 就需要配置一下,不配可能会因为找不到 loader 报错

例如:我们在 loader 文件夹下面,放着我们自己写的 loader

我们就可以怎么配置

const path = require('path');

// 路径处理方法
function resolve(dir){
  return path.join(__dirname, dir);
}

const config = {
  //...
  resolveLoader: {
    modules: ['node_modules',resolve('loader')]
  },
};

# 1.3 externals

externals 配置选项提供了「从输出的 bundle 中排除依赖」的方法。此功能通常对 library 开发人员来说是最有用的,然而也会有各种各样的应用程序用到它。 例如,从 CDN 引入 jQuery,而不是把它打包: 引入链接

<script
  src="https://code.jquery.com/jquery-3.1.0.js"
  integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
  crossorigin="anonymous"
></script>

配置 externals

const config = {
  //...
  externals: {
    jquery: 'jQuery',
  },
};

使用 jQuery

import $ from 'jquery';

$('.my-element').animate(/* ... */);

我们可以用这样的方法来剥离不需要改动的一些依赖,大大节省打包构建的时间。

# 1.4 缩小范围

在配置 loader 的时候,我们需要更精确的去指定 loader 的作用目录或者需要排除的目录,通过使用 include 和 exclude 两个配置项,可以实现这个功能,常见的例如:

  • include:符合条件的模块进行解析
  • exclude:排除符合条件的模块,不解析
  • exclude 优先级更高

例如在配置 babel 的时候

const path = require('path');

// 路径处理方法
function resolve(dir){
  return path.join(__dirname, dir);
}

const config = {
  //...
  module: { 
    // noParse: /jquery|lodash/,
    rules: [
      {
        test: /\.js$/i,
        include: resolve('src'),
        exclude: /node_modules/,
        use: [
          'babel-loader',
        ]
      },
      // ...
    ]
  }
};

# 1.5 noParse

不需要解析依赖的第三方大型类库等,可以通过这个字段进行配置,以提高构建速度 使用 noParse 进行忽略的模块文件中不会解析 import、require 等语法

const config = {
  //...
  module: { 
    noParse: /jquery|lodash/,
    rules:[...]
  }

};

# 1.6 IgnorePlugin

防止在 import 或 require 调用时,生成以下正则表达式匹配的模块:

  • requestRegExp 匹配(test)资源请求路径的正则表达式。
  • contextRegExp 匹配(test)资源上下文(目录)的正则表达式。
new webpack.IgnorePlugin({ resourceRegExp, contextRegExp });

以下示例演示了此插件的几种用法。

  • 安装 moment 插件(时间处理库)
$ npm i -S moment

  • 配置 IgnorePlugin
// 引入 webpack
const webpack = require('webpack')

const config = {
  ...
  plugins:[ // 配置插件
    ...
    new webpack.IgnorePlugin({
      resourceRegExp: /^\.\/locale$/,
      contextRegExp: /moment$/,
    }),
  ]  
};

目的是将插件中的非中文语音排除掉,这样就可以大大节省打包的体积了

# 1.7 多进程配置

注意:实际上在小型项目中,开启多进程打包反而会增加时间成本,因为启动进程和进程间通信都会有一定开销。

# 1.7.1 thread-loader

配置在 thread-loader (opens new window) 之后的 loader 都会在一个单独的 worker 池(worker pool)中运行

https://webpack.docschina.org/loaders/thread-loader/#root

  1. 安装
$ npm i -D  thread-loader
  1. 配置
const path = require('path');

// 路径处理方法
function resolve(dir){
  return path.join(__dirname, dir);
}

const config = {
  //...
  module: { 
    noParse: /jquery|lodash/,
    rules: [
      {
        test: /\.js$/i,
        include: resolve('src'),
        exclude: /node_modules/,
        use: [
          {
            loader: 'thread-loader', // 开启多进程打包
            options: {
              worker: 3,
            }
          },
          'babel-loader',
        ]
      },
      // ...
    ]
  }
};

1.7.2 happypack ❌ 同样为开启多进程打包的工具,webpack5 已弃用。

# 1.8 利用缓存

利用缓存可以大幅提升重复构建的速度

# 1.8.1 babel-loader 开启缓存

babel 在转译 js 过程中时间开销比价大,将 babel-loader 的执行结果缓存起来,重新打包的时候,直接读取缓存 缓存位置: node_modules/.cache/babel-loader

具体配置如下:

const config = {
 module: { 
    noParse: /jquery|lodash/,
    rules: [
      {
        test: /\.js$/i,
        include: resolve('src'),
        exclude: /node_modules/,
        use: [
          // ...
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true // 启用缓存
            }
          },
        ]
      },
      // ...
    ]
  }
}

那其他的 loader 如何将结果缓存呢? cache-loader 就可以帮我们完成这件事情

# 1.8.2 cache-loader

缓存一些性能开销比较大的 loader 的处理结果 缓存位置:node_modules/.cache/cache-loader

  • 安装
$ npm i -D cache-loader
  • 配置 cache-loader
const config = {
 module: { 
    // ...
    rules: [
      {
        test: /\.(s[ac]|c)ss$/i, //匹配所有的 sass/scss/css 文件
        use: [
          // 'style-loader',
          MiniCssExtractPlugin.loader,
          'cache-loader', // 获取前面 loader 转换的结果
          'css-loader',
          'postcss-loader',
          'sass-loader', 
        ]
      }, 
      // ...
    ]
  }
}

# 1.8.3 hard-source-webpack-plugin

hard-source-webpack-plugin. (opens new window) 为模块提供了中间缓存,重复构建时间大约可以减少 80%,但是在 webpack5 中已经内置了模块缓存,不需要再使用此插件

# 1.8.4 dll ❌

在 webpack5.x 中已经不建议使用这种方式进行模块缓存,因为其已经内置了更好体验的 cache 方法

# 1.8.5 cache 持久化缓存

通过配置 cache 缓存生成的 webpack 模块和 chunk,来改善构建速度。

const config = {
  cache: {
    type: 'filesystem',
  },
};