Storybook Web Components 文档指南:基于 custom-elements.json 自动生成 Props 表格与组件文档
在 Storybook 中为 Web Components(Lit、Stencil、Polymer 等框架或纯 Vanilla 自定义元素)编写文档时,与普通 React/Vue 组件最大的差异在于:没有 PropTypes、TypeScript 装饰器这类“就地可查”的类型信息,组件元数据必须依赖 custom-elements.json 这份外部清单文件。本文基于 Storybook 官方文档 WEB_COMPONENTS.md 完整展开,讲解如何为 Web Components 接入 Storybook Docs:安装与配置 setCustomElementsManifest、通过清单文件自动生成 Props 表格、以及用 docs.story.iframeHeight / docs.stories.inline 参数控制故事的 iframe 渲染方式,并结合开源仓库源码说明元数据是如何被解析、映射为 Docs 页面上 argTypes 表格的。
读完本文,你将能够:
- 按文档要求完成
@storybook/addon-docs+ Web Components 渲染器的接入; - 选择或生成符合 v1.0.0 规范的
custom-elements.json文件并理解其结构; - 让 DocsPage 自动渲染出 Properties / Attributes / Events / Slots / CSS Shadow Parts 等分类表格;
- 从源码层面理解
setCustomElementsManifest→getCustomElements→extractArgTypes这条数据链路的实际实现。
前置条件:先完成 Docs 通用安装
官方文档明确要求,在配置 Web Components 专属能力之前,必须先在 README.md 中描述的通用流程下安装 Docs 插件。核心步骤为:
yarn add -D @storybook/addon-docs
然后在 .storybook/main.js 中注册插件并让 stories 可被索引:
export default {
stories: [
'../src/**/*.mdx',
'../src/**/*.stories.@(js|jsx|ts|tsx)',
],
addons: [
'@storybook/addon-docs',
],
};
同时确认项目中已经存在一份可用的 custom-elements.json 文件(后文详述如何生成与校验),否则 Props 表格将无数据可渲染。
安装:向 preview 注入 custom-elements 清单
Web Components 接入 Docs 的第一步,是把 custom-elements.json 的内容注入到预览运行时。按 WEB_COMPONENTS.md 的 Install 一节,在 .storybook/preview.js 中添加:
import { setCustomElementsManifest } from '@storybook/web-components';
import customElements from '../custom-elements.json';
setCustomElementsManifest(customElements);
然后在故事文件中,用标签名字符串作为 component 字段:
export default {
title: 'Demo Card',
component: 'your-component-name', // 该名字必须能在 custom-elements.json 中找到
};
源码视角:清单存到哪里、何时被读取
setCustomElementsManifest 的实现位于 framework-api.ts,它把清单对象挂到全局变量上:
export function setCustomElementsManifest(customElements: any) {
global.__STORYBOOK_CUSTOM_ELEMENTS_MANIFEST__ = customElements;
}
同文件中的 getCustomElements(framework-api.ts)在 Docs 提取 argTypes 时读取该全局变量,并带有历史兼容逻辑——旧版 setCustomElements(对应 __STORYBOOK_CUSTOM_ELEMENTS__ 全局变量)注入的数据同样可用:
export function getCustomElements() {
return global.__STORYBOOK_CUSTOM_ELEMENTS__ || global.__STORYBOOK_CUSTOM_ELEMENTS_MANIFEST__;
}
这意味着如果你维护的是较老版本 Storybook 时代通过 setCustomElements 注入的清单(实验性 tags 结构),迁移到新 API 前功能仍然保持兼容。同时 isValidMetaData(framework-api.ts)会对清单做结构校验:数据中必须包含 tags 数组(实验性格式)或 modules 数组(v1.0.0 格式),否则抛出明确指向 addon-docs 文档的错误提示,帮助定位配置缺失。
Props 表格:custom-elements.json 的结构与生成
要让 Props tables 在 Web Components 上生效,必须提供 custom-elements.json。官方文档给出两条路径:手写,或使用分析器自动生成——后者通常更可靠,且“不同 Web Components 技术栈(sugar)下效果不一(your mileage may vary)”。
文档中列出的已知分析器:
输出 custom-elements.json v1.0.0 的分析器:
@custom-elements-manifest/analyzer(OpenWC 出品)- 支持 Vanilla、LitElement、FAST Element、Stencil、Catalyst、Atomico
输出旧版格式的分析器:
web-component-analyzer- 支持 LitElement、Polymer、Vanilla、(Stencil)
stenciljs自带生成- 支持 Stencil(但元数据不全)
用 Stencil 生成清单文件
如果使用 Stencil,只需在 stencil.config.ts 的 outputTargets 中追加 docs-vscode 输出:
{
type: 'docs-vscode',
file: 'custom-elements.json'
},
清单文件长什么样
一份 v1.0.0 格式的最小结构如下(摘自 WEB_COMPONENTS.md 原文示例,完整示例可参考仓库中的 web-components-kitchen-sink 项目):
{
"schemaVersion": "1.0.0",
"readme": "",
"modules": [
{
"kind": "javascript-module",
"path": "src/my-element.js",
"declarations": [
{
"kind": "class",
"description": "",
"name": "MyElement",
"members": [
{ "kind": "field", "name": "disabled" },
{ "kind": "method", "name": "fire" }
],
"events": [
{ "name": "disabled-changed", "type": { "text": "Event" } }
],
"superclass": { "name": "HTMLElement" },
"tagName": "my-element"
}
],
"exports": [
{
"kind": "custom-element-definition",
"name": "my-element",
"declaration": {
"name": "MyElement",
"module": "src/my-element.js"
}
}
]
}
]
}
关键字段说明:
| 字段 | 作用 |
|---|---|
schemaVersion |
清单版本,Docs 解析器按 v1.0.0 处理 |
modules[].path |
组件源文件路径,用于代码链接等元信息 |
modules[].declarations[].tagName |
自定义元素标签名,Docs 通过它与故事中的 component 字段精确匹配(大小写不敏感) |
members / properties |
属性(Properties)来源,映射到 Props 表格 |
events |
事件(Events)来源,映射为 onXxx action 参数 |
superclass |
继承关系,文档中展示 |
exports |
导出声明,标识元素定义与类的对应关系 |
源码视角:Docs 如何把清单变成表格数据
清单到表格的转换核心在 custom-elements.ts。getMetaData(custom-elements.ts)根据清单版本分派到两条解析路径:
const getMetaData = (tagName: string, manifest: any) => {
if (manifest?.version === 'experimental') {
return getMetaDataExperimental(tagName, manifest);
}
return getMetaDataV1(tagName, manifest);
};
getMetaDataExperimental(custom-elements.ts):面向旧版tags数组结构,按tag.name大小写不敏感匹配;getMetaDataV1(custom-elements.ts):遍历modules[].declarations[],以declaration.tagName === tagName精确匹配 v1.0.0 格式。
两条路径在找不到组件时都会输出警告日志 Component not found in custom-elements.json: <tagName>,这是排查“表格为空”时最重要的控制台线索。
匹配成功后,extractArgTypesFromElements(custom-elements.ts)把各字段映射为 Storybook 的 ArgTypes:
metaData && {
...mapData(metaData.members ?? [], 'properties'),
...mapData(metaData.properties ?? [], 'properties'),
...mapData(metaData.attributes ?? [], 'attributes'),
...mapData(metaData.events ?? [], 'events'),
...mapData(metaData.slots ?? [], 'slots'),
...mapData(metaData.cssProperties ?? [], 'css custom properties'),
...mapData(metaData.cssParts ?? [], 'css shadow parts'),
}
可以推断,这些 table.category 分类就是 DocsPage 上 Properties / Attributes / Events / Slots / CSS Custom Properties / CSS Shadow Parts 各表格的分组依据。映射细节还包括:
mapItem(custom-elements.ts):为属性/事件/插槽生成name、description、type、table.defaultValue等字段;slots 类型固定为string;mapEvent(custom-elements.ts):把disabled-changed这类 kebab-case 事件名转换为onDisabledChanged,并附加action: { name: ... },使 Controls 面板能直接触发该事件、Actions 面板能记录调用——这是 Web Components 文档与框架文档在“事件可控性”上对齐的关键机制;mapData(custom-elements.ts)中显式过滤kind === 'method'的成员,即custom-elements.json中的methods不会出现在 Props 表格里,方法与事件、属性在表格体系里是区分的。
相关测试 custom-elements.test.ts 验证了将清单写入 window.__STORYBOOK_CUSTOM_ELEMENTS_MANIFEST__ 后提取逻辑生效,覆盖了这条全局变量数据链路。
渲染方式:故事默认内联,可切换为 iframe
Web Components 在 DocsPage 中的故事默认以 inline 方式渲染——直接把自定义元素插入文档流。当组件内部使用了 Shadow DOM 隔离的复杂样式、或者内联渲染出现布局问题时,可以改用 iframe 渲染。
按 WEB_COMPONENTS.md 的 “Stories not inline” 一节,做法是在 .storybook/preview.js 中设置:
export const parameters = { docs: { story: { inline: false } } };
iframe 模式下:
- 默认 iframe 高度为
60px; - 可用故事参数
docs.story.iframeHeight调整高度,避免内容被裁切。
这一配置对全局生效;如需只对部分故事生效,可以在单个故事的 parameters 中覆盖同样的 docs.story 结构。
进一步阅读
官方文档末尾的 “More resources” 指向了 Docs 插件的核心参考资料,均位于本仓库中:
- DocsPage 参考:理解零配置文档页的组成
- MDX 参考:长文文档中嵌入故事
- FAQ / Recipes / Theming
- Props 表格参考:argTypes、表格分类与自定义的完整能力
- Docs 插件总览:README.md,其中包含安装细节与 preset options(如
csfPluginOptions、mdxPluginOptions)说明
小结
为 Web Components 编写 Storybook 文档的关键在于三点:一是按 README.md 完成 @storybook/addon-docs 的通用安装;二是准备并注入符合 v1.0.0 规范的 custom-elements.json(推荐 @custom-elements-manifest/analyzer 或 Stencil 的 docs-vscode 输出目标生成),再通过 setCustomElementsManifest 交给运行时;三是善用 docs.story.iframeHeight 与 docs.stories.inline 参数处理 Shadow DOM 组件的内联渲染问题。从源码看,framework-api.ts 负责清单的全局注入与校验,custom-elements.ts 负责把 modules[].declarations 解析为带分类的 ArgTypes,最终由 DocsPage 呈现为可交互的 Props 表格。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00