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

Implement proposal for standardized JSX #25312

Closed
thesoftwarephilosopher opened this issue Aug 30, 2024 · 12 comments
Closed

Implement proposal for standardized JSX #25312

thesoftwarephilosopher opened this issue Aug 30, 2024 · 12 comments

Comments

@thesoftwarephilosopher
Copy link

Proposal is at https://vanillajsx.com/unity/

@marvinhagemeister
Copy link
Contributor

What's the demand for this particular kind of proposal like? Are there any frameworks out there that want to use it?

The way it's specified every JSX node has a different object shape due to spreading the attributes on it. There is no guarantee that two JSX nodes stemming from that proposal have the exact same keys in the same order. Having different shapes makes them megamorphic from a JS engine perspective which will lead to code always being deopted that handles them. It's effectively the worst case scenario for performance in JS engines and would make this proposal significantly slower in real world apps compared to the existing JSX standard.

Overall it's very unlikely that we're going to support it, unless a significant amount of users want it.

@thesoftwarephilosopher
Copy link
Author

@marvinhagemeister

  1. I have a framework that uses it
  2. It's a common denominator for all frameworks to be able to use
  3. It's a form of syntactic sugar that could eventually be standardized into ECMAScript itself
  4. The JSX expression transformation doesn't use any spreads except what the software author uses

@marvinhagemeister
Copy link
Contributor

  1. It's great that it works out for you, but to add it to Deno we need more than one person needing it.
  2. Can you see the other frameworks like Angular, React, Vue or others switching to it some day? What would they gain from this proposal over the existing JSX standard?
  3. If it gets standardized in ECMAScript itself we're going to add it, no questions asked
  4. The playground uses spreads, are you saying that the playground doesn't do the proposal justice?

@thesoftwarephilosopher
Copy link
Author

There are only two major uses left for transpilers: types and JSX.

I said around 2016 that types were going to become standardized, and everyone laughed it off. It's now at stage 1.

JSX will eventually be standardized. But nobody wants to standardize classic or automatic runtime transformations, because we all know they're stop-gap solutions with serious flaws.

I'm proposing an alternative implementation that has the real potential to become standardized due to its isolated nature. I just have no ability to get the word out that my proposal exists.

Deno would implement it if it become standardized, but it won't become standardized unless at least some runtimes implement it. So this feature request was to solve the chicken/egg problem.

I'm not absolutely proposing this be implemented right now. The purpose of this issue, and the one I filed in Bun, and the one I'll file in Babel soon, is to spark conversation with the intent of garnering the interest in framework and runtime authors, to see whether anyone thinks it's worthwhile to start implementing this as a possible solution towards future JSX standardization.

@thesoftwarephilosopher
Copy link
Author

Also yes, the playground uses spreads, but only when code authors use them. That was my point: not all JSX expressions contain spreads, and there's nothing inherent to JSX itself or my proposal that requires spreads. At most you're saying that a currently existing spread optimization will go away with this proposal. And I think that's fair to say. But I also think that it's worth it on the path to standardization. Plus, there are other benefits to my proposal, including the ability to use multiple different, incompatible JSX runtimes in the same file, which make up for the loss of an incidentally current optimization.

@thesoftwarephilosopher
Copy link
Author

Also, vanillajsx.com itself was on the top of hacker news a few weeks ago for a weekend, and had lots of discussion. There is interest about this concept. But my HN submission this morning of https://vanillajsx.com/unity/ got I think 1 view. So I do think there's a lot of potential interest in this proposal, I just can't prove it because I just can't get word out that it exists. I bet if you shared the link with some of your colleagues, at least a few would be interested in the idea.

@BlackAsLight
Copy link

BlackAsLight commented Aug 30, 2024

If you want more attention around your proposal then I think you need to talk more about the flaws of the current implementation and how your one solves them.

You also never addressed the above mentioned supposed issue that your output is bad for performance.

@thesoftwarephilosopher
Copy link
Author

more about the flaws of the current implementation and how your one solves them.

Can you imagine a standardization that says JSX must have auto-imports? Can existing auto-imports support more than one JSX runtime within the same project? Within the same file? Does anyone even recommend the classic runtime? Could you imagine a "well known global function" ever getting standardized?

There's no path to standardization for the JSX status quo.

@thesoftwarephilosopher
Copy link
Author

You also never addressed the above mentioned supposed issue that your output is bad for performance.

I think @marvinhagemeister was misunderstanding the proposal, because the first transformation on the page is

const b1 = {
  [Symbol.for("jsx")]: "a",
  ...attrs,
  href: "/foo",
  children: ["Click me"]
};

and so I think he therefore thought that every JSX transformation results in ...attrs. But that was only for

const b1 = <a {...attrs} href='/foo'>Click me</a>;

All the rest of the examples are like this:

const b1 = <a href='/foo'>Click me</a>;
// becomes
const b1 = {
  [Symbol.for("jsx")]: "a",
  href: "/foo",
  children: ["Click me"]
};

const b2 = <Button w={3} h={4}>Click me</Button>;
// becomes
const b2 = {
  [Symbol.for("jsx")]: Button,
  w: 3,
  h: 4,
  children: ["Click me"]
};

const b3 = <div class='foo' style='color:red' />;
// becomes
const b3 = {
  [Symbol.for("jsx")]: "div",
  class: "foo",
  style: "color:red",
  children: []
};

function Button(attrs, children) {
  return <>Button title: {children}.</>;
}
// becomes
function Button(attrs, children) {
  return {
    [Symbol.for("jsx")]: "",
    children: ["Button title: ", children, "."]
  };
}

@marvinhagemeister
Copy link
Contributor

No, you're too focused on the spread alone. The problem is that b1 has a different shape than b2 which is different from b3 too. This is already 3 distinct object shapes in this little snippet, which means it will be treated as megamorphic in JS engines. Or in other words a jsx render function that takes these JSX nodes as input and renders them to HTML will be immediately deoptimized, because it will see different object shapes on every call.

I'd highly recommend reading up about object shapes, shape transitions and inline caches in JS engines to get a better understanding of this topic. If you google a bit you'll find lots of information about that online. Speaking from experience of maintaining Preact rendering performance over the years and having built the precompile transform in Deno, getting these details right matters quite a bit for performance, both on the server and in the browser.

@thesoftwarephilosopher
Copy link
Author

@marvinhagemeister Thanks for the clarification. You clearly know a lot more than me about the internals of V8 etc. Can you recommend an alternation to my proposal to get rid of that inefficiency? Maybe just moving JSX attrs back into an attrs field like it typically is, would that be enough? Or what would you recommend as the most optimization-friendly transformation(s)?

@thesoftwarephilosopher
Copy link
Author

Thanks to your feedback @marvinhagemeister I've updated the proposal to be compatible with optimizations you mentioned.

It now matches the behavior of jsx-runtime functions, and always transforms into:

declare const jsx: unique symbol;

interface JsxExpr {
  [jsx]: true,
  tag: string | any,
  attrs?: {
    children?: any | any[],
    [attr: string]: any,
  },
}

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

3 participants