文件
Storybook 文件

Component Story Format (CSF)

CSF 3CSF Factories (實驗性功能)

這是一項實驗性功能,(雖然不太可能) API 在未來版本中可能會變更。我們歡迎提供意見回饋與貢獻,以協助改進此功能。

CSF Factories 是 Storybook 的 Component Story Format (CSF) 的下一代演進。這個新的 API 使用稱為 factory functions 的模式,為您的 Storybook stories 提供完整的型別安全,使其更容易正確設定附加元件,並釋放 Storybook 功能的全部潛力。

本參考文件將概述 API,並提供從 CSF 3 升級的遷移指南。

概觀

CSF Factories API 由四個主要函式組成,以協助您撰寫 stories。請注意,其中三個函式作為 factories 運作,每個函式都會產生鏈中的下一個函式 (definePreviewpreview.metameta.story),在每個步驟提供完整的型別安全。

defineMain

使用 CSF Factories,您的主要 Storybook 設定defineMain 函式指定。此函式是型別安全的,並將自動推斷專案的型別。

.storybook/main.js|ts
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, experimental-nextjs-vite)
import { defineMain } from '@storybook/your-framework/node';
 
export default defineMain({
  framework: '@storybook/your-framework',
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
  addons: ['@storybook/addon-a11y'],
});

definePreview

同樣地,definePreview 函式指定您專案的 story 設定。此函式也是型別安全的,並將在您的整個專案中推斷型別。

重要的是,透過在此處指定附加元件,它們的型別將在您的整個專案中可用,從而啟用自動完成和型別檢查。

您將在您的 story 檔案中匯入此函式的結果 preview,以定義元件 meta。

.storybook/preview.js|ts
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, experimental-nextjs-vite)
import { definePreview } from '@storybook/your-framework';
import addonA11y from '@storybook/addon-a11y';
 
export default definePreview({
  // 👇 Add your addons here
  addons: [addonA11y()],
  parameters: {
    // type-safe!
    a11y: {
      options: { xpath: true },
    },
  },
});

當透過 npx storybook add <addon-name> 安裝附加元件或執行 storybook dev 時,預覽設定將自動更新以參考必要的附加元件。

preview.meta

preview 物件上的 meta 函式用於定義stories 的 metadata。它接受一個包含 componenttitleparameters 和其他 story 屬性的物件。

Button.stories.js|ts
// Learn about the # subpath import: https://storybook.dev.org.tw/docs/api/csf/csf-factories#subpath-imports
import preview from '#.storybook/preview';
 
import { Button } from './Button';
 
const meta = preview.meta({
  component: Button,
  parameters: {
    // type-safe!
    layout: 'centered',
  }
});
export default meta;

meta.story

最後,在 meta 物件上的 story 函式定義了 stories。此函式接受一個包含 nameargsparameters 和其他 story 屬性的物件。

Button.stories.js|ts
// ...from above
const meta = preview.meta({ /* ... */ });
 
export const Primary = meta.story({
  args: {
    // type-safe!
    primary: true,
  },
});

子路徑匯入

CSF Factories 利用子路徑匯入來簡化從預覽檔案匯入建構。雖然您仍然可以使用相對路徑匯入,但子路徑匯入提供更方便且可維護的方法

// ✅ Subpath imports won't break if you move story files around
import preview from '#.storybook/preview';
 
// ❌ Relative imports will break if you move story files around
import preview from '../../../.storybook/preview';

請參閱手動遷移步驟,以了解有關設定必要的子路徑匯入的詳細資訊。

如需更多詳細資訊,請參閱子路徑匯入文件

從 CSF 1、2 或 3 升級

您可以逐步或一次性升級專案的 story 檔案至 CSF Factories。但是,在 story 檔案中使用 CSF Factories 之前,您必須升級 .storybook/main.js|ts.storybook/preview.js|ts 檔案。

1. 在 package.json 中新增子路徑匯入

