组件 API
项目组件位于 vp/components/。构建时会加载这些组件;如果组件提供运行时增强代码,会被打包成独立组件脚本,而不是合并进 runtime.js。
目录约定
组件可以是单文件,也可以是带 index.ts 的目录:
-
vp/
-
components/
-
badge.ts
-
callout/
-
index.ts
-
-
-
VanillaPress 会加载 vp/components/*.ts、vp/components/*.js、vp/components/*/index.ts、vp/components/*/index.js。
组件约定
组件模块导出一个组件对象:
TypeScriptimport type { MarkdownComponentDefinition } from 'vanilla-press'export default { name: 'badge', install(md, { markComponent, escapeHtml }) { md.block.ruler.before( 'paragraph', 'badge', (state, startLine, endLine, silent) => { const start = state.bMarks[startLine] const end = state.eMarks[startLine] const line = state.src.slice(start, end).trim() if (!line.startsWith(':::badge')) return false if (silent) return true const content = line.replace(/^:::badge/, '').trim() const token = state.push('badge', 'span', 0) token.content = content markComponent(state.env, 'badge') state.line = startLine + 1 return true } ) md.renderer.rules.badge = (tokens, index) => `<span class="vp-badge" data-vp-component="badge">${escapeHtml(tokens[index].content)}</span>` }, init(root) { root .querySelectorAll( '[data-vp-component="badge"]:not([data-vp-ready="true"])' ) .forEach((node) => { node.setAttribute('data-vp-ready', 'true') }) },} satisfies MarkdownComponentDefinition
也可以使用命名导出。VanillaPress 会按下面顺序解析组件模块:
TypeScript// 方式一:默认导出组件对象export default { name: 'badge', install() {}, init() {},} satisfies MarkdownComponentDefinition// 方式二:导出 component 对象export const component = { name: 'badge', install() {}, init() {},} satisfies MarkdownComponentDefinition// 方式三:直接命名导出字段export const name = 'badge'export const dependsOn = ['tabs']export function install(md, context) {}export function init(root, config) {}
组件名必须匹配 /^[A-Za-z][\w-]*$/,并且在 vp/components/ 中不能重复。
Markdown 使用
Markdown:::badge Stable
构建期 install 用于注册 Markdown 语法;当页面使用组件时,需要调用 markComponent(env, name)。运行时 init 是可选的;如果存在,会被打包成 dist/public/组件名.hash.js,并且只在使用了该组件的 HTML 页面中加载。
组件 init 中静态导入的本地 npm 依赖,会一起打包进该组件脚本文件,不会进入全局 runtime.js。
如果组件没有 init,它只参与 Markdown 构建,不会生成浏览器脚本。
运行时规则
- 渲染后的根元素必须包含
data-vp-component="<name>"。 init(root, config)的root是当前文档根节点,config是站点配置。- 运行时代码只扫描传入的
root范围,不要默认全局操作不相关节点。 - 已标记
data-vp-ready="true"的元素必须跳过。 - 初始化完成后,需要写入
data-vp-ready="true"。 - 如果组件依赖其他组件先初始化,通过
dependsOn声明。 - 组件模块如果提供
init,同一个模块会被打包到浏览器侧;模块顶层代码和顶层静态 import 需要保持浏览器可运行。
依赖示例:
TypeScriptexport default { name: 'panel-tabs', dependsOn: ['tabs'], install(md, context) { // 注册 Markdown 语法。 }, init(root) { // 会在 tabs 之后执行。 },} satisfies MarkdownComponentDefinition
初始化模型
浏览器运行时会合并内置组件和项目组件,自动展开依赖关系,按依赖顺序初始化,并多轮执行直到没有待初始化节点。运行时也会监听动态插入的 DOM,自动补初始化新增的 data-vp-component 节点。
项目组件脚本按页面加载:页面实际使用了哪些项目组件,就动态导入对应的 dist/public/组件名.hash.js。如果组件声明了 dependsOn,并且依赖项也是项目组件,依赖组件脚本也会被加载。