Closed clemmy closed 6 years ago
The authoritative syntax spec is the README.md file. The AST.md file is just a recommended form to be used by parsers but not required.
This needs to be defined fully in README.md without referencing AST.md and currently it doesn't.
In the actual syntax spec, we don't have such a flag precedence so I think it should be its own entry point. I.e.
JSXFragment:
<
>
JSXChildrenopt <
/
>
That makes sense. Do you think it'll be nonsensical to take out the isFragment
flags and simply identify the fragments with JSXIdentifier
whose name is ''
? I guess that would still have the constraints to name being ''
and attributes being []
though.
On another note, do you know how closely implementations follow specifications? I'm just wondering if I'll need to re-write my forks to follow the new spec for the first upstream into Babel.
They follow reasonably close and we try to adjust either the implementation or the spec to align whenever they diverge.
@sebmarkbage Also, why interface JSXFragment <: Node
instead of interface JSXFragment <: Expression
, similar to how JSXElement
is currently?
Ah, yea JSXFragment should probably be Expression. You're right.
@sebmarkbage It seems a bit unnecessary to have JSXOpeningFragment
and JSXClosingFragment
since those always represent <>
and </>
in code. What do you think of just having JSXFragment
, with the grammar being:
<> JSXChildrenopt </>
?
I think in the spec it is enough to just have JSXFragment. The reason you want to also have the OpeningFragment and ClosingFragment in the AST is so that you can associate meta data such as start and end column/line location information with each of those separately.
@sebmarkbage Ready for review again
cc @syranide @RReverser @hzoo @loganfsmyth
We're leaving out keying for the moment? Or is there an intended alternate mechanism (special function)?
Keying is kind of important for a bunch of use-cases so I think it's important to at least have an "answer" for when the question/issue is posted.
You bring up a good point @syranide. I think that key-ing a fragment is a rare enough use case that if you really wanted to do it, you would simply not use the shorthand syntax (<></>
) for it, but be more explicit and use what it de-sugars to (e.g. <React.Fragment key={...}>
or <#fragment key={...}>
). What do you think?
You bring up a good point @syranide. I think that key-ing a fragment is a rare enough use case that if you really wanted to do it, you would simply not use the shorthand syntax (<></>) for it, but be more explicit and use what it de-sugars to (e.g.
or <#fragment key={...}>). What do you think?
E.g. any time you have a conditional with multiple different return-values that shouldn't be reconciled, they should be keyed. So I suspect it isn't as rare as one thinks, simply because most cases goes unnoticed and is only rarely done for that reason (thus; yes rare, but needs an official story). React by chance reconciling correctly or the incorrect behavior not being visually indicated/broken. I've seen a few such posts, where they have been utterly confused because it "usually works" and "looks right".
But it's also interesting to think about this case:
foo() {
return <>...</>;
}
bar() {
return <>...</>;
}
render() {
return <div>
{cond ? foo() : bar()}
</div>;
}
What if you don't want to be reconciled, should you as good practice key the fragments in foo
/bar
or should you apply the key somehow in render
?
Also I wouldn't think it desugars to any of your examples but rather a pre-keyed array (or special React-method). If it desugars to something "sensible" that seems like an ok balance, but I don't think so?
PS. I don't want to stall the proposal. Keying, however it ends up being done, should be well compatible with this proposal.
I think the idea is that the official story is <Fragment key="...">...</Fragment>
and <>...</>
is short hand for <Fragment key={null}>...</Fragment>
. Of course, you're also free to properly key the individual items in the fragment which also avoids the issue.
@syranide If I recall correctly, if the children are different inside foo's returned fragment and bar's returned fragment, then they shouldn't be reconciled. @acdlite may be able to shed more light on that. Also, as @sebmarkbage mentioned, it will be de-sugaring into something sensible.
Something to keep in mind during our discussion is that JSX is meant to be decoupled with React, and I think reconciliation is an implementation detail specific to React.
@sebmarkbage I'm assuming Fragment is a reserved keyword (or a special imported class) and not just the equivalent of a regular component class (i.e. opaque)?
@syranide If I recall correctly, if the children are different inside foo's returned fragment and bar's returned fragment, then they shouldn't be reconciled.
Only if the component classes are different, e.g. if they are two different buttons (save and discard) of the same class then it could have bad side-effects when one gets reconciled with the other.
Something to keep in mind during our discussion is that JSX is meant to be decoupled with React, and I think reconciliation is an implementation detail specific to React.
EDIT: Yeah of course, but how well it works for React seems like a good litmus test.
@syranide Fragment will be an opaque component class exported under the React namespace.
Thanks @sebmarkbage for merging my PR! I'll update my forks to have the correct implementation of the fragment syntax, and try to get it upstream into Babel/Babylon. :)
Something to keep in mind during our discussion is that JSX is meant to be decoupled with React, de-sugars to (e.g.
or <#fragment key={...}>)
How would this affect JSX in a non-React context, i.e with the pragma /* @jsx h */
?
The #fragment
or some kind of global symbol better favors inter-op with non-React libs.
@thysultan For React, we're planning to have React.Fragment export a string, '#fragment'
, but we're not finalized on this yet. Inter-oping with non-React libs is definitely something we take into consideration, thanks for bringing it up! You can check out our discussion and progress here: https://github.com/facebook/react/pull/10783
@clemmy It may not matter, but it seems like your changes to the AST and the README disagree? AST.md permits a fragment as a child of an element or another fragment, but the README doesn't. I saw the comments above that the README should be the authoritative source but I just wanted to check.
@uniqueiniquity Ah, I think you're right - Are you referring to adding it over here:
JSXChild :
JSXText JSXElement { JSXChildExpressionopt }
Yep, that's what I meant.
Thanks for the catch!
Is this already released or do you have a version ETA? I love this :)
Hey @duivvv. We're going to be officially releasing this with a blog post next week. 😄
This is the JSX spec change for supporting
<></>
fragment syntax in JSX. One of the tougher design decisions on this spec was whether to go with a new node type for fragments (e.g.JSXFragment
, or re-use the existingJSXElement
node with an additionalisFragment
field. I opted for the latter because:JSXElement
in terms of the AST (e.g. it contains children, openingElement, and closingElement)To illustrate the last point, I've written forks of babel, babylon, and eslint-plugin-react to prototype, and it turned out to have very few LOC.
Some example scenarios:
< ></> // success
<></> // success
< attrib></> // fail
<>hi</> // success
<><span>hi</span><div>bye</div></> // success
<></something> // fail
UPDATE Spec has been updated to introduce new node type
JSXFragment
instead of re-usingJSXElement
.https://github.com/facebook/jsx/issues/84