加入直播:周四,美国东部时间上午 11 点,Storybook 9 发布及 AMA

Svelte CSF

允许使用 Svelte 语法编写故事

在 Github 上查看

Svelte CSF

这个 Storybook 插件允许您使用 Svelte 语言而不是基于常规 CSF 的 ESM 来编写 Storybook 故事。

npx storybook@latest add @storybook/addon-svelte-csf

使用 Svelte 语言可以更容易地为依赖于片段或插槽的组合组件编写故事,这些组件在 Svelte 文件之外不易重新创建。

🐣 入门

[!TIP] 如果您使用 Storybook 8.2.0 或更高版本初始化了 Storybook 项目,则此插件已为您设置好!

[!重要]
没有运行最新版本的 Storybook 或 Svelte?请务必查看下面的版本兼容性部分

安装此插件最简单的方法是使用 storybook add

npx storybook@latest add @storybook/addon-svelte-csf

您也可以手动添加此插件。首先,安装软件包

npm install --save-dev @storybook/addon-svelte-csf

然后修改您的 Storybook 配置 main.ts,以包含此插件并包含 *.stories.svelte 文件

export default {
-  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)',
+  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx|svelte)'],
  addons: [
+    '@storybook/addon-svelte-csf',
    ...
  ],
  ...
}

重启 Storybook 服务器以使更改生效。

🐓 用法

[!注意] 此处的文档不涵盖 Storybook 的所有功能,仅涵盖与此插件和 Svelte CSF 相关的内容。建议您在 https://storybook.org.cn/docs 熟悉 Storybook 的核心概念。

examples 目录包含描述插件每个功能的示例。Button.stories.svelte 示例是一个很好的入门示例。包含所有示例的 Storybook 已发布到 Chromatic

Svelte CSF 故事文件必须始终使用 .stories.svelte 扩展名。

定义 meta

所有故事文件都必须定义一个“meta”(也称为“默认导出”),其结构遵循 官方文档中关于此主题的描述。要在 Svelte CSF 中定义 meta,请在模块上下文内调用 defineMeta 函数,并传入您想要的 meta 属性

<script module>
  //    👆 notice the module context, defineMeta does not work in a regular <script> tag - instance
  import { defineMeta } from '@storybook/addon-svelte-csf';

  import MyComponent from './MyComponent.svelte';

  //      👇 Get the Story component from the return value
  const { Story } = defineMeta({
    title: 'Path/To/MyComponent',
    component: MyComponent,
    decorators: [
      /* ... */
    ],
    parameters: {
      /* ... */
    },
  });
</script>

defineMeta 返回一个对象,其中包含一个 Story 组件(参见下面的定义故事),您必须解构该对象才能使用它。

定义故事

要定义故事,您可以使用从 defineMeta 函数返回的 Story 组件。根据您希望故事包含的内容,有多种方法可以使用 Story 组件。所有用例的共同点是,常规 CSF 故事的所有属性都作为 props 传递给 Story 组件,但 render 函数除外,它在 Svelte CSF 中没有任何效果。

所有故事都需要 name prop 或 exportName prop

[!TIP] 在此插件 v5 之前的版本中,始终需要使用 <Template> 组件定义模板故事。现在不再需要这样做,如果未设置模板,故事将默认渲染来自 meta 的组件。

普通故事

如果您的组件只接受 props 并且不需要片段或插槽,您可以使用简单的形式定义故事,只使用 args

<Story name="Primary" args={{ primary: true }} />

这将渲染在 meta 中定义的组件,并将 args 作为 props 传递。

带子组件

如果您的组件需要子组件,您可以直接将它们传递给故事,它们将被转发到您的组件

<Story name="With Children">I will be the child of the component from defineMeta</Story>

静态模板

如果您需要对故事进行更多定制,例如组合组件或定义片段,您可以在 Story 上设置 asChild prop。它不会将子组件转发到您的组件,而是直接将子组件用作故事输出。这允许您编写任何您想要的组件结构

<Story name="Composed" asChild>
  <MyComponent>
    <AChild label="Hello world!" />
  </MyComponent>
</Story>

[!重要]
这种格式完全忽略了 args,因为它们不会向下传递到任何已定义的子组件。即使您的故事有 args 和 Controls,它们也不会起作用。请参阅下面的基于片段的格式。

