Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Svelte: Improve argTypes inference with svelte2tsx - support runes #28492

Merged
merged 21 commits into from
Oct 24, 2024

Conversation

ciscorn
Copy link
Contributor

@ciscorn ciscorn commented Jul 9, 2024

What I did

To support Svelte 5 'runes mode', this PR modifies @storybook/svelte-vite to utilize Svelte’s official svelte2tsx + typescript instead of sveltedoc-parser for generating props documentation data.

I found that others have mentioned the same idea as well:

Rationale:

  • sveltedoc-parser cannot parse Svelte 5 ‘runes mode’ files at all, and it has not been maintained for 3 years.
    • In contrast, svelte2tsx is an official component used by Svelte in its language server.
  • By using typescript, it provides perfect type resolution and can also handle type hints written in JavaScript + JSDoc as well as TypeScript.
  • It can handle both Svelte 5's 'runes mode' and the Svelte 4's export let style.
  • It works fast enough. (Around 30ms per stories on my laptop)

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

  1. Open Svelte 4 (svelte-kit/skeleton-ts) and Svelte 5 (svelte-kit/prerelease-ts) sandboxes (feel free to use Chromatic)
  2. Navigate to the Docs of the following stories, and cross reference the Controls table with what is defined in the components, ensuring that the arg types are inferred correctly:

example screenshot:

Screenshot 2024-07-19 at 22 56 15

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This pull request has been released as version 0.0.0-pr-28492-sha-08d3f4f1. Try it out in a new sandbox by running npx [email protected] sandbox or in an existing project with npx [email protected] upgrade.

More information
Published version 0.0.0-pr-28492-sha-08d3f4f1
Triggered by @JReinhold
Repository MIERUNE/storybook
Branch svelte-autodoc-ts
Commit 08d3f4f1
Datetime Thu Oct 17 19:11:04 UTC 2024 (1729192264)
Workflow run 11391471758

To request a new release of this pull request, mention the @storybookjs/core team.

core team members can create a new canary release here or locally with gh workflow run --repo storybookjs/storybook canary-release-pr.yml --field pr=28492

@ciscorn ciscorn force-pushed the svelte-autodoc-ts branch from 88733ed to 700862a Compare July 9, 2024 09:55
@ciscorn ciscorn changed the title Svelte: autodocs with svelte2tsx and TypeScript Svelte: Autodocs with svelte2tsx and TypeScript (Supports 'runes mode') Jul 9, 2024
@ciscorn ciscorn marked this pull request as ready for review July 9, 2024 10:00
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Summary

  • Updated code/frameworks/svelte-vite/package.json to add svelte2tsx and update typescript dependency.
  • Modified code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts to switch from sveltedoc-parser to svelte2tsx and TypeScript.
  • Added code/frameworks/svelte-vite/src/plugins/ts2doc.ts to convert Svelte files to TypeScript documentation.
  • Introduced code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ButtonTypeScriptRunes.svelte for Svelte 5 'runes mode' with TypeScript support.
  • Added code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes-docs.stories.js for Storybook story of ButtonTypeScriptRunes component.

5 file(s) reviewed, 3 comment(s)
Edit PR Review Bot Settings

@ciscorn ciscorn force-pushed the svelte-autodoc-ts branch from 700862a to 9d05996 Compare July 9, 2024 10:09
@ciscorn ciscorn force-pushed the svelte-autodoc-ts branch from 9d05996 to c7a619d Compare July 9, 2024 10:09
@JReinhold JReinhold added svelte builder-vite ci:daily Run the CI jobs that normally run in the daily job. labels Jul 10, 2024
Copy link
Contributor

@JReinhold JReinhold left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very interesting work! 🎉

As you can see from my comments, I'm eager to know if we can just switch completely to using svelte2tsx for all cases and not just in runes mode? Even for Svelte 4 and 5?

I haven't been able to dive into the details of the node visiting code yet.

