Search code examples
vue.jsstorybook

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


Solution

  • 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