Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
40 changes: 40 additions & 0 deletions code/e2e-tests/framework-svelte.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* eslint-disable jest/no-disabled-tests */
import { test, expect } from '@playwright/test';
import process from 'process';
import { SbPage } from './util';

const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:6006';
const templateName = process.env.STORYBOOK_TEMPLATE_NAME;

test.describe('Svelte', () => {
test.skip(
// eslint-disable-next-line jest/valid-title
!templateName?.includes('svelte'),
'Only run this test on Svelte'
);

test.beforeEach(async ({ page }) => {
await page.goto(storybookUrl);
await new SbPage(page).waitUntilLoaded();
});

test('JS story has auto-generated args table', async ({ page }) => {
const sbPage = new SbPage(page);

await sbPage.navigateToStory('stories/renderers/svelte/js-docs', 'docs');
const root = sbPage.previewRoot();
const argsTable = root.locator('.docblock-argstable');
await expect(argsTable).toContainText('Rounds the button');
});

test('TS story has auto-generated args table', async ({ page }) => {
// eslint-disable-next-line jest/valid-title
test.skip(!templateName?.endsWith('ts') || false, 'Only test TS story in TS templates');
const sbPage = new SbPage(page);

await sbPage.navigateToStory('stories/renderers/svelte/ts-docs', 'docs');
const root = sbPage.previewRoot();
const argsTable = root.locator('.docblock-argstable');
await expect(argsTable).toContainText('Rounds the button');
});
});
1 change: 1 addition & 0 deletions code/frameworks/svelte-vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@storybook/svelte": "workspace:*",
"@sveltejs/vite-plugin-svelte": "^2.4.2",
"magic-string": "^0.30.0",
"svelte-preprocess": "^5.0.4",
"sveltedoc-parser": "^4.2.1",
"ts-dedent": "^2.2.0"
},
Expand Down
68 changes: 56 additions & 12 deletions code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@ import type { SvelteParserOptions } from 'sveltedoc-parser';
import { logger } from '@storybook/node-logger';
import { preprocess } from 'svelte/compiler';
import { createFilter } from 'vite';
import { replace, typescript } from 'svelte-preprocess';

/*
* Patch sveltedoc-parser internal options.
* Waiting for a fix for https://github.com/alexprey/sveltedoc-parser/issues/87
*/
const svelteDocParserOptions = require('sveltedoc-parser/lib/options.js');

svelteDocParserOptions.getAstDefaultOptions = () => ({
range: true,
loc: true,
comment: true,
tokens: true,
ecmaVersion: 12,
sourceType: 'module',
ecmaFeatures: {},
});

// Most of the code here should probably be exported by @storybook/svelte and reused here.
// See: https://github.com/storybookjs/storybook/blob/next/app/svelte/src/server/svelte-docgen-loader.ts
Expand Down Expand Up @@ -48,19 +65,43 @@ export function svelteDocgen(svelteOptions: Record<string, any> = {}): PluginOpt
const include = /\.(svelte)$/;
const filter = createFilter(include);

let docPreprocessOptions: Parameters<typeof preprocess>[1] | undefined;

