Webpack: 持久化缓存大幅提升构建性能

news2024/12/30 2:37:14

概述

缓存是一种应用非常广泛性能优化技术,在计算机领域几乎无处不在,例如:操作系统层面 CPU 高速缓存、磁盘缓存,网路世界中的 DNS 缓存、HTTP 缓存,以及业务应用中的数据库缓存、分布式缓存等等。

那自然而然的,我们也可以在 Webpack 使用各式各样的缓存技术,通过牺牲空间来提升构建过程的时间效率,在这篇文章中,我将从 Webpack5 的 持久化缓存 开始介绍用法、性能收益、基本原理;之后再过渡到 Webpack4 中如何借助第三方组件(Loader、Plugin)实现持久化缓存。

Webpack5 中的持久化缓存

持久化缓存 算得上是 Webpack 5 最令人振奋的特性之一,它能够将首次构建的过程与结果数据持久化保存到本地文件系统,在下次执行构建时跳过解析、链接、编译等一系列非常消耗性能的操作,直接复用上次的 Module/ModuleGraph/Chunk 对象数据,迅速构建出最终产物。

持久化缓存的性能提升效果非常出众!以 Three.js 为例,该项目包含 362 份 JS 文件,合计约 3w 行代码,算得上中大型项目:

在这里插入图片描述

配置 babel-loadereslint-loader 后,在我机器上测试,未使用 cache 特性时构建耗时大约在 11000ms 到 18000ms 之间;启动 cache 功能后,第二次构建耗时降低到 500ms 到 800ms 之间,两者相差接近 50 倍!

而这接近 50 倍的性能提升,仅仅需要在 Webpack5 中设置 cache.type = 'filesystem' 即可开启:

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

执行效果:

在这里插入图片描述

此外,cache 还提供了若干用于配置缓存效果、缓存周期的配置项,包括:

  • cache.type:缓存类型,支持 'memory' | 'filesystem',需要设置为 filesystem 才能开启持久缓存;

  • cache.cacheDirectory:缓存文件路径,默认为 node_modules/.cache/webpack

  • cache.buildDependencies:额外的依赖文件,当这些文件内容发生变化时,缓存会完全失效而执行完整的编译构建,通常可设置为各种配置文件,如:

    module.exports = {
      cache: {
        type: 'filesystem',
        buildDependencies: {
          config: [
            path.join(__dirname, 'webpack.dll_config.js'),
            path.join(__dirname, '.babelrc')
          ],
        },
      },
    };
    
  • cache.managedPaths:受控目录,Webpack 构建时会跳过新旧代码哈希值与时间戳的对比,直接使用缓存副本,默认值为 ['./node_modules']

  • cache.profile:是否输出缓存处理过程的详细日志,默认为 false

  • cache.maxAge:缓存失效时间,默认值为 5184000000

使用时通常关注上述配置项即可,其它如 idleTimeoutidleTimeoutAfterLargeChanges 等项均与 Webpack 内部实现算法有关,与缓存效果关系不大,此处不展开介绍。

缓存原理

那么,为什么开启持久化缓存之后,构建性能会有如此巨大的提升呢?

一言蔽之,Webpack5 会将首次构建出的 Module、Chunk、ModuleGraph 等对象序列化后保存到硬盘中,后面再运行的时候,就可以跳过许多耗时的编译动作,直接复用缓存数据

回过头来看看 Webpack 的构建过程,大致上可划分为三个阶段

请添加图片描述

  • 初始化,主要是根据配置信息设置内置的各类插件。

  • Make - 构建阶段,从 entry 模块开始,执行:

    • 读入文件内容;
    • 调用 Loader 转译文件内容;
    • 调用 acorn 生成 AST 结构;
    • 分析 AST,确定模块依赖列表;
    • 遍历模块依赖列表,对每一个依赖模块重新执行上述流程,直到生成完整的模块依赖图 —— ModuleGraph 对象。
  • Seal - 生成阶段,过程:

    • 遍历模块依赖图,对每一个模块执行:
      • 代码转译,如 import 转换为 require 调用;
      • 分析运行时依赖。
    • 合并模块代码与运行时代码,生成 chunk;
    • 执行产物优化操作,如 Tree-shaking;
    • 将最终结果写出到产物文件。

过程中存在许多 CPU 密集型操作,例如调用 Loader 链加载文件时,遇到 babel-loader、eslint-loader、ts-loader 等工具时可能需要重复生成 AST;分析模块依赖时则需要遍历 AST,执行大量运算;Seal 阶段也同样存在大量 AST 遍历,以及代码转换、优化操作,等等。假设业务项目中有 1000 个文件,则每次执行 npx webpack 命令时,都需要从 0 开始执行 1000 次构建、生成逻辑。

