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

Support JSX shorthand properties, for compatibility with Bun #52057

Closed
5 tasks done
fabiospampinato opened this issue Dec 30, 2022 · 23 comments
Closed
5 tasks done

Support JSX shorthand properties, for compatibility with Bun #52057

fabiospampinato opened this issue Dec 30, 2022 · 23 comments
Labels
External Relates to another program, environment, or user action which we cannot control.

Comments

@fabiospampinato
Copy link

fabiospampinato commented Dec 30, 2022

Suggestion

πŸ” Search Terms

  • jsx
  • tsx
  • shorthand
  • properties

βœ… Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Bun implements JSX shorthand properties, presumably because they are extremely convenient to use, even though they haven't been added to the JSX spec, presumably because that's effectively dead.

πŸ“ƒ Motivating Example

Example code:

const onClick = console.log;
const vdom = <div {onClick} />;

console.log ( vdom );

Basically {foo} is just a shorthand for foo={foo}. The same way outside of JSX {foo} is a shorthand for {foo: foo}.

πŸ’» Use Cases

It's a very natural extension of the syntax, and extremely convenient too, for many components that I personally write about half of the JSX is just redundant and could be deleted if I could use shorthand properties.

As massive of an improvement it is though I can't give up type-checking for it.

Basically the engine I'd like to use supports it already, the code I tend to write screams for this feature, but the JSX spec is abandonware and TS doesn't support it.

Can we please consider adding it and improve the lives of millions of developers? If that's not a strong argument enough can we consider adding this for compatibility with Bun?

I'd be more than happy to submit a PR for this.

//cc @Jarred-Sumner

@rossipedia
Copy link

It's a very natural extension of the syntax, and extremely convenient too, for many components that I personally write about half of the JSX is just redundant and could be deleted if I could use shorthand properties.

As massive of an improvement it is though I can't give up type-checking for it.

I'm going to push back against both of these statements. I don't find it natural, and I don't see it as an improvement. I find the syntax noisy and annoying to scan, because now I have to add one more syntax in my head to keep track of when it comes to passing props. It might make writing props marginally easier by saving a few keystrokes, but it will make reading those props a bit more aggravating, especially in scenarios where both the shorthand and longhand are used.

The more ways you have to achieve a single task, the more complex the language becomes, and the harder it is to learn and teach. Even for somebody like me who's been writing JS professionally for 25 years, and TS since it was introduced, I'd much prefer the language move in the direction of being easier to read and understand, even if it means I have to type a few more characters.

@a-tarasyuk
Copy link
Contributor

Is this part of the JSX specification? Or is this a Bun specific syntax?

@fatcerberus
Copy link

I'm going to guess it's not in the spec, going by these remarks in the OP:

...even though they haven't beed added to the JSX spec, presumably because that's effectively dead.

Basically the engine I'd like to use supports it already, the code I tend to write screams for this feature, but the JSX spec is abandonware and TS doesn't support it.

@fatcerberus
Copy link

@rossipedia Interestingly, despite the additional syntax complexity, I do find shorthand properties to mostly improve readability--in JS. They look oddly out of place in JSX, though, for whatever reason.

@remcohaszing
Copy link

There has been some prior discussion in facebook/jsx#23.

@MartinJohns
Copy link
Contributor

MartinJohns commented Dec 30, 2022

As a workaround you can use object spreading:

const onClick = console.log;
const vdom = <div {...{onClick}} />;

Works well with multiple attributes as well:

const onClick = console.log;
const onBlur = console.log;
const vdom = <div {...{onClick, onBlur}} />;

No idea why some people have the urge to downvote this comment when all I do is provide a viable option that already works and is supported by both JSX and TypeScript.

@mrkev
Copy link

mrkev commented Dec 31, 2022

As much as I agree with @rossipedia, the pattern @MartinJohns communicates is one I've seen quite often. In my eyes their comment is not so much a suggestion as a reminder of something that's out there being done all the time.

This has so long been a mild annoyance in JSX, and spreading is such an obvious workaround, that it's literally the first response to facebook/jsx#23.

Just like JavaScript when it stopped evolving in the "dark age" of IE6, people will find workarounds to common annoyances, and as long as <div {...{onClick, onBlur}} /> is valid JS people will do it. It's not sign of weak language design to add a feature like this. On the contrary. It's good language design to make it easier to do the right thing than to do the wrong thing, and {...{onClick, onBlur}} is both easy and "wrong".

@mrkev
Copy link

mrkev commented Dec 31, 2022

Also, there's been mumblings of JSX2 but IMO waiting for a major version to add non-breaking changes like this has disadvantages. Wondering what @sebmarkbage thinks

@sebmarkbage
Copy link

sebmarkbage commented Dec 31, 2022

One of the major benefits of JSX is not breaking the syntax and having pretty much universal support in implementations. Although it’s not actually quite the same in all implementations.

It has allowed a lot of code to move between implementations without anyone really having to think about how amazing and unlikely that is.

