Skip to content

Conversation

dogoku
Copy link
Contributor

@dogoku dogoku commented Jun 7, 2025

Adds support for showing story source code - Fixes #5

Warning

This is still very much a WIP

Changes

  • Updated render.ts (and my-slotted stories, e2e test) to support multiple items for the same slot
  • Updated example .storybook/preview.ts to enable autodocs and codepanel
  • Introduced sourceDecorator to create source code from the stories VNodes
    • This has been for the most part AI generated by following examples from storybook's built-in renderers (e.g Vue)
    • I have tried to support most of things i can think of (primitives, objects, functions), but we need much more testing
    • The decorator only works when our render function is driven by attributes
      • what that means is that fully custom render functions, will return only the component e.g <my-comp />
      • use parameters.docs.source.type = code to instead show the complete story source code (example below)
    • Using prettier/standalone to format the generated string in the browser
    • Introduce preview-docs.ts to enable decorator by default
  • Introduced automatic argTypes via custom-elements-manifest (CEM)
    • introduced setCustomElementsManifest method to laod CEM
    • based on @storybook/web-components, improved and adjusted for stencil
    • updated example stencil config to use @custom-elements-manifest/analyzer
    • updated my-advanced component to have more variety of props, methods, etc

Screenshots

Autodocs with Autogenerated Attributes from CEM

image

Attribute based source

image

Custom renderer source (for more complex demos)

image

dogoku added 2 commits June 7, 2025 16:58
- introduce sourceDecorator to support dynamic story source rendering
- Introduce preview-docs.ts to enable decorator by default
- Updated example to enable autodocs and codepanel

fixes stenciljs#5
@dogoku dogoku force-pushed the feat/source-decorator branch from f3f2211 to 9f682e4 Compare June 7, 2025 14:57
@dogoku dogoku changed the title [WIP] feat(plugin): add sourceDecorator for Stencil components [WIP] feat(plugin): add sourceDecorator and argTypes for Stencil components Jun 9, 2025
- introduced `setCustomElementsManifest` method to laod CEM
- based on @storybook/web-components, improved and adjusted for stencil
- updated example stencil config to use @custom-elements-manifest/analyzer
- updated my-advanced component to have more variety of props, methods, etc
@dogoku dogoku force-pushed the feat/source-decorator branch from 682486b to b4d5c64 Compare June 9, 2025 01:49
@dogoku dogoku changed the title [WIP] feat(plugin): add sourceDecorator and argTypes for Stencil components [WIP] feat(plugin): add sourceDecorator and auto argTypes Jun 9, 2025
Copy link
Member

@christian-bromann christian-bromann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would love to see some e2e tests that the component code is actually rendered correctly in Storybook. Can we add that to this PR?