内联片段

如果您需要组合/片段,但也想要一个响应 args 或故事上下文的动态故事,您可以在 Story 组件中定义一个 template 片段

<Story name="Simple Template" args={{ simpleChild: true }}>
  {#snippet template(args)}
    <MyComponent {...args}>Component with args</MyComponent>
  {/snippet}
</Story>

共享片段

通常您的故事非常相似,唯一的区别是 args 或 play 函数。在这种情况下,一遍又一遍地定义相同的 template 片段可能会很麻烦。您可以通过在顶层定义片段并将它们作为 props 传递给 Story 来共享片段

{#snippet template(args)}
  <MyComponent {...args}>
    {#if args.simpleChild}
      <AChild data={args.childProps} />
    {:else}
      <ComplexChildA data={args.childProps} />
      <ComplexChildB data={args.childProps} />
    {/if}
  </MyComponent>
{/snippet}

<Story name="Simple Template" args={{ simpleChild: true }} {template} />

<Story name="Complex Template" args={{ simpleChild: false }} {template} />

您也可以使用此模式定义多个模板并在不同故事之间共享不同的模板。

默认片段

如果您只需要一个想要共享的模板,在每个 Story 组件中包含 {template} 可能会很繁琐。如下图所示

<Story name="Primary" args={{ variant: 'primary' }} {template} />
<Story name="Secondary" args={{ variant: 'secondary' }} {template} />
<Story name="Tertiary" args={{ variant: 'tertiary' }} {template} />
<!-- ... more ... -->
<Story name="Denary" args={{ variant: 'denary' }} {template} />

与常规 CSF 类似,您可以通过在 defineMeta 调用的 render 属性中引用您的默认片段来定义 meta 级别的 render 函数

<script module>
  import { defineMeta } from '@storybook/addon-svelte-csf';
  import MyComponent from './MyComponent.svelte';

  const { Story } = defineMeta({
    render: template,
    //      👆 the name of the snippet as defined below (can be any name)
  });
</script>

{#snippet template(args)}
  <MyComponent {...args}>
    {#if args.simpleChild}
      <AChild data={args.childProps} />
    {:else}
      <ComplexChildA data={args.childProps} />
      <ComplexChildB data={args.childProps} />
    {/if}
  </MyComponent>
{/snippet}

<Story name="Simple Children" args={{ simpleChild: true }} />

<Story name="Complex Children" args={{ simpleChild: false }} />

故事仍然可以使用任何定义故事级别内容的方法覆盖此默认片段。

[!注意] Svelte 有一个限制,即如果一个片段引用了非模块 <script> 中的任何声明(无论是直接引用还是通过其他片段间接引用),则不能从 <script module> 中引用该片段。参见 svelte.dev/docs/svelte/snippet#Exporting-snippets

自定义导出名称

在底层,每个 <Story /> 定义都会被编译成一个变量导出,例如 export const MyStory = ...;。在大多数情况下,您不必关心这个细节,但有时可能会因此产生命名冲突。变量名是故事名的简化版本,以使它们成为有效的 JavaScript 变量。

这可能导致冲突,例如,名称为 "my story!""My Story" 的两个故事都将被简化为 MyStory

您可以通过传递 exportName prop 来显式定义任何故事的变量名

<Story exportName="MyStory1" name="my story!" />
<Story exportName="MyStory2" name="My Story" />

必须向 Story 组件传递 nameexportName props 中的至少一个 - 同时传递两者也是有效的。

访问 Story context

如果出于某种原因您需要在渲染故事时访问Story context (例如用于模拟),则 <Story /> 的属性 template 片段提供了一个可选的第二个参数。

<Story name="Default">
  {#snippet template(args, context)}
   <!--                    👆 use the optional second argument to access Story context -->
     <MyComponent {...args}>
  {/snippet}
</Story>

TypeScript

故事模板片段在必要时可以是类型安全的。args 的类型是从传递给 defineMetacomponentrender 属性推断出来的。

如果您只是直接渲染组件而没有自定义模板,则可以使用 Svelte 的 ComponentProps 类型和来自插件的 StoryContext 使您的模板片段类型安全

<script module lang="ts">
  import { defineMeta, type StoryContext } from '@storybook/addon-svelte-csf';
  import { type ComponentProps } from 'svelte';

  import MyComponent from './MyComponent.svelte';

  const { Story } = defineMeta({
    component: MyComponent,
  });

  type Args = ComponentProps<MyComponent>;
</script>

{#snippet template(args: Args, context: StoryContext<typeof Layout>)}
  <MyComponent {...args} />
{/snippet}

如果您使用 render 属性定义可能使用自定义 args 的自定义模板,则 args 将从传递给 render 的片段的类型推断出来。当您将原始 args 转换为片段时,这特别有用

<script module lang="ts">
  import { defineMeta, type StoryContext } from '@storybook/addon-svelte-csf';
  import { type ComponentProps } from 'svelte';

  import MyComponent from './MyComponent.svelte';

  const { Story } = defineMeta({
    component: MyComponent,
    render: template, // 👈 args will be inferred from this, which is the Args type below
    argTypes: {
      children: {
        control: 'text',
      },
      footer: {
        control: 'text',
      },
    },
  });

  type Args = Omit<ComponentProps<MyComponent>, 'children' | 'footer'> & {
    children: string;
    footer?: string;
  };
  // OR use the Merge helper from the 'type-fest' package:
  type Args = Merge<
    ComponentProps<MyComponent>,
    {
      children: string;
      footer?: string;
    }
  >;
</script>

<!--                👇 you need to omit 'children' from args to satisfy Svelte's constraints -->
{#snippet template({ children, ...args }: Args, context: StoryContext<typeof MyComponent>)}
  <MyComponent {...args}>
    {children}
    {#snippet footer()}
      {args.footer}
    {/snippet}
  </MyComponent>
{/snippet}

请参阅Types.stories.svelte 示例,了解如何正确使用复杂类型。

旧版 API

此插件的第 5 版在关键领域更改了 v4 的 API,如上所述。但是,引入了一个功能标志以保持对基于 <Template> 的旧版 API 的支持,使其与 v5 之前版本保持一致。

要启用对旧版 API 的支持,请对您的主 Storybook 配置进行以下更改

export default {
  addons: [
-    '@storybook/addon-svelte-csf',
+    {
+      name: '@storybook/addon-svelte-csf',
+      options: {
+         legacyTemplate: true
+    },
    ...
  ],
  ...
}

这可能会使整体体验变慢,因为它在现有基础上增加了更多的转换步骤。应仅在迁移到新 API 期间临时使用它。该插件的未来主要版本很可能会放弃对旧版 API 的支持。

旧版支持并非万无一失,并且可能无法在以前适用的所有场景中工作。如果您在升级到 v5 后遇到问题或错误,请尝试将有问题的故事文件迁移到现代 API。

版本兼容性

最新版本

此插件的第 5 版及更高版本至少需要

依赖 版本
Storybook v8.0.0
Svelte v5.0.0
Vite v5.0.0
@sveltejs/vite-plugin-svelte v4.0.0

[!重要] 自 v5 起,此插件不支持 Webpack。

v4

npm install --save-dev @storybook/addon-svelte-csf@^4

此插件的第 4 版至少需要

  • Storybook v7
  • Svelte v4
  • Vite v4(如果使用 Vite)
  • @sveltejs/vite-plugin-svelte v2(如果使用 Vite)

v3

npm install --save-dev @storybook/addon-svelte-csf@^3

此插件的第 3 版至少需要

  • Storybook v7
  • Svelte v3

v2

npm install --save-dev @storybook/addon-svelte-csf@^2

如果您使用的 Storybook 版本在 v6.4.20 到 v7.0.0 之间,您应该使用此插件的 ^2.0.0 版本。

🤝 贡献

本项目使用 pnpm 进行依赖管理。

  1. 使用 pnpm install 安装依赖
  2. 使用 pnpm start 同时启动编译和内部 Storybook。
  3. 通常需要重启内部 Storybook 才能使更改生效。
贡献者
  • ndelangen
    ndelangen
  • shilman
    shilman
  • tmeasday
    tmeasday
  • ghengeveld
    ghengeveld
  • winkervsbecks
    winkervsbecks
  • yannbf
    yannbf
合作方
    Svelte
标签