ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Gatsby 大规模 Markdown 基准测试:用 markdown_id 探究按 id 索引建站的性能表现

Gatsby 大规模 Markdown 基准测试:用 markdown_id 探究按 id 索引建站的性能表现 Gatsby 大规模 Markdown 基准测试用 markdown_id 探究按 id 索引建站的性能表现【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址: https://gitcode.com/gh_mirrors/ga/gatsby本篇技术指南围绕 Gatsby 官方基准测试项目 benchmarks/markdown_id 展开介绍一个以gatsby-starter-blog为基础、通过程序化生成海量随机 Markdown 页面、并在查询阶段以节点id而非slug作为页面索引键的完整基准方案。读完本文你将掌握该基准的目录结构与运行流程、NUM_PAGES与MAX_NUM_ROWS两个核心环境变量的作用、页面生成脚本与模板的实现原理以及如何复现并对比「按 id 索引」与「按 slug 索引」两种建站方式在构建性能上的差异。基准测试的定位为什么专门做一个 Index id 的变体在 Gatsby 源码仓库的benchmarks/目录下官方维护了一批用于度量 Gatsby 构建能力的基准项目其中 Markdown 系列包含 markdown_id 与 markdown_slug 两个高度对称的姊妹基准。二者共享几乎相同的代码骨架唯一的关键差异体现在 README 的第一行markdown_idMarkdown Benchmark Index id即构建时在 GraphQL 查询里以 Markdown 节点的id字段作为页面定位依据markdown_slugMarkdown Benchmark Index slug即同样场景下改用从文件路径推导出的slug字段。该基准的 README 明确指出原文This particular markdown benchmark will query pages by theirid, which is (at the time of writing) faster than indexing pages by theirslug截至文档撰写时按id查询页面比按slug索引更快。这正是该基准存在的意义——通过可控的页面规模量化不同索引键对 Gatsby 构建管线的影响为后续的查询性能优化提供可复现的度量样本。整个基准的工作流非常直观用脚本预生成一批伪随机Markdown 文件存放在markdown-pages/目录通过gatsby-source-filesystemgatsby-transformer-remark将这些文件转换为 MarkdownRemark 节点在createPages阶段查询全部节点将节点的id注入页面context页面模板通过markdownRemark(id: { eq: $id })精确取回单个节点并渲染 HTML执行gatsby build统计整条管线的耗时与资源占用。基准目录结构一览先完整认识该基准的组成以下文件均位于仓库根目录下路径职责benchmarks/markdown_id/README.md基准说明与使用文档本文的主体benchmarks/markdown_id/md.generate.js页面批量生成脚本受环境变量控制benchmarks/markdown_id/md.tpl.js单页 Markdown 模板基于 faker 生成内容benchmarks/markdown_id/gatsby-node.jscreatePages与onCreateNode的 API 实现benchmarks/markdown_id/gatsby-config.js站点与插件配置benchmarks/markdown_id/package.jsonbench/benchnb等核心脚本定义benchmarks/markdown_id/src/templates/blog-post.js文章页模板内含按 id 查询的 pageQuerybenchmarks/markdown_id/src/pages/index.js首页模板展示全部文章列表benchmarks/markdown_id/src/components/bio、layout、seo 等通用组件benchmarks/markdown_id/scripts/data-update.ts预留的数据更新脚本当前为 noop 占位其中markdown-pages/目录是运行时由生成脚本创建的产物不出现在源码树中安装阶段即会被自动生成见下文postinstall。页面生成原理md.generate.js 与 md.tpl.js生成脚本的入口逻辑md.generate.js 是整个基准的数据源头它通过两个环境变量控制数据规模NUM_PAGES要生成的 Markdown 文件总数默认值为1000parseInt(process.env.NUM_PAGES || 1000)README 与package.json中的脚本则统一按2000作为标准运行量MAX_NUM_ROWS每个页面正文中 API 表格的最大行数默认值为25。脚本对这两个变量做了防御性校验如果解析后不是正整数typeof ! number || !Number.isInteger(...) || 0会直接抛出错误并给出明确提示避免在异常输入下静默产出脏数据。生成过程带进度输出每完成 10% 打印一次百分比便于在NUM_PAGES较大时观察进度。核心生成逻辑非常简单直观const root markdown-pages console.time(Generated in) let p10 Math.round(NUM_PAGES / 10) for (let step 0; step NUM_PAGES; step) { if (step 0 step % p10 0) console.log(-- (step / p10) * 10 %) let page template(step) let where path.join(root, step .md) fs.writeFileSync(where, page) } console.timeEnd(Generated in)即循环NUM_PAGES次每次调用md.tpl.js导出的template(step)生成一页内容并以0.md、1.md……递增命名写入markdown-pages/。如果目录不存在会先创建fs.mkdirSync(root, { recursive: true })。单页模板faker gray-matter 构造真实感内容md.tpl.js 使用faker生成随机文本用gray-matter序列化 frontmatter构造出「看起来像真实博客文章」的页面以逼近真实站点的内容形态与 Markdown 解析成本module.exports index ${matter .stringify(, { title: faker.lorem.sentence(), description: faker.lorem.sentence(), path: /${faker.helpers.slugify(faker.lorem.sentence())}, date: faker.date.recent(1000).toISOString().slice(0, 10), tags: [${faker.random .words(3) .split( ) .map(w ${w}) .join(, )}], }) .trim()} ## Page #${index} : ${faker.random.words(4)} ### API ${new Array(faker.random.number(MAX_NUM_ROWS)) .fill(undefined) .map(() |${faker.lorem.word()}|${faker.lorem.sentence()}|${faker.random.boolean()}| .trim() ) .join(\n)} ### More Detail ${faker.lorem.paragraphs()} 每页由四部分构成frontmatter随机 title、description、path、date近 1000 天内的日期格式YYYY-MM-DD、tags3 个随机单词标签。path字段配合createFilePath用于最终页面路由二级标题## Page #N : ...用序号标识页码便于人工核对API 表格0 ~ MAX_NUM_ROWS行随机 Markdown 表格单词 / 句子 / 布尔值模拟含数据表格的技术类文章正文段落faker.lorem.paragraphs()生成若干随机段落充实 HTML 体积。需要说明的是md.tpl.js中声明的MAX_NUM_ROWS是独立读取的默认 25与md.generate.js中的同名变量保持一致即可获得预期的表格行数。关键环节如何在 GraphQL 中按 id 索引页面onCreateNode为节点推导 sluggatsby-node.js 中的onCreateNode钩子对每个MarkdownRemark节点调用gatsby-source-filesystem提供的createFilePath({ node, getNode })把文件路径转换为 slug 并写入fields.slugexports.onCreateNode ({ node, actions, getNode }) { const { createNodeField } actions if (node.internal.type MarkdownRemark) { const value createFilePath({ node, getNode }) createNodeField({ name: slug, node, value, }) } }注意slug 仍被生成并被首页列表使用但文章页的查询不走 slug而是走 id——这正是本基准与 slug 变体的分水岭。createPages查询全部文章并把 id 注入 contextcreatePages先通过 GraphQL 一次性取回所有文章按 frontmatter 日期倒序随后为每一篇调用createPageposts.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, id: post.node.id, previous, next, }, }) })每页的path依然来自fields.slug保证 URL 与真实站点一致但在context中同时携带了id。previous/next用于文章底部的上一篇 / 下一篇导航与 starter-blog 的行为保持一致。页面模板markdownRemark(id: { eq: $id })文章模板 src/templates/blog-post.js 的pageQuery是整条基准链路的落点query BlogPostById($id: String!) { site { siteMetadata { title } } markdownRemark(id: { eq: $id }) { id excerpt(pruneLength: 160) html frontmatter { title date(formatString: MMMM DD, YYYY) description } } }查询通过$id来自pageContext.id用markdownRemark(id: { eq: $id })精确定位节点。Gatsby 的id是每个节点在数据层的唯一主键查询时走索引查找而 slug 需要先经createFilePath推导、再经字段过滤匹配多一层间接性——这正是 README 中「按 id 查询更快」结论的工程背景。作为对照markdown_slug 基准 的文章模板查询改为markdownRemark(fields: { slug: { eq: $slug } })其余部分几乎完全一致非常适合做 A/B 对比实验。首页 src/pages/index.js 则与文章页不同它一次性查询全部文章的 slug、日期、摘要用于列表渲染allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC })这部分逻辑在两个基准中完全一致。站点配置与依赖gatsby-config.js / package.json数据源与插件配置gatsby-config.js 完整复用了 starter-blog 的插件栈核心配置如下gatsby-source-filesystem第一个实例指向${__dirname}/markdown-pages即生成脚本产出的目录第二个实例指向content/assets存放站点静态资源gatsby-transformer-remark挂载gatsby-remark-imagesmaxWidth: 590、gatsby-remark-responsive-iframe、gatsby-remark-prismjs、gatsby-remark-copy-linked-files、gatsby-remark-smartypants等 remark 插件确保生成的页面包含完整的 Markdown 处理链路——这也是基准要计入的成本之一gatsby-plugin-benchmark-reporting基准专用的上报插件由仓库根目录下 packages/gatsby-plugin-benchmark-reporting 提供用于在自动化运行中汇总构建指标其余为 sharp 图片处理、Google AnalyticstrackingId: do-not-track占位、manifest、offline、react-helmet、typography 等 starter 标配插件。依赖方面package.json 锁定了gatsby ^2.19.5及配套插件版本同时引入faker、gray-matter经 md.tpl.js 间接使用、del-cli、ts-node、typescript等工具链其中ts-node/typescript服务于data-update脚本当前为占位 noop。脚本定义bench 与 benchnb 的差异package.json中定义了四个与本基准直接相关的脚本bench: rm -r markdown-pages; NUM_PAGES${NUM_PAGES:-2000} node md.generate.js; gatsby clean; node --max_old_space_size2000 node_modules/.bin/gatsby build, benchnb: gatsby clean; node --max_old_space_size2000 node_modules/.bin/gatsby build, postinstall: del-cli markdown-pages gatsby clean NUM_PAGES${NUM_PAGES:-2000} node md.generate.js三者的分工是yarn bench完整流程——先删除旧的markdown-pages重新生成NUM_PAGES默认 2000个页面gatsby clean清理缓存再以 2000MB 最大老生代堆内存执行生产构建yarn benchnbno-build 数据复用只做gatsby clean与构建不重新生成页面。当你希望用同一批页面重复测量构建、对比不同改动的影响时使用它保证数据一致、可复现postinstall安装依赖后自动清理并预生成 2000 个页面使首次yarn bench不必等待生成。此外build/clean/develop/serve均为常规 Gatsby 命令data-update调用ts-node scripts/data-update.ts。完整运行步骤从零复现一次基准README 给出了两条路径——一键式与分步式均可直接照抄运行需 Node 8 环境README 以nvm use 8为例提示切换 Node 版本仓库当前以 Gatsby 2.x 为基准。方式一一键运行推荐yarn yarn benchpostinstall会替你完成页面预生成yarn bench随后重新生成并构建。若想复用同一批页面、避免重新生成带来的数据波动改用yarn benchnbREADME 特别说明无论哪种方式中途使用gatsby clean都是安全的它只清理.cache与public不影响markdown-pages中的数据。方式二分步执行适合调试与深入观察# 需要 Node 8 # nvm use 8 yarn rm -r markdown-pages NUM_PAGES2000 node md.generate.js gatsby clean node --max_old_space_size2000 node_modules/.bin/gatsby build各步骤含义yarn安装依赖。README 指出如果你打算配合gatsby-dev本地开发 Gatsby 核心建议用yarn否则yarn与npm install差异不大rm -r markdown-pages清掉旧数据保证从零生成NUM_PAGES2000 node md.generate.js以环境变量指定规模执行生成脚本gatsby clean清空缓存避免旧缓存污染构建结果node --max_old_space_size2000 node_modules/.bin/gatsby build执行生产构建。最后一步在页面数量足够小的时候可以直接用gatsby build--max_old_space_size2000的作用是提高 V8 老生代内存上限——README 明确写道这是for a larger number of pages针对大页面量所必需的页面量大时 Node 默认堆内存可能不足。调整数据规模页面数量由NUM_PAGES环境变量控制README 示例为 400标准脚本默认 2000。md.generate.js在未显式设置该变量时会打印提示Set NUM_PAGES200 to change the volume该提示文案中的 200 为示例实际按需传值即可。做小规模冒烟测试可用NUM_PAGES50观察构建耗时的线性增长时则建议 500 / 1000 / 2000 逐档递增。与 markdown_slug 对比如何做一次有意义的性能对照实验markdown_id的对照实验对象是同目录下的 markdown_slug两个项目除查询字段外结构完全同构维度markdown_idmarkdown_slugREADME 声明按id查询at the time of writing 更快按slug查询相对更慢页面 contextidslugidslug模板查询markdownRemark(id: { eq: $id })markdownRemark(fields: { slug: { eq: $slug } })其余配置与 slug 变体一致与 id 变体一致建议的实验流程在markdown_id与markdown_slug下各执行一次yarn bench控制相同的NUM_PAGES如 2000记录两次gatsby build的总耗时、峰值内存与public/产物规模如需排除冷启动噪声可改用yarn benchnb重复 2~3 次取中间值对比两个基准的createPages阶段耗时差异——该阶段是id与slug索引路径最直接的分水岭。需要强调README 中「按 id 更快」是文档撰写时的结论且基准锁定在 Gatsby 2.xpackage.json中gatsby ^2.19.5。Gatsby 数据层在此后多个版本中持续演进若要得出当前版本的结论务必以本仓库实际环境重新跑一遍对照实验为准不应将历史结论直接外推。小结这个基准能带给你什么从使用者的角度markdown_id至少提供了三重价值可复现的规模化基准一条yarn bench命令即可在本地造出 2000 个含 frontmatter、表格、段落、图片插件的类真实 Markdown 页面完整覆盖source-filesystem → transformer-remark → createPages → 模板查询 → HTML 渲染的整条 Gatsby 构建链路索引策略的对照样本与markdown_slug构成天然对照使「按主键 id 查询 vs 按派生字段 slug 查询」的性能差异可以被量化测量是研究 Gatsby 查询层性能的现成实验台标准化的目录骨架生成脚本、模板、配置、脚本命令分层清晰无论是扩展数据形态改md.tpl.js还是调整规模改NUM_PAGES成本都极低完全可以作为自建性能测试站点的起点。如果你对构建性能指标的上报机制感兴趣还可以进一步阅读本仓库的 gatsby-plugin-benchmark-reporting 插件源码了解基准数据如何被采集与记录而markdown_slug变体的完整实现则在 benchmarks/markdown_slug 中可随时对照。【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址: https://gitcode.com/gh_mirrors/ga/gatsby创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表