而 Webpack5 的持久化缓存功能则将构建结果保存到文件系统中,在下次编译时对比每一个文件的内容哈希或时间戳,未发生变化的文件跳过编译操作,直接使用缓存副本,减少重复计算;发生变更的模块则重新执行编译流程。缓存执行时机如下图:

请添加图片描述

如图,Webpack 在首次构建完毕后将 Module、Chunk、ModuleGraph 三类对象的状态序列化并记录到缓存文件中;在下次构建开始时,尝试读入并恢复这些对象的状态,从而跳过执行 Loader 链、解析 AST、解析依赖等耗时操作,提升编译性能。

Webpack4:使用 cache-loader

Webpack5 的持久化缓存用法简单,效果出众,但可惜在 Webpack4 及之前版本原生还没有相关实现,只能借助一些第三方组件实现类似效果,包括:

  • 使用 cache-loader;
  • 使用 hard-source-webpack-plugin;
  • 使用 Loader(如 babel-loadereslint-loader))自带的缓存能力。

先从 cache-loader 说起,cache-loader 能够将 Loader 处理结果保存到硬盘,下次运行时若文件内容没有发生变化则直接返回缓存结果,用法:

  1. 安装依赖:

    yarn add -D cache
    
  2. 修改配置,注意必须将 cache-loader 放在 loader 数组首位,例如:

    module.exports = {
        // ...
        module: {
            rules: [{
                test: /\.js$/,
                use: ['cache-loader', 'babel-loader', 'eslint-loader']
            }]
        },
        // ...
    };
    

cache-loader 只缓存了 Loader 执行结果,缓存范围与精度不如 Webpack5 内置的缓存功能,所以性能效果相对较低,以 ThreeJS 为例,production 模式下构建耗时从 10602ms 降低到 1540ms;development 模式从 11130ms 降低到 4247ms,多次测试性能提升稳定在 60% ~ 80% 之间。虽然比不上 Webpack5 的持久化缓存,但在 Webpack4 中不失为一种简单而有效的性能优化手段。

此外,cache-loader 还提供了一系列控制缓存逻辑的配置属性,特别是 read/write 可以用于改变缓存数据的持久化逻辑,借助这两个属性我们甚至能够实现多台机器间的缓存共享:

const redis = require("redis");
const client = redis.createClient();

// 读数据
async function read(key, callback) {
  // ...
  const result = await client.get(key);
  const data = JSON.parse(result);
  callback(null, data);
}

// 写数据
async function write(key, data, callback) {
  // ...
  await client.set(key, JSON.stringify(data));
  callback();
}

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: "cache-loader",
            // 传入 read、write 函数
            options: { read, write },
          },
          "babel-loader",
        ],
      },
    ],
  },
};

借助这种能力,我们可以打通本地与线上 CI/CD 环境,实现开发与生产环境构建的构建性能优化。

Webpack4:使用 hard-source-webpack-plugin

hard-source-webpack-plugin 也是一种实现缓存功能的第三方组件,与 cache-loader 不同的是,它并不仅仅缓存了 Loader 运行结果,还保存了 Webpack 构建过程中许多中间数据,包括:模块、模块关系、模块 Resolve 结果、Chunks、Assets 等,效果几乎与 Webpack5 自带的 Cache 对齐。用法:

  1. 安装依赖:

    yarn add -D hard-source-webpack-plugin
    
  2. 添加配置:

    const HardSourceWebpackPlugin = require("hard-source-webpack-plugin");
    
    module.exports = {
      // ...
      plugins: [
        new HardSourceWebpackPlugin(),
      ],
    };
    

首次运行时,hard-source-webpack-plugin 会在缓存文件夹 node_module/.cache 写入一系列日志文件:

下次运行时,hard-source-webpack-plugin 插件会复用缓存中记录的数据,跳过一系列构建步骤,从而提升构建性能。

hard-source-webpack-plugin 插件的底层逻辑与 Webpack5 的持久化缓存很相似,但优化效果稍微差一些,以 ThreeJS 为例,production 模式下构建耗时从 10602ms 降低到 1740ms;development 模式构建从 11130ms 降低到 3280ms,多次测试性能提升稳定在 62% ~ 88% 之间。

使用组件自带的缓存功能

除了上面介绍的持久化缓存、cache-loaderhard-source-webpack-plugin 方案外,我们还可以使用 Webpack 组件自带的缓存能力提升特定领域的编译性能,这一类组件有:

  • babel-loader;
  • eslint-loader:旧版本 ESLint Webpack 组件,官方推荐使用 eslint-webpack-plugin 代替;
  • eslint-webpack-plugin;
  • stylelint-webpack-plugin。

