indexers
(⚠️ 實驗性)
雖然此功能為實驗性,但必須由 StorybookConfig
的 experimental_indexers
屬性指定,詳情請參閱 StorybookConfig
。
父層: main.js|ts 設定
類型: (existingIndexers: Indexer[]) => Promise<Indexer[]>
Indexers 負責建立 Storybook 的 stories 索引,也就是所有 stories 的清單,以及它們中繼資料的子集,例如 id
、title
、tags
等等。索引可以從 Storybook 的 /index.json
路由讀取。
indexers API 是一項進階功能,可讓您自訂 Storybook 的 indexers,這會決定 Storybook 如何將檔案編製索引並剖析成 story 項目。這增加了編寫 stories 的彈性,包括定義 stories 所用的語言或從何處取得 stories。
它們定義為一個函式,該函式會傳回完整的 indexers 清單,包括現有的 indexers。這可讓您將自己的 indexer 新增至清單,或取代現有的 indexer
// 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」前綴附加到從檔名派生的標題上
// 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
/metaId
和 exportName
自動產生
定義條目 story 的自訂 ID。
如果指定,CSF 檔案中的 story *必須* 具有對應的 __id
屬性,才能正確匹配。
僅在您需要覆寫自動產生的 ID 時使用此項。
轉譯為 CSF
IndexInput
中的 importPath
的值必須解析為 CSF 檔案。然而,大多數自訂索引器僅在輸入*不是* CSF 時才需要。因此,您可能需要將輸入轉譯為 CSF,以便 Storybook 可以在瀏覽器中讀取它並呈現您的 stories。
將自訂來源格式轉譯為 CSF 超出了本文件的範圍。這種轉譯通常在建置器層級完成(Vite 和/或 Webpack),我們建議使用 unplugin 來建立多個建置器的外掛程式。
一般架構如下所示
- 使用
stories
設定,Storybook 會找到所有符合您的索引器的test
屬性的檔案 - Storybook 將每個符合的檔案傳遞給您的索引器的
createIndex
函數,該函數使用檔案內容產生並傳回要加入索引的索引條目(stories)清單 - 索引會填入 Storybook UI 中的側邊欄
- 在 Storybook UI 中,使用者導覽到符合 story ID 的 URL,並且瀏覽器會請求索引條目的
importPath
屬性指定的 CSF 檔案 - 回到伺服器端,您的建置器外掛程式會將原始檔案轉譯為 CSF,並將其提供給用戶端
- 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));
};
然後,建置器外掛程式會
- 接收並讀取原始檔案
- 匯入匯出的
generateStories
函數 - 執行該函數以產生 stories
- 將 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。
// 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 範本語法。