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

feat: Bindable types #11225

Merged
merged 17 commits into from
Apr 24, 2024
Merged

feat: Bindable types #11225

merged 17 commits into from
Apr 24, 2024

Conversation

dummdidumm
Copy link
Member

@dummdidumm dummdidumm commented Apr 18, 2024

This is a typings PR and the companion PR to sveltejs/language-tools#2336

It introduces two new types:

  • Binding: Marks a property as being bound (i.e. you must do bind:x)
  • Bindable: Marks a property as being able to be bound (i.e. you can do bind:x)

Language tools then uses this generate code accordingly which then generates type errors like this:

Doing bind:x when you can only do {x}:
image

All the other type gymnastics are there to ensure that you don't interact with these bindable types when using mount or hydrate or ComponentProps<MyComponent>, i.e. these two types should be mostly opaque for day-to-day users.

For backwards-compatibility, all properties are automatically wrapped with Bindable, which means existing type definition files will continue to work from a types perspective. Language tools opts into strict bindability by providing its own constructor definition for all generated classes in runes mode which omits the "wrap everything with bindable" behavior. In Svelte 6 or 7 we can then remove this backwards compatibility and the language tools can stop adding a custom constructor, and the version after that we can adjust the constructor to not include the target etc properties anymore and just be ComponentConstructor<Props> = Props, and one or two versions after that we can remove it completely.

I also added an overload for the parse method to more accurately describe the output type depending on the given modern setting.

Before submitting the PR, please make sure you do the following

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • Prefix your PR title with feat:, fix:, chore:, or docs:.
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.

Tests and linting

  • Run the tests with pnpm test and lint the project with pnpm lint

Copy link

changeset-bot bot commented Apr 18, 2024

🦋 Changeset detected

Latest commit: 359653f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
svelte Patch

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

@Rich-Harris
Copy link
Member

Doing {x} when you can only do bind:x because x is a component export (like export function x() {}):

I recall a conversation (can't find it now) where we decided that this should no longer be supported (at least in runes mode) — if a component has export function x() {} then the way to invoke it should be this:

<script>
  import Child from './Child.svelte';

-  let x;
+  let child;
</script>

-<Child bind:x={x} />
+<Child bind:this={child} />

-<button onclick={() => x()}>click me</button>
+<button onclick={() => child.x()}>click me</button>

This is a much clearer separation between props and exports, which is lacking in Svelte 4. Does that affect this PR at all, or is it an entirely separate conversation?

* https://svelte.dev/docs/svelte-compiler#svelte-parse
* @overload
* @param {string} source
* @param {{ filename?: string; modern: true }} options
Copy link
Member

Choose a reason for hiding this comment

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

tangential to this PR but it seems extremely weird to pass in filename here, just so it can be attached to an error object if there's a parse error. i think we should get rid of that

@dummdidumm
Copy link
Member Author

I recall a conversation (can't find it now) where we decided that this should no longer be supported (at least in runes mode) — if a component has export function x() {} then the way to invoke it should be this ...

I only recall this PR #11064 which is related but didn't talk about this specifically. I would be ok with making that breaking change (since as you say it makes things easier to reason about). It would affect this PR a little insofar that a one or two helper types can be shrunken down a bit. The majority of that work would happen in language tools.

@Rich-Harris
Copy link
Member

Alright, let's do it. PR looks good aside from that

@dummdidumm
Copy link
Member Author

#11238

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants