Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Continuation of experimental support for Svelte v5 #1

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.svelte-kit/
dist/
node_modules/
storybook-static/
Expand Down
6 changes: 0 additions & 6 deletions src/components/Meta.svelte

This file was deleted.

5 changes: 2 additions & 3 deletions src/components/RenderContext.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@
.map(([k, v]) => [v.action, args[k]])
);

$inspect(events, args, storyContext);

$effect(() => setStoryRenderContext(args, storyContext))
$inspect(events, args, storyContext).with(console.trace);

$effect(() => setStoryRenderContext(args, storyContext));
</script>

<svelte:component this={Stories} {...events} />
21 changes: 11 additions & 10 deletions src/components/Story.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,21 @@

const context = useContext();

export let name;
export let template = null;
export let play = null;
const { children, name, template = null, play = null, ...restProps } = $props();

if (!name) {
throw new Error('Missing Story name');
}

context.register({
name,
...$$restProps,
...restProps,
play,
template: template != null ? template : !$$slots.default ? 'default' : null,
template: template != null ? template : !children ? 'default' : null,
});

$: render = context.render && !context.templateName && context.storyName == name;
const render = $derived(context.render && !context.templateName && context.storyName === name);

const ctx = getStoryRenderContext();
const args = ctx.argsStore;
const storyContext = ctx.storyContextStore;
Expand All @@ -29,11 +28,13 @@
}
}

$: if (render) {
injectIntoPlayFunction($storyContext, play);
}
$effect(() => {
if (render) {
injectIntoPlayFunction($storyContext, play);
}
});
</script>

