文档导航
InkStack 文档架构与扩展

插件开发

API v1 的具名插槽与严格配置,在共享骨架中组合新功能。

插槽与生命周期

插件在构建期由 Astro 渲染,不是运行时下载的扩展。协议位于 @inkstack/plugins,应用注册表位于 apps/blog/src/plugins/registry.ts。

页面插槽
首页home.before、home.after、home.sidebar
正文页article.before、article.after
关于页about.before、about.after
全站页脚site.footer

产品手册不是 article 插槽目标;仍使用全站页脚插槽。正文前后插件位于文章搜索索引之外,wrapper 使用 .not-prose。

启用内置插件

在 inkstack.config.ts 的 plugins 字段配置:

TypeScript
plugins: [
  { id: 'writing-activity' },
  { id: 'related-posts', options: { limit: 3 } },
  { id: 'subscription', enabled: true },
]

未配置的插件不渲染;enabled: false 禁用。推荐文章的 limit 为 1–8。创作记录读取公开文章元数据,订阅插件链接真实 RSS 订阅页。

创建组件

在应用插件目录创建 MyWidget.astro:

astro
---
import type { PluginContext } from '@inkstack/plugins'
interface Props {
  context: PluginContext
  options: { title: string }
}
const { context, options } = Astro.props
---
<section aria-label={options.title}>
  <a href={context.sitePath('/archive/')}>
    {options.title} · {context.posts.length}
  </a>
</section>

复用主题组件与 token,不复制整份页面布局。默认保持静态 HTML,不添加框架 hydration。

注册与校验

在 registry 导入组件,按其现有风格加入定义:

TypeScript
definePlugin({
  id: 'my-widget',
  apiVersion: 1,
  slots: ['home.after'],
  order: 10,
  component: MyWidget,
  parseOptions: value => z.object({
    title: z.string().min(1),
  }).strict().parse(value),
})

最后在站点配置启用 my-widget 并提供 title。id 使用唯一 kebab-case;order 必须有限,先按 order 再按配置顺序排列。重复 id、未知插件、未知 slot、版本不匹配和非法 options 都让构建失败;不要静默忽略配置错误。

上下文与信任

context.posts 仅包含公开索引文章的元数据。文章槽位的 context.post 是当前文章,可能为本地草稿或 unlisted;不要把它外传。sitePath() 统一处理子路径部署。

默认接口不提供私有正文,但受信任插件仍能主动读取仓库。插件与 MDX 同级,不是恶意代码沙箱。第三方插件应经过代码与依赖审查,不支持访客上传或网络动态安装。

交互与测试

需要交互时使用局部 script、可访问名称、键盘支持与 reduced-motion。关闭 JavaScript 后主要内容和链接仍应有效。增加测试覆盖配置错误、静态渲染、移动端溢出、索引隔离与交互回归。

任何第三方服务、网络依赖或较大客户端资源都需要重新评估安全与性能预算;不要通过插件配置绕开发布门禁。