為了能夠從專案中的任何位置一致地匯入預覽檔案,您需要在 package.json 中新增子路徑匯入。如需更多資訊,請參閱子路徑匯入文件

package.json
{
  "imports": {
    "#*": ["./*", "./*.ts", "./*.tsx"],
  },
}

2. 更新您的主要 Storybook 設定檔

更新您的 .storybook/main.js|ts 檔案以使用新的 defineMain 函式。

.storybook/main.js|ts
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, experimental-nextjs-vite)
+ import { defineMain } from '@storybook/your-framework/node';
- import { StorybookConfig } from '@storybook/your-framework';
 
+ export default defineMain({
- export const config: StorybookConfig = {
    // ...current config
+ });
- };
- export default config;

3. 更新您的預覽設定檔

更新您的 .storybook/preview.js|ts 檔案以使用新的 definePreview 函式。

哪些附加元件應在 preview 中指定?

附加元件提供註解型別 (parametersglobals 等) 的能力是新的,並非所有附加元件都支援它。

如果附加元件提供註解 (即它分發 ./preview 匯出),則可以透過兩種方式匯入

  1. 對於官方 Storybook 附加元件,您匯入預設匯出:import addonName from '@storybook/addon-name'

  2. 對於社群附加元件,您應匯入整個模組並從該處存取附加元件:import * as addonName from 'community-addon-name'

.storybook/preview.js|ts
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, experimental-nextjs-vite)
+ import { definePreview } from '@storybook/your-framework';
- import { Preview } from '@storybook/your-renderer';
// 👇 Import the addons you are using
+ import addonA11y from '@storybook/addon-a11y';
 
+ export default definePreview({
- export const preview: Preview = {
    // ...current config
    // 👇 Add your addons here
+   addons: [addonA11y()],
+ });
- };
- export default preview;

4. 更新您的 story 檔案

Story 檔案已更新,以提升可用性。使用新格式

  • 從 Storybook 預覽檔案匯入預覽建構
  • meta 物件現在透過 preview.meta 函式建立,並且不必作為預設匯出
  • Stories 現在從 meta 物件透過 meta.story 函式建立

以下範例顯示從 CSF 3 升級到 CSF Factories 所需的變更。您也可以使用類似的步驟從 CSF 1 或 2 升級。

Button.stories.js|ts
// Learn about the # subpath import: https://storybook.dev.org.tw/docs/api/csf/csf-factories#subpath-imports
+ import preview from '#.storybook/preview';
- import { Meta, StoryObj } from '@storybook/your-renderer';
 
import { Button } from './Button';
 
+ const meta = preview.meta({
- const meta = {
    // ...current meta
+ });
- } satisfies Meta<typeof Button>;
- export default meta;
 
- type Story = StoryObj<typeof meta>;
 
+ export const Primary = meta.story({
- export const Primary: Story = {
    // ...current story
+ });
- };

請注意,不再需要匯入或手動將任何型別套用至 meta 或 stories。由於 factory function 模式,現在會自動推斷型別。

4.1 重複使用 story 屬性

先前,當在另一個 story 中重複使用 story 屬性 (例如 Story.argsStory.parameters) 時,會直接存取它們。雖然仍然支援以這種方式存取它們,但在 CSF Factories 中已棄用。

所有 story 屬性現在都包含在一個名為 composed 的新屬性中,應改為從該屬性存取。例如,Story.composed.argsStory.composed.parameters

Button.stories.js|ts
// ...rest of file
 
+ export const Primary = meta.story({
- export const Primary: Story = {
    args: { primary: true },
+ });
- };
 
+ export const PrimaryDisabled = meta.story({
- export const PrimaryDisabled: Story = {
    args: {
+     ...Primary.composed.args,
-     ...Primary.args,
      disabled: true,
    }
+ });
- };

之所以選擇屬性名稱 "composed",是因為其中的值是由 story、其元件 meta 和預覽設定組成的。

