首页
/ Storybook 侧边栏排序指南:用 main 配置中 stories 数组顺序精确控制 Sidebar 展示次序

Storybook 侧边栏排序指南:用 main 配置中 stories 数组顺序精确控制 Sidebar 展示次序

2026-09-07 22:39:04作者:裴锟轩Denise

在 Storybook 中,组件树(Sidebar)中 Story 的展示顺序并非随机的:它由 .storybook/main.{js,ts}stories 数组的声明顺序直接决定。本篇指南围绕 docs/_snippets/main-config-stories-ordered.md 的核心配置思路,讲解如何把 MDX 文档排在 Story 之前、如何为每个 glob 模式排队,并深入 normalizeStoriesStoriesSpecifier 类型定义,从源码层面解释"顺序如何被加载与保持"。读完你将能精确编排 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。核心做法是在一个数组里写下多个有序 globmain-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 入口导入。不同框架的写法如下(同样遵循数组顺序即展示顺序):

  • Reactreact/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.tsnormalizeStories按数组顺序逐个把字符串或对象条目规整为 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));
};

其中有两个值得注意的工程细节:

  1. 空数组会被拒绝stories 不能为空或未定义,否则直接抛出 InvalidStoriesEntryError,阻止 Storybook 启动。这正是它被标记为"必填(Required)"的原因。
  2. 每个条目被编译为正则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 场景可借助 StoriesSpecifierdirectory/files/titlePrefix 精细切分并统一前缀。通过 normalize-stories.ts 可以看到,Storybook 在启动早期便逐条、按序地把每个 glob 编译为 importPathMatcher 正则,从根上保证了声明顺序与最终索引、渲染顺序的一致性。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388