return {
name: 'storybook:svelte-docgen-plugin',
async transform(src: string, id: string) {
if (!filter(id)) return undefined;

if (preprocessOptions && !docPreprocessOptions) {
/*
* We can't use vitePreprocess() for the documentation
* because it uses esbuild which removes jsdoc.
*
* By default, only typescript is transpiled, and style tags are removed.
*
* Note: these preprocessors are only used to make the component
* compatible to sveltedoc-parser (no ts), not to compile
* the component.
*/
docPreprocessOptions = [replace([[/<style.+<\/style>/gims, '']])];

try {
const ts = require.resolve('typescript');
if (ts) {
docPreprocessOptions.unshift(typescript());
}
} catch {
// this will error in JavaScript-only projects, this is okay
}
}

const resource = path.relative(cwd, id);

let docOptions;
if (preprocessOptions) {
// eslint-disable-next-line @typescript-eslint/no-shadow
const src = fs.readFileSync(resource).toString();
if (docPreprocessOptions) {
const rawSource = fs.readFileSync(resource).toString();

const { code: fileContent } = await preprocess(src, preprocessOptions, {
const { code: fileContent } = await preprocess(rawSource, docPreprocessOptions, {
filename: resource,
});

Expand All @@ -79,21 +120,24 @@ export function svelteDocgen(svelteOptions: Record<string, any> = {}): PluginOpt

const s = new MagicString(src);

let componentDoc: any;
try {
const componentDoc = await svelteDoc.parse(options);
// get filename for source content
const file = path.basename(resource);

componentDoc.name = path.basename(file);

const componentName = getNameFromFilename(resource);
s.append(`;${componentName}.__docgen = ${JSON.stringify(componentDoc)}`);
componentDoc = await svelteDoc.parse(options);
} catch (error: any) {
componentDoc = { keywords: [], data: [] };
if (logDocgen) {
logger.error(error);
}
}

// get filename for source content
const file = path.basename(resource);

componentDoc.name = path.basename(file);

const componentName = getNameFromFilename(resource);
s.append(`;${componentName}.__docgen = ${JSON.stringify(componentDoc)}`);

return {
code: s.toString(),
map: s.generateMap({ hires: true, source: id }),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script lang="ts">
/**
* @component Button TypeScript
* @wrapper
*/
import { global as globalThis } from '@storybook/global';
// @ts-ignore
const Button = globalThis.Components?.Button;

/**
* Rounds the button
*/
export let primary: boolean = false;

/**
* Displays the count
*/
export let count: number = 0;

/**
* Button text
* @slot
*/
export let text: string = 'You clicked';

function handleClick(_event: MouseEvent) {
count += 1;
}
</script>

<h1>Button TypeScript</h1>

<Button {primary} on:click on:click={handleClick} label="{text}: {count}" />

<p>A little text to show this is a view.</p>
<p>If we need to test components in a Svelte environment, for instance to test slot behaviour,</p>
<p>then wrapping the component up in a view</p>
<p>made just for the story is the simplest way to achieve this.</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import ButtonTypescript from './ButtonTypeScript.svelte';

export default {
title: 'stories/renderers/svelte/ts-docs',
component: ButtonTypescript,
args: {
primary: true,
},
tags: ['autodocs'],
};

export const Primary = {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script lang="ts">
/**
* @component Button TypeScript
* @wrapper
*/
import { global as globalThis } from '@storybook/global';
// @ts-ignore
const Button = globalThis.Components?.Button;

/**
* Rounds the button
*/
export let primary: boolean = false;

/**
* Displays the count
*/
export let count: number = 0;

/**
* Button text
* @slot
*/
export let text: string = 'You clicked';

function handleClick(_event: MouseEvent) {
count += 1;
}
</script>

<h1>Button TypeScript</h1>

<Button {primary} on:click on:click={handleClick} label="{text}: {count}" />

<p>A little text to show this is a view.</p>
<p>If we need to test components in a Svelte environment, for instance to test slot behaviour,</p>
<p>then wrapping the component up in a view</p>
<p>made just for the story is the simplest way to achieve this.</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import ButtonTypescript from './ButtonTypeScript.svelte';

export default {
title: 'stories/renderers/svelte/ts-docs',
component: ButtonTypescript,
args: {
primary: true,
},
tags: ['autodocs'],
};

export const Primary = {};
43 changes: 31 additions & 12 deletions code/presets/svelte-webpack/src/svelte-docgen-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ import * as fs from 'fs';
import { preprocess } from 'svelte/compiler';
import { logger } from '@storybook/node-logger';

/*
* Patch sveltedoc-parser internal options.
* Waiting for a fix for https://github.com/alexprey/sveltedoc-parser/issues/87
*/
const svelteDocParserOptions = require('sveltedoc-parser/lib/options.js');

svelteDocParserOptions.getAstDefaultOptions = () => ({
range: true,
loc: true,
comment: true,
tokens: true,
ecmaVersion: 12,
sourceType: 'module',
ecmaFeatures: {},
});

// From https://github.com/sveltejs/svelte/blob/8db3e8d0297e052556f0b6dde310ef6e197b8d18/src/compiler/compile/utils/get_name_from_filename.ts
// Copied because it is not exported from the compiler
function getNameFromFilename(filename: string) {
Expand Down Expand Up @@ -73,27 +89,30 @@ export default async function svelteDocgen(this: any, source: string) {

let docgen = '';

let componentDoc: any;
try {
// FIXME
// @ts-expect-error (Converted from ts-ignore)
const componentDoc = await svelteDoc.parse(options);
componentDoc = await svelteDoc.parse(options);
} catch (error) {
componentDoc = { keywords: [], data: [] };
if (logDocgen) {
logger.error(error as any);
}
}

// get filename for source content
const file = path.basename(resource);
// get filename for source content
const file = path.basename(resource);

// populate filename in docgen
componentDoc.name = path.basename(file);
// populate filename in docgen
componentDoc.name = path.basename(file);

const componentName = getNameFromFilename(resource);
const componentName = getNameFromFilename(resource);

docgen = dedent`
docgen = dedent`
${componentName}.__docgen = ${JSON.stringify(componentDoc)};
`;
} catch (error) {
if (logDocgen) {
logger.error(error as any);
}
}

// inject __docgen prop in svelte component
const output = source + docgen;

Expand Down
2 changes: 1 addition & 1 deletion code/renderers/svelte/template/stories/args.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
STORY_RENDERED,
} from '@storybook/core-events';
import { addons } from '@storybook/preview-api';
import ButtonView from './views/ButtonView.svelte';
import ButtonView from './views/ButtonJavaScript.svelte';

export default {
component: ButtonView,
Expand Down
11 changes: 11 additions & 0 deletions code/renderers/svelte/template/stories/js-docs.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import ButtonJavaScript from './views/ButtonJavaScript.svelte';

export default {
component: ButtonJavaScript,
args: {
primary: true,
},
tags: ['autodocs'],
};

export const Primary = {};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ButtonView from './views/ButtonView.svelte';
import ButtonView from './views/ButtonJavaScript.svelte';
import BorderDecoratorRed from './views/BorderDecoratorRed.svelte';
import BorderDecoratorBlue from './views/BorderDecoratorBlue.svelte';
import BorderDecoratorProps from './views/BorderDecoratorProps.svelte';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { global as globalThis } from '@storybook/global';
import { Meta, Story, Canvas } from '@storybook/addon-docs';
import ButtonView from './views/ButtonView.svelte';
import ButtonView from './views/ButtonJavaScript.svelte';
import BorderDecoratorRed from './views/BorderDecoratorRed.svelte';

<Meta title="stories/renderers/svelte/svelte-mdx" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
* @wrapper
*/
import { global as globalThis } from '@storybook/global';
const Button = globalThis.Components.Button;
// @ts-ignore
const Button = globalThis.Components?.Button;

/**
* Rounds the button
Expand Down
1 change: 1 addition & 0 deletions code/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8061,6 +8061,7 @@ __metadata:
"@types/node": ^18.0.0
magic-string: ^0.30.0
svelte: ^4.0.0
svelte-preprocess: ^5.0.4
sveltedoc-parser: ^4.2.1
ts-dedent: ^2.2.0
typescript: ~4.9.3
Expand Down