storybook icon indicating copy to clipboard operation
storybook copied to clipboard

Dynamic code preview rendering for vue components

Open visini opened this issue 4 years ago • 24 comments

Recently storybook added support for dynamic rendering of code preview for vue components.

See this pull request: https://github.com/storybookjs/storybook/pull/12812

Instead of

(arg, { argTypes }) => ({
  components: { MyButton },
  props: Object.keys(argTypes),
  template: '<MyButton v-bind="$props" />',
})

... the code preview dynamically generates a code snippet (see example in pull request above). Example:

<template>
  <my-button rounded color="#f00">A Button with rounded edges</my-button>
</template>

See @storybook/vue demo: https://storybookjs.netlify.app/vue-kitchen-sink/?path=/docs/addon-controls--rounded (Storybook 6.1.11)

Screenshot 2020-12-17 at 18 01 23

Looking forward to support for this via @nuxtjs/storybook!

visini avatar Dec 17 '20 16:12 visini

Any chance you can make a PR about it?

atinux avatar Dec 17 '20 17:12 atinux

As I suspected, it's a more complex issue than I thought, and not simply related to @storybook/vue version.

Unfortunately I don't believe I'm the right person to take this on, my knowledge of Vue and Nuxt very limited and barely managed to track down the things that follow. I spent a few hours digging around in storybook docs vue source and manually tried to find which parts of the code are connected with this added functionality. Here's my investigation so far:

I cloned this repo and modified playground/components/MyButton.stories.js:

import MyButton from "./MyButton";
export default {
  title: "Button",
  component: MyButton,
  argTypes: {
    rounded: { control: "boolean" },
  },
};

export const Template = (arg, { argTypes }) => ({
  components: { MyButton },
  props: Object.keys(argTypes),
  template: '<MyButton v-bind="$props">123</MyButton>',
});

export const Primary = Template.bind({});
Primary.args = {
  label: "Primary",
  rounded: true,
};

Then, I investigated within sourceDecorator.js from storybook docs addon vue code – I saw console errors from this specific file:

See this specific file and code segment (error occurred in last line of highlighted portion): https://github.com/storybookjs/storybook/blob/b747c5c2396786682f36e5ac9b78ea990b6f952d/addons/docs/src/frameworks/vue/sourceDecorator.ts#L50-L56

Related error: Failed to generate dynamic story source: TypeError: null is not an object (evaluating 'storyNode._vnode') sourceDecorator — sourceDecorator.js:136

After I comment out dynamic story instance identification via vm, and manually use window.$nuxt..., it seems to be a step in the right direction. See here:

    var channel = _addons.addons.getChannel();
    // var storyComponent = getStoryComponent(story.options.STORYBOOK_WRAPS);
    // console.log("storyComponent", storyComponent())
    // var storyNode = lookupStoryInstance(vm, storyComponent);
    // console.log("storyNode", storyNode)
    var code = vnodeToString(window.$nuxt.$children[0].$children[0].$children[0].$vnode);
    console.log("code",  code)

In the console, I can see the logs of component stories being formatted with each change of args (e.g., toggle "rounded" via the toggle button in docs):

sourceDecorator.js:142:

code <MyButton rounded label="Primary">123</MyButton>

Initially, the formatted code displayed below the story stays the same:

Screenshot 2020-12-17 at 21 20 07

But then, after I change args a few times (e.g., toggle "rounded" via the toggle button in docs) twice, the formatted code is updated accordingly:

Screenshot 2020-12-17 at 21 20 12

Now, obviously these are nothing more than a few leads, but I'm afraid that I feel out of my depth. I'll be following this issue and am ready to reproduce or confirm any behavior, though.

Many thanks!

visini avatar Dec 17 '20 20:12 visini

Awesome work @visini, Thanks for the issue and investigation. This is definitely is a tough issue to solve. Unfortunately storybook does not have proper way to customize internals.
This might be solve by modifying modules internals without need to change sourceDecorator. If not we should write our own sourceDecorator with same logic you explained.

farnabaz avatar Dec 23 '20 11:12 farnabaz

Hello @farnabaz @Atinux , does the current storybook / nuxtjs support as suggested by @visini .

aacassandra avatar Jan 24 '21 15:01 aacassandra

hmm .. for a while I used the original storybook package and it worked. i hope it works on nuxt/storybook soon. thankyou friends

aacassandra avatar Jan 25 '21 11:01 aacassandra

Starting from [email protected], the sourceDecorator reuses an existing Vue node tree instead of new Vue-ing. Could you try it?

https://github.com/storybookjs/storybook/releases/tag/v6.2.0-beta.2

pocka avatar Mar 04 '21 01:03 pocka

https://github.com/storybookjs/storybook/pull/14002 is the PR for the change @pocka mentioned

philwolstenholme avatar Mar 04 '21 09:03 philwolstenholme

Thank you @pocka @philwolstenholme for mentioning the change.

Integrating 6.2 needs to consider some changes. I think we should wait for 6.2 official release to support it.

farnabaz avatar Mar 04 '21 15:03 farnabaz

@farnabaz I wonder if the release of 6.2 will help us with this issue 🤔

philwolstenholme avatar Apr 01 '21 13:04 philwolstenholme

In order to get it worked you need to define first argument for all your stories. This way storybook detects that your story depends on arguments and renders the code. @philwolstenholme

const SampleComponent = (args) => '<div>With Source</div>'

