Skip to content

Commit

Permalink
Provide usage test & fix runtime issues
Browse files Browse the repository at this point in the history
  • Loading branch information
xeho91 committed Jul 6, 2024
1 parent 1e70e3a commit 460fc9f
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 49 deletions.
4 changes: 2 additions & 2 deletions src/compiler/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ export async function preTransformPlugin(): Promise<Plugin> {
let magicLegacyCode = new MagicString(legacyCode);

const svelteAST = getSvelteAST({ code: legacyCode, filename: id });
const legacyNodes = extractLegacyNodes(svelteAST);
const legacyNodes = await extractLegacyNodes(svelteAST);

codemodLegacyNodes({
await codemodLegacyNodes({
code: magicLegacyCode,
legacyNodes,
});
Expand Down
45 changes: 32 additions & 13 deletions src/compiler/pre-transform/codemods/template-to-snippet.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getStringValueFromAttribute } from '#parser/analyse/story/attributes';
import type { Attribute, Component, SnippetBlock } from 'svelte/compiler';
import type { Attribute, Component, LetDirective, SnippetBlock } from 'svelte/compiler';

/**
*
Expand Down Expand Up @@ -40,24 +40,43 @@ export function transformTemplateToSnippet(component: Component): SnippetBlock {
component,
});

const letDirectiveArgs = attributes.find((attr) => {
if (attr.type === 'LetDirective') {
return attr.name === 'args';
}
// Will TypeScript 5.5 handle type inference for this one better? 🤔
}) as LetDirective | undefined;

const letDirectiveContext = attributes.find((attr) => {
if (attr.type === 'LetDirective') {
return attr.name === 'context';
}
// Will TypeScript 5.5 handle type inference for this one better? 🤔
}) as LetDirective | undefined;

let parameters: SnippetBlock['parameters'] = [];

if (letDirectiveArgs) {
parameters.push({
type: 'Identifier',
name: 'args',
});
}

if (letDirectiveContext) {
parameters.push({
type: 'Identifier',
name: 'context',
});
}

return {
type: 'SnippetBlock',
expression: {
type: 'Identifier',
name: id ?? 'children',
},
// WARN: I suspect at this point, it doesn't matter if user used one of directives `let:args` and `let:context`.
// W provide both parameters, just in case.
parameters: [
{
type: 'Identifier',
name: 'args',
},
{
type: 'Identifier',
name: 'storyContext',
},
],
parameters,
body: fragment,
// NOTE: Those are useless, but I want TypeScript to 🤫
start,
Expand Down
5 changes: 3 additions & 2 deletions src/compiler/pre-transform/extractor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Component, SvelteNode } from 'svelte/compiler';
import { walk } from 'zimmerframe';

interface Results {
componentsTemplate: Component[];
Expand All @@ -8,7 +7,9 @@ interface Results {
/**
* Extract legacy AST nodes, for usage with codemods into modern syntax.
*/
export function extractLegacyNodes(parsed: SvelteNode): Results {
export async function extractLegacyNodes(parsed: SvelteNode): Promise<Results> {
const { walk } = await import('zimmerframe');

const state: {
componentsTemplate: Component[];
} = {
Expand Down
7 changes: 3 additions & 4 deletions src/compiler/pre-transform/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { print } from 'svelte-ast-print';

import type { extractLegacyNodes } from '#compiler/pre-transform/extractor';
import { transformTemplateToSnippet } from './codemods/template-to-snippet';
import type MagicString from 'magic-string';

interface Params {
code: MagicString;
legacyNodes: ReturnType<typeof extractLegacyNodes>;
legacyNodes: Awaited<ReturnType<typeof extractLegacyNodes>>;
}

export function codemodLegacyNodes(params: Params): void {
export async function codemodLegacyNodes(params: Params): Promise<void> {
const { print } = await import('svelte-ast-print');
const { legacyNodes, code } = params;
const { componentsTemplate } = legacyNodes;

Expand Down
3 changes: 2 additions & 1 deletion src/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ export const viteFinal: StorybookConfig['viteFinal'] = async (config, options) =
return {
...config,
plugins: [
/** TODO: Is this the place for `options.supportLegacy`? */
preTransformPlugin(),
...(config.plugins ?? []),
/** TODO: Is this the place for `options.supportLegacy`? */ preTransformPlugin(),
postTransformPlugin(),
],
};
Expand Down
34 changes: 10 additions & 24 deletions src/runtime/Template.svelte
Original file line number Diff line number Diff line change
@@ -1,29 +1,15 @@
<script lang="ts">
import type { Snippet } from 'svelte';
/**
* This is a legacy template, this functionality is just a "mock".
* To allow user still have typing experience
* Vite pre-transform hook does codemod where this component gets transformed into Svelte v5 SnippetBlock.
*/
import { useStoryRenderer, type StoryRendererContext } from './contexts/renderer.svelte';
import { type StoryRendererContext } from './contexts/renderer.svelte';
interface Props {
/**
* The content of template to be rendered inside the `<Story />`
*/
children: Snippet<
[
//
StoryRendererContext['args'],
StoryRendererContext['storyContext'],
]
>;
/**
* Unique id of template - to be used in `<Story templateId={id} />`
* @default id
*/
id?: string;
}
const renderer = useStoryRenderer();
let { children, id = 'default' }: Props = $props();
let id: string = 'default';
let args: StoryRendererContext['storyContext'];
let context: StoryRendererContext['args'];
</script>

{@render children(renderer.args, renderer.storyContext)}
<slot {context} {args} />
6 changes: 3 additions & 3 deletions src/runtime/contexts/renderer.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ function buildContext<TOverrideArgs extends Args, TCmp extends Cmp, TMeta extend
}

export type StoryRendererContext<
TOverrideArgs extends Args,
TCmp extends Cmp,
TMeta extends Meta<TCmp>,
TOverrideArgs extends Args = Args,
TCmp extends Cmp = Cmp,
TMeta extends Meta<TCmp> = Meta<TCmp>,
> = ReturnType<typeof buildContext<TOverrideArgs, TCmp, TMeta>>;

function createStoryRendererContext<
Expand Down
44 changes: 44 additions & 0 deletions tests/stories/LegacyTemplate.stories.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script context="module" lang="ts">
import { Template, defineMeta } from '@storybook/addon-svelte-csf';
import LegacyTemplate from './LegacyTemplate.svelte';
/**
* Description set explicitly in the comment above `defineMeta`.
*
* Multiline supported. And also Markdown syntax:
*
* * **Bold**,
* * _Italic_,
* * `Code`.
*/
const { Story } = defineMeta({
title: 'LegacyTemplate',
component: LegacyTemplate,
tags: ['autodocs'],
});
</script>

<script lang="ts">
let count = $state(0);
function handleClick() {
count += 1;
}
</script>

<Template let:args let:context>
<p>Using default template</p>
<LegacyTemplate {...args} onclick={handleClick} />
</Template>

<Template id="rounded" let:args>
<p>Using rounded template</p>
<LegacyTemplate {...args} onclick={handleClick} rounded />
</Template>

<!-- TODO: Need to restore legacy Story first -->
<Story name="Default" {children} />

<!-- TODO: Need to restore legacy Story first -->
<Story name="Rounded" children={rounded} />
27 changes: 27 additions & 0 deletions tests/stories/LegacyTemplate.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
interface Props extends HTMLAttributes<HTMLButtonElement> {
rounded?: boolean;
}
let { rounded = false, ...restProps }: Props = $props();
</script>

<button class="button" class:rounded {...restProps}>
<strong>{rounded ? 'Round' : 'Square'} corners</strong>
<hr />
</button>

<style>
.rounded {
border-radius: 35px;
}
.button {
border: 3px solid;
padding: 10px 20px;
background-color: white;
outline: none;
}
</style>

0 comments on commit 460fc9f

Please sign in to comment.