{#if render}
<slot {...$args} context={$storyContext} args={$args} />
{@render children({ ...$args, context: $storyContext })}
{/if}
7 changes: 4 additions & 3 deletions src/components/Template.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@

const context = useContext();

export let id = 'default';
const { children, id = 'default' } = $props();

context.register({ id, isTemplate: true });

$: render = context.render && context.templateId === id;
const render = $derived(context.render && context.templateId === id);

const ctx = getStoryRenderContext();
const args = ctx.argsStore;
const storyContext = ctx.storyContextStore;
</script>

{#if render}
<slot {...$args} context={$storyContext} args={$args} />
{@render children({ ...$args, context: $storyContext })}
{/if}
25 changes: 15 additions & 10 deletions src/components/__tests__/TestStories.svelte
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
<script>
import Meta from '../Meta.svelte';
import Story from '../Story.svelte';
import Template from '../Template.svelte';
<script lang="ts" context="module">
import { type Meta } from '@storybook/svelte';

export const meta = {
title: 'Test',
} satisfies Meta;
</script>

<Meta title="Test"/>
<script>
import Story from '../Story.svelte';
import Template from '../Template.svelte';
</script>

<Template id="tpl1">
<div>tpl1</div>
<div>tpl1</div>
</Template>

<Template id="tpl2">
<div>tpl2</div>
<div>tpl2</div>
</Template>

<Story name="Story1" template="tpl1" args={{ tpl1:true }}/>
<Story name="Story2" template="tpl2" source args={{ tpl1:true }}/>
<Story name="Story1" template="tpl1" args={{ tpl1: true }} />
<Story name="Story2" template="tpl2" source args={{ tpl1: true }} />
<Story name="Story3" source="xyz">
<div>story3</div>
<div>story3</div>
</Story>
118 changes: 47 additions & 71 deletions src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,86 +1,62 @@
import type { SvelteComponent } from 'svelte'
import type { Addon_BaseMeta as BaseMeta, Addon_BaseAnnotations as BaseAnnotations, StoryContext, WebRenderer } from '@storybook/types';
import type { SvelteComponent } from 'svelte';
import type {
Addon_BaseAnnotations as BaseAnnotations,
StoryContext,
WebRenderer,
} from '@storybook/types';


type DecoratorReturnType = void | SvelteComponent | {
Component: any,
props?: any
}
type DecoratorReturnType =
| void
| SvelteComponent
| {
Component: any;
props?: any;
};

interface StoryProps extends BaseAnnotations<any, DecoratorReturnType, WebRenderer> {
/**
* Id of the story.
*
* Optional, auto-generated from name if not specified.
*/
id?: string;
/**
* Name of the story.
*/
name: string;
/**
* Id of the template used by this story.
*
* Optional. Used if the story has no body.
* If not specified, use the 'default' template.
*/
template?: string;
/**
* Specify which sources should be shown.
*
* By default, sources for an args story are auto-generated.
* If source is true, then the source of the story will be used instead.
* If source is a string, it replaces the source of the story.
*/
source?: boolean | string
/**
* Id of the story.
*
* Optional, auto-generated from name if not specified.
*/
id?: string;
/**
* Name of the story.
*/
name: string;
/**
* Id of the template used by this story.
*
* Optional. Used if the story has no body.
* If not specified, use the 'default' template.
*/
template?: string;
/**
* Specify which sources should be shown.
*
* By default, sources for an args story are auto-generated.
* If source is true, then the source of the story will be used instead.
* If source is a string, it replaces the source of the story.
*/
source?: boolean | string;
}

interface TemplateProps extends BaseAnnotations<any, DecoratorReturnType> {
/**
* Id of the template.
*
* Optional. Use 'default' if not specified.
*/
id?: string;
}

interface MetaProps extends BaseMeta<any>, BaseAnnotations<any, DecoratorReturnType> {
/**
* Enable the tag 'autodocs'.
*
* @see [Automatic documentation](https://storybook.js.org/docs/svelte/writing-docs/autodocs)
*/
autodocs?: boolean;
/**
* List of tags to add to the stories.
*
* It should be a static array of strings.
*
* @example tags={['autodocs']}
*/
tags?: string[];
/**
* Id of the template.
*
* Optional. Use 'default' if not specified.
*/
id?: string;
}

interface Slots {
default: {
args: any;
context: StoryContext;
[key: string]: any;
}
}
/**
* Meta.
*
* @deprecated Use `export const meta`. See https://github.com/storybookjs/addon-svelte-csf for an example
*/
export class Meta extends SvelteComponent<MetaProps> { }
/**
* Story.
*/
export class Story extends SvelteComponent<StoryProps, any, Slots> { }
export class Story extends SvelteComponent<StoryProps, any, Slots> {}
/**
* Template.
*
*
* Allow to reuse definition between stories.
*/
export class Template extends SvelteComponent<TemplateProps, any, Slots> { }
export class Template extends SvelteComponent<TemplateProps, any, Slots> {}
1 change: 0 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export { default as Meta } from './components/Meta.svelte';
export { default as Story } from './components/Story.svelte';
export { default as Template } from './components/Template.svelte';

Expand Down
45 changes: 14 additions & 31 deletions src/parser/collect-stories.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
/* eslint-env browser */

import RenderContext from '../components/RenderContext.svelte';
import { logger } from '@storybook/client-logger';
import { combineParameters } from '@storybook/preview-api';
import type { Meta } from '@storybook/svelte';
import { mount, unmount } from 'svelte';

import type { StoriesDef, Story } from './types.js';
import { extractId } from './extract-id.js';
import { logger } from '@storybook/client-logger';
import type { Meta, StoriesDef, Story } from './types.js';
import { type SvelteComponent } from 'svelte';

import RenderContext from '../components/RenderContext.svelte';
import RegisterContext from '../components/RegisterContext.svelte';
import { asClassComponent } from 'svelte/legacy';

/* Called from a webpack loader and a jest transformation.
*
Expand All @@ -24,47 +25,29 @@ const createFragment = document.createDocumentFragment
? () => document.createDocumentFragment()
: () => document.createElement('div');

export default (
StoriesComponent: SvelteComponent,
{ stories = {}, meta: parsedMeta = {}, allocatedIds = [] }: StoriesDef,
exportedMeta = undefined
export default <Component extends Parameters<typeof mount>[0]>(
StoriesComponent: Component,
{ stories = {}, meta = {}, allocatedIds = [] }: StoriesDef
) => {
const repositories = {
meta: null as Meta | null,
meta: null as Meta<Component> | null,
stories: [] as Story[],
};

// extract all stories
try {
const context = new (asClassComponent(RegisterContext))({
const context = mount(RegisterContext, {
target: createFragment() as Element,
props: {
Stories: StoriesComponent,
repositories: () => repositories,
},
});
context.$destroy();
unmount(context);
} catch (e: any) {
logger.error(`Error extracting stories ${e.toString()}`, e);
}

const meta = exportedMeta || repositories.meta;
if (!meta) {
logger.error('Missing module meta export or <Meta/> tag');
return {};
}

// Inject description extracted from static analysis.
if (parsedMeta.description && !meta.parameters?.docs?.description?.component) {
meta.parameters = combineParameters(meta.parameters, {
docs: {
description: {
component: parsedMeta.description,
},
},
});
}

const { component: globalComponent } = meta;

// collect templates id
Expand All @@ -79,7 +62,7 @@ export default (

if (duplicateTemplatesId.length > 0) {
logger.warn(
`Found duplicates templates id for stories '${meta.name}': ${duplicateTemplatesId}`
`Found duplicates templates id for stories '${meta.component?.name}': ${duplicateTemplatesId}`
);
}

Expand Down
27 changes: 0 additions & 27 deletions src/parser/extract-stories.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,33 +423,6 @@ describe('extractSource', () => {
}
`);
});
test('Meta tag description', () => {
expect(
extractStories(`
<script>
import { Story } from '@storybook/svelte';
import Button from './Button.svelte';
</script>

<!-- Meta Description -->
<Meta title="a title"/>
`)
).toMatchInlineSnapshot(`
{
"allocatedIds": [
"default",
"Story",
"Button",
],
"meta": {
"description": "Meta Description",
"id": undefined,
"title": "a title",
},
"stories": {},
}
`);
});
test('With description', () => {
expect(
extractStories(`
Expand Down
Loading