@Component({
tag: 'my-advanced',
styleUrl: 'my-advanced.css',
shadow: false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we set scoped: true instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually on purpose. i wanted to test that it works for shadowless components and since we already had 2 shadow components, I made this one as false.
Are there any concerns?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scoped: true creates a shadow less component. My concern with this is that this may create a non-scoped/non-shadow component which we may deprecate in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern with this is that this may create a non-scoped/non-shadow component which we may deprecate in the future.

My whole library is like that T_T (but I guess that's my problem)
Ok fair enough, i'll change it to scoped: true

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting shadow: false might be the same as scoped: true, not sure

Comment on lines +21 to +60
if (parameters.slots) {
Object.entries(parameters.slots).forEach(([key, value]) => {
// if the parameter key is 'default' don't give it a slot name so it renders just as a child
const slot = key === 'default' ? undefined : key;

// Handle array of values for the same slot
const values = Array.isArray(value) ? value : [value];

values.forEach(item => {
if (item === undefined || item === null) return;

if (typeof item === 'string') {
// For strings, create a vnode with the string as the children
children.push(h('span', { slot }, item));
} else if (Array.isArray(item)) {
// Handle nested arrays (flatten them)
item.forEach(nestedItem => {
if (nestedItem !== undefined && nestedItem !== null) {
children.push({
...nestedItem,
$attrs$: {
...(nestedItem.$attrs$ || {}),
slot,
},
});
}
});
} else {
// For VNodes or other objects
children.push({
...item,
$attrs$: {
...(item.$attrs$ || {}),
slot,
},
});
}
});
});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (parameters.slots) {
Object.entries(parameters.slots).forEach(([key, value]) => {
// if the parameter key is 'default' don't give it a slot name so it renders just as a child
const slot = key === 'default' ? undefined : key;
// Handle array of values for the same slot
const values = Array.isArray(value) ? value : [value];
values.forEach(item => {
if (item === undefined || item === null) return;
if (typeof item === 'string') {
// For strings, create a vnode with the string as the children
children.push(h('span', { slot }, item));
} else if (Array.isArray(item)) {
// Handle nested arrays (flatten them)
item.forEach(nestedItem => {
if (nestedItem !== undefined && nestedItem !== null) {
children.push({
...nestedItem,
$attrs$: {
...(nestedItem.$attrs$ || {}),
slot,
},
});
}
});
} else {
// For VNodes or other objects
children.push({
...item,
$attrs$: {
...(item.$attrs$ || {}),
slot,
},
});
}
});
});
}
Object.entries(parameters.slots || {}).forEach(([key, value]) => {
// if the parameter key is 'default' don't give it a slot name so it renders just as a child
const slot = key === 'default' ? undefined : key;
// Handle array of values for the same slot
const values = Array.isArray(value) ? value : [value];
values.forEach(item => {
if (item === undefined || item === null) return;
if (typeof item === 'string') {
// For strings, create a vnode with the string as the children
children.push(h('span', { slot }, item));
} else if (Array.isArray(item)) {
// Handle nested arrays (flatten them)
item.forEach(nestedItem => {
if (nestedItem !== undefined && nestedItem !== null) {
children.push({
...nestedItem,
$attrs$: {
...(nestedItem.$attrs$ || {}),
slot,
},
});
}
});
} else {
// For VNodes or other objects
children.push({
...item,
$attrs$: {
...(item.$attrs$ || {}),
slot,
},
});
}
});
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this one, no issue with your suggestion, but I also noticed that you just merged a PR that also changes this.
I handle a slightly different scenario, but I was wondering if it's worth getting David's opinion on my changes.
@davidpett thoughts? :)

@davidpett
Copy link
Contributor

@dogoku in our current use of storybook and stencil we are using this addon to get the docs and controls: https://github.com/RocketCommunicationsInc/storybook-addon-docs-stencil#readme I think it might be a good idea to see how they are documenting things. I just tried adding it to the example app here and it partially works for the docs.

@dogoku
Copy link
Contributor Author

dogoku commented Jun 9, 2025

@dogoku in our current use of storybook and stencil we are using this addon to get the docs and controls: https://github.com/RocketCommunicationsInc/storybook-addon-docs-stencil#readme I think it might be a good idea to see how they are documenting things. I just tried adding it to the example app here and it partially works for the docs.

@davidpett oh cool, i didn't know this existed. they are using stencil's docs-json, but i am using CEM. I wanted to use docs-json, but since web-components did the heavy lifting, I just followed their logic and adapted it to work for stencil.

Also I noticed that what you shared is a fork, that hasn't been updated for a long time.
The main repo seems alive: https://github.com/pixtron/storybook-addon-docs-stencil

That main repo also has a custom stencil renderer function, which is also something we have. So I am seeing a significant overlap between our 2 endeavors.

I guess there is a decision to be made here

  • I am ok to just remove my changes for argTypes and tell people to use their plugin
  • and/or we can keep my solution as an alternative for people using CEM, but perhaps not turned on by default?
  • or we ask them to collaborate and incorporate everything into one community plugin

@christian-bromann i defer to you

@christian-bromann
Copy link
Member

@dogoku thanks for providing your insights here!

  • I am ok to just remove my changes for argTypes and tell people to use their plugin

I personally prefer to have this plugin contain all required primitives to have a great Storybook integration. Having users to add another (not by Stencils community maintained) plugin may create friction in the future. Is there a way we can keep your suggested CEM approach while allowing users to choose a different plugin if desired?

  • and/or we can keep my solution as an alternative for people using CEM, but perhaps not turned on by default?

I think this is a good idea, thoughts @davidpett ?

  • or we ask them to collaborate and incorporate everything into one community plugin

I think this should be always an option.

@davidpett
Copy link
Contributor

@christian-bromann having an integrated solution is a great idea and I would love to not have to reach out to an additional addon for this functionality. I did do a quick comparison between the docs-json output target and the CEM output and although very similar, docs-json is a little more verbose and handles complex types a little better. I wonder since it is an official stencil output target if we should rely on it instead of CEM as the default?

@christian-bromann
Copy link
Member

I wonder since it is an official stencil output target if we should rely on it instead of CEM as the default?

The docs-json is an official Stencil Output Target and part of the core project.

@dogoku
Copy link
Contributor Author

dogoku commented Jun 12, 2025

@christian-bromann @davidpett
I will be closing this PR since the main issue i was trying to solve seems to have been resolved, just not as I intended.
I will reopen, after rebasing and cleaning up, following David's language: 'jsx' suggestion.
(p.s sorry for the double tag, i accidentally posted the comment with my work github acc)

@dogoku dogoku closed this Jun 12, 2025
@dogoku dogoku deleted the feat/source-decorator branch June 12, 2025 13:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Story Source

3 participants