Storybook 侧边栏排序指南:用 main 配置中 stories 数组顺序精确控制 Sidebar 展示次序
在 Storybook 中,组件树(Sidebar)中 Story 的展示顺序并非随机的:它由 .storybook/main.{js,ts} 里 stories 数组的声明顺序直接决定。本篇指南围绕 docs/_snippets/main-config-stories-ordered.md 的核心配置思路,讲解如何把 MDX 文档排在 Story 之前、如何为每个 glob 模式排队,并深入 normalizeStories 与 StoriesSpecifier 类型定义,从源码层面解释"顺序如何被加载与保持"。读完你将能精确编排 Storybook 侧边栏,让文档页、组件示例、单测文件各就其位。
侧边栏顺序由 stories 数组决定
Storybook 通过 main-config-stories.mdx 中定义的 stories 字段(必填)指定从哪些位置加载 story 文件。它支持两种基本形态:一个 glob(字符串)数组,或一个 StoriesSpecifier 配置对象数组。官方文档明确指出:
Stories are loaded in the order they are defined in the array. This allows you to control the order in which stories are displayed in the sidebar.
也就是说,数组的顺序即侧边栏的顺序。默认配置中常见的单一 glob:
stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
在这种形态下,Storybook 将按文件名扫描结果把 story 依次放入侧边栏。而一旦你的项目同时包含 .mdx 文档页与 .stories.* 组件 Story(例如希望文档优先展示在组件之前),就需要把匹配模式拆成多个 glob,并按期望顺序书写。
有序加载:让 MDX 文档排在 Story 之前
下面是最典型的排序需求——先展示文档(MDX),再展示组件 Story。核心做法是在一个数组里写下多个有序 glob,main-config-stories-ordered.md 片段给出的注释(// 👈 These will display first in the sidebar)正是对这一意图的直接说明。
CSF 3 形态(JS)
export default {
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
framework: '@storybook/your-framework',
stories: [
'../src/**/*.mdx', // 👈 These will display first in the sidebar
'../src/**/*.stories.@(js|jsx|mjs|ts|tsx)', // 👈 Followed by these
],
};
CSF 3 形态(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 = {
framework: '@storybook/your-framework',
stories: [
'../src/**/*.mdx', // 👈 These will display first in the sidebar
'../src/**/*.stories.@(js|jsx|mjs|ts|tsx)', // 👈 Followed by these
],
};
export default config;
CSF Next 实验形态(defineMain)
在新一代 CSF Next API 下,主流框架都提供了带类型提示的 defineMain 辅助函数,用于从 @storybook/<framework>/node 入口导入。不同框架的写法如下(同样遵循数组顺序即展示顺序):
- React(
react/react-vite/nextjs等均可用,@storybook/your-framework按需替换为@storybook/react-vite等):
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite)
import { defineMain } from '@storybook/your-framework/node';
export default defineMain({
framework: '@storybook/your-framework',
stories: [
'../src/**/*.mdx', // 👈 These will display first in the sidebar
'../src/**/*.stories.@(js|jsx|mjs|ts|tsx)', // 👈 Followed by these
],
});
- Vue 3:
import { defineMain } from '@storybook/vue3-vite/node';
export default defineMain({
framework: '@storybook/vue3-vite',
stories: [
'../src/**/*.mdx', // 👈 These will display first in the sidebar
'../src/**/*.stories.@(js|jsx|mjs|ts|tsx)', // 👈 Followed by these
],
});
- Angular:
import { defineMain } from '@storybook/angular/node';
export default defineMain({
framework: '@storybook/angular',
stories: [
'../src/**/*.mdx', // 👈 These will display first in the sidebar
'../src/**/*.stories.@(js|jsx|mjs|ts|tsx)', // 👈 Followed by these
],
});
- Web Components:
import { defineMain } from '@storybook/web-components-vite/node';
export default defineMain({
framework: '@storybook/web-components-vite',
stories: [
'../src/**/*.mdx', // 👈 These will display first in the sidebar
'../src/**/*.stories.@(js|jsx|mjs|ts|tsx)', // 👈 Followed by these
],
});
此外,相同 glob 既可以 .mdx(文档)与 story 文件分别列队,也可以进一步拆分成更细的目录模式——比如"先展示 src/atoms、再 src/molecules、最后 src/pages",借此实现按组件层级组织的侧边栏。
核心概念:目录、files 与默认文件名模式
当采用纯字符串 glob(如 '../src/**/*.mdx')时,Storybook 会在启动阶段用 picomatch 兼容的语法解析它。也可以显式改用配置对象 StoriesSpecifier(完整示例见 main-config-stories-with-object.md),其类型定义位于 indexer.ts:
export interface StoriesSpecifier {
/** When auto-titling, what to prefix all generated titles with (default: '') */
titlePrefix?: string;
/** Where to start looking for story files */
directory: string;
/** A glob, relative to directory... (default applies when unset) */
files?: string;
}
各字段要点:
| 字段 | 必填 | 类型 | 默认值 | 语义 |
|---|---|---|---|---|
directory |
是 | string |
— | 从项目根目录(working dir)开始查找 story 文件的起始目录 |
files |
否 | string |
`'**/*.@(mdx | stories.@(js |
titlePrefix |
否 | string |
'' |
使用 CSF 3 自动标题(auto-titling)时,为生成标题统一添加的前缀 |
把 directory + files 拼接成一个伪 glob(如 ../src/**/*.mdx),就是你在字符串形态中直接书写的模式。files 不填时,将默认匹配该目录下所有 .mdx 文档,以及所有 .stories.(js|jsx|mjs|ts|tsx) 的 CSF story 文件。该默认值在 normalize-stories.ts 中被定义为 DEFAULT_FILES_PATTERN。
titlePrefix 的语义与 Sidebar/URL 的自动标题机制绑定(参见 sidebar-and-urls.mdx 中关于 CSF 3.0 auto-titles 的说明)。例如为某个 monorepo 包单独设置 titlePrefix: 'Design System',该 specifier 匹配到的所有 story 标题都会被统一加前缀,从而让多包项目共享同一侧边栏时层次清晰。
源码视角:顺序是如何被保留的
从源码结构看,stories 数组"顺序即侧边栏顺序"这一行为,在配置加载早期就已确立。normalize-stories.ts 的 normalizeStories 会按数组顺序逐个把字符串或对象条目规整为 NormalizedStoriesSpecifier:
export const normalizeStories = (entries: StoriesEntry[], options: NormalizeOptions) => {
if (!entries || (Array.isArray(entries) && entries.length === 0)) {
throw new InvalidStoriesEntryError();
}
return entries.map((entry) => normalizeStoriesEntry(entry, options));
};
其中有两个值得注意的工程细节:
- 空数组会被拒绝:
stories不能为空或未定义,否则直接抛出InvalidStoriesEntryError,阻止 Storybook 启动。这正是它被标记为"必填(Required)"的原因。 - 每个条目被编译为正则:
normalizeStoriesEntry对每个条目做三件事——用pico.scan解析字符串中的 glob 与目录前缀;把目录从.storybook配置目录相对路径换算为项目工作目录相对路径;最后通过globToRegexp生成importPathMatcher: RegExp。因此底层最终使用的是一组顺序保持的"导入路径匹配器"。数组先后位置不因规范化过程而改变,后续对每个 specifier 匹配到的文件自然按此先后参与索引构建。
也就是说,如果 stories[0] 匹配到 Introduction.mdx,而 stories[1] 匹配到 Button.stories.tsx,那么 Introduction.mdx 的文档页会先行出现在侧边栏,这正是有序 glob 生效的底层链路。
命名约定的影响与自定义 glob 注意事项
- 文档 main-config-stories.mdx 提醒:如果改用其他命名约定,可以使用 picomatch 支持的语法修改 glob;但部分 addon 可能假设 Storybook 的默认命名约定(例如默认的
*.stories.@(js|jsx|mjs|ts|tsx)),改动前请确认所用 addon 的兼容性。 - Storybook 官方推荐的做法是把 story 文件与它描述的组件同目录放置(colocate),形如:
components/
├── Button.ts
└── Button.stories.ts
这样单个目录即可同时覆盖组件与文档,配合有序数组可让 .mdx 总在 .stories.* 之前渲染。
高级扩展与性能注意事项
- 函数形态:
stories还支持异步函数:async (list: (string | StoriesSpecifier)[]) => (string | StoriesSpecifier)[],可在返回前对规则列表做程序化增删排序。 - 自定义逻辑:如果常规 glob 无法表达"从多个条件复杂的源加载 story"的需求,可以返回自定义处理逻辑(参考 main-config-stories-with-logic.md)。但需要注意,文档明确警告:Storybook 现在会对配置文件做静态分析以提升性能,使用自定义实现可能导致该优化失效。
- 验证效果:保存配置后重启
storybook dev(或执行storybook build后查看静态产物),即可在侧边栏看到数组顺序对应的展示次序;文档页、组件 Story 与测试条目(如*.stories旁的 test 变体)会按你声明的队列各归其位。
小结
控制 Storybook 侧边栏顺序的关键,就是把 stories 当作一条"有先后顺序的加载队列"来书写:.mdx 在前则文档置顶,.stories.* 紧随其后;monorepo 场景可借助 StoriesSpecifier 的 directory/files/titlePrefix 精细切分并统一前缀。通过 normalize-stories.ts 可以看到,Storybook 在启动早期便逐条、按序地把每个 glob 编译为 importPathMatcher 正则,从根上保证了声明顺序与最终索引、渲染顺序的一致性。
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 StartedRust0627
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