I think once we pull the bandaid and accept breaking changes we can do an even better JSX. That’s what the JSX 2.0 concept is all about.

That said, if we design a JSX 1.5 without breaking changes, what would that look like as a whole? ES2015 was a lot of work because figuring out the interplay and where we wanted to go next was important.

What else would go into a potential JSX 1.5?

@Jarred-Sumner
Copy link

Jarred-Sumner commented Dec 31, 2022

What else would go into a potential JSX 1.5?

Here are a couple ideas, but you would be better at answering:

@dsherret
Copy link
Contributor

dsherret commented Dec 31, 2022

I think this should be unchecked in the viability checklist of the original post?

This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)

That should then answer the question of if this should be implemented in TypeScript at this time.

@nickserv
Copy link
Contributor

nickserv commented Dec 31, 2022

I think the TypeScript team has made the right decision in not encouraging additional nonstandard syntax. JSX is acceptable, since it can still be strongly typed (and both JSX and Flow were designed to be compatible with this approach originally). But I don't think it's a good idea to implement JSX syntax that's not in Facebook's standard, as it will further fragment React and TypeScript tooling from tools implementing the syntax.

@Jarred-Sumner
Copy link

Jarred-Sumner commented Dec 31, 2022

I think this should be unchecked in the viability checklist of the original post?

What makes it a runtime feature? The changes would impact build-time, but not runtime (tsc but not tslib, would not modify transpiled output in an observably different way from what <div foo={foo} /> does)

@MartinJohns
Copy link
Contributor

MartinJohns commented Dec 31, 2022

What makes it a runtime feature?

The relevant parts are probably:

non-ECMAScript syntax with JavaScript output, new syntax sugar for JS

This is generally a no-go. Except for down leveling and legacy features added before this rule (e.g. enums) the TypeScript should output equivalent JavaScript. Though given the special situation that TSX is in it's not super clear if the rule applies here. After all the TSX is not compiled to JSX. (Unless you enable preserve, then <div {onClick} /> compiles to <div {...onClick} />.)

When the subject of JSX extensions comes up with the TypeScript team I've seen these two statements: They want to stick to the JSX spec and align with what Babel generates. Does Babel support this feature?

@fabiospampinato
Copy link
Author

What else would go into a potential JSX 1.5?

@sebmarkbage you'd possibly know best about that, but I would like whoever has the ability to ship new features here to think "shipping what when would deliver the most value to people?"

Like maybe shipping 5 things one at a time delivers more value than shipping all 5 at once but in 5 years. Or shipping 1 feature next month maybe delivers more value than shipping 20 extremely thoughful ones but in 10 years.

@dsherret
Copy link
Contributor

dsherret commented Jan 2, 2023

I think this should be unchecked in the viability checklist of the original post?

What makes it a runtime feature? The changes would impact build-time, but not runtime (tsc but not tslib, would not modify transpiled output in an observably different way from what <div foo={foo} /> does)

@Jarred-Sumner As Martin mentioned, this part "non-ECMAScript syntax with JavaScript output, new syntax sugar for JS" alludes to what "runtime feature" means. By "runtime feature", my understanding of that is any code that will be transpiled for runtime rather than code for types that will be stripped when transpiling.

This is syntax sugar that's not in the JSX spec and just because a spec becomes stagnant doesn't mean it should be abandonedβ€”in my opinion, instead it should be resurrected. If the JSX spec is "abandonware" then the hard work should be done by getting people to adopt a forked spec rather than making unilateral moves in the ecosystem around JSX syntax. Also, it's true JSX is a bit of an exception because it's not in ecmascript, but I don't think that's an excuse to deviate from the JSX spec since many tools need to support this syntax. Making everything work well together (like TypeScript and Bun) is easier to do when everyone follows the spec.

@nickserv
Copy link
Contributor

nickserv commented Jan 2, 2023

Also specs that are forked can be merged upstream if the core team later decides it's valuable (for example io.js merging back into Node.js after being forked to add ES6/harmony features).

@RyanCavanaugh RyanCavanaugh added the External Relates to another program, environment, or user action which we cannot control. label Jan 4, 2023
@RyanCavanaugh
Copy link
Member

I don't think it's appropriate for TypeScript to become the de facto new manager of the JSX spec by taking suggestions like these. We don't have the resources to do that (we're very busy writing a type checker), we don't have the necessary context on which syntaxes have which critical runtime implications on React / future iterations of React / other JSX runtimes (aside: I don't believe this is "just sugar", due to questions of object identity), and we don't want to create arbitrary new work for alternate transpilers in the absence of a strong community consensus. We don't do this to JavaScript (anymore) and it doesn't make sense to starting doing this to JSX now.

even though they haven't been added to the JSX spec, presumably because that's effectively dead.

Hard disagree on this one. I've been involved in many recent conversation in the JSX spec and, as witnessed here, the JSX spec owners are very much alive and active. It's their prerogative to decline any or all new features without presumption of neglect. This feature in particular was more or less explicitly rejected facebook/jsx#118

