Storybook 的 `previewHead` 与 `previewBody` 配置指南:以编程方式调整预览 HTML
Storybook 的previewHead与previewBody配置指南以编程方式调整预览 HTML【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook本篇指南讲解 Storybook 中两个由 Preset 驱动的配置入口previewHead与previewBody。它们在.storybook/main.js或main.ts中通过函数接收 Storybook 渲染 iframe 当前的head、body内容并返回修改后的字符串可用于按环境条件注入脚本、样式或字体也能被 addon 作者用于封装 UI 定制能力。读完本文你将掌握两种配置的签名、适用场景、静态文件preview-head.html/preview-body.html与编程方案的取舍以及底层模板是如何与 HTML 文件合并的。配置项速览类型与文档位置previewHead与previewBody属于 main.js|ts 顶层配置 家族官方文档分别定义在main-config-preview-head.mdx类型为(head: string) string用于程序化调整预览区head。main-config-preview-body.mdx类型为(body: string) string用于程序化调整预览区body。在类型层面它们被统一声明在核心类型模块 code/core/src/types/modules/core-common.ts 中/** * Programmatically modify the preview head/body HTML. The previewHead and previewBody functions * accept a string, which is the existing head/body, and return a modified string. */ previewHead?: PresetValueStorybookConfigRaw[previewHead]; previewBody?: PresetValueStorybookConfigRaw[previewBody];注意这里的PresetValue语义这两个字段本质上是 Preset 入口preset 机制的一个组成部分。也就是说它们并不只是你项目的某次性配置而是 Storybook 在加载、合并 presets 时对每个 preset 的previewHead/previewBody依次调用后拼接得到最终 HTML 的预设值。正因如此writing-presets.mdx 明确指出预设 API 提供对 UI 配置的访问包括通过previewHead与previewBody配置的 preview 的head、bodyHTML 元素与使用preview-head.html、preview-body.html文件的效果类似。调用链路common-preset.ts在实现层面Storybook 的核心预设 code/core/src/core-server/presets/common-preset.ts 提供了两个核心 preset 函数export const previewHead async (base: any, { configDir, presets }: Options) { const interpolations await presets.applyRecordstring, string(env); return getPreviewHeadTemplate(configDir, interpolations); }; export const previewBody async (base: any, { configDir, presets }: Options) { const interpolations await presets.applyRecordstring, string(env); return getPreviewBodyTemplate(configDir, interpolations); };它们做的事是先通过presets.apply(env)收集环境变量插值再读取模板文件内容详见下文模板如何生成拿到已有字符串后交给你的previewHead(head ...)/previewBody(body ...)修改。换句话说你函数收到的字符串参数是底层模板已经渲染好的head/body内容你只需在其基础上追加或改写并原样返回。何时用编程式配置、何时用静态 HTML 文件Storybook 提供了两套等价机制向预览 iframe 注入内容需求场景静态 HTML 文件方案编程式 preset 方案注入固定脚本 / 样式无需条件判断在.storybook/preview-head.html或preview-body.html中添加内容无需使用previewHead/previewBody按环境、按特性开关条件注入无法实现HTML 文件是纯静态的previewHead/previewBody函数返回拼接结果开发插件 / addon 需要修改 UI用户难以复用 addon 的逻辑addon 在 preset 中导出这两个字段随 addon 一并生效官方文档在 main-config-preview-head.mdx 给出的 Callout 很明确如果你不需要程序化调整 preview 的head可以直接改用preview-head.html添加脚本与样式previewBody对应preview-body.html。反之需要条件判断或要在 addon 里封装能力时就使用previewHead/previewBody函数。这个设计在 template.ts 的实现中也得到印证模板函数会先检查.storybook/preview-head.html/preview-body.html是否存在存在则把静态文件内容与内置基础模板拼接再交给预设管线。也就是说HTML 文件方案与函数方案并不会冲突而是分层处理——文件内容先被合并进基础 HTML你的函数再拿到合并结果做最后一步加工。模板如何生成preview-head.html/preview-body.html与基础模板合并previewHead/previewBody的函数参数到底长什么样看 code/core/src/common/utils/template.ts 的实现export function getPreviewHeadTemplate(configDirPath, interpolations?) { const base readFileSync( join(resolvePackageDir(storybook), assets/server/base-preview-head.html), utf8 ); const headHtmlPath resolve(configDirPath, preview-head.html); let result base; if (existsSync(headHtmlPath)) { result readFileSync(headHtmlPath, utf8); } return interpolate(result, interpolations); }对应地getPreviewBodyTemplate的逻辑是将用户preview-body.html的内容前置到内置基础模板base-preview-body.html之前再合并。两处都会经过interpolate()做环境变量插值——即把%VAR_NAME%占位符替换为presets.apply(env)得到的环境变量值。配套单元测试 code/core/src/common/utils/tests/template.test.ts 也验证了两个关键行为当.storybook/preview-body.html不存在时返回空内容 / 仅基础模板当文件存在时返回用户文件内容 基础模板的合并结果。值得注意的实现细节head合并时静态内容被追加到基础模板之后result fileContent而body合并时静态内容被插入到基础模板之前fileContent result。这意味着你自定义的head内容会出现在 Storybook 内置 head 内容之后通常更适合做覆盖而自定义 body 内容会出现在基础 body 之前。在.storybook/main.ts中使用previewHead文档示例的主用法是按环境条件注入样式或脚本。以 CSF 3 与标准配置文件为例修改 代码示例片段 中Head (CSF 3)对应的 TS 写法previewHead版本其他渲染器与格式仅导入路径不同// .storybook/main.ts // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. import type { StorybookConfig } from storybook/your-framework; const config: StorybookConfig { previewHead: (head) ${head} style html, body { background: #827979; } /style , }; export default config;核心要点必须保留${head}收到的参数是当前已有head内容的完整字符串忘记拼接会直接破坏 Storybook 预览所需的既有脚本与样式。返回完整的新字符串你的函数签名是(head: string) string返回什么Storybook 就使用什么。JSCommonJS/ESM写法与之完全一致只是把类型注解去掉、直接导出对象// .storybook/main.js export default { previewHead: (head) ${head} style html, body { background: #827979; } /style , };在.storybook/main.ts中使用previewBodypreviewBody的签名是(body: string) string典型用法是在页面底部按条件追加第三方脚本例如分析脚本。文档中的 Body 示例CSF 3 的 TS 写法为// .storybook/main.ts // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. import type { StorybookConfig } from storybook/your-framework; const config: StorybookConfig { previewBody: (body) ${body} ${ process.env.ANALYTICS_ID ? script srchttps://cdn.example.com/analytics.js/script : } , }; export default config;这里展示了与previewHead不同的典型用途把注入逻辑建立在环境变量判断之上process.env.ANALYTICS_ID存在时才输出script标签因为返回的是普通字符串你可以做任意字符串处理如条件拼接、压缩空白或插值使用该功能时开发与生产构建环境下process.env的可用变量以 main-config-env 描述的加载机制为准。各框架 / 格式的代码形态CSF 3 与 CSF Next针对不同渲染器和新的配置写法官方代码片段 main-config-preview.md 覆盖了多个变体完整包含写法形态导入来源配置对象CSF 3.storybook/main.js直接导出{ previewHead, previewBody }普通对象字面量CSF 3.storybook/main.tsimport type { StorybookConfig } from storybook/your-framework类型为StorybookConfigCSF Next 实验性main.tsimport { defineMain } from storybook/your-framework/node用defineMain({ ... })包裹CSF Next 的实验形态示例以 Vue 渲染器为例// .storybook/main.ts import { defineMain } from storybook/vue3-vite/node; export default defineMain({ previewHead: (head) ${head} style html, body { background: #827979; } /style , });而 React 系列的 CSF Next 写法把类型导入从主包换到 Node 入口如storybook/react-vite/nodeAngular 为storybook/angular/nodeWeb Components 为storybook/web-components-vite/node。JS 文件同样支持defineMain只需去掉类型注解。需要区分文档中的previewHead/previewBody配置与另一组head/body静态文件方案并不冲突同时配置时按前面模板如何生成一节的顺序合并。此外向**管理界面manager**注入内容应使用 main-config-manager-head 对应的机制不要与这里的预览区配置混用。典型场景与最佳实践小结综合官方文档与源码可以总结以下使用建议addon 作者的首选封装官方在 main-config-preview-head.mdx 与 main-config-preview-body.mdx 中明确写Most often used by addon authors最常被插件作者使用。addon 在自身 preset 中导出previewHead/previewBody用户安装后即可自动获得注入效果无需手动编辑 HTML 文件。条件注入优先用函数从仓库的common-preset.ts看函数形式在整个 preset 管线中最后执行拿到的是合并后的完整内容因此可以基于process.env或自身逻辑决定保留、替换或追加。永远保留入参前缀两个函数都是接收完整字符串 → 返回完整字符串的纯函数式约定见 core-common.ts 的注释漏掉${head}/${body}会导致 Storybook 预览页面缺失运行时所需的基础 HTML。静态内容优先用文件若注入的是固定不变的脚本/样式直接用preview-head.html/preview-body.html更直观、无需经过函数层仓库模板函数template.ts会自动检测并合并这些文件。头部与 body 的合并顺序不同自定义 head 内容追加在基础 head 之后、自定义 body 内容前置在基础 body 之前涉及样式覆盖或首个脚本执行时机时可利用这一顺序差异。通过previewHead与previewBody你可以在不修改 Storybook 内部渲染模板的前提下对组件预览 iframe 的文档头部与正文做精确、可复用、可条件化的定制——无论是为项目引入全局主题样式、按环境加载分析脚本还是以 addon 形式向用户交付开箱即用的 UI 增强能力。【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
