iii 技术规格演示文稿托管指南:两棵树布局、frontmatter 注册与一键部署
2026/9/15 10:16:13 网站建设 项目流程

iii 技术规格演示文稿托管指南:两棵树布局、frontmatter 注册与一键部署

【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii

本指南以 skills/presentation/reference/hosting.md 为核心骨架,结合仓库内tech-specs/website/roadmap/的真实实现与 CI 流水线源码,系统讲解 iii 仓库如何用"单一站点承载全部规格演示文稿(deck)"的托管模型:规格(spec)只允许 Markdown、deck 是可选的 React 内容层、注册完全靠 frontmatter、构建产物可整体搬移到任意 CDN。读完你将掌握该布局的配对契约、构建产物清单、日常开发/校验/发布命令,以及如何把旧的"每 deck 一个 Vite 工程"布局安全迁移到新模型。

为什么是"一棵树,而不是每个 deck 一棵树"

iii 仓库把每一份技术规格(tech spec)做成一份可公开浏览的演示文稿——既有纯 Markdown 的正文,也可能有交互式 React 演示层。托管该层的铁律是:整个仓库的规格演示文稿只属于一个 Vite 项目(即 "base"),不存在每个 deck 单独建工程、单独安装依赖、单独部署的做法。

这一决策直接消灭了一类历史问题:在旧模型下,每个 deck 各自持有package.json、lockfile、vite/tsconfig 与_gallery/构建脚本,任何一次构建工具升级或依赖变更都要重复发生在 N 个工程里。新模型让"加一个 spec"和"加一个 deck"都只触及自己的一小块目录,PR 之间天然互不冲突。

两棵树:spec 树与 base 树

