Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Storybook is displaying everything in Show Code

I am using Vue 3 + Storybook. Everything is working fine, except when I click "Show Code", it just shows everything instead of just the template.. what am I doing wrong?

enter image description here

Here is my story:

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',
};
like image 498
R-b-n Avatar asked Mar 08 '21 11:03

R-b-n


2 Answers

As outlined in the screenshot below, it does work, as you would expect in Vue 2.

Vue 2 storybook

However, I'm getting the same results as you with Vue 3.

Vue 3 storybook


The simple answer

It's not implemented for Vue 3 yet.

As you can see in the source code of the docs add-on for Storybook, there is a separate implementation for the Vue 3 framework. However, the Vue 3 implementation lacks the source decorator, which generates a rendered version of the source code.

Hotfix

If you don't want to wait until the Storybook team has released an update, you can use the following code to generate your own docs, based on your arguments. Keep in mind that this does not cover all use cases.

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

Another temporary solution is to manually write the source code, instead of having it automatically generated.

Primary.parameters = {
  docs: {
    source: { code: '<my-button primary label="Button" />' },
  },
};
like image 184
Keimeno Avatar answered Oct 20 '22 13:10

Keimeno


This is a known issue

One of possible options is to use current workaround that I found in the GH issue by the link above.

Create file withSource.js in the .storybook folder with following content:

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

And then add this decorator to preview.js:

import { withSource } from './withSource'

...

export const decorators = [
  withSource
]

Author of the solution

like image 1
Aleksandr K. Avatar answered Oct 20 '22 12:10

Aleksandr K.