组件覆盖#
在项目根目录添加一个 components.ts(或 components.tsx)并导出 defineComponents。其中的 mdx map 既可以替换内置组件,也可以新增一个组件 —— 新组件在任何 .mdx 页面里都能用,无需 import。
import { defineComponents } from "blume";
import Callout from "./components/Callout.astro";
import Pricing from "./components/Pricing.astro";
export default defineComponents({
mdx: {
Callout, // 替换内置的 Callout
Pricing, // 新增一个 <Pricing /> 组件
},
});
键就是你在 MDX 里书写的名称(<Callout>、<Pricing>)。当你 import React 组件时,请使用 .tsx 文件名。
引用形式#
每个覆盖项 —— 无论在 mdx 还是 layout 中 —— 都接受三种形式:
import { defineComponents } from "blume";
import Callout from "./components/Callout.astro";
export default defineComponents({
mdx: {
Callout, // 1. import 进来的组件
Note: "./components/Note.astro", // 2. 路径字符串(相对项目根目录解析)
Chart: { component: "./components/Chart.tsx", client: "load" }, // 3. 描述符
},
});
Blume 以静态方式读取 components.ts —— 它从不执行该文件 —— 因此这三种形式就是它接受的全部。内联函数或表达式、在文件内部声明的组件、展开运算符、计算得到的键,以及不是下列模式字符串的 client,都会触发 BLUME_COMPONENTS_INVALID 错误并指明具体条目:blume dev 会在终端和浏览器浮层中报告它,blume build 则会直接失败。
描述符形式额外提供一个 hydration 模式,让交互式的 React/Vue/Svelte 组件携带自己的 JavaScript 并在客户端激活。没有 client 模式时,框架组件会渲染为静态 HTML —— Blume 发现这种情况时会打印构建警告,因为这通常是个错误。
client |
hydration 时机 |
|---|---|
"load" |
页面加载时立即激活 |
"idle" |
主线程空闲时激活 |
"visible" |
滚动进入视口时激活 |
"media" |
media 查询匹配时激活(附加 media: "(min-width: 40rem)") |
"only" |
仅客户端,绝不服务端渲染 |
带 client 模式的 mdx 条目就是一个交互岛:它在每个页面都可用,hydration 行为与放进 islands/ 文件夹的组件完全一致。那个文件夹仍然是零配置的路径 —— 当你想要一个不同于文件名的名称、一个 media 查询,或希望交互岛与其他覆盖项并排放置时,再使用 components.ts 条目。
为覆盖项标注类型#
当你替换内置组件时,从 blume/components 中 import 它的 prop 类型,让你的组件符合那份契约 —— 这些类型是从组件本身推导出来的,因此永远不会失配:
import type { CalloutProps } from "blume/components";
export default function Callout(props: CalloutProps) {
// ……你自己的提示框,prop 与内置版相同
}
内容组件的 prop 类型都有导出(CalloutProps、CardProps、TabsProps、StepsProps、BadgeProps 等)。
布局插槽#
layout map 用你自己的组件替换 Blume 的某块界面外壳。每个覆盖项接收到的 prop 与它所替换的内置组件完全相同,因此你可以包裹默认实现,也可以从零开始。
import { defineComponents } from "blume";
import Footer from "./components/Footer.astro";
import Logo from "./components/Logo.astro";
export default defineComponents({
layout: {
Logo, // 页头中的品牌标识 + 标题
Footer, // 全站页脚,取代 `footer` 配置的那个
},
});
已接线的插槽:
| 插槽 | 替换对象 | Props |
|---|---|---|
Layout |
整个页面外壳(RootLayout) |
内置布局接收到的全部内容,外加 layout map |
Header |
顶部导航栏 | site、logo、navigation、route、searchEnabled 等 |
Logo |
页头中的品牌链接(标识 + 标题) | site、logo、locale |
Search |
页头搜索触发按钮 + 弹窗 | navigation、strings、locale、assistantEnabled |
Sidebar |
主导航树 | items、currentRoute |
MobileNav |
移动抽屉内的导航(默认复用 Sidebar) |
items、currentRoute |
Breadcrumbs |
面包屑路径 | crumbs |
TableOfContents |
本页大纲 | headings、title、variant |
Pagination |
上一页/下一页页脚链接 | prev、next、strings |
Feedback |
文章下方的“这个页面有帮助吗?”评分(仅在 feedback开启时渲染) |
strings、comments |
PageHeader |
文章上方的注入点(无内置实现) | page、headings、route |
PageFooter |
文章下方的注入点(无内置实现) | page、headings、route |
Footer |
内容网格之后的站点页脚:链接、社交资料、仓库链接和 Cookie 设置 | footer、locale、site、navigation、ui |
PageHeader 和 PageFooter 没有内置组件 —— 在你设置它们之前什么都不渲染,这使它们成为放置促销横幅或“最后更新于”提示的便捷注入点。内置的 Footer 保存着通往你的仓库和社交资料的唯一链接,因此 Footer 覆盖项(配置无法表达的营销型页脚该放的地方)应该保留它们。设置了 consent 时,同样要保留重新打开它的入口:任何带 data-blume-consent-open 的元素都可以充当 Cookie 设置链接。
布局插槽接受与 MDX 覆盖项相同的三种引用形式,因此插槽可以是路径字符串,也可以在你想要交互式页头或页脚时使用已 hydration 的描述符({ component, client })。
交互岛#
对于交互式 UI(React、Vue 或 Svelte),把组件放进 islands/ 文件夹,然后在任意 MDX 页面中使用它 —— Blume 会替你完成 hydration,无需任何包装或注册:
import { useState } from "react";
export default function Counter() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>Clicked {n}</button>;
}
Use it anywhere: <Counter />
hydration 策略和框架配置参见 Islands。
自定义页面#
在 pages/ 文件夹下添加 .astro 文件,让完全自定义的路由与文档并存 —— 一个落地页、一个定价页,或手工搭建的索引页。它们保留自己的位置,因此相对 import 和 getStaticPaths 都照常工作,并且可以从 blume:data 模块读取你的配置、导航和路由。
完整指南参见 Custom pages。
组件注册表#
blume add 会把一个由 Blume 维护的组件以源码形式复制到你的项目里 —— 它归你所有,你可以随意编辑。不带参数运行它可以列出可用的组件:
blume add
安装一个布局插槽(页头、侧边栏、面包屑、目录、分页或反馈)或任意内容组件(提示框、卡片、标签页、步骤、折叠面板等):
blume add callout
blume add pagination
复制过来的文件从 blume/* 引入框架的其余部分,因此在你改动之前它的渲染效果与内置版完全一致。blume add 会打印用于注册它的 defineComponents 片段 —— 内容组件放在 mdx 下,布局部件放在 layout 下。
Astro 集成#
通过 blume.config.ts 顶层 integrations 数组添加任意 Astro 集成。请先在你的站点中安装该集成;Blume 不会把它加入生成运行时的依赖,也不会管理它的 Astro 兼容性。
npm install @astrojs/partytown
import partytown from "@astrojs/partytown";
import { defineConfig } from "blume";
export default defineConfig({
integrations: [
partytown({
config: { forward: ["dataLayer.push"] },
}),
],
});
Blume 保持自身内置集成的原有顺序,然后按声明顺序追加你的条目。它不会排序或去重,因此两个 name 相同的集成都会运行。Blume 只校验 integrations 是数组,每个条目则由 Astro 校验并报告无效集成。
由于 Blume 加载集成的方式是从生成的 Astro 配置中重新 import blume.config.ts,而不是复制实例,配置模块每次运行会求值两次 —— 一次在 Blume 读取你的配置时,一次在 Astro 加载它时。请保持集成工厂无副作用(返回集成即可,不要在构造时写文件或打开连接),这样第二次求值就是无害的。
同样的集成在 blume dev 和 blume build 中都会运行。在 blume dev 期间编辑 blume.config.ts 会重新生成隐藏的 Astro 配置并触发配置重启;如果改动后的集成没有生效,请重启 blume dev。Blume 无法判断哪些配置改动会影响集成,因此一旦 integrations 非空,对 blume.config.ts 的每一次编辑 —— 即使改的是无关字段 —— 都会重启开发服务器,而不是热更新。Blume 只跟踪 blume.config.ts 的内容,因此编辑它 import 的其他文件不会单独触发生成 —— 这类编辑之后请重启 blume dev。如果你 eject 了,Blume 托管的 astro.config.mjs 会保留一条指向 blume.config.ts 的相对桥接,因此配置好的集成仍会运行;之后你可以在完全接管时把它们直接移进 Astro 配置。
Eject#
当你想要完全的控制权时,把生成的运行时 eject 成一个独立的 Astro 项目:
npx blume eject --yes
eject 是单向操作:隐藏的 .blume/ 运行时变成一个归你所有、可直接修改的普通 Astro 应用。blume 包依然可以 import,因此你保留了它的组件、主题和 Markdown 处理器。
eject 会把 package.json 里的脚本指向 astro dev 和 astro build,并按名称加入这个 Astro 应用所 import 的包 —— 生成的样式表需要 astro、@tailwindcss/vite、tailwindcss 和 @tailwindcss/typography,还有搜索客户端、集成、配置接线的 adapter,交互岛/示例/assistant 用到 React 时需要 react,assistant 路由需要 ai,EPUB 导出需要 epub-gen-memory —— 版本范围与 Blume 自身使用的一致。在 dev 或 build 之前先跑一次安装;eject 会列出它添加了什么。ejected 应用中的每个路径都是相对的,因此在任何检出目录下都能构建,CI 也不例外。
从那以后,请通过应用自己的脚本运行它(npm run dev、npm run build):在 ejected 项目里 blume dev、blume build、blume check、blume sync 和 blume preview 都会停下并提示你改用这些脚本。再次运行 blume eject 同样会被拒绝,因为它会覆盖你的修改;传 --force 可以无论如何重新生成应用。
eject 保留了什么#
隐藏运行时作为页面提供的路由会被写进应用的 src/pages,因此它响应同样的 URL:每个页面的 Markdown 孪生版本、带 /404.md 与 /404.json 孪生版本的 404 页面、托管的 MCP server,以及位于 /api/docs/… 和 /openapi.json 的 JSON 文档 API —— llms.txt 和未找到页面会把 agent 指向它们。
ejected 应用的 build 脚本运行的是普通的 astro build,而 blume build 叠加在其上的产物 —— 搜索索引(以及托管提供方的索引同步)、llms.txt 和 llms-full.txt、sitemap.xml、robots.txt、agent-readability.json、.well-known 探测文件、Agent Skills,以及平台的 _redirects/_headers 文件 —— 依然会产出:ejected 的 astro.config.mjs 中的 Blume 集成会从 Astro 的 astro:build:done hook 写出它们,扫描项目(你的 blume.config.ts 和内容)的方式与 CLI 当年一致。ejected 构建不再做的是 CLI 的 adapter 后处理:Vercel 和 Cloudflare 的 Accept: text/markdown 路由拼接、Vercel 函数包审计、node() 服务器入口包装(.well-known 探测文件的 media type 和 CORS 头、对下载 SVG 的沙箱处理,以及每个重定向的确切状态码)、netlify() 服务器构建写入 .netlify/v1/config.json 的响应头规则、Cloudflare Worker 的命名(取自你的项目名)及其用于在项目根目录执行 wrangler deploy 的 .wrangler/deploy 重定向,以及 --analyze/--budget-* 质量门禁。