<repo>/tech-specs/<slug>/*.md # spec —— 仅 Markdown # (README.md 的 frontmatter = 注册) <base>/<slug>/ # deck 的内容层(可选) <base>/src/ # 共享组件库 + 设计令牌

在 iii 仓库中,<base>就是 website/roadmap/。指针文件 tech-specs/README.md 明确写出 base 目录的位置,任何仓库的构建都能据此找到 base——这正是构建的第一阶段(文档中称为 Phase 0)定位方式。

spec 树中每一个 spec 是一个以日期为前缀的目录,例如 tech-specs/2026-07-14-worker-compose/,目录内全部是.md文件(README.md 作为索引,其余按主题拆分成多个 md)。仓库目前已有 agentic、rbac-proxy-worker、codegen、worker-compose、injectable-ui 五个 spec 目录,与之配对的是 website/roadmap/ 下同名的五个 deck 内容层目录,以及_viewer/(纯 Markdown 阅读器)和src/(共享组件库)。

配对契约:slug 三处一致

slug永远等于 spec 目录的 basename(不含日期前缀),且必须在三处严格一致:

  1. tech-specs/<slug>/—— spec 的 Markdown 所在;
  2. <base>/<slug>/—— deck 内容层所在;
  3. URL/roadmap/<slug>/—— 公开访问路径。

构建时若发现"孤儿 deck"(有 deck 目录、无对应 spec 目录)会直接失败。这条规则从机制上消灭了旧模型"manifest 里写的 slug ≠ 目录名 → 404"这一类漂移 bug:目录本身就是身份(the folder is the identity),根本不需要 manifest 来对账。

仓库中的契约检查实现在 website/scripts/validate-roadmap.ts:它遍历 base 下所有含src/App.tsx的目录,逐一确认tech-specs/<同名目录>存在,否则打印✗ orphan deck并使构建失败;成功时输出✓ roadmap contracts: N spec(s), M deck(s)

注册 = frontmatter,没有中央 manifest

一个 spec 是否出现在 roadmap 上,完全由其README.md顶部的 YAML frontmatter 决定:

--- title: the developer experience overhaul # fallback:正文第一个 H1 tagline: one file, one command, zero zombies. # fallback:正文第一段 date: 2026-06-21 # YYYY-MM-DD;fallback:目录名 # 前缀。天级精度驱动 # roadmap 排序与标签 tags: [dx, cli] # ≤ 4 status: live # 或 draft —— 卡片置灰,不进入 # index.json 与 sitemap featured: false # 置顶到落地页 feed # (index.json);roadmap 本身 # 仍按时间倒序 ---

字段规则与工程化保障:

  • slug永远不能是 frontmatter 字段——一旦出现,构建硬错误(hard-error)。解析器在 website/roadmap/scripts/manifest.mjs 中直接用throw new Error('frontmatter declares slug — the directory name IS the slug; remove the field')终止构建。
  • deck 存在性是派生的,从不声明:判定标准是<base>/<slug>/src/App.tsx是否存在(manifest.mjshasDeck字段),而不是任何配置项。
  • 没有 frontmatter 的 spec 依然会列出title回退到第一个 H1,tagline回退到第一段,date回退到目录名的YYYY-MM-DD前缀;每个派生的字段构建时都会给出警告。
  • 新增 spec 只触碰自己的文件夹→ 两个 spec 的 PR 永远不会冲突。

该解析器刻意做成零依赖(不引 YAML 库),只支持六个已知键(titletaglinedatetagsstatusfeatured),未知键会以警告形式上报。日期同时被解析出month("2026 · june" 时间线分组)与dayLabel("jun 29" 卡片标注)两个派生标签,供 roadmap 画廊渲染时间轴。

构建产物:一个站点,五种输出

以 base 目录为工作区执行node build.mjs--only=<slug>可只构建单个 spec 做快速局部验证),产出如下:

dist/index.html # roadmap 主页 —— 单列时间线,最新 spec 在前、 # 按月分组;数据来自 virtual:spec-manifest dist/index.json # 机器可读的 spec 列表(供 iii.dev 落地页时间线 # 消费;draft 排除) dist/<slug>/index.html # deck —— 若无 deck 则输出通用 md 阅读器 # (_viewer/,构建一次后复制) dist/<slug>/<file>.md # 原始 spec Markdown,可直接链接 dist/<slug>/spec.json # (仅阅读器页面)阅读器运行时拉取的文件清单

在 iii 的集成形态下,"构建"由 Astro 完成而不是独立的 build.mjs:deck 页面在 website/src/pages/roadmap/ 下作为 Astro 路由生成(index.astro[slug]/index.astro[slug]/[file].tsindex.json.ts[slug]/spec.json.ts),而dist/roadmap/就是这套产物的落点。index.json.ts的实现印证了 frontmatter 规则:status === 'draft'的 spec 会被过滤掉,不进入 feed。

可移植性保证:每个 deck 都以base: './'构建、资源带独立 hash,因此dist/<slug>/可以单独搬走——放到任意 CDN、任意路径前缀,甚至直接从本地磁盘打开都能工作。这正是"其他仓库无需任何部署配置"的根基。

spec-docs glob:全布局唯一一处脆弱耦合

deck 的src/spec-docs.ts在构建期把 spec 的 Markdown 打包进 bundle,供#/spec阅读模式使用。仓库真实实现(website/roadmap/2026-06-08-agentic/src/spec-docs.ts):

export const SPEC_DOCS = import.meta.glob('../../../../tech-specs/2026-06-08-agentic/*.md', { query: '?raw', import: 'default', eager: true, }) as Record<string, string>

要点:

  • import.meta.glob是相对导入文件解析的,因此这个字面量编码了从<base>/<slug>/src/到 spec 树的固定深度(本仓库为四层../../../../);
  • skill 脚手架在生成 deck 时用__SPEC_MD_GLOB__占位符替换该路径;
  • 如果 base 目录将来移动,所有 deck 的 glob 会一起失配——但pnpm type-check/build会立刻报错,属于"会炸但炸得响亮"的可控耦合。

这也是文档与website/roadmap/README.md都反复强调的"两棵树之间唯一跨树耦合",日常新增 spec/deck 时它不需要任何改动。

开发、校验、发布

pnpm dev # 单服务器:/ 为画廊,每个 /<slug>/ 一个 deck pnpm type-check # 严格模式:共享库 + 画廊 + 阅读器 + 全部 deck node build.mjs --only=<slug> # 画廊 + 单个 spec(快速局部验证) node build.mjs --strict-registry # registry 对账升级为硬失败(CI 用) pnpm build && pnpm preview # 完整站点,预览端口 :4173

iii 的集成形态:deck = Astro 路由中的 React 岛

与"每个仓库一个独立 Vite 工程"的通用形态不同,iii 仓库把 deck 直接做成iii-website包的 Astro 路由:

  • website/src/pages/roadmap/[slug]/index.astro 在getStaticPaths()中枚举全部 spec,然后通过<DeckHost client:only="react" slug={spec.slug} hasDeck={spec.hasDeck} />挂载 deck;
  • 共享宿主 website/roadmap/src/DeckHost.tsx 用一个import.meta.glob('../*/src/App.tsx')懒加载 + 按 deck 代码分割:页面只下载自己那个 deck 的 chunk——这正是旧"每 deck 独立 Vite 构建"曾提供的隔离性;无 deck 的 spec 则懒加载_viewer/src/ViewerApp作为通用 Markdown 阅读器;
  • 契约检查集中在 website/scripts/validate-roadmap.ts(孤儿 deck、COMPONENTS.md registry 对账,--strict模式把注册漂移升级为致命错误);
  • 产物落在website/dist/roadmap/,与独立构建同构(带 hash 的资源统一放在站点级/_astro/下);
  • base 目录本身不持有每个 deck 的index.htmlbuild.mjs或自己的package.json——依赖全部在 website/package.json 工作区里。

