@storybook/addon-svelte-csf 4.0.6 → 4.0.7
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 +10 -5
- 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,19 +14,24 @@ 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 content="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
|
<Button {...args} on:click={handleClick}>
|
|
32
37
|
You clicked: {count}
|
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