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

Introduce $typed() rune, complimentary to $bindable() #14844

Open
angryziber opened this issue Dec 27, 2024 · 5 comments
Open

Introduce $typed() rune, complimentary to $bindable() #14844

angryziber opened this issue Dec 27, 2024 · 5 comments

Comments

@angryziber
Copy link

angryziber commented Dec 27, 2024

Describe the problem

As explained in #14810 and #9241, using $props rune is inconvenient with TypeScript.

Describe the proposed solution

There is already a typed $bindable rune that can be used for inline types. We could introduce a $typed (non-bindable) rune to be able to specify fallback value and type at the declaration site of the prop, not separately:

let {
  size = $typed<'sm' | 'md' | 'lg'>('md'), // optional
  color = $typed<Color>(), // required
  value = $bindable<string>()
} = $props()

instead of

let {
  size = 'md',
  color,
  value = $bindable()
}: {
  size?: 'sm' | 'md' | 'lg',
  color: Color,
  value: string
} = $props()

Importance

would make my life easier

@Leonidaz
Copy link

Leonidaz commented Dec 27, 2024

IMO, it seems like it would be a good compromise and, without necessarily encouraging its usage, let people who prefer this syntax use it instead. Also, this would be completely compiled away so no change in the generated code.

I think that maybe a better name for it would be $readable() or $readonly(). So, in addition to enforcing types and required vs optional, semantically it would work in opposition to $bindable().

UPDATE:

I think $typed is a good name for it, as $readable can be reserved for the future usage, e.g. readable signal.

I think it could also only "exist" when declaring props and only if the code block is typescript. Otherwise, svelte can disable the usage of this rune, e.g. show error / warning, e.g. This rune is only available in typescript

@kran6a
Copy link

kran6a commented Dec 28, 2024

Personal opinion ahead

I don't think this should be added to svelte for various reasons:

  1. Not all people use svelte with typescript. There are also js weirdos out there. JS folks wouldn't be able to use $typed with type parameters and it does nothing and provides zero value if used without type parameters
  2. I think the required changes outweight the DX improvement. Personally I like my frameworks and libraries small and concise as they are less bug-prone, easier to maintain and update and easier to learn and master
  3. Unlike $bindable, it can trivially be implemented in userspace. If you want better UX define it on a "polyfill" file and add the definition to typescript list of globals and it is available globally just like $bindable

Example userspace implementation

function $typed<Type>(): undefined;
function $typed<Type>(def: Type) : Type;
function $typed(def?: any) {
    return def;
}
const a = $typed<"a" | `n-${number}`>();
const b = $typed<"a" | `n-${number}`>("b"); //Error
const n = $typed<"a" | `n-${number}`>("n-1");

@Leonidaz
Copy link

Leonidaz commented Dec 28, 2024

Personal opinion ahead

I don't think this should be added to svelte for various reasons:

  1. Not all people use svelte with typescript. There are also js weirdos out there. JS folks wouldn't be able to use $typed with type parameters and it does nothing and provides zero value if used without type parameters

Then those people won't use it but if they happen to use it, it's completely harmless.

The documentation also helps. In addition, if the code block is not typescript, svelte can disable the usage of this rune, e.g. show error / warning, e.g. This rune is only available in typescript

  1. I think the required changes outweight the DX improvement. Personally I like my frameworks and libraries small and concise as they are less bug-prone, easier to maintain and update and easier to learn and master

I don't see this is a problem, if you don't use it, you don't see it. The change is pretty trivial and it adds zero code to the output, as it's completely compiled away, and thus it can't cause any bugs.

  1. Unlike $bindable, it can trivially be implemented in userspace. If you want better UX define it on a "polyfill" file and add the definition to typescript list of globals and it is available globally just like $bindable

Example userspace implementation

function $typed<Type>(): undefined;
function $typed<Type>(def: Type) : Type;
function $typed(def?: any) {
    return def;
}
const a = $typed<"a" | `n-${number}`>();
const b = $typed<"a" | `n-${number}`>("b"); //Error
const n = $typed<"a" | `n-${number}`>("n-1");

This or anything created in user-land doesn't work with $props() destructuring which is the whole point of this rune. We need some compiler magic to transform it to a regular props retrieval in the output.

@webJose
Copy link
Contributor

webJose commented Dec 28, 2024

let {
  size = 'md',
  color,
  value = $bindable()
}: {
  /**
   * Sets the desired size of the component.
  */
  size?: 'sm' | 'md' | 'lg',
  /**
   * Sets the desired color of the component's text.
  */
  color: Color,
  /**
   * Sets the component's displayed value.
  */
  value: string
} = $props()

How would you allow JsDoc? All our properties for all components in our component library carry these as they show up in Intellisense, making the use of the components significantly easier.

@Leonidaz
Copy link

Leonidaz commented Dec 28, 2024

let {
  size = 'md',
  color,
  value = $bindable()
}: {
  /**
   * Sets the desired size of the component.
  */
  size?: 'sm' | 'md' | 'lg',
  /**
   * Sets the desired color of the component's text.
  */
  color: Color,
  /**
   * Sets the component's displayed value.
  */
  value: string
} = $props()

How would you allow JsDoc? All our properties for all components in our component library carry these as they show up in Intellisense, making the use of the components significantly easier.

That wouldn't change since you're not defining the types "inline". You wouldn't use this rune at all.

This rune is only needed if you wanted to define types inline:

let {
  size = $typed<'sm' | 'md' | 'lg'>('md'), // optional
  color = $typed<Color>(), // required
  value = $bindable<string>()
} = $props();

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

No branches or pull requests

4 participants