Deploy(iii 仓库):合并即发布

合并到 main 会触发 .github/workflows/deploy-website.yml,其流水线可概括为"构建 → 双轨同步 S3 → 路由映射同步 → CloudFront 失效":

  1. pnpm --filter iii-website build一次性产出整个站点(Astro 页面 +dist/roadmap/的 decks + 生成的 llms.txt、AGENTS.md、sitemap.xml);
  2. 通过 GitHub OIDC 获取 AWS 凭据(aws-actions/configure-aws-credentials);
  3. 双轨 S3 同步:带 hash 的静态资源(/_astro/、deck 资源、字体、图片)以public,max-age=31536000,immutable长缓存同步;而内容可变文件——HTML、XML、JSON(roadmap feed、viewer 的 spec.json)、Markdown(原始 spec、AGENTS.md)、txt(robots.txt、llms.txt)以及posthog-consent.js——以public,max-age=0,must-revalidate同步,保证新部署在 CloudFront 失效后立即可见;
  4. 把"美观 URL → .html"的路由映射同步进 CloudFront KeyValueStore(scripts/routes-kvs.ts计算 puts/deletes 增量,--if-matchETag 乐观并发控制),让新页面无需terraform apply即可生效;
  5. /*创建 CloudFront invalidation 并写入 Job summary。

值得注意的部署语义:CloudFront 的 viewer-request 函数会把/roadmap/…/目录型 URL 重写到…/index.html,并把无尾斜杠的形式 301 到带尾斜杠的规范形态——与/blog/用的是同一套机制。全程没有 Vercel、没有手动部署、没有每 deck 独立的流水线。

Deploy(其他仓库):静态产物,自带可移植性

对 iii 之外采用本托管模型的仓库,dist/完全静态且资源路径全部相对的:无论仓库用什么样的 CI,只要把dist/发布到任意路径前缀下即可。skill 永远不会为仓库生成部署配置——部署方式完全交给仓库自己决定。

迁移:把旧布局移植到两棵树

旧模型(每个 deck 一个独立 Vite 工程 +tech-specs/build.mjs+_gallery/)可以一次迁移一个 deck,六步完成:

  1. 把 spec 的 md 挪进tech-specs/<slug>/(只放 Markdown),并按旧_gallery/src/content/presentations.ts条目里的取值补上 frontmatter 块;
  2. 把 deck 的内容层(index.html→ 入口./src/main.tsxsrc/{App,sections,pages,content})搬进<base>/<slug>/
  3. 删掉重复的机制文件:package.json、lockfile、vite/tsconfig、src/{components,hooks,lib}index.css、markdown 工具、SpecPage;
  4. 重写 import(共享部分改为@lib/…,deck 本地部分改为相对路径),从content/deck.ts贯通meta/nav/footer属性,新增src/spec-docs.tsPAGES.spec
  5. 真正属于单个 spec 的示意图留在<slug>/src/diagrams/,通用示意图按 skills/presentation/reference/component-standards.md 提升到共享库;
  6. pnpm type-check && node build.mjs --only=<slug>并在/browse验证,然后删除旧工程;当最后一个 deck 迁移完成时,删除旧的_gallery/与根级胶水脚本。

组件提升(步骤 5)在 website/roadmap/README.md 中有明确的判定门槛:必须是纯 props 驱动且不含 spec 数据、对应反复出现的规格形态(生命周期、树、时间线、扇出……)、且通过设计系统检查清单(仅用设计令牌、reduced-motion 门槛、键盘可操作、aria-label、容器查询、横向溢出滚动、无阴影/渐变、文档化 props)。提升必须同一次变更内登记进 website/roadmap/COMPONENTS.md,否则validate-roadmap.tsunregistered component--strict模式直接失败。

常见故障速查

症状根因与处置
卡片 404deck 目录名 ≠ spec 目录名(配对契约被破坏),统一两边 slug 即可
frontmatter 警告对照上文 schema 表检查字段;pnpm build会打印具体违规规则
unregistered component走了组件提升流程 (c),需要同时补 COMPONENTS.md 条目
orphan deck(validate-roadmap)spec 目录被移动/改名,deck 失去了配对;把目录名改回一致

至此,你可以把这份托管模型套用到任意仓库:spec 只写 Markdown、deck 随需补充、注册交给 frontmatter、发布交给仓库自己的 CI,而 iii 仓库本身就是这套模型最完整的参考实现——从 website/roadmap/scripts/manifest.mjs 的零依赖解析器,到 website/scripts/validate-roadmap.ts 的契约检查,再到 .github/workflows/deploy-website.yml 的双轨同步与边缘路由,全部可以直接对照阅读。

【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询