Gatsby v4.1 发布解析:File System Route API 接入 DSG 与 JSX Runtime 配置
【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址: https://gitcode.com/gh_mirrors/ga/gatsby
导读
gatsby@4.1.0(2021 年 11 月第 1 个版本)是本仓库中 Gatsby 核心框架在 v4 时代的重要迭代。本指南围绕官方发布说明 docs/docs/reference/release-notes/v4.1/index.md 展开,重点剖析两大新特性:在 File System Route API 中通过config()函数启用 Deferred Static Generation(DSG),以及在gatsby-config.js中新增jsxRuntime/jsxImportSource配置项。读完本文,你将掌握config()函数的完整写法与执行时机、DSG 的工作原理与限制,以及如何让 JSX 在无需手动导入 React 的情况下运行,并了解本版本附带的关键 bugfix 与性能改进。
前置说明:本文涉及的文件与代码均来自当前仓库实际内容。
v4.1属于 2021 年的历史版本,文中源码引用以当前仓库实现为准,用于佐证 API 的设计意图与底层机制。
一、Deferred Static Generation 概述
Deferred Static Generation(DSG)是 Gatsby 的渲染选项之一,其核心思想是:不再于构建阶段生成所有页面,而是将非关键页面延迟到首次用户请求时才生成。这样做可以显著缩短大型站点的构建时间——例如拥有大量历史文章、访问频率低的博客站点,就能把"老文章页面"的生成推迟到运行时。
DSG 的完整说明见仓库内的 DSG API 参考 与 DSG 使用指南,要点如下:
- 页面首次请求时属于"缓存未命中",因为该页面的 HTML/JSON 尚未生成;随后在后台生成产物并缓存,第二次请求起直接由 CDN 提供缓存响应。
- 直接访问页面返回 HTML;通过 Gatsby 的
Link组件进行客户端导航时返回 JSON,由路由在客户端渲染。 - DSG 需要运行 NodeJS 服务器,当前完全支持
gatsby serve命令及 Gatsby Cloud;在gatsby develop下 DSG 不生效。 - 延迟页面通过
defer键标记,默认页面在构建时生成(defer缺省为false)。
在 v4.1 之前,启用 DSG 的唯一方式是使用createPageaction 并传入defer: true(在gatsby-node.js中)。v4.1 的核心变化是:File System Route API 模板文件也支持 DSG 了,其入口是新增的config()函数。
二、核心特性一:config()函数让 File System Route API 支持 DSG
File System Route API 允许通过文件名中的花括号语法自动创建动态页面,例如src/pages/products/{Product.name}.js会为每个Product节点生成/products/burger这样的路由。其完整语法(点号字段、__嵌套字段、( )联合类型、gatsbyPath链接解析等)见 file-system-route-api.md。
v4.1 在其中引入了一个全新的 API:模板内可导出的异步config()函数。
2.1config()函数的基本形态
config()的典型写法如下(来自发布说明中的完整示例,文件为src/pages/{MarkdownRemark.fields__slug}.jsx):
import React from "react" import { graphql } from "gatsby" export default function Component(props) { return <pre>{JSON.stringify(props, null, 2)}</pre> } export async function config() { // Get all posts that were created before 2020-10-31 const { data } = graphql` { oldPosts: allMarkdownRemark( filter: { frontmatter: { date: { lt: "2020-10-31" } } } ) { nodes { fields { slug } } } } ` // Create a Set for easier comparison/lookup const oldPosts = new Set(data.oldPosts.nodes.map(n => n.fields.slug)) // Return a function that when called will return a config for FS route pages // (right now only `defer` is supported) return ({ params }) => { return { // Defer pages older than 2020-10-31 defer: oldPosts.has(params.fields__slug), } } } export const pageQuery = graphql` query BlogPost($id: String!) { markdownRemark(id: { eq: $id }) { html frontmatter { title date(formatString: "MMMM DD, YYYY") description } } } `2.2config()的语法规则与执行机制
从发布说明与 file-system-route-api.md 的configfunction 章节 可以归纳出以下规则:
- 外层异步函数可以执行 GraphQL 查询:
config()内部可以像页面查询一样使用graphql模板标签查询数据层;也可以完全不查询,直接使用普通 JavaScript。 - 必须返回一个函数:
config()返回的内层函数接收params参数并返回配置对象。params与页面组件中props.params是同一对象,例如src/pages/{Product.name}.js中可通过params.name取到节点字段值。 - 内层函数中不能运行 GraphQL 查询:查询只能放在外层
config()中,内层仅负责根据params计算并返回配置。 - 当前仅支持
defer一个配置键:返回对象形如{ defer: boolean },布尔值决定该页面是否被标记为延迟生成。
最小化版本——延迟当前 File System Route 模板生成的所有页面:
export async function config() { return ({ params }) => { return { defer: true, } } }2.3 从源码看config()的执行流程
config()并非仅在模板里"写出来即可",Gatsby 在构建阶段会真正加载并执行它。当前仓库中 packages/gatsby/src/utils/page-mode.ts 实现了相关逻辑,可以从源码结构看到完整调用链:
preparePageTemplateConfigs(graphql)(第 118-145 行)遍历所有组件,对声明了config导出的组件,通过pageRenderer.getPageChunk({ componentChunkName })拿到页面模板实例,然后调用componentInstance.config()获取"配置工厂函数",并校验其必须是函数(否则抛出Unexpected result of config factory...错误),最后存入pageConfigMap。resolvePageMode(page, component)(第 37-80 行)对每个页面调用pageConfigMap中对应的配置函数:以page.context.__params作为params传入,若返回的pageConfig.defer为布尔值,则据此把页面模式解析为DSG或SSG;若未命中则回退到页面自身的page.defer判断。materializePageMode()(第 89-116 行)将 DSG/SSR 页面的mode持久化,确保gatsby serve能正确工作。
由此可以推断config()的设计意图:外层查询一次数据层并"闭包捕获"结果,内层函数则针对每个页面实例(每个 URL)快速做布尔判断,从而避免为每个页面重复执行 GraphQL 查询,兼顾正确性与构建性能。
2.4 典型实战:按日期延迟"旧文章"
结合 DSG 使用指南 中的真实场景:博客使用gatsby-transformer-remark,每篇 Markdown 的 frontmatter 含slug与date,目标是延迟所有早于 2021-10-31 的文章。模板src/pages/{MarkdownRemark.frontmatter__slug}.jsx:
import * as React from "react" import { graphql } from "gatsby" export default function Component(props) { return <pre>{JSON.stringify(props, null, 2)}</pre> } export const query = graphql` query ($id: String) { markdownRemark(id: { eq: $id }) { html frontmatter { slug date } } } ` export async function config() { const { data } = graphql` { oldPosts: allMarkdownRemark( filter: { frontmatter: { date: { lt: "2021-10-31" } } } ) { nodes { frontmatter { slug } } } } ` const oldPosts = new Set(data.oldPosts.nodes.map(n => n.frontmatter.slug)) return ({ params }) => { return { defer: oldPosts.has(params.frontmatter__slug) } } }这里params.frontmatter__slug对应文件路径中的{MarkdownRemark.frontmatter__slug}动态段;将当前文章的 slug 与"旧文章集合"比对,命中则defer: true。
2.5 对照:gatsby-node.js中使用createPage+defer
若你使用createPagesAPI(而非文件系统路由),DSG 的写法是在createPage中直接传defer参数(参考 DSG API 参考 与 使用指南):
const blogPostTemplate = require.resolve(`./src/templates/blog-post.js`) exports.createPages = async ({ graphql, actions, reporter }) => { const { createPage } = actions const result = await graphql(` query { allMdx(sort: { frontmatter: { date: DESC }}) { nodes { slug } } } `) if (result.errors) { reporter.panicOnBuild(`There was an error loading posts`, result.errors) return } const posts = result.data.allMdx.nodes posts.forEach((post, index) => { createPage({ path: post.slug, component: blogPostTemplate, context: { slug: post.slug, }, // index is zero-based index defer: index + 1 > 100, }) }) }查询结果按日期降序排列,前 100 篇(defer: false)在构建时生成,其余 900 篇(defer: true)延迟到首次请求。defer参数是可选的,缺省表示构建时生成。
2.6 测试config()的注意事项
发布说明明确指出:
gatsby develop下 DSG 不生效,目前只能通过gatsby build测试config();- 这是
config()API 的首个迭代版本,Gatsby 团队公开征集反馈; - 本地测试延迟生成的具体方式是执行
gatsby build后运行gatsby serve,延迟页面会在首次请求时生成。
2.7 DSG 的已知限制
从 DSG API 参考 可以看到两条重要限制,使用前需知悉:
gatsby-config中不允许使用函数:配置文件会被打包进 DSG 引擎,必须是可序列化的,函数/回调会失效:
module.exports = { plugins: [ { resolve: `gatsby-plugin-acme`, options: { // ⚠️ Doesn't work optionA: () => `foobar`, // OK optionB: `foobar` } } ] }- DSG 引擎不支持
onCreateWebpackConfig:通过该 API 修改的 webpack 配置不会应用到 DSG 引擎;在gatsby-node的createResolvers/createSchemaCustomization等 API 中若依赖自定义 webpack 变更(如路径别名)则无法工作。
三、核心特性二:gatsby-config.js中的 JSX Runtime 选项
3.1 新配置项
v4.1 允许在gatsby-config.js中直接配置jsxRuntime与jsxImportSource:
module.exports = { jsxRuntime: "automatic", jsxImportSource: "@emotion/react", }jsxRuntime:设置为"automatic"后,JSX 无需显式导入 React 即可使用(对应 React 官方新的 JSX 转换)。取值与校验规则见 packages/gatsby/src/joi-schemas/joi.ts 第 57-58 行:Joi.string().valid('automatic', 'classic').default('classic'),即仅允许automatic/classic两个取值,默认值为classic(兼容旧行为:需手动导入 React)。jsxImportSource:指定 React 底层 JSX 转换所使用的包,典型场景是搭配 Emotion 等 CSS-in-JS 库(如示例中的@emotion/react),让 JSX 转换自动注入来自该包的运行时。
3.2 配置如何传递到编译管线
从当前仓库源码可以看到这两个配置项的实际流向:
- 配置合并层 packages/gatsby/src/utils/merge-gatsby-config.ts 第 25-26 行定义了
IGatsbyConfigInput中的类型:jsxRuntime?: "classic" | "automatic"、jsxImportSource?: string,表明二者属于配置输入的一部分,且在主题/用户配置合并时遵循该文件的合并规则。 - Webpack/Babel 编译层 packages/gatsby/src/utils/webpack-utils.ts 第 393-410 行:JS loader 的 options 中把
reactRuntime: config.jsxRuntime与reactImportSource: config.jsxImportSource传给 Babel loader,进而影响 JSX 转换插件的行为;此外第 828 行还会根据config.jsxRuntime === 'automatic'决定是否启用相应的 ESLint 规则(例如不再强制要求导入 React 的校验)。
也就是说,在gatsby-config.js里声明这两个顶层键后,Gatsby 会自动把它们注入 Babel/ESLint 编译配置,站点内的所有页面与组件统一生效,无需在每个文件中手工写/** @jsxRuntime automatic */之类的 pragma。
四、本版本的其他改进与 Bugfix
发布说明的 "Notable bugfixes & improvements" 部分记录了 v4 时代随附的若干修复与优化:
| 影响包 | 内容 | 类型 |
|---|---|---|
gatsby | 缓存 Query Engine 与 SSR engine(使用 DSG 时),以缩短构建时间 | 性能优化 |
gatsby | 将pageContext传入getServerData() | 功能修复 |
gatsby-source-contentful | 修复downloadLocal选项不生效的问题 | Bugfix |
gatsby-plugin-image | 修复重渲染时的闪烁/眨眼问题;修复GatsbyImage在 IE11 不显示图片 | Bugfix |
gatsby-remark-images | 修复设置GATSBY_EMPTY_ALT时 figure 图注的生成 | Bugfix |
gatsby-plugin-sharp | 在使用gatsby-plugin-image时向 sharp 传递failOnError | Bugfix |
| 文档 | 更新 "Creating a Source Plugin" 指南以适配 v4 变更 | 文档改进 |
其中与本主题最相关的是:DSG 场景下缓存 SSR engine与getServerData()获得pageContext这两项,它们分别从构建速度与 SSR/DSG 数据传递两个维度完善了动态渲染能力。需要说明的是,以上条目来自发布说明的历史记录,当前仓库代码已在此基础上持续演进,此处仅作版本事实陈述。
此外,发布说明还收录了大量社区贡献者(约 20 人)的 PR,涉及gatsby-source-wordpress、gatsby-plugin-mdx、gatsby-source-faker、gatsby-remark-images等包的文档与类型修正——这些细节体现了 v4.1 作为"功能落地 + 社区巩固"版本的特点。
五、快速上手清单与版本说明
若要在自己的项目中体验 v4.1 的两大新特性,可按以下步骤操作:
- 升级 Gatsby:
npm install gatsby@4.1.0(发布说明同时提示,想抢先体验新功能可安装gatsby@next,即 4.2 及后续预发布版本)。 - 使用 DSG + File System Route API:
- 确认站点使用
gatsby build+gatsby serve进行测试(gatsby develop下 DSG 不生效); - 在
src/pages/下的集合路由模板(如{MarkdownRemark.frontmatter__slug}.jsx)中导出异步config()函数,外层可选查询 GraphQL,内层返回{ defer: boolean }; - 验证
defer: true的页面在首次访问时才生成,且后续请求命中缓存。
- 确认站点使用
- 启用自动 JSX Runtime:
- 在
gatsby-config.js顶层添加jsxRuntime: "automatic"(可选再配jsxImportSource); - 移除组件中多余的
import React from "react",确认构建通过; - 注意取值必须为
"automatic"或"classic",缺省为"classic"(见 joi.ts)。
- 在
- 若使用
createPages而非文件系统路由,则在createPage调用中传defer: true/false(参考 DSG API 参考)。 - 遵守 DSG 限制:
gatsby-config内不得使用函数(需可序列化),避免依赖onCreateWebpackConfig的定制在 DSG 引擎中生效。
六、进一步阅读
- File System Route API 完整语法与
config()参考:file-system-route-api.md - DSG API 参考(含限制与工作原理解释):deferred-static-generation.md
- DSG 实战指南(
createPage与文件系统路由双路线):using-deferred-static-generation.md config()执行流程与页面模式解析源码:page-mode.ts- JSX Runtime 配置的校验与编译传递源码:joi.ts、merge-gatsby-config.ts、webpack-utils.ts
- 仓库内完整示例:examples/route-api(File System Route API 各种用法的可运行 demo)
【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址: https://gitcode.com/gh_mirrors/ga/gatsby
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考