例如使用 babel-loader 时,只需设置 cacheDirectory = true 即可开启缓存功能,例如:

module.exports = {
    // ...
    module: {
        rules: [{
            test: /\.m?js$/,
            loader: 'babel-loader',
            options: {
                cacheDirectory: true,
            },
        }]
    },
    // ...
};

以 Three.js 为例,开启缓存后生产环境构建耗时从 3500ms 降低到 1600ms;开发环境构建从 6400ms 降低到 4500ms,性能提升约 30% ~ 50% 。

默认情况下,缓存内容会被保存到 node_modules/.cache/babel-loader 目录,你也可以通过 cacheDirectory = 'dir' 属性设置缓存路径。

此外,ESLint 与 Stylelint 这一类耗时较长的 Lint 工具也贴心地提供了相应的缓存能力,只需设置 cache = true 即可开启,如:

// webpack.config.js
module.exports = {
  plugins: [
    new ESLintPlugin({ cache: true }),
    new StylelintPlugin({ files: '**/*.css', cache: true }),
  ],
};

依然以 Three.js 为例,开启 ESLint 缓存后生产环境构建耗时从 6400ms 降低到 1400ms;开发环境构建从 7000ms 降低到 2100ms,性能提升达到 70% ~ 80%。

总结

Webpack5 持久化缓存用法简单,且优化效果非常出色,确实是一个特别让人振奋的新功能,甚至特定情况下能够让构建性能达到 Unbundle 方案的量级,妥妥的 Webpack 性能优化利器!

而在 Webpack4 中,我们还可以借助下述组件实现缓存优化:

  • cache-loader:针对 Loader 运行结果的通用缓存方案;
  • hard-source-webpack-plugin:针对 Webpack 全生命周期的通用缓存方案;
  • babel-loader:针对 Babel 工具的专用缓存能力;
  • eslint-loader/eslint-webpack-plugin:针对 ESLint 的专用缓存方案;
  • stylelint-webpack-plugin:针对 StyleLint 的专用缓存方案。

这些方案各有特色,但都无可置疑地能有效提升编译性能,建议你在尝试做性能优化时优先选用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1889692.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机图形学入门23:蒙特卡洛路径追踪

1.前言 前面几篇文章介绍了Whitted-style光线追踪,还介绍了基于物理渲染的基础知识,包括辐射度量学、BRDF以及渲染方程,但并没有给出解渲染方程的方法,或者说如何通过该渲染方程计算出屏幕上每一个坐标的像素值。 Whitted-style光…

从0开始transformer代码理解(附带调试和个人原理理解)

代码来源 本次代码来源自github https://github.com/graykode/nlp-tutorial 里面的5.1 transformer代码 第一步 数据准备(从main函数开始) 首先这里是自定义了三句话,分别是给到encoder的输入和decoder的输入还有测试的输入 sentences […

使用python做飞机大战

代码地址: 点击跳转

【多媒体】Java实现MP4和MP3音视频播放器【JavaFX】【音视频播放】

在Java中播放音视频可以使用多种方案,最常见的是通过Swing组件JFrame和JLabel来嵌入JMF(Java Media Framework)或Xuggler。不过,JMF已经不再被推荐使用,而Xuggler是基于DirectX的,不适用于跨平台。而且上述方案都需要使用第三方库…

Spring Boot2.x教程:(四)Spring Boot2.6及之后版本整合Knife4j的问题

Spring Boot2.6及之后版本整合Knife4j的问题 1、概述2、问题出现原因及解决办法3、拓展3.1、为什么发生这种变化 4、总结 大家好,我是欧阳方超,可以扫描下方二维码关注我的公众号“欧阳方超”,后续内容将在公众号首发。 1、概述 今天在2.7…

66.WEB渗透测试-信息收集- WAF、框架组件识别(6)

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 内容参考于: 易锦网校会员专享课 上一个内容:65.WEB渗透测试-信息收集- WAF、框架组件识别(5) 关于waf的一些识…

day62--若依框架(基础应用篇)

若依搭建 若依版本 官方 若依官方针对不同开发需求提供了多个版本的框架,每个版本都有其独特的特点和适用场景: 前后端混合版本:RuoYi结合了SpringBoot和Bootstrap的前端开发框架,适合快速构建传统的Web应用程序,其…

ByteTrack论文阅读笔记

