Gatsby Build Caching 构建缓存 API 实战指南:跨构建持久化数据,为插件与站点开发提速
Gatsby Build Caching 构建缓存 API 实战指南跨构建持久化数据为插件与站点开发提速【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址: https://gitcode.com/gh_mirrors/ga/gatsby导读Gatsby 在每次构建gatsby build与开发启动gatsby develop时都会经历数据拉取、节点创建、图像转换等重量级流程。本文基于官方文档 docs/docs/build-caching.md 系统讲解 Gatsby 的Build Caching构建缓存机制插件如何通过 Node API 中注入的cache对象以 JSON 形式把数据持久化到.cache目录并在后续构建中复用同时结合当前仓库中 Gatsby 核心源码如 packages/gatsby/src/utils/cache.ts、packages/gatsby/src/utils/api-node-helpers-docs.js与官方插件的真实用法深入剖析缓存 API 的底层实现、失效策略与最佳实践。读完本文你将能为自己开发的插件或 Gatsby 站点项目接入构建缓存把重复的图像处理、远程数据下载等耗时操作从每次构建中消除。什么是 Build CachingBuild Caching 是 Gatsby 提供的一种跨构建consecutive builds持久化数据的机制插件可以把任意数据以 JSON 对象的形式写入缓存并在下一次构建时读取回来。它的核心价值在于避免重复执行性能开销大的操作例如图像变换、远程 API 请求、大文件下载显著缩短gatsby develop/gatsby build的 bootstrap 时间让站点在开发过程中反复重启后仍能快速恢复到上次的工作状态。Gatsby 自身及官方插件已经在广泛使用该机制例如source / transformer 插件创建的节点nodes会被缓存gatsby-plugin-sharp 会缓存已生成的缩略图避免每次构建重新处理同一张图片。构建产物存储位置缓存与构建产物分别存放在项目根目录下的两个目录中目录作用.cache存放缓存数据与 Gatsby 内部中间状态public存放最终生成的静态站点产物Cache API 概览从 Node API 注入的cache对象cache是 Gatsby 的Node API 辅助对象Node API helper它随 Node API 一起注入。凡是实现了 Gatsby Node APIs通常是插件的函数都可以从第一个参数中解构出它exports.onPostBootstrap async function ({ cache, store, graphql }) {}从源码看cache在 packages/gatsby/src/utils/api-node-helpers-docs.js#L167-L172 中被定义为一个键值存储key-value store/** * Key-value store used to persist results of time/memory/cpu intensive * tasks. All functions are async and return promises. * type {GatsbyCache} */ module.exports.cache true;也就是说cache专用于持久化“耗时、耗内存、耗 CPU 任务”的结果且所有方法均为异步并返回 Promise。官方对每个方法的文档化描述同样来自该文件GatsbyCache定义见 api-node-helpers-docs.js#L83-L111。set写入缓存缓存一个值cache.set(key: string, value: any) Promiseanykey缓存键字符串建议使用带语义的命名空间前缀例如contentful-asset-${id}-${locale}避免键冲突value任意可序列化的 JavaScript 值返回值Promiseresolve 为被缓存的值写入失败时 resolve 为undefined不会抛错。await cache.set(unique-key, value)get读取缓存获取之前缓存的值cache.get(key: string) Promiseany返回值Promiseresolve 为缓存的值若键不存在则 resolve 为undefined可据此判断是否需要重新计算。const value await cache.get(unique-key)del删除缓存额外能力除官方文档重点介绍的get/set外核心源码与 API 文档还暴露了第三个方法del用于按 key 删除缓存项cache.del(key: string) Promisevoidawait cache.del(unique-key)其实现位于 packages/gatsby/src/utils/cache.ts#L96-L104。gatsby-plugin-sharp等插件正是借助它清理不再需要的缓存数据相关注释见 cache.ts#L23-L26。更完整的 API 说明可参考 Node API helpers 文档 与 cache 相关 jsdoc 源码。底层实现从.cache/caches/name到内存 文件系统双层缓存了解 API 用法之后我们深入看一下当前仓库中cache的真实实现便于理解它的行为边界。核心实现类是GatsbyCache见 packages/gatsby/src/utils/cache.tsexport default class GatsbyCache { constructor({ name db, store fsStore }: ICacheProperties {}) { this.name name this.store store this.directory path.join( global.__GATSBY?.root ?? process.cwd(), .cache, caches, name ) } init(): GatsbyCache { fs.ensureDirSync(this.directory) const configs: ArrayStoreConfig [ { store: memory, max: MAX_CACHE_SIZE, ttl: TTL }, { store: this.store, ttl: TTL, options: { path: this.directory, ttl: TTL } }, ] const caches configs.map(cache manager.caching(cache)) this.cache manager.multiCaching(caches) return this } // ... }关键结论全部可由源码确认存储位置每个具名缓存实例的数据落在项目根/.cache/caches/name目录cache.ts#L32-L37由fs.ensureDirSync自动创建双层架构init()通过cache-manager的multiCaching组合了内存缓存memory最大 250 项MAX_CACHE_SIZE 250与文件系统缓存fsStore即 packages/gatsby/src/cache/cache-fs.ts兼顾读取速度与跨进程/跨构建持久化TTL 默认无限TTL Number.MAX_SAFE_INTEGER即缓存默认不过期除非你显式传args覆盖错误安全get/set内部把底层错误吞掉——get出错 resolve 为undefinedset出错 resolve 为原值或undefined见 cache.ts#L66-L94所以插件中直接使用不会因缓存问题导致构建崩溃必须 init 后才能使用未调用init()就调用get/set会抛出GatsbyCache wasnt initialised yet...错误——这一点在测试 packages/gatsby/src/utils/tests/cache.ts#L125-L137 中有明确断言而插件侧拿到的cache已被 Gatsby 初始化直接使用即可。另外Gatsby 还提供了getCache(id)帮助函数用于按名称获取具名缓存实例它“只应被接受子插件subplugins的插件使用”见 api-node-helpers-docs.js#L159-L165。同名缓存在同一运行期内会被复用相关行为可由测试 packages/gatsby/src/utils/tests/get-cache.ts 印证。插件实战示例缓存带过期时间的 GraphQL 查询结果官方文档给出了一个完整可运行的插件示例位于插件项目的gatsby-node.js中。它演示了三个典型技巧先查缓存、命中即用、未命中则计算并回写以及基于时间的过期策略exports.onPostBuild async function ({ cache, graphql }, { query }) { const cacheKey some-key-name const twentyFourHoursInMilliseconds 24 * 60 * 60 * 1000 // 86400000 let obj await cache.get(cacheKey) if (!obj) { obj { created: Date.now() } const data await graphql(query) obj.data data } else if (Date.now() obj.lastChecked twentyFourHoursInMilliseconds) { /* Reload after a day */ const data await graphql(query) obj.data data } obj.lastChecked Date.now() await cache.set(cacheKey, obj) /* Do something with data ... */ }逐行拆解这段代码的逻辑定义缓存键cacheKey是字符串建议在真实项目中包含命名空间与可辨识的标识如资源 ID、语言环境例如my-plugin-data-2024-01首次命中cold cachecache.get(cacheKey)返回undefined于是创建新对象{ created: Date.now() }执行 GraphQL 查询并存入obj.data过期重取warm cache若缓存对象存在且超过 24 小时86400000毫秒未刷新则重新执行查询覆盖obj.data维护时间戳无论何种分支最后都更新obj.lastChecked并回写cache.set(cacheKey, obj)业务消费注释/* Do something with data ... */处即使用obj.data的后续逻辑例如用actions.createPage基于数据创建页面。一个值得注意的细节示例代码检查的是Date.now() obj.lastChecked twentyFourHoursInMilliseconds但首次创建的对象并没有lastChecked字段此时会得到NaN比较恒为false因此首次写入后同一次运行不会再触发重取——从逻辑上看更严谨的写法可以在首次创建分支中也初始化lastChecked。这里保留了官方文档的原始写法实际使用时可自行修正。为什么这对插件开发者很重要gatsby develop被高频执行任何未缓存的远程请求或图像处理都会拖慢每次启动把这类工作放进 cache 后只有首次运行或缓存过期时才真正执行对最终用户的开发体验是质的提升。官方插件的真实缓存实践官方文档在结尾列举了多个已经落地 cache API 的官方插件实现。当前仓库中均有对应源码可作为深入学习的范本gatsby-source-contentful远程资源下载缓存packages/gatsby-source-contentful/src/download-contentful-assets.js 中downloadContentfulAssets从gatsbyFunctions解构出cache对每个 Contentful 资源使用带 ID 与 locale 的缓存键const remoteDataCacheKey contentful-asset-${id}-${locale} const cacheRemoteData await cache.get(remoteDataCacheKey) // Avoid downloading the asset again if its been cached if (cacheRemoteData) { fileNodeID cacheRemoteData.fileNodeID touchNode(getNode(cacheRemoteData.fileNodeID)) } // If we dont have cached data, download the file if (!fileNodeID) { const fileNode await createRemoteFileNode({ url, ... }) // ...创建节点并 cache.set(remoteDataCacheKey, ...) }这段代码展示了缓存最典型的收益场景避免重复下载远程图片/资源。命中缓存时直接touchNode复用已下载的本地文件节点未命中时才调用createRemoteFileNode真正下载。配套测试见 packages/gatsby-source-contentful/src/tests/download-contentful-assets.js其中对cache.get/cache.set的调用参数有精确断言。此外 create-schema-customization.js 中还用cache.set(CACHE_CONTENT_TYPES, contentTypeItems)/cache.get(...)缓存内容类型定义避免每次构建重复拉取 schema。其他官方插件参考gatsby-source-shopify在 packages/gatsby-source-shopify/src/nodes.js 中缓存节点数据gatsby-source-wordpress在 packages/gatsby-source-wordpress/src/normalize.js 中缓存规范化后的数据gatsby-transformer-remark在 packages/gatsby-transformer-remark/src/extend-node-type.js 中缓存 Markdown 处理结果。这些插件的共同模式都是“缓存键 资源标识 版本/语言信息命中即跳过昂贵操作”与本文示例中的模式完全一致。清除缓存手动清理与自动失效手动清除由于缓存文件存放在.cache目录内清除缓存有两种方式直接删除.cache目录缓存随之全部清除同时被删除的还有 Gatsby 的其他中间状态使用gatsby clean命令官方推荐的方式它会同时删除.cache与public两个目录让站点回到完全干净的状态该命令的完整说明见 Gatsby CLI 文档。自动失效cache invalidationGatsby 在以下几种情况会自动使缓存失效删除站点的.cache目录package.json发生变化例如某个依赖被更新或新增gatsby-config.js发生变化例如新增或修改了插件配置gatsby-node.js发生变化例如调用了新的 Node API或修改了某个createPage调用。上述规则的底层依据可以在核心初始化流程 packages/gatsby/src/services/initialize.ts 中找到。该文件initialize.ts#L309-L354展示了具体实现思路Gatsby 会计算所有已安装插件的版本号、站点package.json、gatsby-config.js、gatsby-node.js含.ts变体以及 trailingSlash 配置的MD5 哈希并与上次运行存储在状态中的PLUGINS_HASH比较一旦哈希不一致就会提示One or more of your plugins have changed since the last time you ran Gatsby. As a precaution, were deleting your sites cache to ensure theres no stale data.“检测到插件/配置变更作为预防措施删除站点缓存确保没有过期数据”。此外 initialize.ts#L356-L365 还处理了一种“缓存损坏”场景当.cache目录存在但public目录不存在时缓存不完整同样会删除缓存以防陈旧数据。这意味着开发者在排障“为什么缓存没生效/出现奇怪结果”时应首先检查上述文件是否有改动。结论与最佳实践通过 Cache API你可以让数据在多次构建之间持久化复用。这对高频执行gatsby develop的开发场景尤其有价值——性能密集的操作如图像变换或远程数据下载会显著拖慢 Gatsby 的启动bootstrap而把这些优化加入自己的插件能为最终用户带来巨大的体验提升。综合官方文档与当前仓库源码整理出以下实践要点选对生命周期钩子onPostBootstrap、onPostBuild、sourceNodes等 Node API 中均可使用cache选择与你的数据产生时机匹配的钩子缓存键要有命名空间形如contentful-asset-${id}-${locale}的键既防冲突又可读后续排查也方便总是处理“未命中”分支cache.get返回undefined时重新计算并cache.set回写形成完整的“读缓存 → 计算 → 写缓存”闭环必要时自己管理过期底层 TTL 默认是Number.MAX_SAFE_INTEGER不过期需要定期刷新时请在缓存对象内记录时间戳如官方示例的lastChecked自行判断缓存出错不应中断构建底层实现会吞掉缓存读写错误get返回undefined、set返回原值所以插件代码无需为缓存异常做防御性 try/catch可专注于业务逻辑善用gatsby clean遇到疑似陈旧数据导致的问题时先检查package.json、gatsby-config.js、gatsby-node.js是否变动再执行gatsby clean彻底重置.cache与public。想继续深入可在当前仓库中重点阅读cache 实现、API jsdoc 定义、缓存相关测试以及上文列举的官方插件缓存代码。【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址: https://gitcode.com/gh_mirrors/ga/gatsby创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考