Storybook Docs 多框架适配开发指南:如何为新框架优化 Docs 体验
本文以 @storybook/addon-docs 的多框架开发指南为主体,系统讲解当你把 Docs 接入一个非 React 视图层(如 Vue、Angular、Web Components、Ember)时,如何分别优化“框架专属配置、Args 表格自动提取、组件描述抽取、故事内联渲染、源码动态渲染”这五大能力。读完你将能对照 addons/docs 的 preset 机制、argTypesEnhancers/extractArgTypes 数据流与 SNIPPET_RENDERED 事件通道,为新框架补齐 Docs 所需的框架级代码,并理解每条参数在源码中的落点。
框架专属配置
Storybook Docs 开箱即支持除 React Native 外的所有视图层,但部分框架(React、Vue 3 等)额外做了 Docs 优化,例如自动 props 表格、内联故事渲染等。要为某个框架补齐这类优化,首先需要处理“框架专属配置”——比如追加 webpack loader,或注入全局 decorator / story parameters。
Docs addon 通过“文件命名约定”来承载这种定制:它的通用 preset 会按 ../<framework>/{preset,config}.[tj]sx? 的规则去查找框架文件,其中 <framework> 是框架标识符(如 vue3、angular、react)。这一机制的目的在于让各框架把“构建配置”和“Docs 抽取配置”分离在两个文件里,互不干扰。
以 Vue 为例,它的 webpack 配置需要引入 vue-docgen-loader,同时还有用于 props 表格 与 组件描述 的自定义抽取函数。Docs for Vue 定义了一个 preset.ts,遵循 preset 文件结构:
export function webpack(webpackConfig: any = {}, options: any = {}) {
webpackConfig.module.rules.push({
test: /\.vue$/,
loader: 'vue-docgen-loader',
enforce: 'post',
});
return webpackConfig;
}
这段代码只是追加了 vue-docgen-loader,此刻的 webpackConfig 已包含通用 preset 所做的修改,因此顺序上“后追加”是安全的。对 props 表格与描述这两个能力,则定义在 config.jsx 中。
从当前仓库源码结构看,这套“框架配置分离”的思路仍然成立,只是入口位置发生了迁移:@storybook/addon-docs 自身的 preset 负责通用能力(如 MDX loader、react/react-dom 别名、CSF enrichment),而各框架的 client 配置被抽离到独立的 framework 包里。例如 Angular 的框架级入口 code/frameworks/angular/src/client/config.ts 同时声明了 parameters(含 docs.extractArgTypes、docs.extractComponentDescription)与 argTypesEnhancers,并从中转出 render、applyDecorators 等视图层能力:
// code/frameworks/angular/src/client/config.ts
export { render, renderToCanvas } from './render.ts';
export { decorateStory as applyDecorators } from './decorateStory.ts';
export const parameters: Parameters = {
renderer: 'angular',
docs: {
story: { inline: true },
extractArgTypes,
extractComponentDescription,
},
};
export const argTypesEnhancers: ArgTypesEnhancer[] = [enhanceArgTypes];
而 addon-docs 内仍保留了针对特定框架的轻量入口文件,如 Angular 入口 暴露了 setCompodocJson,它会把 Compodoc 生成的 documentation.json 挂到全局变量上,供 Controls 与 Docs 读取(在开启 experimentalDocgenServer 特性时该调用会被忽略并告警)。
Arg tables(参数表格自动生成)
每个框架都可以自动生成 ArgTable,方式是导出一个或多个 ArgType enhancer,它们把组件的属性抽取成一个通用数据结构。在框架专属的 preview.js 中通常这样写:
import { enhanceArgTypes } from './enhanceArgTypes';
export const argTypesEnhancers = [enhanceArgTypes];
enhanceArgTypes 函数接收一个 StoryContext(含 story id、parameters、args、argTypes 等),并返回一个更新后的 ArgTypes 对象:
export interface ArgType {
name?: string;
description?: string;
defaultValue?: any;
[key: string]: any;
}
export interface ArgTypes {
[key: string]: ArgType;
}
不同框架的元数据来源不同,抽取路径也不同:
- React / Vue:preset 往用户配置里加一个 webpack loader;该 loader 给组件标注一个
__docgenInfo字段,内含若干元数据;视图层专属的enhanceArgTypes再把这份元数据翻译成ArgTypes。 - Angular / Web components / Ember:读取用户
.storybook/preview.json里的 JSON 文件并注入一个全局变量;视图层专属的enhanceArgTypes再把元数据翻译成ArgTypes。 - 对于你自己的框架,也可以采用完全不同的实现方式。
当前仓库源码印证了这条数据流。核心 enhancer 实现在 enhanceArgTypes.ts:它从 context 中取出 component 与 parameters.docs.extractArgTypes,若二者都存在则调用 extractArgTypes(component),再用 combineParameters 把抽取结果与用户手工写的 argTypes 合并(用户显式值优先):
export const enhanceArgTypes = <TRenderer extends Renderer>(
context: StoryContextForEnhancers<TRenderer>
) => {
const {
component,
argTypes: userArgTypes,
parameters: { docs = {} },
} = context;
const { extractArgTypes } = docs;
if (!extractArgTypes || !component) {
return userArgTypes;
}
const extractedArgTypes = extractArgTypes(component);
return extractedArgTypes ? combineParameters(extractedArgTypes, userArgTypes) : userArgTypes;
};
多框架的“多个 enhancer 叠加”由 CSF 组合逻辑保证。在 composeConfigs.ts 中,argTypesEnhancers 是通过 getArrayField 从多个模块导出列表里聚合(concat)起来的,因此 addon、preview、框架包各自导出的 enhancer 会按序执行;对应的行为在 composeConfigs.test.ts 的 “concats argTypesEnhancers in two passes” 用例中有覆盖。
__docgenInfo 这条 React/Vue 路径在源码里依然可见:判断组件是否带 docgen 元数据、以及读取其 description/displayName,分别落在 docgenInfo.ts 与 utils.ts。Angular 的 documentation.json 注入路径则由 setCompodocJson 写入 __STORYBOOK_COMPODOC_JSON__ 全局变量,再由 extractArgTypesFromData(见 code/frameworks/angular/src/client/compodoc.ts)把 JSON 转成 ArgTypes。
关于各框架 Controls 的自动生成细节,可参考 props 表格文档。
组件描述(Component descriptions)
组件描述由 docs.extractComponentDescription 参数启用,它把组件描述(通常来自源码注释)抽取成一个 Markdown 字符串。它沿用了上一节 Arg tables 的模式,只是更简单——函数输出只是一个字符串(若无描述则返回 null)。
当前实现中,该参数在 Description.tsx 中被调用:当 parameters.docs.extractComponentDescription 存在时,以组件与上下文为入参求得描述并渲染。默认实现会从组件源码注释中抽取 JSDoc;你也可以像 recipes 文档 里演示的那样,用 notes/自定义逻辑覆盖它。
内联故事渲染(Inline story rendering)
内联故事渲染是另一个框架级优化,由 docs.prepareForInline 参数实现。仍以 Vue 的框架专属 preview.js 为例:
import toReact from '@egoist/vue-to-react';
addParameters({
docs: {
// `container`、`page` 等
prepareForInline: (storyFn, { args }) => {
const Story = toReact(storyFn());
return <Story {...args} />;
},
},
});
输入是 story 函数与 story 上下文(id、parameters、args 等),输出是一个 React element——因为 Docs 页面本身是用 React 渲染的。对 Vue 来说,所有转换工作都由 @egoist/vue-to-react 库完成;如果你的框架没有类似库,就得自己想办法把该框架的渲染结果转成 React 可渲染的元素。
参数在文档侧的组合方式在 DocsPage 参考 中有说明:把 inlineStories 设为 true 后,story 不再被放进 iframe,而 prepareForInline 则负责把非 React 的 story 内容转换为 React 可渲染的形式。两者配合,即可让非 React 视图层的故事“无缝”内联进 DocsPage。
动态源码渲染(Dynamic source rendering)
自 Storybook 6.0 起,Source doc block 对 story 的源码渲染做了增强,其中之一就是 dynamic 源码类型——它基于 story 函数的输出渲染一段代码片段。这种动态渲染是框架相关的,因此每个框架都需要单独实现。
以 React 的 dynamic 片段实现作为参考(供其他框架实现该特性时对照):
import { StoryContext, addons } from '@storybook/preview-api';
import { SNIPPET_RENDERED } from '../../shared';
export const jsxDecorator = (storyFn: any, context: StoryContext) => {
const story = storyFn();
// 只有当 Source block 真正会消费它时才渲染 JSX,否则只是拖慢性能
if (skipJsxRender(context)) {
return story;
}
const channel = addons.getChannel();
const options = {}; // 从 story parameters 中读取
const jsx = renderJsx(story, options);
const { id, args } = context;
channel.emit(SNIPPET_RENDERED, { id, args, source: jsx });
return story;
};
上面片段有两个关键点:
renderJsx负责把 story 函数的输出转换成框架专属(这里是 React)的字符串;- 转换出的片段字符串通过
channel.emit()在 Storybook 通道上发出,随后被该 story 对应的 Source block 消费(若存在)。
配置如何展示时,则通过导出 decorators 让该 decorator 作用于每个 story:
import { jsxDecorator } from './jsxDecorator';
export const decorators = [jsxDecorator];
当前仓库中,这条“事件驱动”的源码渲染链路仍然完整存在,且常量与消费方都已收敛到 internal/docs-tools:
- 事件常量
SNIPPET_RENDERED定义在 shared.ts(形如${ADDON_ID}/snippet-rendered); - 发送侧封装在 emitTransformCode.ts:它先读取
parameters.docs.source.transform对原始source做可选转换,再通过addons.getChannel().emit(SNIPPET_RENDERED, { id, source, args, warning })发出; - 接收侧在 SourceContainer.tsx 中通过
channel.on(SNIPPET_RENDERED, handleSnippetRendered)监听,并在卸载时channel.off解除监听。
可以看出,原文档里“decorator 里手工 channel.emit(SNIPPET_RENDERED, ...)”的写法,在当前版本被抽象成了 emitTransformCode + docs.source.transform 的统一入口;框架专属的差异从“整个 decorator 实现”收敛为“transform 转换器”这一可注入点。理解这一点,对实现新框架的动态源码渲染尤为关键:你只需提供把 story 输出转换为该框架源码字符串的转换逻辑,事件通道的收发由核心统一处理。
更多资源
围绕 Docs 的框架适配,仓库内可供深入的资料包括:
- 总览与框架支持矩阵:Storybook Docs README(其中“Framework support”一节列出各视图层的 Docs 支持现状);
- Docs 的核心 preset 与通用能力实现:preset.ts、manager 入口;
- 各框架的 client 配置样例(含
argTypesEnhancers、docs.extractArgTypes/extractComponentDescription):Angular 配置; - Arg tables 核心 enhancer 与
__docgenInfo读取:enhanceArgTypes.ts、docgenInfo.ts; - 动态源码渲染事件链路:shared.ts、emitTransformCode.ts、SourceContainer.tsx;
- 组件描述调用点:Description.tsx。
需要说明的是,本文中的 Vue preset/config、jsxDecorator 等示例代码继承自 多框架开发指南原文,用于讲解“框架级优化由谁提供、放在哪”的设计约定;而参数在运行时如何流转(enhancer 合并、__docgenInfo 读取、SNIPPET_RENDERED 事件),则以当前仓库源码为准。为某个具体框架补齐 Docs 能力时,建议先通读对应 framework 包的 client/config.ts,再对照上述核心实现逐条对齐。
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 StartedRust0625
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