Skip to content

Commit

Permalink
Native Svelte Story Format
Browse files Browse the repository at this point in the history
  • Loading branch information
j3rem1e committed Jan 29, 2021
1 parent e9cb044 commit a178eea
Show file tree
Hide file tree
Showing 51 changed files with 952 additions and 11 deletions.
1 change: 1 addition & 0 deletions addons/docs/src/frameworks/svelte/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Configuration } from 'webpack';
export function webpackFinal(webpackConfig: Configuration, options: any = {}) {
webpackConfig.module.rules.push({
test: /\.svelte$/,
exclude: /\.stories\.svelte$/,
loader: path.resolve(`${__dirname}/svelte-docgen-loader`),
enforce: 'post',
});
Expand Down
23 changes: 23 additions & 0 deletions app/svelte/jest-transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const svelte = require('svelte/compiler');

const parser = require.resolve('./src/client/parse-stories').replace(/[/\\]/g, '/');

function process(src, filename) {
const result = svelte.compile(src, {
format: 'cjs',
filename,
});

const code = result.js ? result.js.code : result.code;

const z = {
code: `${code}
const { default: parser } = require('${parser}');
module.exports = parser(module.exports, {});
Object.defineProperty(exports, "__esModule", { value: true });`,
map: result.js ? result.js.map : result.map,
};
return z;
}

exports.process = process;
2 changes: 2 additions & 0 deletions app/svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
},
"dependencies": {
"@storybook/addons": "6.2.0-alpha.19",
"@storybook/client-api": "6.2.0-alpha.19",
"@storybook/client-logger": "6.2.0-alpha.19",
"@storybook/core": "6.2.0-alpha.19",
"core-js": "^3.8.2",
"global": "^4.4.0",
Expand Down
6 changes: 6 additions & 0 deletions app/svelte/src/client/components/Meta.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<script>
import { useContext } from './context';
useContext().meta = $$props;
</script>

11 changes: 11 additions & 0 deletions app/svelte/src/client/components/RegisterContext.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script>
import { createRegistrationContext } from './context';
export let Stories;
export let repositories;
createRegistrationContext(repositories);
</script>

<svelte:component this={Stories} />
13 changes: 13 additions & 0 deletions app/svelte/src/client/components/RenderContext.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script>
/**
* @component
* @wrapper
*/
import { createRenderContext } from './context';
export let Stories;
createRenderContext($$props);
</script>

<svelte:component this={Stories}/>
24 changes: 24 additions & 0 deletions app/svelte/src/client/components/Story.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script>
import { useContext } from './context';
const context = useContext();
export let name;
export let template;
if (!name) {
throw new Error('Missing Story name');
}
context.register({
name,
...$$restProps,
template: template != null ? template : !$$slots.default ? 'default' : null,
});
$: render = context.render && !context.templateName && context.storyName == name;
</script>

{#if render}
<slot args={context.args} {...context.args}/>
{/if}
16 changes: 16 additions & 0 deletions app/svelte/src/client/components/Template.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script>
import { useContext } from './context';
const context = useContext();
export let name = 'default';
context.register({name, isTemplate: true});
$: render = context.render && context.templateName === name;
</script>

{#if render}
<slot args={context.args} {...context.args}/>
{/if}

21 changes: 21 additions & 0 deletions app/svelte/src/client/components/__tests__/TestStories.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script>
import Meta from '../Meta.svelte';
import Story from '../Story.svelte';
import Template from '../Template.svelte';
</script>

<Meta title="Test"/>

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

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

<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>
</Story>
34 changes: 34 additions & 0 deletions app/svelte/src/client/components/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { getContext, hasContext, setContext } from 'svelte';

const CONTEXT_KEY = 'storybook-registration-context';

export function createRenderContext(props: any = {}) {
setContext(CONTEXT_KEY, {
render: true,
register: () => {},
meta: {},
args: {},
...props,
});
}

export function createRegistrationContext(repositories: any) {
setContext(CONTEXT_KEY, {
render: false,
register: (story: any) => {
repositories.stories.push(story);
},
set meta(value: any) {
// eslint-disable-next-line no-param-reassign
repositories.meta = value;
},
args: {},
});
}

export function useContext() {
if (!hasContext(CONTEXT_KEY)) {
createRenderContext();
}
return getContext(CONTEXT_KEY);
}
5 changes: 5 additions & 0 deletions app/svelte/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ export {
raw,
} from './preview';

export { default as Meta } from './components/Meta.svelte';
export { default as Story } from './components/Story.svelte';
export { default as Template } from './components/Template.svelte';
export { useContext } from './components/context';

if (module && module.hot && module.hot.decline) {
module.hot.decline();
}
111 changes: 111 additions & 0 deletions app/svelte/src/client/parse-stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/* eslint-env browser */
import { logger } from '@storybook/client-logger';
import { combineParameters } from '@storybook/client-api';

import RegisterContext from './components/RegisterContext.svelte';
import RenderContext from './components/RenderContext.svelte';

/* Called from a webpack loader and a jest transformation.
*
* It mounts a Stories component in a context which disables
* the rendering of every <Story/> and <Template/> but instead
* collects names and properties.
*
* For every discovered <Story/>, it creates a storyFun which
* instanciate the main Stories component: Every Story but
* the one selected is disabled.
*/

const createFragment = document.createDocumentFragment
? () => document.createDocumentFragment()
: () => document.createElement('div');

export default (module, sources) => {
const { default: Stories } = module;

const repositories = {
meta: null,
stories: [],
};

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

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

const { component: globalComponent } = meta;

// collect templates id
const templatesId = repositories.stories
.filter((story) => story.isTemplate)
.map((story) => story.name);

// check for duplicate templates
const duplicateTemplatesId = templatesId.filter(
(item, index) => templatesId.indexOf(item) !== index
);

if (duplicateTemplatesId.length > 0) {
logger.warn(`Found duplicates templates id :${duplicateTemplatesId}`);
}

const found = repositories.stories
.filter((story) => !story.isTemplate)
.map((story) => {
const { name, template, component, source = false, ...props } = story;

const unknowTemplate = template != null && templatesId.indexOf(template) < 0;

const storyFn = (args) => {
if (unknowTemplate) {
throw new Error(`Story ${name} is referencing an unknown template ${template}`);
}

return {
Component: RenderContext,
props: {
Stories,
storyName: name,
templateName: template,
args,
sourceComponent: component || globalComponent,
},
};
};

storyFn.storyName = name;
Object.entries(props).forEach(([k, v]) => {
storyFn[k] = v;
});
// inject sources into story
const storySource = source === true ? sources[template ? `tpl:${template}` : name] : source;

if (storySource) {
storyFn.parameters = combineParameters(storyFn.parameters || {}, {
docs: { source: { code: storySource } },
});
}

return storyFn;
});

return {
default: meta,
...found,
};
};
36 changes: 36 additions & 0 deletions app/svelte/src/client/parse-stories.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import parseStories from './parse-stories';
import TestStories from './components/__tests__/TestStories.svelte';

describe('parse-stories', () => {
test('Extract Stories', () => {
const stories = parseStories({ default: TestStories }, { 'tpl:tpl2': 'tpl2src' });
expect(stories.default).toMatchInlineSnapshot(`
Object {
"title": "Test",
}
`);

expect(stories['0'].storyName).toBe('Story1');
expect(stories['0'].parameters).toMatchInlineSnapshot(`undefined`);
expect(stories['1'].storyName).toBe('Story2');
expect(stories['1'].parameters).toMatchInlineSnapshot(`
Object {
"docs": Object {
"source": Object {
"code": "tpl2src",
},
},
}
`);
expect(stories['2'].storyName).toBe('Story3');
expect(stories['2'].parameters).toMatchInlineSnapshot(`
Object {
"docs": Object {
"source": Object {
"code": "xyz",
},
},
}
`);
});
});
Loading

0 comments on commit a178eea

Please sign in to comment.