@storybook/addon-svelte-csf 4.0.6 → 4.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -6
- package/dist/components/RenderContext.svelte +17 -1
- package/dist/index.d.ts +18 -1
- package/dist/parser/collect-stories.d.ts +1 -1
- package/dist/parser/collect-stories.js +2 -2
- package/dist/parser/extract-stories.d.ts +1 -0
- package/dist/parser/extract-stories.js +85 -6
- package/dist/parser/extract-stories.test.js +136 -0
- package/dist/parser/svelte-stories-loader.js +3 -2
- package/dist/plugins/vite-svelte-csf.js +3 -1
- package/dist/preset/indexer.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,21 +14,29 @@ It supports:
|
|
|
14
14
|
## Example
|
|
15
15
|
|
|
16
16
|
```svelte
|
|
17
|
-
<script>
|
|
18
|
-
import { Meta, Story, Template } from '@storybook/addon-svelte-csf';
|
|
19
|
-
|
|
17
|
+
<script context="module">
|
|
20
18
|
import Button from './Button.svelte';
|
|
21
19
|
|
|
20
|
+
export const meta = {
|
|
21
|
+
title: "Button",
|
|
22
|
+
component: Button
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<script>
|
|
27
|
+
import { Story, Template } from '@storybook/addon-svelte-csf';
|
|
28
|
+
|
|
22
29
|
let count = 0;
|
|
23
30
|
function handleClick() {
|
|
24
31
|
count += 1;
|
|
25
32
|
}
|
|
26
33
|
</script>
|
|
27
34
|
|
|
28
|
-
<Meta title="Button" component={Button}/>
|
|
29
|
-
|
|
30
35
|
<Template let:args>
|
|
31
|
-
|
|
36
|
+
<!--👇 'on:click' allows to forward event to addon-actions -->
|
|
37
|
+
<Button {...args}
|
|
38
|
+
on:click
|
|
39
|
+
on:click={handleClick}>
|
|
32
40
|
You clicked: {count}
|
|
33
41
|
</Button>
|
|
34
42
|
</Template>
|
|
@@ -43,6 +51,9 @@ It supports:
|
|
|
43
51
|
</Story>
|
|
44
52
|
```
|
|
45
53
|
|
|
54
|
+
Actions are automatically registered by Storybook. To be used by this addon, you just have to forward the event (`on:click` in the previous example).
|
|
55
|
+
|
|
56
|
+
|
|
46
57
|
## Getting Started
|
|
47
58
|
|
|
48
59
|
1. `npm install --save-dev @storybook/addon-svelte-csf` or `yarn add --dev @storybook/addon-svelte-csf`
|
|
@@ -9,9 +9,25 @@
|
|
|
9
9
|
export let args = {};
|
|
10
10
|
export let storyContext = {};
|
|
11
11
|
|
|
12
|
+
/** @type {import('svelte').SvelteComponent} */
|
|
13
|
+
let instance;
|
|
14
|
+
|
|
12
15
|
createRenderContext($$props);
|
|
13
16
|
|
|
17
|
+
// events are static and don't need to be reactive
|
|
18
|
+
const eventsFromArgTypes = Object.fromEntries(
|
|
19
|
+
Object.entries(storyContext.argTypes)
|
|
20
|
+
.filter(([k, v]) => v.action && args[k] != null)
|
|
21
|
+
.map(([k, v]) => [v.action, args[k]])
|
|
22
|
+
);
|
|
23
|
+
|
|
14
24
|
$: setStoryRenderContext(args, storyContext);
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
$: if (instance) {
|
|
28
|
+
Object.entries(eventsFromArgTypes).forEach(([event, handler]) => instance.$on(event, handler));
|
|
29
|
+
}
|
|
30
|
+
|
|
15
31
|
</script>
|
|
16
32
|
|
|
17
|
-
<svelte:component this={Stories} />
|
|
33
|
+
<svelte:component this={Stories} bind:this={instance}/>
|
package/dist/index.d.ts
CHANGED
|
@@ -44,7 +44,22 @@ interface TemplateProps extends BaseAnnotations<any, DecoratorReturnType> {
|
|
|
44
44
|
id?: string;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
interface MetaProps extends BaseMeta<any>, BaseAnnotations<any, DecoratorReturnType> {
|
|
47
|
+
interface MetaProps extends BaseMeta<any>, BaseAnnotations<any, DecoratorReturnType> {
|
|
48
|
+
/**
|
|
49
|
+
* Enable the tag 'autodocs'.
|
|
50
|
+
*
|
|
51
|
+
* @see [Automatic documentation](https://storybook.js.org/docs/svelte/writing-docs/autodocs)
|
|
52
|
+
*/
|
|
53
|
+
autodocs?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* List of tags to add to the stories.
|
|
56
|
+
*
|
|
57
|
+
* It should be a static array of strings.
|
|
58
|
+
*
|
|
59
|
+
* @example tags={['autodocs']}
|
|
60
|
+
*/
|
|
61
|
+
tags?: string[];
|
|
62
|
+
}
|
|
48
63
|
|
|
49
64
|
interface Slots {
|
|
50
65
|
default: {
|
|
@@ -55,6 +70,8 @@ interface Slots {
|
|
|
55
70
|
}
|
|
56
71
|
/**
|
|
57
72
|
* Meta.
|
|
73
|
+
*
|
|
74
|
+
* @deprecated Use `export const meta`. See https://github.com/storybookjs/addon-svelte-csf for an example
|
|
58
75
|
*/
|
|
59
76
|
export class Meta extends SvelteComponent<MetaProps> { }
|
|
60
77
|
/**
|
|
@@ -7,7 +7,7 @@ import { logger } from '@storybook/client-logger';
|
|
|
7
7
|
const createFragment = document.createDocumentFragment
|
|
8
8
|
? () => document.createDocumentFragment()
|
|
9
9
|
: () => document.createElement('div');
|
|
10
|
-
export default (StoriesComponent, { stories = {}, allocatedIds = [] }) => {
|
|
10
|
+
export default (StoriesComponent, { stories = {}, allocatedIds = [] }, exportedMeta = undefined) => {
|
|
11
11
|
const repositories = {
|
|
12
12
|
meta: null,
|
|
13
13
|
stories: [],
|
|
@@ -26,7 +26,7 @@ export default (StoriesComponent, { stories = {}, allocatedIds = [] }) => {
|
|
|
26
26
|
catch (e) {
|
|
27
27
|
logger.error(`Error extracting stories ${e.toString()}`, e);
|
|
28
28
|
}
|
|
29
|
-
const
|
|
29
|
+
const meta = exportedMeta || repositories.meta;
|
|
30
30
|
if (!meta) {
|
|
31
31
|
logger.error('Missing <Meta/> tag');
|
|
32
32
|
return {};
|
|
@@ -1,19 +1,80 @@
|
|
|
1
1
|
import * as svelte from 'svelte/compiler';
|
|
2
2
|
import dedent from 'dedent';
|
|
3
3
|
import { extractId } from './extract-id.js';
|
|
4
|
+
function lookupAttribute(name, attributes) {
|
|
5
|
+
return attributes.find((att) => (att.type === 'Attribute' && att.name === name) ||
|
|
6
|
+
(att.type === 'Property' && att.key.name === name));
|
|
7
|
+
}
|
|
4
8
|
function getStaticAttribute(name, node) {
|
|
5
9
|
// extract the attribute
|
|
6
|
-
const attribute =
|
|
10
|
+
const attribute = lookupAttribute(name, node);
|
|
7
11
|
if (!attribute) {
|
|
8
12
|
return undefined;
|
|
9
13
|
}
|
|
10
14
|
const { value } = attribute;
|
|
11
|
-
// expect the attribute to be static, ie only one Text node
|
|
15
|
+
// expect the attribute to be static, ie only one Text node or Literal
|
|
16
|
+
if (value?.type === 'Literal') {
|
|
17
|
+
return value.value;
|
|
18
|
+
}
|
|
12
19
|
if (value && value.length === 1 && value[0].type === 'Text') {
|
|
13
20
|
return value[0].data;
|
|
14
21
|
}
|
|
15
22
|
throw new Error(`Attribute ${name} is not static`);
|
|
16
23
|
}
|
|
24
|
+
function getStaticBooleanAttribute(name, attributes) {
|
|
25
|
+
// extract the attribute
|
|
26
|
+
const attribute = lookupAttribute(name, attributes);
|
|
27
|
+
if (!attribute) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const { value } = attribute;
|
|
31
|
+
// expect the attribute to be static and a boolean
|
|
32
|
+
if (typeof value === 'boolean') {
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
throw new Error(`Attribute ${name} is not a static boolean`);
|
|
36
|
+
}
|
|
37
|
+
function getMetaTags(attributes) {
|
|
38
|
+
const finalTags = getStaticBooleanAttribute('autodocs', attributes) ? ["autodocs"] : [];
|
|
39
|
+
const tags = lookupAttribute('tags', attributes);
|
|
40
|
+
if (tags) {
|
|
41
|
+
let valid = false;
|
|
42
|
+
let { value } = tags;
|
|
43
|
+
if (value && value.length === 1) {
|
|
44
|
+
value = value[0];
|
|
45
|
+
}
|
|
46
|
+
const { type, expression, data } = value;
|
|
47
|
+
if (type === 'Text') {
|
|
48
|
+
// tags="autodocs"
|
|
49
|
+
finalTags.push(data);
|
|
50
|
+
valid = true;
|
|
51
|
+
}
|
|
52
|
+
else if (type === 'ArrayExpression') {
|
|
53
|
+
// tags={["autodocs"]} in object
|
|
54
|
+
const { elements } = value;
|
|
55
|
+
elements.forEach((e) => finalTags.push(e.value));
|
|
56
|
+
valid = true;
|
|
57
|
+
}
|
|
58
|
+
else if (type === 'MustacheTag' && expression.type === 'ArrayExpression') {
|
|
59
|
+
// tags={["autodocs"]} in template
|
|
60
|
+
const { elements } = expression;
|
|
61
|
+
elements.forEach((e) => finalTags.push(e.value));
|
|
62
|
+
valid = true;
|
|
63
|
+
}
|
|
64
|
+
if (!valid) {
|
|
65
|
+
throw new Error('Attribute tags should be a static string array or a string');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return finalTags;
|
|
69
|
+
}
|
|
70
|
+
function fillMetaFromAttributes(meta, attributes) {
|
|
71
|
+
meta.title = getStaticAttribute('title', attributes);
|
|
72
|
+
meta.id = getStaticAttribute('id', attributes);
|
|
73
|
+
const tags = getMetaTags(attributes);
|
|
74
|
+
if (tags.length > 0) {
|
|
75
|
+
meta.tags = tags;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
17
78
|
/**
|
|
18
79
|
* Parse a Svelte component and extract stories.
|
|
19
80
|
* @param component Component Source
|
|
@@ -57,6 +118,25 @@ export function extractStories(component) {
|
|
|
57
118
|
}
|
|
58
119
|
const stories = {};
|
|
59
120
|
const meta = {};
|
|
121
|
+
if (ast.module) {
|
|
122
|
+
svelte.walk(ast.module.content, {
|
|
123
|
+
enter(node) {
|
|
124
|
+
if (node.type === 'ExportNamedDeclaration' &&
|
|
125
|
+
node.declaration?.type === 'VariableDeclaration' &&
|
|
126
|
+
node.declaration?.declarations.length === 1 &&
|
|
127
|
+
node.declaration?.declarations[0]?.id?.name === 'meta') {
|
|
128
|
+
if (node.declaration?.kind !== 'const') {
|
|
129
|
+
throw new Error('meta should be exported as const');
|
|
130
|
+
}
|
|
131
|
+
const init = node.declaration?.declarations[0]?.init;
|
|
132
|
+
if (init?.type !== 'ObjectExpression') {
|
|
133
|
+
throw new Error('meta should export on object');
|
|
134
|
+
}
|
|
135
|
+
fillMetaFromAttributes(meta, init.properties);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
60
140
|
svelte.walk(ast.html, {
|
|
61
141
|
enter(node) {
|
|
62
142
|
if (node.type === 'InlineComponent' &&
|
|
@@ -64,13 +144,13 @@ export function extractStories(component) {
|
|
|
64
144
|
this.skip();
|
|
65
145
|
const isTemplate = node.name === 'Template';
|
|
66
146
|
// extract the 'name' attribute
|
|
67
|
-
let name = getStaticAttribute('name', node);
|
|
147
|
+
let name = getStaticAttribute('name', node.attributes);
|
|
68
148
|
// templates has a default name
|
|
69
149
|
if (!name && isTemplate) {
|
|
70
150
|
name = 'default';
|
|
71
151
|
}
|
|
72
152
|
const id = extractId({
|
|
73
|
-
id: getStaticAttribute('id', node),
|
|
153
|
+
id: getStaticAttribute('id', node.attributes),
|
|
74
154
|
name,
|
|
75
155
|
}, isTemplate ? undefined : allocatedIds);
|
|
76
156
|
if (name && id) {
|
|
@@ -93,8 +173,7 @@ export function extractStories(component) {
|
|
|
93
173
|
}
|
|
94
174
|
else if (node.type === 'InlineComponent' && node.name === localNames.Meta) {
|
|
95
175
|
this.skip();
|
|
96
|
-
meta
|
|
97
|
-
meta.id = getStaticAttribute('id', node);
|
|
176
|
+
fillMetaFromAttributes(meta, node.attributes);
|
|
98
177
|
}
|
|
99
178
|
},
|
|
100
179
|
});
|
|
@@ -205,6 +205,142 @@ describe('extractSource', () => {
|
|
|
205
205
|
}
|
|
206
206
|
`);
|
|
207
207
|
});
|
|
208
|
+
test('Add tags autodocs', () => {
|
|
209
|
+
expect(extractStories(`
|
|
210
|
+
<script>
|
|
211
|
+
import { Story, Meta } from '@storybook/addon-svelte-csf';
|
|
212
|
+
</script>
|
|
213
|
+
|
|
214
|
+
<Meta title='test' autodocs/>
|
|
215
|
+
|
|
216
|
+
<Story name="Story1">
|
|
217
|
+
<div>story 1</div>
|
|
218
|
+
</Story>
|
|
219
|
+
`)).toMatchInlineSnapshot(`
|
|
220
|
+
{
|
|
221
|
+
"allocatedIds": [
|
|
222
|
+
"default",
|
|
223
|
+
"Story",
|
|
224
|
+
"Meta",
|
|
225
|
+
],
|
|
226
|
+
"meta": {
|
|
227
|
+
"id": undefined,
|
|
228
|
+
"tags": [
|
|
229
|
+
"autodocs",
|
|
230
|
+
],
|
|
231
|
+
"title": "test",
|
|
232
|
+
},
|
|
233
|
+
"stories": {
|
|
234
|
+
"Story1": {
|
|
235
|
+
"hasArgs": false,
|
|
236
|
+
"name": "Story1",
|
|
237
|
+
"source": "<div>story 1</div>",
|
|
238
|
+
"storyId": "test--story-1",
|
|
239
|
+
"template": false,
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
}
|
|
243
|
+
`);
|
|
244
|
+
});
|
|
245
|
+
test('Add tags', () => {
|
|
246
|
+
expect(extractStories(`
|
|
247
|
+
<script>
|
|
248
|
+
import { Story, Meta } from '@storybook/addon-svelte-csf';
|
|
249
|
+
</script>
|
|
250
|
+
|
|
251
|
+
<Meta title='test' tags={['a','b']}/>
|
|
252
|
+
|
|
253
|
+
<Story name="Story1">
|
|
254
|
+
<div>story 1</div>
|
|
255
|
+
</Story>
|
|
256
|
+
`)).toMatchInlineSnapshot(`
|
|
257
|
+
{
|
|
258
|
+
"allocatedIds": [
|
|
259
|
+
"default",
|
|
260
|
+
"Story",
|
|
261
|
+
"Meta",
|
|
262
|
+
],
|
|
263
|
+
"meta": {
|
|
264
|
+
"id": undefined,
|
|
265
|
+
"tags": [
|
|
266
|
+
"a",
|
|
267
|
+
"b",
|
|
268
|
+
],
|
|
269
|
+
"title": "test",
|
|
270
|
+
},
|
|
271
|
+
"stories": {
|
|
272
|
+
"Story1": {
|
|
273
|
+
"hasArgs": false,
|
|
274
|
+
"name": "Story1",
|
|
275
|
+
"source": "<div>story 1</div>",
|
|
276
|
+
"storyId": "test--story-1",
|
|
277
|
+
"template": false,
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
}
|
|
281
|
+
`);
|
|
282
|
+
});
|
|
283
|
+
test('Add Only one tag', () => {
|
|
284
|
+
expect(extractStories(`
|
|
285
|
+
<script>
|
|
286
|
+
import { Story, Meta } from '@storybook/addon-svelte-csf';
|
|
287
|
+
</script>
|
|
288
|
+
|
|
289
|
+
<Meta title='test' tags='a'/>
|
|
290
|
+
|
|
291
|
+
<Story name="Story1">
|
|
292
|
+
<div>story 1</div>
|
|
293
|
+
</Story>
|
|
294
|
+
`)).toMatchInlineSnapshot(`
|
|
295
|
+
{
|
|
296
|
+
"allocatedIds": [
|
|
297
|
+
"default",
|
|
298
|
+
"Story",
|
|
299
|
+
"Meta",
|
|
300
|
+
],
|
|
301
|
+
"meta": {
|
|
302
|
+
"id": undefined,
|
|
303
|
+
"tags": [
|
|
304
|
+
"a",
|
|
305
|
+
],
|
|
306
|
+
"title": "test",
|
|
307
|
+
},
|
|
308
|
+
"stories": {
|
|
309
|
+
"Story1": {
|
|
310
|
+
"hasArgs": false,
|
|
311
|
+
"name": "Story1",
|
|
312
|
+
"source": "<div>story 1</div>",
|
|
313
|
+
"storyId": "test--story-1",
|
|
314
|
+
"template": false,
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
}
|
|
318
|
+
`);
|
|
319
|
+
});
|
|
320
|
+
test('Meta as exported module object', () => {
|
|
321
|
+
expect(extractStories(`
|
|
322
|
+
<script context='module'>
|
|
323
|
+
export const meta = {
|
|
324
|
+
title: 'MyStory',
|
|
325
|
+
tags: ['a']
|
|
326
|
+
};
|
|
327
|
+
</script>
|
|
328
|
+
`)).toMatchInlineSnapshot(`
|
|
329
|
+
{
|
|
330
|
+
"allocatedIds": [
|
|
331
|
+
"default",
|
|
332
|
+
],
|
|
333
|
+
"meta": {
|
|
334
|
+
"id": undefined,
|
|
335
|
+
"tags": [
|
|
336
|
+
"a",
|
|
337
|
+
],
|
|
338
|
+
"title": "MyStory",
|
|
339
|
+
},
|
|
340
|
+
"stories": {},
|
|
341
|
+
}
|
|
342
|
+
`);
|
|
343
|
+
});
|
|
208
344
|
test('Duplicate Id', () => {
|
|
209
345
|
expect(extractStories(`
|
|
210
346
|
<script>
|
|
@@ -42,10 +42,11 @@ function transformSvelteStories(code) {
|
|
|
42
42
|
.filter(([, def]) => !def.template)
|
|
43
43
|
.map(([id]) => `export const ${id} = __storiesMetaData.stories[${JSON.stringify(id)}]`)
|
|
44
44
|
.join('\n');
|
|
45
|
-
const
|
|
45
|
+
const metaExported = code.includes('export { meta }');
|
|
46
|
+
const codeWithoutDefaultExport = code.replace('export default ', '//export default').replace('export { meta };', '// export { meta };');
|
|
46
47
|
return `${codeWithoutDefaultExport}
|
|
47
48
|
const { default: parser } = require('${parser}');
|
|
48
|
-
const __storiesMetaData = parser(${componentName}, ${JSON.stringify(storiesDef)});
|
|
49
|
+
const __storiesMetaData = parser(${componentName}, ${JSON.stringify(storiesDef)}${metaExported ? ', meta' : ''});
|
|
49
50
|
export default __storiesMetaData.meta;
|
|
50
51
|
${storyDef};
|
|
51
52
|
`;
|
|
@@ -32,10 +32,12 @@ export default function csfPlugin(svelteOptions) {
|
|
|
32
32
|
const namedExportsOrder = Object.entries(stories)
|
|
33
33
|
.filter(([, def]) => !def.template)
|
|
34
34
|
.map(([storyId]) => storyId);
|
|
35
|
+
const metaExported = code.includes('export { meta }');
|
|
36
|
+
s.replace('export { meta };', '// export { meta };');
|
|
35
37
|
const output = [
|
|
36
38
|
'',
|
|
37
39
|
`import parser from '${parser}';`,
|
|
38
|
-
`const __storiesMetaData = parser(${component}, ${JSON.stringify(all)});`,
|
|
40
|
+
`const __storiesMetaData = parser(${component}, ${JSON.stringify(all)}${metaExported ? ', meta' : ''});`,
|
|
39
41
|
'export default __storiesMetaData.meta;',
|
|
40
42
|
`export const __namedExportsOrder = ${JSON.stringify(namedExportsOrder)};`,
|
|
41
43
|
storyDef,
|
package/dist/preset/indexer.d.ts
CHANGED