webpack性能优化 - webpack篇

webpack 性能优化

前言

webpack 打包优化并没有什么固定的模式,一般我们常见的有话就是拆包、分块、压缩等,并不是对每一个项目都适用,针对特定项目,需要不断调试不断优化。

对于 webpack4,建议从零开始配置,在项目初期使用 webpack4 默认的配置。

分析打包速度

优化 webpack 构建速度的第一步是知道需要将精力集中在哪里。我们可以通过 speed-measure-webpack-plugin 测量你的 webpack 构建期间各个阶段花费的时间:

import SpeedMeasurePlugin from 'speed-measure-webpack-plugin'

const smp = new SpeedMeasurePlugin()

export default smp.wrap(config)

分析影响打包速度环节

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

    搜索所有的依赖模块,这需要占用一定的时间,即我们优化的第一个时间是 搜索时间

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

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

    所以我们需要优化的第二个时间是 解析时间

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

    将所有解析完成的代码,打包到一个文件中,为了使浏览器加载的包更小(减少白屏时间),所以 webpack 会对代码进行优化。

    JS 压缩时发布编译的最后阶段,时间耗费会比较久,因为压缩 JS 需要将代码解析成 AST 语法树,然后根据复杂的规则去解析和处理 AST,最后将 AST 还原回 JS。

    所以我们需要优化的第三个时间是 压缩时间

  4. 二次打包

    当我们只改动了项目中的一个小小的文件,所有文件都会重新打包,但大部分文件并没有变更。

    所以我们需要优化的第四个时间是 二次打包时间

优化搜索时间

缩小文件搜索范围,减少不必要的编译工作

webpack 打包时,会从配置的 entry 出发,解析入口文件的导入语句,再递归解析。

在遇到导入语句时,webpack 会做两件事情:

  • 根据导入语句去寻找对应的要导入的文件;
  • 根据找到要导入文件的后缀,使用配置中的 loader 去处理文件。例如:使用了 ES Next 语法需要用到 babel-loader。

这两件事情一旦项目文件数量增多,速度会显著降低,所以虽然无法避免以上两件事情,但是可以尽量减少事情的发生以提高速度。

  1. 优化 loader 配置

    使用 loader 时可以通过 test、include、exclude 三个配置项来命中 loader 要应用规则的文件;

  2. 优化 resolve.modules 配置

    resolve.modules 用于配置 webpack 去哪些目录下寻找第三方模块,resolve.modules 的默认值是 [‘node_modules’],含义是先去当前目录下的 ./node_modules 寻找,没有找到就去上一级目录中找,一路递归;

  3. 优化 resolve.alias 配置

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

  4. 优化 resolve.extensions 配置

    在导入语句中没带文件后缀时,webpack 会根据 resolve.extensions 自动带上后缀去尝试询问文件是否存在,所以配置 resolve.extensions 应注意:

    • resolve.extensions 列表要尽可能小,不要把不存在的后缀添加进去;
    • 高频后缀名放在前面,以便尽快退出查找过程;
    • 在写代码时,尽可能带上后缀名,从而避免寻找过程。
  5. 优化 resolve.mainFields 配置

    有一些第三方模块会针对不同环境提供几份代码,路径一般会写在 package.json 中。

    webpack 会根据 mainFields 中配置去决定优先采用哪份代码,只会使用找到的第一个。

  6. 优化 module.noParse 配置

    module.noParse 配置项可以让 webpack 忽略对部分没采用模块化的文件的递归处理,这样做的好处是能提高构建性能。原因是部分年代比较久远的库体积庞大且没有采用模块化标准,让 webpack 去解析这些文件没有任何意义

优化解析时间

运行在 Node.JS 上的 webpack 是单线程模式,也就是说 webpack 打包只能逐个文件处理,当文件数量比较多时,打包时间就会比较漫长,所以我们需要开启多线程来提高解析速度。

thread-loader(webpack4 官方推荐)

把这个 loader 放在其他 loader 之前,放置在其之后的 loader 就会在一个单独的 worker【worker pool】池里运行,一个 worker 就是一个 Node.JS 进程,每个单独进程处理时间上限为 600ms,各个进程的数据交换也会限制在这个时间内。

import { Configuration } from 'webpack'

const config: Configuration = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: ['thread-loader', 'babel-loader']
      },
      {
        test: /\.less$/,
        exclude: /node_modules/,
        use: [
          'style-loader',
          'thread-loader',
          'css-loader',
          'less-loader'
        ]
      }
    ]
  }
}

注意: 由于工作原理的限制,thread-loader 需要放在 style-loader 之后,原因是 thread-loader 后的 loader 没法存取文件,也没法获取 webpack 的选项配置。

官方说每个 worker 大概都要花费 600ms,所以为了防止启动 worker 时的高延迟,提供了对 worker 池的优化:预热

import threadLoader from 'thread-loader'

const jsWorkerPool = {
  // 产生的 worker 数量,默认是cpu核心数 - 1
  // 当 require('os').cpus() 是 undefined时则为 1
  worker: 2, 
  
  // 闲置时定时删除 worker 进程
  // 默认为 500ms
  // 可以设置为无穷大,监视模式(--watch)下可以保持 worker 持续存在
  poolTimeout: 2000 
}

const cssWorkerPool = {
  // 一个 worker 进程中并行执行工作的数量
  // 默认为 20
  wokerParallelJobs: 2,
  poolTimeout: 2000
}

threadLoader.warmup(jsWorkerPool, ['babel-loader'])
threadLoader.warmup(cssWorkerPool, ['css-loader'])

注意:请仅在耗时的 loader 上使用

优化压缩时间

webpack 4 默认使用 terser-webpack-plugin 压缩插件压缩优化代码,该插件使用 terser 来缩小 JavaScript。

terser 是什么

官方定义:用于 ES Next 的 JavaScript 解析器、mangler/compressor(压缩器)工具包。

为什么 webpack 选择 terser

不再维护 uglify-es,并且 uglify-js 不支持 es6+。

terser 时 uglify-es 的一个分支,主要保留了与 uglify-es 和 uglify-js@3 的 API 和 CLI 兼容性。

启动多进程

使用多进程来并行运行提高构建速度,默认并发数量为 os.cspus().length - 1

import { Configuration } from 'webpack'
import TerserPlugin from 'terser-webpack-plugin'

const config: Configuration = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true
      })
    ]
  }
}

优化二次打包时间

将改动少的文件缓存起来,二次打包直接读取缓存,显著提升打包时间。

使用 webpack 缓存的方法有几种,例如 cache-loader,HardSourceWebpackPlugin 或 babel-loader 的 cacheDirectory 标志。这些缓存方法都有启动开销,重新运行期间在本地节省的时间很大,但是初次启动实际上会更慢。

cache-loader

和 thread-loader 用法一样,在性能开销比较大的 loader 之前添加此 loader,以将结果缓存到磁盘

import { Configuration } from 'webpack'
import { resolve } from 'path'

const config: Configuration = {
  module: {
    rules: [
      {
        test: /\.js$/
        use: ['cache-loader', ...loaders],
    		include: resolve('src')
      }
    ]
  }
}

HardSourceWebpackPlugin

  • 第一次构建将花费正常时间
  • 第二次构建将显著加快(大约提升 90% 的构建速度)
import { Configuration } from 'webpack'
import HardSourceWebpackPlugin from 'hard-source-webpack-plugin'

const config: Configuration = {
  plugins: [
    new HardSourceWebpackPlugin()
  ]
}

参考链接:
[1]:https://github.com/i-want-offer/FE-Interview-questions

学习,留下足迹!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值