文件
Storybook 文件

indexers

(⚠️ 實驗性)

雖然此功能為實驗性,但必須由 StorybookConfigexperimental_indexers 屬性指定,詳情請參閱 StorybookConfig

父層: main.js|ts 設定

類型: (existingIndexers: Indexer[]) => Promise<Indexer[]>

Indexers 負責建立 Storybook 的 stories 索引,也就是所有 stories 的清單,以及它們中繼資料的子集,例如 idtitletags 等等。索引可以從 Storybook 的 /index.json 路由讀取。

indexers API 是一項進階功能,可讓您自訂 Storybook 的 indexers,這會決定 Storybook 如何將檔案編製索引並剖析成 story 項目。這增加了編寫 stories 的彈性,包括定義 stories 所用的語言或從何處取得 stories。

它們定義為一個函式,該函式會傳回完整的 indexers 清單,包括現有的 indexers。這可讓您將自己的 indexer 新增至清單,或取代現有的 indexer

.storybook/main.ts
// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite)
import type { StorybookConfig } from '@storybook/your-framework';
 
const config: StorybookConfig = {
  framework: '@storybook/your-framework',
  stories: [
    '../src/**/*.mdx',
    '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)',
    // 👇 Make sure files to index are included in `stories`
    '../src/**/*.custom-stories.@(js|jsx|ts|tsx)',
  ],
  experimental_indexers: async (existingIndexers) => {
    const customIndexer = {
      test: /\.custom-stories\.[tj]sx?$/,
      createIndex: // See API and examples below...
    };
    return [...existingIndexers, customIndexer];
  },
};
 
export default config

除非您的 indexer 執行相對簡單的操作 (例如 以不同的命名慣例索引 stories),否則除了索引檔案之外,您可能還需要將它轉譯為 CSF,以便 Storybook 可以在瀏覽器中讀取它們。

Indexer

類型

{
  test: RegExp;
  createIndex: (fileName: string, options: IndexerOptions) => Promise<IndexInput[]>;
}

指定要將哪些檔案編製索引,以及如何將它們編製為 stories 的索引。

test

(必要)

類型: RegExp

針對 stories 設定中包含的檔案名稱執行的正規表示式,應符合此 indexer 所處理的所有檔案。

createIndex

(必要)

類型: (fileName: string, options: IndexerOptions) => Promise<IndexInput[]>

接受單一 CSF 檔案並傳回要編製索引的項目清單的函式。

fileName

類型: string

用於建立索引項目的 CSF 檔案名稱。

IndexerOptions

類型

{
  makeTitle: (userTitle?: string) => string;
}

編製檔案索引的選項。

makeTitle

類型: (userTitle?: string) => string

一個函式,接受使用者提供的標題並傳回索引項目的格式化標題,該標題用於側邊欄。如果未提供使用者標題,則會根據檔案名稱和路徑自動產生一個標題。

如需使用範例,請參閱 IndexInput.title

IndexInput

類型

{
  exportName: string;
  importPath: string;
  type: 'story';
  rawComponentPath?: string;
  metaId?: string;
  name?: string;
  tags?: string[];
  title?: string;
  __id?: string;
}

一個代表要加入到 stories 索引中的 story 的物件。

exportName

(必要)

類型: string

對於每個 IndexInput,索引器將會把這個 export(來自 importPath 中找到的檔案)作為索引中的一個條目加入。

importPath

(必要)

類型: string

要匯入的檔案,例如 CSF 檔案。

被索引的 fileName 很可能不是 CSF,在這種情況下,您需要將其轉譯為 CSF,以便 Storybook 可以在瀏覽器中讀取它。

type

(必要)

類型:'story'

條目的類型。

rawComponentPath

類型: string

提供 meta.component 的檔案的原始路徑/套件,如果有的話。

metaId

類型: string

預設值:從 title 自動產生

定義條目 meta 的自訂 ID。