如果您想存取 story 的直接輸入,可以使用 Story.input 而不是 Story.composed

5. 更新您的 Vitest 設定檔

無論您是使用 Storybook 的 Test addon 還是 Vitest 中的可攜式 stories,您都可以使用 Vitest 設定檔來設定您的 stories。此檔案必須更新為使用新的 CSF Factories 格式。

請注意,這僅適用於您對所有測試的 stories 使用 CSF Factories 的情況。如果您混合使用 CSF 1、2 或 3 和 CSF Factories,則必須維護兩個獨立的設定檔。

vitest.setup.js|ts
import { beforeAll } from 'vitest';
// 👇 No longer necessary
- import { setProjectAnnotations } from '@storybook/react';
- import * as addonAnnotations from 'my-addon/preview';
+ import preview from './.storybook/preview';
- import * as previewAnnotations from './.storybook/preview';
 
// No longer necessary
- const annotations = setProjectAnnotations([previewAnnotations, addonAnnotations]);
 
// Run Storybook's beforeAll hook
+ beforeAll(preview.composed.beforeAll);
- beforeAll(annotations.beforeAll);

6. 在測試檔案中重複使用 stories

Storybook 的 Test addon 允許您直接在 Storybook 內測試您的元件。所有 stories 都會自動轉換為 Vitest 測試,使整合在您的測試套件中無縫接軌。

如果您無法使用 Storybook Test,您仍然可以使用 可攜式 stories 在您的測試檔案中重複使用 stories。在先前的 story 格式中,您必須先組合 stories,然後才能在測試檔案中渲染它們。使用 CSF Factories,您現在可以直接重複使用 stories。

Button.test.js|ts
import { test, expect } from 'vitest';
import { screen } from '@testing-library/react';
- import { composeStories } from '@storybook/react';
 
// Import all stories from the stories file
import * as stories from './Button.stories';
 
+ const { Primary } = stories;
- const { Primary } = composeStories(stories);
 
test('renders primary button with default args', async () => {
  // The run function will mount the component and run all of Storybook's lifecycle hooks
  await Primary.run();
  const buttonElement = screen.getByText('Text coming from args in stories file!');
  expect(buttonElement).not.toBeNull();
});

Story 物件也提供 Component 屬性,使您能夠使用您選擇的任何方法渲染元件,例如 Testing Library。您也可以透過 composed 屬性存取其 composed 屬性 (argsparameters 等)。

以下是如何透過渲染其元件在測試檔案中重複使用 story 的範例

Button.test.tsx
import { test, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
 
// Import all stories from the stories file
import * as stories from './Button.stories';
 
const { Primary, Secondary } = stories;
 
test('renders primary button with default args', async () => {
  // Access the story's component via the .Component property
  render(<Primary.Component />);
  const buttonElement = screen.getByText('Text coming from args in stories file!');
  expect(buttonElement).not.toBeNull();
});
 
test('renders primary button with overridden props', async () => {
  // You can override props by passing them directly to the story's component
  render(<Primary.Component>Hello world</Primary.Component>);
  const buttonElement = screen.getByText(/Hello world/i);
  expect(buttonElement).not.toBeNull();
});

常見問題 (FAQ)

我是否必須將所有 stories 遷移到這個新格式?

在可預見的未來,Storybook 將繼續支援 CSF 1、CSF 2CSF 3。這些先前的格式皆未被棄用。

在使用 CSF Factories 時,您仍然可以使用較舊的格式,只要它們沒有在同一個檔案中混合使用即可。如果您想將現有檔案遷移到新格式,請參閱上方的升級章節

此格式是否適用於 MDX 文件頁面?

是的,用於在 MDX 檔案中參考 stories 的文件區塊支援 CSF Factories 格式,無需進行任何變更。

我如何能更了解此格式並提供意見回饋?

如需有關此實驗性格式的原始提案的更多資訊,請參閱其在 GitHub 上的 RFC。我們歡迎您的評論!