I also would like to see the test stories test out more cases:

  1. a prop being typed by an imported type
  2. another component that doesn't have lang="ts" but instead only uses JSDoc comments only.

code/frameworks/svelte-vite/package.json Outdated Show resolved Hide resolved
code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts Outdated Show resolved Hide resolved
code/frameworks/svelte-vite/src/plugins/ts2doc.ts Outdated Show resolved Hide resolved
code/frameworks/svelte-vite/src/plugins/ts2doc.ts Outdated Show resolved Hide resolved
code/frameworks/svelte-vite/src/plugins/ts2doc.ts Outdated Show resolved Hide resolved
code/frameworks/svelte-vite/src/plugins/ts2doc.ts Outdated Show resolved Hide resolved
code/frameworks/svelte-vite/src/plugins/ts2doc.ts Outdated Show resolved Hide resolved
Copy link

nx-cloud bot commented Jul 10, 2024

☁️ Nx Cloud Report

CI is running/has finished running commands for commit 08d3f4f. As they complete they will appear below. Click to see the status, the terminal output, and the build insights.

📂 See all runs for this CI Pipeline Execution


✅ Successfully ran 1 target

Sent with 💌 from NxCloud.

@ciscorn ciscorn changed the title Svelte: Autodocs with svelte2tsx and TypeScript (Supports 'runes mode') Svelte: Autodocs with svelte2tsx (Supports 'runes mode') Jul 11, 2024
@ciscorn ciscorn marked this pull request as draft July 11, 2024 00:51
@ciscorn ciscorn force-pushed the svelte-autodoc-ts branch from f1a2912 to a4f4a6a Compare July 11, 2024 18:07
@jasonlyu123
Copy link

Hi. svelte2tsx maintainer here. svelte2tsx uses TypeScript API to parse script tag content so I don't think it'll work if you don't have TypeScript. Also, we don't recommend using AST static analysis for props type. exportedNames is there because we previously did that but later switched to the TypeScript type-checker. If you don't want TypeScript to be a dependency, maybe you'll need to make the original version a fallback.

Another problem with the approach is that the $$ComponentProps type won't be declared if the entire props, not a single one, is annotated with the named type. So you can't rely on that to determine if this is rune mode. This is also another reason why static analysis is not reliable, The named type might be imported from another file.

@ciscorn ciscorn force-pushed the svelte-autodoc-ts branch from 95d5665 to 43bc7d5 Compare July 12, 2024 07:59
@JReinhold
Copy link
Contributor

I don't think it'll work if you don't have TypeScript.

Thanks for your valuable input! ❤️ Then I think we should just keep typescript in, with the widest possible version range.

  • This PR fixes a top 3 most requested feature for Svelte support.
  • 80% of users are using TypeScript anyway, so they'll have it installed
  • It won't ever be part of the final bundle, so it's "only" a problem when installing dependencies.

@shilman @kasperpeulen feel free to argue against me here, given we are very actively working on reducing Storybook's dependency graph where possible.

@jasonluy given you're a svelte2tsx maintainer I'd love for any other feedback from you on this approach, or a code review if possible. You might see stuff that we're missing here.

@ciscorn
Copy link
Contributor Author

ciscorn commented Jul 12, 2024

@jasonlyu123, thank you so much!

After rewriting to use @babel/parser, I realized that svelte2tsx depends on typescript (even though the package.json of svelte2tsx says it is a devDependency). I'll need to roll back to using TypeScript.

$$ComponentProps type won’t be declared if the entire props, not a single one, is annotated with the named type

I just realized this myself a moment ago :)

The named type might be imported from another file.

AFAIK, react-docgen also handles only simple cases. I think we have to make some compromises as well.

@shilman
Copy link
Member

shilman commented Jul 12, 2024

AFAIK, react-docgen also handles only simple cases. I think we have to make some compromises as well.