farnabaz avatar Apr 05 '21 07:04 farnabaz

Hi @farnabaz , I don't quite understand?

I am using 4.0.1 of @nuxtjs/storybook. This is an example story of mine:

import * as components from '~/.nuxt-storybook/components';

export default {
  title: 'atoms/Button',
  component: components.AtomsVaButton,
  argTypes: {
    iconPosition: {
      control: {
        type: 'select',
        options: ['left', 'right'],
      },
    },
  },
  args: {
    slotContent: 'Button slot content',
  },
};

const Template = (arg, { argTypes }) => ({
  props: Object.keys(argTypes),
  template: `<atoms-va-button v-bind="$props">{{ slotContent }}</atoms-va-button>`,
});

export const Default = Template.bind({});
Default.storyName = 'VaButton';

export const WithIcon = Template.bind({});
WithIcon.storyName = 'With icon';
WithIcon.args = {
  iconName: 'va/right-arrow',
};

export const WithIconOnRight = Template.bind({});
WithIconOnRight.storyName = 'With icon on right';
WithIconOnRight.args = {
  iconName: 'va/left-arrow',
  iconPosition: 'right',
};

and this is what appears in the Docs tab preview:

Note the missing component visual preview:

A Docs tab in Storybook with no visual preview of the component and showing Storybook specific code rather than the component code

And the code:

(arg, { argTypes }) => ({
  props: Object.keys(argTypes),
  template: `<atoms-va-button v-bind="$props">{{ slotContent }}</atoms-va-button>`,
})

philwolstenholme avatar Apr 07 '21 17:04 philwolstenholme

Have same issue with code preview

vaban-ru avatar Apr 08 '21 06:04 vaban-ru

Try using Template without bind. @philwolstenholme

export const Default = Template;

farnabaz avatar Apr 14 '21 14:04 farnabaz

Hmmm so if I try

const Template = (arg, { argTypes }) => ({
  props: Object.keys(argTypes),
  template: `<atoms-va-button v-bind="$props">{{ slotContent }}</atoms-va-button>`,
});

export const Default = Template;
Default.storyName = 'VaButton';

Then I get no preview in the Docs tab, and Storybook says no code is available either. The Canvas tab still works though:

Screenshot 2021-04-15 at 11 42 45 Screenshot 2021-04-15 at 11 41 55

philwolstenholme avatar Apr 15 '21 10:04 philwolstenholme

Conducted an experiment and found out the following.

I try:

import BaseButton from '@/components/base/BaseButton'

export default {
  title: 'Base/Buttons',
  component: BaseButton,
}

export const DefaultButton = (args) => ({
  components: { BaseButton },
  template: '<base-button>Кнопка</base-button>',
})

On docs page i see:

image

But, if i click on "Refresh controls" button, i see:

image

vaban-ru avatar Apr 15 '21 11:04 vaban-ru

I have the same problem as @philwolstenholme .

Why are you guys suggesting to not use Template.bind({}) anymore? This goes against writing DRY code :/ How would we tackle many stories of a component? Should we write a new template function every time?

Rigo-m avatar Apr 22 '21 15:04 Rigo-m

Any update of this issue guys?

vaban-ru avatar Jun 09 '21 07:06 vaban-ru

I also have the same issue, any update on how to fix it guys?

schristian30 avatar Jul 02 '21 10:07 schristian30

While this is not a final solution, I have been working around this issue by providing a source parameter:

Default.parameters = {
  docs: {
    source: {
      code: '<MyComponent />
    }
  }
}

See https://storybook.js.org/docs/vue/writing-docs/doc-blocks#docspage-1 for details

Note that this option will not reflect dynamic properties as it's static.

Decipher avatar Jul 04 '21 08:07 Decipher

While this is not a final solution, I have been working around this issue by providing a source parameter:

Default.parameters = {
  docs: {
    source: {
      code: '<MyComponent />
    }
  }
}

It will be legen "wait-wait" dary, legendary!

vaban-ru avatar Jul 05 '21 05:07 vaban-ru

While this is not a final solution, I have been working around this issue by providing a source parameter:

Default.parameters = {
  docs: {
    source: {
      code: '<MyComponent />
    }
  }
}

Still not fixed mine, for me it just show " <MyComponent /> " as code

schristian30 avatar Jul 05 '21 06:07 schristian30

@schristian30 Is your story exported as Default? This is just an example, for the correct documentation please refer to https://storybook.js.org/docs/vue/writing-docs/doc-blocks#docspage-1

Decipher avatar Jul 05 '21 06:07 Decipher

I've run into the same problems as highlighted above, where the 'code preview' shows correctly only after a change on the story (when hot reload is working), and otherwise it shows the content of the Template that is bound to the individual stories.

While searching for temporary solutions, I came across https://stackoverflow.com/a/66589490 which does get it working for me.

Using

  • nuxt: 2.15.7
  • @nuxtjs/storybook: 4.1.1

Hope this helps

petetrickey avatar Oct 28 '21 22:10 petetrickey

For anyone looking at this and using @nuxtjs/storybook updating the @nuxtjs/storybook package to 4.3.1 resolved the issue for me (updated from 4.2.0).

mrillusion avatar Apr 27 '22 01:04 mrillusion

v4 of this module is no longer actively supported. Please try the newest version and open an new issue if the problem persists. Thank you for your understanding.

tobiasdiez avatar May 01 '24 07:05 tobiasdiez