If someone wants to fork the JSX spec, get another major transpiler or two on board to commit to following it, outline how the owners of that spec have a vested interest in its long-term success, and put in some new features and bugfixes, I'd be fine with that, AKA facebook/jsx#119. But I'm not going to do that here through adding new de facto functionality to a spec that is clearly someone else's.

@STRd6
Copy link

STRd6 commented Jan 5, 2023

For people interested in JSX enhancements and don't mind adding another transpiler to the mix Civet could be an option. It's a language that transpiles to TypeScript and explores some new ground while aiming to remain semantically compatible and familiar. We're working out the details of how to extend JSX in line with modern sensibilities as well as provide syntactic sugar.

Some JSX features Civet supports

In order for things to become a standard or consensus they need to be explored and used in practice so if you are interested in shaping the bleeding edge of JSX check Civet out and share your thoughts.

@fabiospampinato
Copy link
Author

fabiospampinato commented Jan 5, 2023

@RyanCavanaugh fair enough πŸ‘

I don't believe this is "just sugar", due to questions of object identity

Could you expand on that? I don't see how {foo} wouldn't just be sugar for foo={foo} in the context of this proposal, like I'm not proposing that {foo} emebedded in JSX should be literally considered to be a JS plain object, it's just syntax.

even though they haven't been added to the JSX spec, presumably because that's effectively dead.

Hard disagree on this one. I've been involved in many recent conversation in the JSX spec and, as witnessed here, the JSX spec owners are very much alive and active.

As far as I can see JSX was introduced in 2014 and never updated since, if that's correct just looking at the spec that seems indistinguishable from a dead spec, like less tangible progress than that seems impossible.

Maybe the spec is perfect as it is, maybe extreme care is being taken in preparation for the next update, whatever the case may be I think we are all eager to see an update eventually, hopefully it will actually happen.

This feature in particular was more or less explicitly rejected facebook/jsx#118

It looks to me like @sebmarkbage said the following:

I don't think I'd want this to be the solution for a JSX 2.0 world where we could make more breaking changes to avoid the superfluous punctuation. However the bar for doing something like JSX 2.0 is very high and needs to be much more carefully considered. That said, this is probably the least objectionable non-breaking change that we could reasonably easily do.

Which sounds like a "maybe" to me. And the PR was closed by its author after they didn't receive any reply for a couple of weeks after asking Seb for clarification basically.

Unless I'm misunderstanding something Seb in this very thread said that this feature may be part of some hypothetical JSX v1.5.

If anybody with control over the JSX spec could please state what the current position on this proposal is it would probably clarify the situation for everyone.

@RyanCavanaugh
Copy link
Member

Could you expand on that? I don't see how {foo} wouldn't just be sugar for foo={foo} in the context of this proposal,

Shouldn't it be sugar for <elem {...{foo}} /> ? Since I'd expect to be able to write

<bar { a, b, c } />;

the same way I can in an object literal, including using spreads

<bar { a, b, ...c } />

So we have to decide if this is sugar for <bar a={a}, b={b} { ...c } /> or <bar {... { a, b, ...c }} />

If you take something like this and put it through Babel

const tmp = { a, b, ...c };
const m2 = <foo {...tmp} />;
const m3 = <foo a={a} b={b} {...c} />;

you'll see slightly different code generated since the native spread remains as a native spread, but the {...c} goes through _extends which is in turn basically an alias for Object.assign, and Object.assign and native spread do have slightly different semantics. Maybe those differences aren't observable in practice but the burden of proof lies on the positive claim here.

The same problem has come up with template string literals in JSX, where people want to write

<foo a=`literal` />

which is "just sugar", but that's also a mess because of HTML entities which are interpreted in JSX string literals but not in JS template literals, so you have to decide if this is sugar for a JSX string with a template-computed value, or sugar for a JS template string, both of which have extremely strong consistency arguments in favor of them.

"This is just sugar for that" is almost always 98% true, but the other 2% matters. Different people will regularly state that something is obviously sugar for two different things which have material differences in practice. Someone (not us) needs to figure out what this syntax means and write it down so that everyone can agree on the behavior.

@Jarred-Sumner
Copy link

Jarred-Sumner commented Jan 5, 2023

Shouldn't it be sugar for <elem {...{foo}} /> ? Since I'd expect to be able to write

In bun's current implementation, we don't support multiple identifiers in one {}, just <div {foo} />. The rationale was to keep the parser changes as simple as possible and minimize edgecases. From a DX perspective, it would be better to support multiple but I think it's still net improvement to ship without supporting it

For template literal JSX attribute values, which bun doesn't currently implement, in my opinion which is not super relevant as I am not a spec maintainer, it should not support HTML entity codes. Maybe the parser should emit a warning if they are used to explain this

@typescript-bot
Copy link
Collaborator

This issue has been marked as 'External' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
External Relates to another program, environment, or user action which we cannot control.
Projects
None yet
Development

No branches or pull requests