Storybook 正在显示代码中的所有内容

Storybook is displaying everything in Show Code

我正在使用 Vue 3 + Storybook。一切正常,除了当我单击“显示代码”时,它只显示所有内容而不仅仅是模板。我做错了什么?

这是我的故事:

import Button from './Button.vue';

export default {
  title: 'Components/Button',
  component: Button
};

const Template = (args) => ({
  // Components used in your story `template` are defined in the `components` object
  components: { Button },
  // The story's `args` need to be mapped into the template through the `setup()` method
  setup() {
    return { args };
  },
  // And then the `args` are bound to your component with `v-bind="args"`
  template: '<my-button v-bind="args" />',
});

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};

export const Large = Template.bind({});
Large.args = {
  size: 'large',
  label: 'Button',
};

export const Small = Template.bind({});
Small.args = {
  size: 'small',
  label: 'Button',
};

如下面的屏幕截图所示,它确实有效,正如您在 Vue 2 中所期望的那样。

但是,我使用 Vue 3 得到的结果与您相同。


简单的答案

它还没有为 Vue 3 实现。

正如您在 Storybook 文档插件的 source code 中看到的那样, Vue 3 框架有一个单独的实现。但是,Vue 3 实现缺少源装饰器,它生成源代码的呈现版本。

修补程序

如果您不想等到 Storybook 团队发布更新,您可以根据您的论点使用以下代码生成您自己的文档。请记住,这并未涵盖所有用例。

const stringifyArguments = (key, value) => {
    switch (typeof value) {
    case 'string':
        return `${key}="${value}"`;
    case 'boolean':
        return value ? key : '';
    default:
        return `:${key}="${value}"`;
    }
};

const generateSource = (templateSource, args) => {
    const stringifiedArguments = Object.keys(args)
    .map((key) => stringifyArguments(key, args[key]))
    .join(' ');

    return templateSource.replace('v-bind="args"', stringifiedArguments);
};

const template = '<my-button v-bind="args" />';

const Template = (args) => ({
    components: { MyButton },
    setup() {
    return { args };
    },
    template,
});

export const Primary = Template.bind({});
Primary.args = {
    primary: true,
    label: 'Button',
};

Primary.parameters = {
    docs: {
    source: { code: generateSource(template, Primary.args) },
    },
};

另一个临时解决方案是手动编写源代码,而不是自动生成。

Primary.parameters = {
  docs: {
    source: { code: '<my-button primary label="Button" />' },
  },
};

This is a known issue

一种可能的选择是使用我在上述 link 的 GH 问题中找到的当前解决方法。

.storybook 文件夹中创建文件 withSource.js,内容如下:

import { addons, makeDecorator } from "@storybook/addons";
import kebabCase from "lodash.kebabcase"
import { h, onMounted } from "vue";

// this value doesn't seem to be exported by addons-docs
export const SNIPPET_RENDERED = `storybook/docs/snippet-rendered`;

function templateSourceCode (
  templateSource,
  args,
  argTypes,
  replacing = 'v-bind="args"',
) {
  const componentArgs = {}
  for (const [k, t] of Object.entries(argTypes)) {
    const val = args[k]
    if (typeof val !== 'undefined' && t.table && t.table.category === 'props' && val !== t.defaultValue) {
      componentArgs[k] = val
    }
  }

  const propToSource = (key, val) => {
    const type = typeof val
    switch (type) {
      case "boolean":
        return val ? key : ""
      case "string":
        return `${key}="${val}"`
      default:
        return `:${key}="${val}"`
    }
  }

  return templateSource.replace(
    replacing,
    Object.keys(componentArgs)
      .map((key) => " " + propToSource(kebabCase(key), args[key]))
      .join(""),
  )
}

export const withSource = makeDecorator({
  name: "withSource",
  wrapper: (storyFn, context) => {
    const story = storyFn(context);

    // this returns a new component that computes the source code when mounted
    // and emits an events that is handled by addons-docs
    // this approach is based on the vue (2) implementation
    // see https://github.com/storybookjs/storybook/blob/next/addons/docs/src/frameworks/vue/sourceDecorator.ts
    return {
      components: {
        Story: story,
      },

      setup() {
        onMounted(() => {
          try {
            // get the story source
            const src = context.originalStoryFn().template;
            
            // generate the source code based on the current args
            const code = templateSourceCode(
              src,
              context.args,
              context.argTypes
            );

            const channel = addons.getChannel();

            const emitFormattedTemplate = async () => {
              const prettier = await import("prettier/standalone");
              const prettierHtml = await import("prettier/parser-html");

              // emits an event  when the transformation is completed
              channel.emit(
                SNIPPET_RENDERED,
                (context || {}).id,
                prettier.format(`<template>${code}</template>`, {
                  parser: "vue",
                  plugins: [prettierHtml],
                  htmlWhitespaceSensitivity: "ignore",
                })
              );
            };

            setTimeout(emitFormattedTemplate, 0);
          } catch (e) {
            console.warn("Failed to render code", e);
          }
        });

        return () => h(story);
      },
    };
  },
});

然后将这个装饰器添加到preview.js:

import { withSource } from './withSource'

...

export const decorators = [
  withSource
]

Author of the solution