如果指定,CSF 檔案中的 export default (meta) *必須* 具有對應的 id 屬性,才能正確匹配。

name

類型: string

預設值:從 exportName 自動產生

條目的名稱。

tags

類型:string[]

用於在 Storybook 及其工具中篩選條目的標籤。

title

類型: string

預設值:從 importPath 的預設 export 自動產生

決定條目在側邊欄中的位置。

大多數時候,您*不應該*指定標題,這樣您的索引器將使用預設的命名行為。當指定標題時,您*必須*使用 IndexerOptions 中提供的 makeTitle 函數來使用此行為。例如,以下是一個索引器,它僅將「Custom」前綴附加到從檔名派生的標題上

.storybook/main.ts
// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite)
import type { StorybookConfig } from '@storybook/your-framework';
import type { Indexer } from '@storybook/types';
 
const combosIndexer: Indexer = {
  test: /\.stories\.[tj]sx?$/,
  createIndex: async (fileName, { makeTitle }) => {
    // 👇 Grab title from fileName
    const title = fileName.match(/\/(.*)\.stories/)[1];
 
    // Read file and generate entries ...
 
    return entries.map((entry) => ({
      type: 'story',
      // 👇 Use makeTitle to format the title
      title: `${makeTitle(title)} Custom`,
      importPath: fileName,
      exportName: entry.name,
    }));
  },
};
 
const config: StorybookConfig = {
  framework: '@storybook/your-framework',
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
  experimental_indexers: async (existingIndexers) => [...existingIndexers, combosIndexer];
};
 
export default config;
__id

類型: string

預設值:從 title/metaIdexportName 自動產生

定義條目 story 的自訂 ID。

如果指定,CSF 檔案中的 story *必須* 具有對應的 __id 屬性,才能正確匹配。

僅在您需要覆寫自動產生的 ID 時使用此項。

轉譯為 CSF

IndexInput 中的 importPath 的值必須解析為 CSF 檔案。然而,大多數自訂索引器僅在輸入*不是* CSF 時才需要。因此,您可能需要將輸入轉譯為 CSF,以便 Storybook 可以在瀏覽器中讀取它並呈現您的 stories。

將自訂來源格式轉譯為 CSF 超出了本文件的範圍。這種轉譯通常在建置器層級完成(Vite 和/或 Webpack),我們建議使用 unplugin 來建立多個建置器的外掛程式。

一般架構如下所示

Architecture diagram showing how a custom indexer indexes stories from a source file

  1. 使用 stories 設定,Storybook 會找到所有符合您的索引器的 test 屬性的檔案
  2. Storybook 將每個符合的檔案傳遞給您的索引器的 createIndex 函數,該函數使用檔案內容產生並傳回要加入索引的索引條目(stories)清單
  3. 索引會填入 Storybook UI 中的側邊欄

Architecture diagram showing how a build plugin transforms a source file into CSF

  1. 在 Storybook UI 中,使用者導覽到符合 story ID 的 URL,並且瀏覽器會請求索引條目的 importPath 屬性指定的 CSF 檔案
  2. 回到伺服器端,您的建置器外掛程式會將原始檔案轉譯為 CSF,並將其提供給用戶端
  3. Storybook UI 會讀取 CSF 檔案,匯入 exportName 指定的 story,並呈現它

讓我們來看一個範例,說明這可能如何運作。

首先,以下是一個非 CSF 原始檔案的範例

// Button.variants.js|ts
 
import { variantsFromComponent, createStoryFromVariant } from '../utils';
import { Button } from './Button';
 
/**
 * Returns raw strings representing stories via component props, eg.
 * 'export const PrimaryVariant = {
 *    args: {
 *      primary: true
 *    },
 *  };'
 */
export const generateStories = () => {
  const variants = variantsFromComponent(Button);
  return variants.map((variant) => createStoryFromVariant(variant));
};

