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

概述

缓存是一种应用非常广泛性能优化技术,在计算机领域几乎无处不在,例如:操作系统层面 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.mzph.cn/pingmian/38042.shtml

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

相关文章

路径规划 | 基于蜣螂优化算法的栅格地图机器人路径规划(Matlab)

目录 效果一览基本介绍程序设计参考文献 效果一览 基本介绍 路径规划 | 基于蜣螂优化算法的栅格地图机器人路径规划(Matlab) 1.利用蜣螂算法DBO优化栅格地图机器人路径规划,效果如图所示,包括迭代曲线图、栅格地图等等&#xff5e…

VScode配置

1.设置鼠标悬停提示 1.1 问题描述 打开vscode,按住ctrl鼠标左键不能跳转定义(右键没有go to definition) 1.2 解决办法 打开设置界面:文件->首选项->设置在搜索框中搜索intelli Sense Engine (需要先安装C/C…

【Python】已解决:ModuleNotFoundError: No module named ‘sklearn‘

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决:ModuleNotFoundError: No module named ‘sklearn‘ 一、分析问题背景 在进行机器学习项目时,Scikit-Learn(简称sklearn)是一…

Linux多进程和多线程(二)-进程间通信-管道用法

进程间通信 关于多进程的通信管道无名管道(匿名管道)创建无名管道示例:创建子进程,父进程通过管道向子进程发送消息无名管道(匿名管道) 的特点 有名管道(命名管道) 创建有名管道需要调⽤ mkfifo() 函数示例:创建两个没有关联关系的进程,通过有名管道通信 注意: 缺点优点 关于判…

Linux多进程和多线程(一)-进程的概念和创建

进程 进程的概念进程的特点如下进程和程序的区别LINUX进程管理 getpid()getppid() 进程的地址空间虚拟地址和物理地址进程状态管理进程相关命令 ps toppstreekill 进程的创建 并发和并行fork() 父子进程执行不同的任务创建多个进程 进程的退出 exit()和_exit() exit()函数让当…

七日世界Once Human跳ping、延迟高、丢包怎么办?

七日世界是一款开放世界为轴点的生存射击游戏,玩家将进入一个荒诞、荒芜的末日世界,在这里与好友一起对抗可怖的怪物和神秘物质星尘的入侵,给这个星球留下最后的希望,共筑一片安全的领地。不过有部分玩家在游玩七日世界的时候&…

昇思MindSpore学习笔记7--函数式自动微分

摘要: 介绍了昇思MindSpore神经网络训练反向传播算法中函数式自动微分的使用方法和步骤。包括构造计算函数和神经网络、grad获得微分函数,以及如何处理停止渐变、获取辅助数据等内容。 一、概念要点 神经网络训练主要使用反向传播算法: 准备…

从AICore到TensorCore:华为910B与NVIDIA A100全面分析

华为NPU 910B与NVIDIA GPU A100性能对比,从AICore到TensorCore,展现各自计算核心优势。 AI 2.0浪潮汹涌而来,若仍将其与区块链等量齐观,视作炒作泡沫,则将错失新时代的巨大机遇。现在,就是把握AI时代的关键…

RAG 基本流程及处理技巧 with LangChain

LLM 主要存在两个问题:幻想和缺乏领域知识。领域知识缺乏的原因是因为训练 LLM 本身的知识更新慢,对特定领域的知识也没有太细致的输入。 RAG 主要是解决 LLM 缺乏领域知识的问题。底层的逻辑是:把 LLM 作为逻辑推理引擎,而不是信…

机器学习--概念理解

知识点 一、机器学习概述 人工智能 机器学习 深度学习 学习的范围:模式识别、数据挖掘、统计学习、计算机视觉、语音识别、自然语言处理 可以解决的问题:给定数据的预测问题 二、机器学习的类型 监督学习 分类 回归 无监督学习 聚类 降维 强化…

恢复的实现技术-日志和数据转储

一、引言 在系统正常运行的情况下,事务处理的恢复机制应采取某些技术措施为恢复做好相应的准备,保证在系统发生故障后,能将数据库从一个不一致的错误状态恢复到一个一致性状态 恢复技术主要包括 生成一个数据库日志,来记录系统中…

Unity制作一个简单抽卡系统(简单好抄)

业务流程:点击抽卡——>播放动画——>显示抽卡面板——>将随机结果添加到面板中——>关闭面板 1.准备素材并导入Unity中(包含2个抽卡动画,抽卡结果的图片,一个背景图片,一个你的展示图片) 2.给…

创建一个vue3+vite+ts项目

目录 创建项目 ​编辑 下载jsx 插件 在根目录在新建.env vue.config.js tsconfig.json tsconfig.node.json 下载ui组件库和路由(组件库根据自己的项目需要选择) 在根目录下新建views/index.tsx 在根目录下新建router/index.ts 修改App.vue 创建…

机器学习原理之 -- 朴素贝叶斯分类器:由来及原理详解

朴素贝叶斯(Naive Bayes)分类器是一类基于贝叶斯定理(Bayes Theorem)的简单而有效的概率分类算法。由于其假设特征之间的条件独立性,因此被称为“朴素”贝叶斯分类器。尽管这种独立性假设在现实中很少完全成立&#xf…

mac系统docker默认不支持host主机网络模式

环境描述:在mac系统上安装docker及docker-compose服务,并且打算搭建一个redis集群 问题描述:mac默认不支持host网络模式,导致集群无法通过外部主机访问 具体验证步骤: docker-compose.yml如下: version…

reactor网络模型的原理与实现

一、rector网络模型 对高并发编程,网络连接上的消息处理,可以分为两个阶段:等待消息准备好、消息处理。当使用默认的阻塞套接字时,往往是把这两个阶段合而为一,这样操作套接字的代码所在的线程就得睡眠来等待消息准备好…

Mysql常用SQL:日期转换成周_DAYOFWEEK(date)

有时候需要将查询出来的日期转换成周几,Mysql本身语法就是支持这种转换的,就是DAYOFWEEK()函数 语法格式:DAYOFWEEK(date) (date:可以是指定的具体日期( 如2024-06-29 ),也可以是日期…

为什么word生成的PDF内容显示不全?

在现代办公环境中,将文档从一个格式转换为另一个格式是一个常见的任务。然而,有时候我们可能会遇到意想不到的问题,比如使用Word转换成PDF时,生成的PDF文件只显示了整个界面的四分之一内容。这种问题不仅令人困扰,也可…

斜率优化DP——AcWing 303. 运输小猫

斜率优化DP 定义 斜率优化DP(Slope Optimization Dynamic Programming)是一种高级动态规划技巧,用于优化具有特定形式的状态转移方程。它主要应用于那些状态转移涉及求极值(如最小值或最大值)的问题中,通…

CesiumJS【Basic】- #028 天空盒

文章目录 天空盒1 目标2 代码2.1 main.ts3 资源天空盒 1 目标 配置显示天空盒 2 代码 2.1 main.ts import * as Cesium from cesium;// 创建 Cesium Viewer 并配置地形数据和天空盒 const viewer = new Cesium.Viewer(