Storybook Docs 多框架适配开发指南:如何为新框架优化 Docs 体验
Storybook Docs 多框架适配开发指南如何为新框架优化 Docs 体验【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook本文以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方式是导出一个或多个ArgTypeenhancer它们把组件的属性抽取成一个通用数据结构。在框架专属的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 / Vuepreset 往用户配置里加一个 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: StoryContextForEnhancersTRenderer ) { 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 起Sourcedoc 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 作用于每个 storyimport { 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, ...)”的写法在当前版本被抽象成了emitTransformCodedocs.source.transform的统一入口框架专属的差异从“整个 decorator 实现”收敛为“transform 转换器”这一可注入点。理解这一点对实现新框架的动态源码渲染尤为关键你只需提供把 story 输出转换为该框架源码字符串的转换逻辑事件通道的收发由核心统一处理。更多资源围绕 Docs 的框架适配仓库内可供深入的资料包括总览与框架支持矩阵Storybook Docs README其中“Framework support”一节列出各视图层的 Docs 支持现状Docs 的核心 preset 与通用能力实现preset.ts、manager 入口各框架的 client 配置样例含argTypesEnhancers、docs.extractArgTypes/extractComponentDescriptionAngular 配置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再对照上述核心实现逐条对齐。【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考