然後,建置器外掛程式會

  1. 接收並讀取原始檔案
  2. 匯入匯出的 generateStories 函數
  3. 執行該函數以產生 stories
  4. 將 stories 寫入 CSF 檔案

然後,Storybook 會對產生的 CSF 檔案進行索引。它看起來會像這樣

// virtual:Button.variants.js|ts
 
import { Button } from './Button';
 
export default {
  component: Button,
};
 
export const Primary = {
  args: {
    primary: true,
  },
};

範例

自訂索引器的一些範例用法包括

從 fixture 資料或 API 端點動態產生 stories

此索引器根據 JSON fixture 資料產生元件的 stories。它會在專案中尋找 *.stories.json 檔案,將它們加入索引,並將它們的內容分別轉換為 CSF。

.storybook/main.ts
// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite)
import type { StorybookConfig } from '@storybook/your-framework';
import type { Indexer } from '@storybook/types';
 
import fs from 'fs/promises';
 
const jsonStoriesIndexer: Indexer = {
  test: /stories\.json$/,
  createIndex: async (fileName) => {
    const content = JSON.parse(fs.readFileSync(fileName));
 
    const stories = generateStoryIndexesFromJson(content);
 
    return stories.map((story) => {
      type: 'story',
      importPath: `virtual:jsonstories--${fileName}--${story.componentName}`,
      exportName: story.name
    });
  },
};
 
const config: StorybookConfig = {
  framework: '@storybook/your-framework',
  stories: [
    '../src/**/*.mdx',
    '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)',
    // 👇 Make sure files to index are included in `stories`
    '../src/**/*.stories.json',
  ],
  experimental_indexers: async (existingIndexers) => [...existingIndexers, jsonStoriesIndexer];
};
 
export default config;

範例輸入 JSON 檔案可能看起來像這樣

{
  "Button": {
    "componentPath": "./button/Button.jsx",
    "stories": {
      "Primary": {
        "args": {
          "primary": true
        },
      "Secondary": {
        "args": {
          "primary": false
        }
      }
    }
  },
  "Dialog": {
    "componentPath": "./dialog/Dialog.jsx",
    "stories": {
      "Closed": {},
      "Open": {
        "args": {
          "isOpen": true
        }
      },
    }
  }
}

然後,建置器外掛程式將需要將 JSON 檔案轉換為常規 CSF 檔案。可以使用類似於以下的 Vite 外掛程式完成此轉換

// vite-plugin-storybook-json-stories.ts
 
import type { PluginOption } from 'vite';
import fs from 'fs/promises';
 
function JsonStoriesPlugin(): PluginOption {
  return {
    name: 'vite-plugin-storybook-json-stories',
    load(id) {
      if (!id.startsWith('virtual:jsonstories')) {
        return;
      }
 
      const [, fileName, componentName] = id.split('--');
      const content = JSON.parse(fs.readFileSync(fileName));
 
      const { componentPath, stories } = getComponentStoriesFromJson(content, componentName);
 
      return `
        import ${componentName} from '${componentPath}';
 
        export default { component: ${componentName} };
 
        ${stories.map((story) => `export const ${story.name} = ${story.config};\n`)}
      `;
    },
  };
}
使用替代 API 產生 stories

您可以使用自訂索引器和建置器外掛程式來建立您的 API,以定義擴展 CSF 格式的 stories。若要深入瞭解,請參閱以下的 概念驗證,以設定自訂索引器來動態產生 stories。它包含支援此功能所需的一切,包括索引器、Vite 外掛程式和 Webpack 加載器。

以非 JavaScript 語言定義 stories

自訂索引器可以用於進階用途:以任何語言(包括範本語言)定義 stories,並將檔案轉換為 CSF。若要查看實際的範例,您可以參考 @storybook/addon-svelte-csf 以取得 Svelte 範本語法,以及 storybook-vue-addon 以取得 Vue 範本語法。