Closed ivanRemotely closed 1 year ago
Am I missing something here?
Using a story definition of:
export const Primary = { args: { primary: true, label: 'Button', }, render: (args) => { return { components: { MyButton }, setup() { return { args }; }, template: '<my-button v-bind="args" />', } }, };
results in:
I expected to see some nicely rendered vue3 code like this:
Same problem here. Our stories use a simliar format to be able to use the Auto Events Addon for Storybook (https://storybook.js.org/addons/storybook-auto-events)
Our story looks like this:
const storyMeta = {
component: MyComponent,
title: "common/comment/components/MyComponent",
parameters: {
docs: {
description: {
component: "Foo"
},
},
},
render: (args, { events }) => ({
// events --> https://storybook.js.org/addons/storybook-auto-events
components: { MyComponent},
setup() {
return { args, events };
},
template: `
<my-component v-bind="args" v-on="events"></my-component>
`,
}),
} satisfies Meta<typeof MyCComponent>;
When trying to display the source code, we get only the args definition and not the HTML code.
Using Storybook 7.6.17. I hope an upgrade to Storybook 8 helps us.
(we use this set for our UI library, and all works pretty well).
preview.js
import { vue3SourceDecorator } from "./decorators/vue3SourceDecorator";
export default {
decorators: [vue3SourceDecorator],
//... (other config)
};
vue3SourceDecorator.js
import { addons, makeDecorator } from "@storybook/preview-api";
import { h, onMounted, watch } from "vue";
export const vue3SourceDecorator = makeDecorator({
name: "vue3SourceDecorator",
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
// watch args and re-emit on change
return {
components: { story },
setup() {
onMounted(() => {
setSourceCode();
});
watch(context.args, () => {
setSourceCode();
});
function setSourceCode() {
try {
const src = context.originalStoryFn(context.args).template;
const code = templateSourceCode(src, context.args, context.argTypes);
const channel = addons.getChannel();
const emitFormattedTemplate = async () => {
const prettier = await import("prettier2");
const prettierHtml = await import("prettier2/parser-html");
const formattedCode = prettier.format(code, {
parser: "html",
plugins: [prettierHtml],
htmlWhitespaceSensitivity: "ignore",
});
// emits an event when the transformation is completed
channel.emit("storybook/docs/snippet-rendered", {
id: context.id,
args: context.args,
source: formattedCode,
});
};
emitFormattedTemplate();
} catch (e) {
// eslint-disable-next-line no-console
console.warn("Failed to render code", e);
}
}
return () => h("div", { style: `padding: 2rem 1.5rem 3rem 1.5rem;` }, [h(story)]);
},
};
},
});
function templateSourceCode(templateSource, args, argTypes) {
const componentArgs = {};
for (const [key, val] of Object.entries(argTypes)) {
const value = args[key];
if (
typeof val !== "undefined" &&
val.table &&
val.table.category === "props" &&
value !== val.defaultValue
) {
componentArgs[key] = val;
}
}
const slotTemplateCode =
// eslint-disable-next-line vue/max-len
'<template v-for="(slot, index) of slots" :key="index" v-slot:[slot]><template v-if="args[slot]">{{ args[slot] }}</template></template>';
const templateDefaultRegEx = /<template #default>([\s\S]*?)<\/template>/g;
return templateSource
.replace(/>[\s]+</g, "><")
.trim()
.replace(slotTemplateCode, "")
.replace(templateDefaultRegEx, "$1")
.replace(
'v-bind="args"',
Object.keys(componentArgs)
.map((key) => " " + propToSource(kebabCase(key), args[key]))
.join(""),
);
}
function propToSource(key, val) {
const type = typeof val;
switch (type) {
case "boolean":
return val ? key : "";
case "string":
return `${key}="${val}"`;
default:
return `:${key}="${val}"`;
}
}
function kebabCase(str) {
return str
.split("")
.map((letter, idx) => {
return letter.toUpperCase() === letter
? `${idx !== 0 ? "-" : ""}${letter.toLowerCase()}`
: letter;
})
.join("");
}
Example of the story: button.stories.js
import { getArgTypes, getSlotNames } from "vueless/service.storybook";
import UButton from "vueless/ui.button";
import UIcon from "vueless/ui.image-icon";
import URow from "vueless/ui.container-row";
import UGroup from "vueless/ui.container-group";
export default {
id: "1010",
title: "Buttons & Links / Button",
component: UButton,
args: {
text: "Button",
},
argTypes: {
...getArgTypes(UButton.name),
},
};
const DefaultTemplate = (args) => ({
components: { UButton },
setup() {
const slots = getSlotNames(UButton.name);
return { args, slots };
},
template: `
<UButton v-bind="args">
<template v-for="(slot, index) of slots" :key="index" v-slot:[slot]>
<template v-if="args[slot]">{{ args[slot] }}</template>
</template>
</UButton>
`,
});
const SlotTemplate = (args) => ({
components: { UButton, UIcon },
setup() {
return { args };
},
template: `
<UButton v-bind="args">
${args.slotTemplate}
</UButton>
`,
});
const VariantsTemplate = (args, { argTypes } = {}) => ({
components: { UButton, URow },
setup() {
return {
args,
variants: argTypes.variant.options,
};
},
template: `
<URow>
<UButton
v-for="(variant, index) in variants"
v-bind="args"
:variant="variant"
:text="variant"
:key="index"
/>
</URow>
`,
});
export const Default = DefaultTemplate.bind({});
Default.args = {};
export const variants = VariantsTemplate.bind({});
variants.args = {};
export const slotDefault = SlotTemplate.bind({});
slotDefault.args = {
slotTemplate: `
<template #default>
🤘🤘🤘
</template>
`,
};
Describe the bug When clicking the "View Source" option in the docs tab, the presented code is the template for the story as opposed to the generated Vue code. (See screenshot below)
To Reproduce Steps to reproduce the behavior:
The issue is present in the vue3 cli example, so you can:
Expected behavior The presented panel should show usable Vue code.
Screenshots
System
Environment Info:
System: OS: macOS 10.15.6 CPU: (8) x64 Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz Binaries: Node: 12.13.0 - ~/.nvm/versions/node/v12.13.0/bin/node npm: 6.12.0 - ~/.nvm/versions/node/v12.13.0/bin/npm Browsers: Chrome: 88.0.4324.150 Firefox: 85.0.2 Safari: 13.1.2 npmPackages: @storybook/addon-actions: 6.2.0-alpha.28 => 6.2.0-alpha.28 @storybook/addon-essentials: 6.2.0-alpha.28 => 6.2.0-alpha.28 @storybook/addon-links: 6.2.0-alpha.28 => 6.2.0-alpha.28 @storybook/addon-storyshots: 6.2.0-alpha.28 => 6.2.0-alpha.28 @storybook/vue3: 6.2.0-alpha.28 => 6.2.0-alpha.28 npmGlobalPackages: @storybook/cli: 6.2.0-alpha.24
Additional context I understand Vue 3 support is still a WIP. I'd be more than happy to help fix the issue if you can point me in the general direction of the problem. Thank you for you time and help!