Compared to tsc, react-docgen only handles simple cases. However, it has the concept of a "resolve" so types imported from other paths should work, even if those paths rely on TS aliases. Thus it's much more usable than it has been in the past.

@benmccann
Copy link
Contributor

I realized that svelte2tsx depends on typescript (even though the package.json of svelte2tsx says it is a devDependency).

I checked to make sure we're doing the right thing in svelte2tsx and it looks like Typescript is listed in peerDependencies

@ciscorn
Copy link
Contributor Author

ciscorn commented Jul 15, 2024

I checked to make sure we're doing the right thing in svelte2tsx and it looks like Typescript is listed in peerDependencies

Sorry. I was confused by the Yarn used by Storybook. Yarn does not automatically install peerDependencies.

However, if svelte2tsx directly depends on TypeScript, wouldn't it be better to specify it in dependencies? (I'm not very familiar with Node's package resolution mechanism, though.)

@ciscorn ciscorn marked this pull request as ready for review July 15, 2024 20:53
@ciscorn ciscorn requested a review from JReinhold July 15, 2024 20:53
Copy link

@jasonlyu123 jasonlyu123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not quite familiar with storybook so I didn't dive deep into the integration. But I can give some general suggestions with TypeScript and svelte2tsx API you can explore.

}
let propsRuneUsed = false;

const options = loadCompilerOptions(targetFileName);
Copy link

@jasonlyu123 jasonlyu123 Jul 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parse tsconfig also scans the file system for the files included in the tsconfig include config. It's returned in the ParsedCommandLine.fileNames. It might be quite heavy. It might be worth watching the file and cache the result.

The createProgram is also heavy so might be better in a larger codebase to reuse the problem. You can probably try creating the program with ParsedCommandLine.fileNames but maybe only the svelte files since the ts/js file might never be used. When the targetFileName is not in the list or file content changes then recreate the program. It's also worth exploring passing the old program to createProgram and TypeScript will try to reuse the last program.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for your suggestions! I added some caching mechanisms:

  • If the previously loaded ParsedCommandLine.fileNames includes the target .svelte file, it doesn't reload the tsconfig.
  • Passing the previous program as oldProgram to createProgram. Subsequent calls to createProgram take less than 10ms for the Storybook sveltekit/prerelease-ts example on my machine.

I believe these measures ensure adequate performance. Watching for file changes seems difficult for me and might be too heavy to implement in this PR. What do you think? @jasonlyu123 @JReinhold

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can reference the file watching here.
https://github.com/vitejs/vite/blob/8d0a9c1ab8ddd26973509ca230b29604e872e2cd/packages/vite/src/node/plugins/esbuild.ts#L110

One thing I am also curious about is that sometimes the user starts the dev server when svelte-kit hasn't generated the ./svelte-kit/tsconfig.json file. Does that affect the amount of svelte files being loaded? or maybe the plugin will be loaded after the svelte-kit plugin so it's fine? If it does, maybe the file watch is necessary.

code/frameworks/svelte-vite/src/plugins/generateDocgen.ts Outdated Show resolved Hide resolved
code/frameworks/svelte-vite/src/plugins/generateDocgen.ts Outdated Show resolved Hide resolved
@ciscorn ciscorn force-pushed the svelte-autodoc-ts branch from ca36a55 to 38a16a5 Compare July 28, 2024 02:04
@ciscorn ciscorn force-pushed the svelte-autodoc-ts branch from 6654d3c to 7e2d0f8 Compare July 29, 2024 12:35
@ciscorn ciscorn requested review from JReinhold and jasonlyu123 July 29, 2024 12:48
Copy link
Contributor

@JReinhold JReinhold left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Added a few minor comments, and there's still some left from previous reviews.

stories look great!

@ciscorn
Copy link
Contributor Author

ciscorn commented Sep 7, 2024

@JReinhold Sorry for the delay. I have reflected your review!

@benmccann
Copy link
Contributor

