Gatsby 实战:用 GraphQL 查询 Markdown 博客内容——以 graphql-reference 示例的 History of Magic 为例
前端静态站点Web框架【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址https://gitcode.com/gh_mirrors/ga/gatsby点击查看免费下载本篇技术指南以 Gatsby 仓库中 graphql-reference 示例项目 的博客数据文件 History of Magic 为具体样本完整讲解 Gatsby 如何把带 frontmatter 的 Markdown 文件转换为 GraphQL 数据节点并通过allMarkdownRemark完成筛选filter、排序sort、分页limit/skip、日期格式化formatString等查询操作。读完本文你将掌握在真实 Gatsby 站点中编写从基础到进阶的 GraphQL 查询、在 GraphiQL 中调试数据层以及将查询变量、分组、片段、别名与条件指令用于页面渲染的完整能力。一、这份文档在示例项目中扮演什么角色History of Magic并非一篇普通的技术文档而是 Gatsby 官方仓库中 graphql-reference 示例项目 里的演示内容数据。该项目的定位正如其 README 所述Example project containing a bunch of content. Makes it possible to show GraphQL queries for the documentation.一个包含大量内容的示例项目用于演示文档中的 GraphQL 查询。它与其他七篇博客文章如 Break with a Banshee、Hogwarts: A History 等一起共同构成一份可供查询的「魔法世界图书目录」数据集每篇文章都带有结构化的 frontmatter 元数据是验证 filter、sort、group 等 GraphQL 特性的理想样本。1.1 文档的 frontmatter 结构解析该文件的开头是一段标准的 YAML frontmatter--- title: History of Magic date: 1947-01-01 author: Bathilda Bagshot categories: [historical] ---四个字段的含义分别是title文章标题字符串类型在gatsby-node.js的页面创建查询中被用于过滤ne: date发布日期日期类型是后续 sort、格式化、fromNow演示的关键字段author作者名本项目通过gatsby-config.js中的mapping配置把它关联到AuthorYaml类型见下文categories分类数组用于in操作符与 group 分组的演示例如[historical]。frontmatter 之后是两段正文。正文内容是占位性质的示例文本由 Alohamora、Hogwarts 等词汇组成的填充文字其作用是为excerpt摘要字段提供可截取的内容。二、从 Markdown 文件到 GraphQL 节点的完整链路要理解为什么可以用allMarkdownRemark查询这篇文档需要看数据在构建期经过的三个环节均可在本仓库中直接核实。2.1 gatsby-source-filesystem把文件变成 File 节点在 gatsby-config.js 中项目通过gatsby-source-filesystem将content目录注册为内容源{ resolve: gatsby-source-filesystem, options: { path: ${__dirname}/content, name: content, }, },这一步让 Gatsby 扫描content目录下的所有文件为每个文件创建一个File节点——这是后续所有转换的基础。2.2 gatsby-transformer-remark把 Markdown 变成 MarkdownRemark 节点同一份配置中加载了gatsby-transformer-remarkgatsby-config.js它读取 File 节点中的 Markdown 内容生成MarkdownRemark节点并把 frontmatter 解析为可查询的frontmatter字段title、date、author、categories同时生成html、excerpt、timeToRead等字段。allMarkdownRemark这一连接Connection查询入口就是由该插件提供的。2.3 作者字段的跨类型关联mappinggatsby-config.js 中有一段值得注意的配置mapping: { MarkdownRemark.frontmatter.author: AuthorYaml, },它把MarkdownRemark的author字段从字符串映射为AuthorYaml类型——后者来自 author.yaml通过gatsby-transformer-yaml解析。因此查询中可以展开author { id bio }这种嵌套对象结构例如frontmatter { author { id bio } }对应模板 blog-post.js 中的渲染逻辑Written by: {post.frontmatter.author.id} - {post.frontmatter.author.bio}。三、页面是如何从这些数据生成的gatsby-node.js 是理解数据如何变为 URL 的关键文件包含两个核心钩子。3.1 onCreateNode为每篇 Markdown 生成 slugexports.onCreateNode ({ node, actions, getNode }) { const { createNodeField } actions if (node.internal.type MarkdownRemark) { const value createFilePath({ node, getNode }) createNodeField({ name: slug, node, value }) } }它对每个MarkdownRemark节点调用createFilePath来自gatsby-source-filesystem根据文件路径生成fields.slug如/History-of-Magic/使查询中能通过fields { slug }取得路由。3.2 createPages批量创建博客详情页exports.createPages ({ graphql, actions }) { const { createPage } actions const blogPost path.resolve(./src/templates/blog-post.js) return graphql( { allMarkdownRemark( sort: { frontmatter: { date: DESC } } limit: 1000 filter: { frontmatter: { title: { ne: } } } ) { edges { node { fields { slug } frontmatter { title } } } } } ).then(result { const posts result.data.allMarkdownRemark.edges posts.forEach((post, index) { const previous index posts.length - 1 ? null : posts[index 1].node const next index 0 ? null : posts[index - 1].node createPage({ path: post.node.fields.slug, component: blogPost, context: { slug: post.node.fields.slug, previous, next }, }) }) }) }这段代码本身就是一次完整的 GraphQL 实战它用sort: { frontmatter: { date: DESC } }按日期倒序、filter: { frontmatter: { title: { ne: } } }过滤掉无标题文章然后为每篇文章用 blog-post.js 模板创建页面并计算上一篇/下一篇previous/next传入context——这也是「查询变量」在页面中的真实用法。页面模板中的查询blog-post.js展示了带参数查询的写法query BlogPostBySlug($slug: String!) { site { siteMetadata { title } } markdownRemark(fields: { slug: { eq: $slug } }) { id excerpt(pruneLength: 160) html frontmatter { title date(formatString: MMMM DD, YYYY) author { id bio } } } }四、基础查询site 元数据与节点连接4.1 查询站点元数据siteMetadata定义于 gatsby-config.js本项目为Harry Potter - Books Authors可通过根类型site查询{ site { siteMetadata { title } } }在 GraphiQL 编辑器中按Ctrl Space可查看自动补全选项Ctrl Enter运行当前查询。4.2 查询多个数据节点edges/nodes 结构Gatsby 把内容组织为nodes集合节点之间以edges相连。下面的查询返回本站所有插件总数以及每个插件的名称、版本与描述{ allSitePlugin { totalCount edges { node { name version packageJson { description } } } } }从 Gatsby2.2.0起可以省略edges/node层级直接使用nodes简写{ allSitePlugin { totalCount nodes { name version packageJson { description } } } }五、字段参数Field Argumentslimit / skip / filter5.1 limit 与 skip控制返回数量limit限制返回条数skip跳过前 N 条。例如本项目共 8 篇文章limit: 2只取前两条queries.md 中也有同样示例{ allMarkdownRemark(limit: 2) { totalCount edges { node { frontmatter { title } } } } }{ allMarkdownRemark(skip: 3) { totalCount edges { node { frontmatter { title } } } } }5.2 filter 与完整操作符清单filter参数使用基于 Sift 的 MongoDB 风格查询语法支持嵌套字段查询。以「筛选出有标题的文章」为例{ allMarkdownRemark(filter: { frontmatter: { title: { ne: } } }) { totalCount edges { node { frontmatter { title } } } } }完整的操作符列表操作符含义说明eqequal等于必须与给定数据完全匹配nenot equal不等于必须与给定数据不同regexregular expression正则必须匹配给定模式globglobal通配*作为任意非空字符串的占位符inin array属于数组必须是数组的某个元素ninnot in array不属于数组必须不是数组的某个元素gtgreater than大于必须大于给定值gtegreater than or equal大于等于必须大于等于给定值ltless than小于必须小于给定值lteless than or equal小于等于必须小于等于给定值elemMatchelement match元素匹配针对数组字段用前述操作符对每个元素过滤一个覆盖全部操作符的查询示例源自 graphql-reference 官方文档{ # eq标题完全等于 Fantastic Beasts and Where to Find Them example_eq: allMarkdownRemark( filter: { frontmatter: { title: { eq: Fantastic Beasts and Where to Find Them } } } ) { edges { node { frontmatter { title } } } } # ne标题不等于空字符串 example_ne: allMarkdownRemark( filter: { frontmatter: { title: { ne: } } } ) { edges { node { frontmatter { title } } } } # regex标题不以 T 开头即 /^[^T]/ example_regex: allMarkdownRemark( filter: { frontmatter: { title: { regex: /^[^T]/ } } } ) { edges { node { frontmatter { title } } } } # glob标题包含单词 History* 代表任意非空字符串 example_glob: allMarkdownRemark( filter: { frontmatter: { title: { glob: *History* } } } ) { edges { node { frontmatter { title } } } } # in标题是数组中二者之一 example_in: allMarkdownRemark( filter: { frontmatter: { title: { in: [Childrens Anthology of Monsters, Hogwarts: A History] } } } ) { edges { node { frontmatter { title, date } } } } # nin标题不是二者中任何一个 example_nin: allMarkdownRemark( filter: { frontmatter: { title: { nin: [Childrens Anthology of Monsters, Hogwarts: A History] } } } ) { edges { node { frontmatter { title, date } } } } # lte阅读时间小于等于 4 分钟 example_lte: allMarkdownRemark(filter: { timeToRead: { lte: 4 } }) { edges { node { frontmatter { title } } } } # elemMatch插件依赖中包含 chokidar example_elemMatch: allSitePlugin( filter: { packageJson: { dependencies: { elemMatch: { name: { eq: chokidar } } } } } ) { edges { node { name } } } }多字段同时过滤AND 语义多个过滤条件用逗号分隔语义为 AND。下面的查询要求文章属于magical creatures分类且标题包含Fantastic{ allMarkdownRemark( filter: { frontmatter: { categories: { in: [magical creatures] } title: { regex: /Fantastic/ } } } ) { totalCount edges { node { frontmatter { title } } } } }操作符组合使用同一个字段可以叠加多个操作符。下面的查询先用regex: /History/匹配出Hogwarts: A History与History of Magic再用ne排除History of Magic最终只剩Hogwarts: A History{ allMarkdownRemark( filter: { frontmatter: { title: { regex: /History/, ne: History of Magic } } } ) { totalCount edges { node { frontmatter { title } } } } }六、sort结果排序按frontmatter.date升序排列{ allMarkdownRemark(sort: { frontmatter: { date: ASC } }) { totalCount edges { node { frontmatter { title, date } } } } }6.1 多字段排序Childrens Anthology of Monsters与Break with Banshee日期相同1992-01-01单字段排序无法决定二者先后追加title排序即可稳定顺序{ allMarkdownRemark( sort: [{ frontmatter: { date: ASC } }, { frontmatter: { title: ASC } }] ) { totalCount edges { node { frontmatter { title, date } } } } }6.2 排序方向ASC升序与DESC降序可混合使用例如按日期升序、标题降序{ allMarkdownRemark( sort: [{ frontmatter: { date: ASC } }, { frontmatter: { title: DESC } }] ) { totalCount edges { node { frontmatter { title, date } } } } }七、格式化日期与摘要7.1 formatString 日期格式化Gatsby 借助 Moment.js 提供formatString参数支持 Moment 的全部 tokendddd、DD、MMMM、YYYY等{ allMarkdownRemark(filter: { frontmatter: { date: { ne: null } } }) { edges { node { frontmatter { title date(formatString: dddd DD MMMM YYYY) } } } } }还可以传入locale让输出适配语言例如德语de-DE{ allMarkdownRemark(filter: { frontmatter: { date: { ne: null } } }) { edges { node { frontmatter { title date(formatString: dddd DD MMMM YYYY, locale: de-DE) } } } } }fromNow 与 differencedate(fromNow: true)用 Moment 的fromNow返回相对时间描述date(difference: days)返回与当前时间的差值单位由参数指定如days{ one: allMarkdownRemark(filter: { frontmatter: { date: { ne: null } } }, limit: 2) { edges { node { frontmatter { title, date(fromNow: true) } } } } two: allMarkdownRemark(filter: { frontmatter: { date: { ne: null } } }, limit: 2) { edges { node { frontmatter { title, date(difference: days) } } } } }7.2 excerpt 摘要选项excerpt支持三个参数pruneLength截取长度、truncate是否硬截断、formatPLAIN或HTML{ allMarkdownRemark(filter: { frontmatter: { date: { ne: null } } }, limit: 5) { edges { node { frontmatter { title } excerpt(format: PLAIN, pruneLength: 200, truncate: true) } } } }这正是列表页 index.js 渲染文章摘要时用到的机制——它查询excerpt并通过dangerouslySetInnerHTML输出。7.3 组合示例sort filter limit format 一起使用{ allMarkdownRemark( limit: 3 filter: { frontmatter: { date: { ne: null } } } sort: { frontmatter: { date: DESC } } ) { edges { node { frontmatter { title date(formatString: dddd DD MMMM YYYY) } } } } }八、查询变量Query Variables除直接内联参数外GraphQL 支持把参数以「查询变量」形式传入标量或对象均可。下面的查询与上一节功能等价但把limit、filter、sort提取为变量query GetBlogPosts( $limit: Int $filter: MarkdownRemarkFilterInput $sort: MarkdownRemarkSortInput ) { allMarkdownRemark(limit: $limit, filter: $filter, sort: $sort) { edges { node { frontmatter { title date(formatString: dddd DD MMMM YYYY) } } } } }# Query Variables { limit: 5, filter: { frontmatter: { date: { ne: null } } }, sort: { fields: frontmatter___title, order: DESC } }注意变量模式中sort使用的是fields字段路径frontmatter___title用三个下划线连接嵌套路径order的写法。要在页面组件中传查询变量就在gatsby-node.js调用createPage时把值放入context对象本项目已把slug、previous、next放入 context 作为范例。九、group字段分组统计可按字段如分类分组得到fieldValue分组值、totalCount出现次数与edges该组的节点{ allMarkdownRemark(filter: { frontmatter: { title: { ne: } } }) { group(field: { frontmatter: { tags: SELECT } }) { fieldValue totalCount edges { node { frontmatter { title } } } } nodes { frontmatter { title categories } } } }以本项目数据为例magical creatures分类下共有 3 本书Break with a Banshee、Childrens Anthology of Monsters、Fantastic Beasts and Where to Find Themgroup 查询即可得到该统计结果。这是构建分类归档页、标签云的常用手段。十、Fragments片段与 Aliasing别名10.1 片段复用片段用于复用频繁出现的查询片段。把片段放在 Gatsby 可感知的任意文件中并作为具名导出即可在项目内任意 GraphQL 查询中全局使用因此片段名必须全局唯一fragment fragmentName on Site { siteMetadata { title } } { site { ...fragmentName } }10.2 别名别名允许对同一数据源发起多个查询并在结果中用别名而非原始根查询名引用data.someEntries/data.someMoreEntries{ someEntries: allMarkdownRemark(skip: 3, limit: 3) { edges { node { frontmatter { title } } } } someMoreEntries: allMarkdownRemark(limit: 3) { edges { node { frontmatter { title } } } } }字段同样可以起别名——这在「同一字段多种展示形态」时非常实用{ allMarkdownRemark(skip: 3, limit: 3) { edges { node { frontmatter { header: title date relativeDate: date(fromNow: true) } } } } }十一、Conditionalsinclude 与 skip 指令GraphQL 支持根据变量值条件性地包含/跳过查询片段适合按需渲染页面局部内容。include(if: $withDate)在条件为真时包含skip(if: $withDate)在条件为真时跳过query GetBlogPosts($withDate: Boolean false) { allMarkdownRemark(limit: 3, skip: 1) { edges { node { frontmatter { title date include(if: $withDate) } } } } }指令可用于查询的任何层级甚至作用于片段query GetBlogPosts($preview: Boolean true) { allMarkdownRemark(limit: 3, skip: 1) { edges { node { ...BlogPost skip(if: $preview) ...BlogPostPreview include(if: $preview) } } } allFile(limit: 2) skip(if: $preview) { edges { node { relativePath } } } } fragment BlogPost on MarkdownRemark { html frontmatter { title date } } fragment BlogPostPreview on MarkdownRemark { excerpt frontmatter { title } }十二、本地运行与调试本示例项目的脚本定义在 package.json 中gatsby develop或npm run dev等价于gatsby develop -o自动打开浏览器启动开发服务器gatsby build生产构建。启动后在浏览器访问http://localhost:8000/___graphql注意是三个下划线即可进入 GraphiQL 编辑器对allMarkdownRemark、filter、sort、formatString等所有本节提到的查询进行实时调试。示例项目还内置了一份完整的查询备忘清单位于 content/queries.md覆盖基础查询、limit、skip、filter、sort、format、组合查询、查询变量、group、fragments、aliasing 等全部场景可作为速查手册对照练习。十三、小结一条数据样本背后的完整数据流回顾整条链路History of Magic/index.md先由gatsby-source-filesystem扫描为 File 节点再由gatsby-transformer-remark解析为MarkdownRemark节点frontmatter 变成frontmatter字段作者经mapping关联到AuthorYaml随后gatsby-node.js在onCreateNode中生成 slug、在createPages中用一次带 sort/filter 的 GraphQL 查询批量创建页面最终页面模板通过markdownRemark(fields: { slug: { eq: $slug } })读取单篇文章并渲染。掌握了这条数据流与上述全部查询技巧你就能在任何 Gatsby 项目中自如地查询、过滤、排序、格式化 Markdown 内容并借助查询变量、片段、别名与条件指令构建灵活的内容页面——这正是 graphql-reference 官方文档 与示例项目 graphql-reference 想要传达的核心能力。赞分享前端静态站点Web框架【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址https://gitcode.com/gh_mirrors/ga/gatsby点击查看免费下载相关推荐Gatsby GraphQL 数据层实战以 graphql-reference 示例中的 Break with a Banshee 为样本读懂 Markdown 内容如何变成可查询节点Gatsby GraphQL 数据层实战以 graphql reference 示例中的 Break with a Banshee 为样本读懂 Mark前端静态站点Web框架Gatsby RSS Feed 生成实战以 gatsby-plugin-feed 与 Markdown 博客内容为例Gatsby RSS Feed 生成实战以 gatsby plugin feed 与 Markdown 博客内容为例 导读 本文以 Gatsby 官方仓库中的前端静态站点Web框架Gatsby GraphQL 查询实战指南基于 graphql-reference 示例掌握查询、过滤、排序与组合技巧Gatsby GraphQL 查询实战指南基于 graphql reference 示例掌握查询、过滤、排序与组合技巧 Gatsby 的核心优势之一在于其内置前端静态站点Web框架上一篇IDM试用期到期怎么办开源IAS脚本三步冻结永久试用附全部参数与排错指南下一篇10个AlienFX-Tools实用技巧打造个性化灯光效果与系统监控方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考