目录 ByteTrack: Multi-Object Tracking by Associating Every Detection Box摘要INTRODUCTION — 简介BYTE算法BYTE算法用Python代码实现实验评测指标轻量模型的跟踪性能 总结SORT算法简介ByteTrack算法和SORT算法的区别 ByteTrack: Multi-Object Tracking by Associating Eve…

idm 支持断点续传吗 idm 断点续传如何使用 idm断点续传怎么解决 idm下载中断后无法继续下载

断点续传功能,让我再也不会惧怕下载大型文件。在断点续传的帮助下,用户可以随时暂停下载任务,并在空闲时继续之前的下载进程。下载文件不惧网络波动,断点续传让下载过程更稳定。有关 idm 支持断点续传吗,idm 断点续传如…

css---before和after伪元素

1.什么是伪元素 伪元素不是真正的页面元素,html没有对应的元素,但是其所有用法和表现行为与真正的页面元素一样,可以对其使用如页面元素一样的CSS样式,表面上看上去貌似是页面的某些元素来展现,实际上CSS样式展现的行…

京东e卡怎么用?

京东618过去后,就没有多大购物欲望了,最后导致我手里还有好几张200块钱面值的e卡没地方用 本来说送朋友,但是又感觉面值太小了 最后还是在收卡云上把提取出来了,主要回收价格不错,而且到账也快,很方便

TI电池电量计应用指导

前言: 电池电量计应用指导,来源:TI,因PDF有200页,在文尾附有目录;上传提示资源重复,请自行下载,找不到的可私信。 电池充电曲线,红色为电压,蓝色为电流 图2.10 匹配化学 ID 所需要的电压电流曲…

大模型补贴政策来了!!!

广州琶洲人工智能与数字经济试验区管理委员会 广州市海珠区科技工业商务和信息化局关于印发广州市海珠区建设人工智能大模型应用示范区实施细则的通知 各有关单位: 为进一步促进海珠区人工智能大模型产业发展,加快建设人工智能大模型应用示范区&#xf…

启动游戏提示缺少XAPOFX1_5.dll怎么处理?七个解决方法教你快速修复

xapofx1_5.dll是什么,打开软件或软件突然出现找不到xapofx1_5.dll文件,相信各位都很疑惑为什么会出现这个问题。今天我就教大家遇到这个问题怎么解决,和介绍xapofx1_5.dll文件全面分析以及xapofx1_5.dll多种解决方法! 一、xapofx1…

页面替换菜单栏图标

图标素材库:https://www.iconfont.cn/?spma313x.collections_index.i3.2.51703a81hOhc8B 1、找到自己喜欢的图标下载svg 2、添加到icons中 3、在components中创建对应的vue页面添加对应图标svg中代码 4、在router中引入 5、在对应的菜单下使用图标

el-tree懒加载数据改变后不能实时刷新问题

简介 设置lazy后&#xff0c;加载数据通过load获取&#xff0c;如果外部搜索条件改变&#xff0c;数据源变动后&#xff0c;el-tree仍然是老数据不更新最新数据&#xff0c;因为el-tree加载后会缓存&#xff0c;需要主动设置data去更新最新数据 效果 源码 <!DOCTYPE html…

微信小程序云开发引入车牌识别踩坑

创造灵感 因为我写的是一个汽车轮胎商城小程序,要记录客户车牌信息,用到了车牌识别,小程序的插件服务里面有ORC插件支持,只需要把这个插件的车牌识别引进来就能用,这个插件每天有100次的免费识别,基本就够用了,下面把引入的踩坑过程记录一下.如果有需要汽车轮胎的也可以到我们…

softmax从零开始实现

softmax从零开始实现 代码结果 代码 import numpy as np import torch import torchvision import torchvision.transforms as transforms from torch.utils import data# H,W,C -> C,H,W mnist_train torchvision.datasets.FashionMNIST(root"./data", trainTr…

《石化技术 》是什么级别的期刊?是正规期刊吗?能评职称吗?

问题解答 问&#xff1a;《石化技术 》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的第一批认定学术期刊。 问&#xff1a;《石化技术 》级别&#xff1f; 答&#xff1a;国家级。主管单位&#xff1a;中国石油化工集团有限公司 主办单位&…

虚拟机下基于海思移植QT(一)——虚拟机下安装QT

0.参考资料 1.海思Hi3516DV300 移植Qt 运行并在HDMI显示器上显示 2.搭建海思3559A-Qt4.8.7Openssl开发环境 1.报错解决 通过下面命令查询 strings /lib/x86_64-linux-gnu/libc.so.6 | grep GLIBC_通过命令行没有解决&#xff1a; sudo apt install libc6-dev libc6参考解决…