@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 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
  /**
@@ -5,7 +5,7 @@ interface Meta {
5
5
  declare const _default: (StoriesComponent: any, { stories, allocatedIds }: {
6
6
  stories?: {} | undefined;
7
7
  allocatedIds?: never[] | undefined;
8
- }) => {
8
+ }, exportedMeta?: undefined) => {
9
9
  meta?: undefined;
10
10
  stories?: undefined;
11
11
  } | {
@@ -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 { meta } = repositories;
29
+ const meta = exportedMeta || repositories.meta;
30
30
  if (!meta) {
31
31
  logger.error('Missing <Meta/> tag');
32
32
  return {};
@@ -7,6 +7,7 @@ interface StoryDef {
7
7
  interface MetaDef {
8
8
  title?: string;
9
9
  id?: string;
10
+ tags?: string[];
10
11
  }
11
12
  interface StoriesDef {
12
13
  meta: MetaDef;
@@ -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 = node.attributes.find((att) => att.type === 'Attribute' && att.name === name);
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.title = getStaticAttribute('title', node);
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 codeWithoutDefaultExport = code.replace('export default ', '//export default');
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,
@@ -4,6 +4,7 @@ export declare function svelteIndexer(fileName: any, { makeTitle }: {
4
4
  meta: {
5
5
  title: any;
6
6
  id?: string | undefined;
7
+ tags?: string[] | undefined;
7
8
  };
8
9
  stories: {
9
10
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storybook/addon-svelte-csf",
3
- "version": "4.0.6",
3
+ "version": "4.0.7",
4
4
  "description": "Allows to write stories in Svelte syntax",
5
5
  "keywords": [
6
6
  "storybook-addons",