If sveltedoc-parser is able to be removed then it would remove about 100 dependencies from the dependency graph, which is another thing @JReinhold has been looking at. https://npmgraph.js.org/?q=sveltedoc-parser

@JReinhold
Copy link
Contributor

If sveltedoc-parser is able to be removed then it would remove about 100 dependencies from the dependency graph, which is another thing @JReinhold has been looking at. https://npmgraph.js.org/?q=sveltedoc-parser

Unfortunately that would be a breaking change because sveltedoc-parser is slightly better when it comes to slots, see #28492 (comment)

I don't know how many actually use that feature (if any), but I have a hard time justifying dropping the support in a minor, even though I want to.

We should definitely remove it in 9.0 though.

@benmccann
Copy link
Contributor

I wonder if we need the typescript and svelte2tsx dependencies. I'm not an expert in this area, but passing along a note from @dummdidumm:

Comments are returned as part of the AST. You would "just" need to analyze the AST afterwards to find the correct comments. With $props this should be a lot more straightforward. And since Svelte understands TS natively now you also don't need to switch to something else for that (which would've been a big selling point of svelte2tsx before)

I expect that this is perhaps only possible with Svelte 5. Either way, this is probably an improvement as it is, but if we could remove the need for those dependencies as well longer-term certainly that would be nice

Copy link
Contributor

@JReinhold JReinhold left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

Copy link
Contributor

@JReinhold JReinhold left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's actually a regression, see the Chromatic snapshot for argtypes inference:

https://www.chromatic.com/test?appId=6308736456ad2046275c0ae7&id=66dbf220a2751a07f5d4fdc2

It's not easy to spot, but for objects, it changes slightly from:

  "style": {
    "control": {
      "type": "object"
    },
    "name": "style",
    "description": "Styles to use for this component",
    "type": {
      "required": false,
      "name": "object" 👈 correct
    },
    "table": {
      "type": {
        "summary": "object"
      },
      "defaultValue": {},
      "category": "properties"
    }
  },

to:

  "style": {
    "control": {
      "type": "object"
    },
    "name": "style",
    "description": "Styles to use for this component",
    "type": {
      "required": false,
      "name": "{}" 👈 incorrect
    },
    "table": {
      "type": {
        "summary": "{}"
      },
      "defaultValue": {
        "summary": "{}"
      },
      "category": "properties"
    }
  },

The rest of the changes looks good, they just appear to be more correct than what we had before, but that name change is wrong because the Storybook API expects name: 'object':

https://storybook.js.org/docs/api/arg-types#type

I think we can just special case it here:

https://github.com/MIERUNE/storybook/blob/08d3f4f186d18dcca5ecf3c30e030f90a063a81d/code/renderers/svelte/src/docs/extractArgTypes.ts#L45

With something like

name: item.type?.text === '{}' ? 'object' : item.type?.text as SBScalarType['name'],

If you give me edit access @ciscorn I can help get this PR over the finish line, I'd love to merge it in the coming days. 🙏
https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork

@JReinhold
Copy link
Contributor

JReinhold commented Oct 18, 2024

I've fixed the issue reported above and fixed merge conflicts locally, now I just need permission to push to the branch. 🙏

The feature freeze for 8.4.0 goes into effect on Wednesday, Oct 23, so we need to get it in before then.

@JReinhold JReinhold merged commit 08d3f4f into storybookjs:next Oct 24, 2024
101 of 107 checks passed
@JReinhold
Copy link
Contributor

GitHub UI is a bit weird here, this PR didn't actually get "merged", but the other PR #29423 that I created included all the commits from here, and that got merged, which confuses GitHub slightly.

@storybook-bot
Copy link
Contributor

Failed to publish canary version of this pull request, triggered by @JReinhold. See the failed workflow run at: https://github.com/storybookjs/storybook/actions/runs/11980658402

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
builder-vite ci:daily Run the CI jobs that normally run in the daily job. feature request svelte
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants