-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
feat: Variadic snippets #9988
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
feat: Variadic snippets #9988
Conversation
🦋 Changeset detectedLatest commit: 84c6eda The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
@tcc-sejohnson at one stage we had planned to make the |
I think it's just... slightly uncanny the other way, and shrug no reason to prevent ourselves from doing rest args in the future if we want to. |
sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
almost done with review, just taking a break for dinner
struggling a bit with the CI failure. can't reproduce locally. that's the only thing blocking merging right now |
actually scratch that, I can reproduce it. haven't figured it out yet though it does appear to be related to recent changes to |
The generated types were invalid TypeScript ("cannot use unique symbol at this position"). Use utility types to ensure the types are not unpacked during type generation. Leftover from #9988
* give this another try * fix: lint * fix: Forgot to save * feat: it works boiiii * look, ok, it did work, i just needed to update the snapshots * bruh * changeset * feat: ok I think the client snippet block finally works * feat: current tests pass; I'm sure I'm missing stuff for new things * fix: snapshot * feat: I think non-destructured rest should work now? * chore: duplicated computation * feat: Tests (passing and failing * feat: it's... alive? * chore: Clean up my messes * chore: devtime stuff * fix: fmt * chore: see if this fixes repl * chore: make naming more offensive * fix: Don't throw on missing keys, return undefined as it usually would * Update packages/svelte/src/compiler/phases/1-parse/state/tag.js Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * Update packages/svelte/src/compiler/phases/1-parse/state/tag.js Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * fix: Hopefully default param values now work * dumb * types * feat: Test it * fix: Turns out javascript parameters are optional * feat: The Final Solution * document function * feat: Better bracket matching, unit tests * feat: exclude test files from publish * feat: More unit tests * feat: Use more efficient parsing for @const * Update .changeset/curvy-cups-cough.md Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * Update packages/svelte/package.json Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * Update packages/svelte/src/compiler/phases/1-parse/utils/bracket.js Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * fix: changesets * chore: additional comments * fix: kill foreach * fix: foreach again * feat: Docs * Revert "fix: kill foreach" This reverts commit 9a688cc. * fix: My own stupidity * fix: style * fix - maybe * Update sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md * Update tag.js Co-authored-by: Rich Harris <richard.a.harris@gmail.com> * Update .changeset/curvy-cups-cough.md Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * chore: Remove rest params * Delete .changeset/eighty-rivers-wash.md * fix: Honestly idk why it was broken but it's fixed now * fix: var name lol * fix: typegen * fix: idk * fix: It looks like a bunch of unformatted shit came in through main?? idk * Revert "fix: It looks like a bunch of unformatted shit came in through main?? idk" This reverts commit ab851d5. * fix: format again * this is getting ridiculous * Update tag.js Co-authored-by: Rich Harris <richard.a.harris@gmail.com> * fix errors * simplify a bit * use read_context * use read_context for const as well * remove unused code * unused import * unused export * remove spread args. sorry elliott * tidy up SnippetBlock interface * fix test * simplify * tweak * revert example, so that it matches the surrounding text * move PropsWithChildren back to public.d.ts * update typing docs, so that it flows from previous example * temporarily revert const parsing changes, to get prettier working again (???) * oops --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> Co-authored-by: Rich Harris <richard.a.harris@gmail.com> Co-authored-by: Rich Harris <rich.harris@vercel.com>
The generated types were invalid TypeScript ("cannot use unique symbol at this position"). Use utility types to ensure the types are not unpacked during type generation. Leftover from #9988
A REPL showing that this handles every case I could think of
Summary
This would close #9672, assuming we want it. I kind of like it!
This allows snippets to have multiple arguments in the type system. For example, you could type a component expecting a two-argument snippet as:
Since spreading is also now acceptable, you could also:
The only gotcha being that spreading a
$state
array would lose reactivity unless the items are boxed state.Implementation notes:
Adding additional arguments was easy -- basically just improving the parsing for snippet blocks, removing a "one arg only" check in one place, and sticking loops in some other places.
Adding support for rest params and spread arguments was harder. In the case of spread arguments, a function call like this:
is a problem for us, because when compiling calls to snippet functions, we turn each argument into a thunk. We obviously can't thunk
bar
in this case:because it would explode. The only solution I could come up with was to basically deferred-thunk rest arguments, so:
I don't think this is any significant performance penalty; JavaScript would already have to turn any spread iterable into an array anyway, so basically all we're doing is the additional runtime thunkification. It'd be worth testing, though, to make sure we're not significantly regressing.
In the case of rest params, the problem was even more complicated. A "normal" snippet gets compiled like this:
becomes:
Basically, we make sure that all of the arguments to the function are accessible by their original names -- for non-destructured arguments, we do nothing; for destructured arguments, we find all of the destructured identifiers and thunkify them into variables that can then be called elsewhere (as you can see with
one()
andtwo()
.With rest params, we lose the ability to do this -- because
...rest
is an array, we can't anticipate when or where the actual items inside will be accessed, and therefore can't make sure their thunkified values are called.rest[0]
would result in an uncalled thunk rather than the value expected!The only solution I could come up with for this was to wrap the
rest
array in a super simple proxy:This basically says "if you try to access a numeric property on this array, I'll return the value behind the function at that index" -- it's the exact opposite of
thunkspread
. This also works with other array methods likefind
andmap
, which have to use numeric property accessors to get their values out of the array.This enables the compiler to do this:
becomes this:
Is there a performance penalty? Yes, but crucially, only for rest params -- other params are unaffected. And in practice, I'm not sure how big that penalty actually is over the penalty faced by normal rest params.
Before submitting the PR, please make sure you do the following
feat:
,fix:
,chore:
, ordocs:
.Tests and linting
pnpm test
and lint the project withpnpm lint