文档
Storybook Docs

框架

Storybook 的架构旨在支持包括 React、Vue、Angular、Web Components、Svelte 和其他十几种在内的各种 Web 框架。本指南将帮助您开始添加对 Storybook 的新框架支持。

构建新框架

第一步是在您自己的仓库中构建您的框架支持。

我们建议采用与 Storybook monorepo 相同的项目结构。该结构包含框架包(app/<framework>)和一个示例应用(examples/<framework>-kitchen-sink),以及其他所需的文档和配置。

这似乎比实际需要更多的层级。但因为其结构镜像了 Storybook monorepo 的结构,您可以重用 Storybook 的工具。如果将来您希望将该框架移入 Storybook monorepo,这种结构也会使其更加容易。

我们建议使用 @storybook/html 作为入门框架,因为它最简单且不包含任何特定于框架的特殊性。这里有一个样板文件可以帮助您入门:此处

框架架构

在 Storybook 中支持新框架通常包含两个主要方面:

  1. 配置服务器。在 Storybook 中,服务器是运行 storybook devstorybook build 时的 node 进程。配置服务器通常意味着以特定于框架的方式配置 babel 和 webpack。

  2. 配置客户端。客户端是在浏览器中运行的代码,配置它意味着提供一个特定于框架的故事渲染函数。

配置服务器

Storybook 拥有“预设”(presets)的概念,它们通常是用于文件加载的 babel/webpack 配置。如果您的框架有自己的文件格式(例如,“.vue”),您可能需要在加载时将其转换为 JavaScript 文件。如果您认为您的框架的所有用户都需要此功能,则应将其添加到框架中。到目前为止,添加到 Storybook 的所有框架都这样做了,因为 Storybook 的核心配置非常精简。

包结构

在添加框架预设之前,了解 Storybook 的包结构很有帮助。每个框架通常在其 package.json 中公开两个可执行文件。

package.json
{
  "bin": {
    "storybook": "./bin/index.js",
    "build-storybook": "./bin/build.js"
  }
}

这些脚本将一个 options 对象传递给 storybook/internal/server,这是一个抽象了 Storybook 所有与框架无关的代码的库。

例如,以下是使用 storybook dev 启动开发服务器的样板文件:

your-framework/src/server/index.ts
import { buildDev } from '@storybook/core/server';
 
import options from './options';
 
buildDev(options);

因此,添加框架预设的本质就是填充该 options 对象。

服务器选项

如上所述,服务器 options 对象完成了配置服务器的繁重工作。

让我们看看 @storybook/vue 的 options 定义。

vue/src/server/options.ts
import { readFileSync } from 'node:fs';
import * as pkg from 'empathic/package';
 
export default {
  packageJson: JSON.parse(readFileSync(pkg.up({ cwd: process.cwd() }))),
  framework: 'vue',
  frameworkPresets: [import.meta.resolve('./framework-preset-vue.js')],
};

framework 选项的值(即“vue”)是传递给插件的内容,它允许插件执行与您的框架相关的特定任务。

此文件的核心是框架预设,这些是标准的 Storybook 预设——您可以查看 Storybook monorepo 中的框架包(例如 ReactVueWeb Components)以查看特定于框架的自定义示例。

在开发您自己(非 Storybook 维护的)的自定义框架时,您可以使用 frameworkPath 键指定该位置文件的路径。

my-framework/src/server/options.ts
import { readFileSync } from 'node:fs';
import * as pkg from 'empathic/package';
 
export default {
  packageJson: JSON.parse(readFileSync(pkg.up({ cwd: process.cwd() }))),
  framework: 'my-framework',
  frameworkPath: '@my-framework/storybook',
  frameworkPresets: [import.meta.resolve('./framework-preset-my-framework.js')],
};

您可以为 frameworkPath 添加相对路径。请记住,它们默认从 Storybook 配置目录(即 .storybook)解析。

请确保 frameworkPath 指向您框架应用程序内的 dist/client/index.js 文件。

配置客户端

要配置客户端,您必须提供一个特定于框架的渲染函数。在深入细节之前,了解用户编写的故事与屏幕上渲染的内容之间的关系至关重要。

可渲染对象

Storybook 故事是返回“可渲染对象”的 ES6 对象。

考虑以下 React 故事:

Button.stories.js|jsx
import { Button } from './Button';
 
export default {
  component: Button,
};
 
export const Sample = {
  render: () => <Button label="hello button" />,
};

在这种情况下,可渲染对象是 React 元素 <Button .../>

在大多数其他框架中,可渲染对象实际上是一个普通的 JavaScript 对象。

考虑以下假设示例:

Button.stories.ts|tsx
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { Button } from './Button';
 
const meta = {
  component: Button,
} satisfies Meta<typeof Button>;
 
export default meta;
type Story = StoryObj<typeof meta>;
 
export const Sample: Story = {
  render: () => ({
    template: '<button :label=label />',
    data: {
      label: 'hello button',
    },
  }),
};

这个“可渲染对象”的设计是特定于框架的,并且应该最好地匹配该框架的习惯用法。

渲染函数

框架的渲染函数负责将可渲染对象转换为 DOM 节点。它通常采用以下形式:

your-framework/src/client/preview/render.ts
const rootElement = document.getElementById('root');
 
export default function renderMain({ storyFn }: RenderMainArgs) {
  const storyObj = storyFn();
  const html = fn(storyObj);
  rootElement.innerHTML = html;
}

包结构

在客户端,关键文件是 src/client/preview.js

your-framework/src/client/preview/index.ts
import { start } from 'storybook/preview-api';
 
import './globals';
 
import render from './render';
 
const api = start(render);
 
// the boilerplate code

globals 文件通常会设置一个全局变量,客户端代码(例如插件提供的装饰器)可以在需要时引用该变量来了解它正在运行的是哪个框架。

vue/src/client/preview/globals.ts
import { global } from '@storybook/global';
 
const { window: globalWindow } = global;
 
globalWindow.STORYBOOK_ENV = 'vue';

start 函数抽象了 Storybook 所有与框架无关的客户端(浏览器)代码,并接受我们上面定义的渲染函数。有关渲染函数的示例,请参阅 Storybook monorepo 中的 ReactVueAngularWeb Components