Docs
Storybook Docs

框架

Storybook 的架构支持各种 Web 框架,包括 React、Vue、Angular、Web Components、Svelte 和十几个其他框架。本指南可帮助你开始为 Storybook 添加新的框架支持。

¥Storybook is architected to support diverse web frameworks, including React, Vue, Angular, Web Components, Svelte, and over a dozen others. This guide helps you get started on adding new framework support for Storybook.

搭建新框架

¥Scaffolding a new framework

要做的第一件事是在自己的 repo 中搭建框架支持。

¥The first thing to do is to scaffold your framework support in its own repo.

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

¥We recommend adopting the same project structure as the Storybook monorepo. That structure contains the framework package (app/<framework>) and an example app (examples/<framework>-kitchen-sink) as well as other associated documentation and configuration as needed.

它似乎比必要的层次结构多一点。但是,由于结构反映了 Storybook 的 monorepo 的结构方式,因此你可以重复使用 Storybook 的工具。如果需要,它还可以更轻松地稍后将框架移入 Storybook monorepo。

¥It may seem like a little more hierarchy than what’s necessary. But because the structure mirrors the way Storybook’s monorepo is structured, you can reuse Storybook’s tooling. It also makes it easier to move the framework into the Storybook monorepo later if that is desirable.

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

¥We recommend using @storybook/html as a starter framework since it’s the simplest and contains no framework-specific peculiarities. There is a boilerplate to get you started here.

框架架构

¥Framework architecture

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

¥Supporting a new framework in Storybook typically consists of two main aspects:

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

    ¥Configuring the server. In Storybook, the server is the node process that runs when you run storybook dev or storybook build. Configuring the server typically means configuring babel and webpack in framework-specific ways.

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

    ¥Configuring the client. The client is the code that runs in the browser, and configuring it, means providing a framework-specific story rendering function.

配置服务器

¥Configuring the server

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

¥Storybook has the concept of presets, which are typically babel/webpack configurations for file loading. If your framework has its own file format (e.g., “.vue”), you might need to transform them into JavaScript files at load time. If you assume every user of your framework needs this, you should add it to the framework. So far, every framework added to Storybook has done it because Storybook’s core configuration is extremely minimal.

包结构

¥Package structure

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

¥It's helpful to understand Storybook's package structure before adding a framework preset. Each framework typically exposes two executables in its package.json:

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

这些脚本将 options 对象传递给 @storybook/core/server@storybook/core/server 是一个抽象 Storybook 所有独立于框架的代码的库。

¥These scripts pass an options object to @storybook/core/server, a library that abstracts all of Storybook’s framework-independent code.

例如,这是使用 storybook dev 启动开发服务器的样板:

¥For example, here’s the boilerplate to start the dev server with storybook dev:

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

因此,添加框架预设的本质只是填写该选项对象。

¥Thus the essence of adding framework presets is just filling in that options object.

服务器选项

¥Server options

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

¥As described above, the server options object does the heavy lifting of configuring the server.

让我们看看 @storybook/vue 的选项定义:

¥Let’s look at the @storybook/vue’s options definition:

vue/src/server/options.ts
import { sync } from 'read-pkg-up';
 
export default {
  packageJson: sync({ cwd: __dirname }).packageJson,
  framework: 'vue',
  frameworkPresets: [require.resolve('./framework-preset-vue.js')],
};

framework 选项(即“vue”)的值会传递给插件并允许它们执行与你的框架相关的特定任务。

¥The value of the framework option (i.e., ‘vue’) is something that gets passed to addons and allows them to do specific tasks related to your framework.

此文件的本质是框架预设,这些是标准 Storybook 预设 - 你可以查看 Storybook monorepo 中的框架包(例如 ReactVueWeb 组件)以查看特定于框架的自定义示例。

¥The essence of this file is the framework presets, and these are standard Storybook presets -- you can look at framework packages in the Storybook monorepo (e.g. React, Vue, Web Components) to see examples of framework-specific customizations.

在开发 Storybook 未维护的自定义框架时,你可以使用 frameworkPath 键指定位置文件的路径:

¥While developing your custom framework, not maintained by Storybook, you can specify the path to the location file with the frameworkPath key:

my-framework/src/server/options.ts
import { sync } from 'read-pkg-up';
 
export default {
  packageJson: sync({ cwd: __dirname }).packageJson,
  framework: 'my-framework',
  frameworkPath: '@my-framework/storybook',
  frameworkPresets: [require.resolve('./framework-preset-my-framework.js')],
};

你可以向 frameworkPath 添加相对路径。不要忘记它们默认从 Storybook 配置目录(即 .storybook)解析。

¥You can add a relative path to frameworkPath. Don't forget that they resolve from the Storybook configuration directory (i.e., .storybook) by default.

确保 frameworkPath 最终位于框架应用中的 dist/client/index.js 文件。

¥Make sure the frameworkPath ends up at the dist/client/index.js file within your framework app.

配置客户端

¥Configuring the client

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

¥To configure the client, you must provide a framework-specific render function. Before diving into the details, it’s essential to understand how user-written stories relate to what renders on the screen.

可渲染对象

¥Renderable objects

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

¥Storybook stories are ES6 objects that return a “renderable object.”

考虑以下 React 故事:

¥Consider the following React story:

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

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

¥In this case, the renderable object is the React element, <Button .../>.

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

¥In most other frameworks, the renderable object is actually a plain JavaScript object.

考虑以下假设示例:

¥Consider the following hypothetical example:

Button.stories.ts|tsx
// Replace your-framework with the name of your framework
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { Button } from './Button';
 
const meta: Meta<typeof Button> = {
  component: Button,
};
 
export default meta;
type Story = StoryObj<typeof Button>;
 
export const Sample: Story = {
  render: () => ({
    template: '<button :label=label />',
    data: {
      label: 'hello button',
    },
  }),
};

这个“可渲染对象”的设计是特定于框架的,理想情况下应该与该框架的习语相匹配。

¥The design of this “renderable object” is framework-specific and should ideally match the idioms of that framework.

渲染函数

¥Render function

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

¥The framework's render function is the entity responsible for converting the renderable object into DOM nodes. It is typically of the form:

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;
}

包结构

¥Package structure

在客户端,密钥文件是 src/client/preview.js

¥On the client side, the key file is 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

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

¥The globals file typically sets up a single global variable that client-side code (such as addon-provided decorators) can refer to if needed to understand which framework it's running in:

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

start 函数抽象了 Storybook 所有独立于框架的客户端(浏览器)代码,它采用了我们上面定义的渲染函数。有关渲染函数的示例,请参阅 Storybook monorepo 中的 ReactVueAngularWeb 组件

¥The start function abstracts all of Storybook’s framework-independent client-side (browser) code, and it takes the render function we defined above. For examples of render functions, see React, Vue, Angular, and Web Components in the Storybook monorepo.