facebook / jsx

The JSX specification is a XML-like syntax extension to ECMAScript.
http://facebook.github.io/jsx/
1.96k stars 133 forks source link

JSX 2.0 #65

Open sebmarkbage opened 7 years ago

sebmarkbage commented 7 years ago

We have accumulated a number of these nice-to-haves breaking changes that would be nice to incorporate. However, at this point JSX is a hugely adopted syntax in all kinds of tooling. Making breaking changes will churn that ecosystem significantly. I think it is probably only worth doing as a batch when the accumulated amount of changes makes it worth while.

Let's call it JSX 2.0.

What should go into it?

Since a completely incompatible version of JSX has landed in Reason it would be good to find a way to unify them.

I'd say at least we'd want:

Some more controversial/problematic ones that could plausibly be fixed:

What else?

cc @jordwalke @SanderSpies

caub commented 7 years ago

you don't need to spread the 'fragment' array, React.createElement does it https://jsfiddle.net/crl/fsrny4jL/ (same in jsx)

oleksandr-shvets commented 7 years ago

@mstijak this feature was deprecated, but you still can turn on it, see https://github.com/RReverser/acorn-jsx: jsx: { allowNamespacedObjects: true } Or use dashes: <input type="text" value-bind="firstName">.

ghost commented 7 years ago

I think the biggest problem of jsx is that jsx does not provide some metadata of evaluated expressions so react don't have the ability to diff only on dynamic data and skip all static bindings.

mstijak commented 7 years ago

@alexander-shvets: I'm aware of the options. I like the dot syntax for attributes which require some kind of post-processing. Using namespaces for that purpose is convenient, but feels hacky.

kemurphy commented 7 years ago

@caub: There's a subtle difference -- in the example you gave, React will see a single child that's an array and emit warnings about wanting keys. With spread, the equivalent would be:

var h = React.createElement;
ReactDOM.render(
  h('div', null,
    h('span'), 
    h('span'),
    h('span')
  )
, app)

No arrays, no useless flattening of a statically-known fragment, no key warnings. :)

gaearon commented 7 years ago

Hey folks, just to make it clear, there is no immediate work planned on "JSX 2.0". This is just an issue to batch a few old proposals and also start figuring out how to eventually converge with Reason's version of JSX.

This is an umbrella issue for a few proposals that seem to make sense together. It's not a list of features of "JSX 2.0", nor an announcement of an intent to implement "JSX 2.0".

Also, while we appreciate the input, let's not turn this thread into a giant bikeshed. If you'd like to discuss a specific proposal please create a new issue (unless one already exists which is also likely).

Thanks!

taypo commented 7 years ago

I always felt the need for an xpath like object query language built in. Or may be a simpler expression language (eg. java uel, spring spel etc.).

toddlucas commented 7 years ago

I would like to see class in addition to className and for in addition to htmlFor. I know they're reserved keywords in JavaScript, but JSX isn't JavaScript. It seems like something that would be possible to handle at that level without interfering with JS parsing.

The reason for suggesting this feature is that it makes it more difficult to copy and paste code between environments. There are other considerations, of course, such as embedded, bracketed code (e.g., the way the style attribute value is handled as an object literal).

Regardless, thanks!

cpojer commented 7 years ago

It seems like based on the current wishlist, even if this is a breaking change, we can easily create codemods that go both ways: from JSX to JSX2 and from JSX2 to JSX. No need to freak out.

pirate commented 7 years ago

@kevinSuttle re: Supporting <style> tags in components, I think a current consensus is to use JS template strings (which can span multiple lines), or variables.

<style>{`
    .your-css-here {color: #333;}
`}</style>
// or
<style>{component_style}</style>

I'd rather not introduce a new parsing exception just for <style> tags.

Perhaps a better solution is an escaping mechanism that allows generally including non-JSX text inside of any tags? Maybe something like plain ` without the curly braces, which ties into #21, #55, #64 above:

<style>`
    sdf
`</style>
markshust commented 7 years ago

this is a common occurance with arrays (and js for that matter):

{(users && users.length) &&
  <ul>
    {users.map(user => <li>{user.username}</li>)}
  </ul>
}

perhaps we can have some sort of special character for this that eliminates the need to check for the object:

{~users.length &&
  <ul>
    {users.map(user => <li>{user.username}</li>)}
  </ul>
}

and perhaps it can even work with other methods:

<ul>
  {~users.map(user => <li>{user.username}</li>)}
</ul>

and object props:

<ul>
  <li>{~user.username}</li>
</ul>

i'm all for anything that can make code more readable with less typing, and still expressive enough to know what is going on.

better yet, if the special character maybe isn't even needed and jsx does the checks automatically.

chrisnankervis commented 7 years ago

I'd like to see a way to comfortably use deprecated HTML attributes. We're using JSX in an application that is used to build HTML emails; to avoid warnings we're prefixing the unsupported attributes with "data-", and then replacing them before send.

arisAlexis commented 7 years ago

I'd love to have conditionals so I don't have to write this.enabled && <div>

jpuri commented 7 years ago

Nice changes proposed @sebmarkbage, it would be nicer to have JSX closer to JS then HTML.

OpakAlex commented 7 years ago

Don't change anything. It works good, learn some machine learning, if you don't known where put your time. Or maybe just do world better, but please, don't change API :)

holloway commented 7 years ago

While I'm generally wary of conditionals in JSX I do notice the syntactic complexity of React routers, and the hoops they have to jump through to lazy load components (in order to avoid components being instantiated immediately).

<Route path="/user" component={User}/>
<Route path="/task" component={Task}/>
<Route path="/task" component={Footer}/>
...

When a more natural syntax for any router would be,

<Route path="/user">
    <User andAnyArbitraryProps={here}/>
</Route>
<Route path="/task">
    <Task moreProps={here}/>
    <Footer/>
</Route>
...

But they can't do that because <User>, <Task>, and <Footer> are instantiated immediately, regardless of the route. Some routers can have hundreds of components, and it would be extremely inefficient to instantiate all those components and then only use one or two, so instead they typically use a component={User} approach with the router being responsible for instantiating the component itself, and of course each router has its own custom way of passing props. This all feels like a weakness of JSX.

Essentially what these routers probably need is delayed / lazy loading / optional execution of JSX functions.

One possible solution... JSX could support deferring the instantiation of components by wrapping them in a function. A parent component's this.props.children wouldn't be the instances themselves but would be a function that returns the instances. This means that JSX would need to support generating something like React.createElement( () => React.createElement(User) ) (compared to the existing React.createElement(User)). I'm guessing that this decision to allow lazy loading would be based on the parent component <Route> having a flag set, such as:

Route.deferChildren = true;

Then because the Route has opted-in to having lazy-loaded children it would have a render() that included {this.props.children()} (rather than {this.props.children}). This way it wouldn't require any coordination with React (I think), and it would just be a JSX change.

This would also happen to allow 3rd-party libraries to do conditionals by making an If component... eg, <If test={condition}><LazyLoadedChild/></If>.

Considering all it's doing is just wrapping props in an arrow function, then it might be useful to generalise it to all props:

Route.deferProps = ['children', 'someOtherCPUIntensiveProp']

A problem I see with this idea is that arrow functions are cheap but not free, and so rewrapping the components every time render() is called would waste memory. I guess caching and reusing these arrow functions (eg. cache them on the parent component) might be a good optimisation.

jamesseanwright commented 7 years ago

@toddlucas While className and htmlFor may seem nuanced, they're named after Element.prototype.className and HTMLLabelElement.prototype.htmlFor respectively, which in turn are named to circumvent the conflicts you've described. JSX is simply providing a means of setting these properties.

miraage commented 7 years ago

@mohsen1 I always separate same kind of logic into own method. IMO render() method should be simple and contain zero logic from first to last line of JSX = we see what is exactly returned.

class Foo extends Component {
  renderCTA() {
    const { isLoggedIn, onCTAClick } = this.props;

    if (isLoggedIn) {
      return (
        <button className="foo__cta" onClick={onCTAClick}>
          CTA!
        </button>
      );
    }

    return null;
  }

  render() {
    return (
      <div className="foo">
        {this.renderCTA()}
        <div className="foo__description">
          Lorem ipsum...
        </div>
      </div>
    );
  }
}

Same goes for stateless components - just use hoisting.

const Foo = ({ isLoggedIn, onCTAClick }) => {
  return (
    <div className="foo">
      {renderCTA()}
      <div className="foo__description">
        Lorem ipsum...
      </div>
    </div>
  );

  function renderCTA() {
    if (isLoggedIn) {
      return (
        <button className="foo__cta" onClick={onCTAClick}>
          CTA!
        </button>
      );
    }

    return null;
  }
};
SanderSpies commented 7 years ago

I would prefer to remove:

That said, I wonder if it's really a bad thing to diverge a bit. For example the JSX spread attributes are also not possible within Reason (well maybe with a lot of pain).

Also I would also like to add support for fragments, which @syranide already mentioned, and Reason already has.

ConAntonakos commented 7 years ago

Echoing @nkohari's concerns. I think trying to balance conditionality intermixed in JSX was my biggest struggle. My workaround was utilizing the do expression: http://wiki.ecmascript.org/doku.php?id=strawman:do_expressions

klimashkin commented 7 years ago

35 is the most anticipated feature since in our projects we don't have plain text in jsx anymore, only intl calls

jmar777 commented 7 years ago

Edit: I misunderstood something (see the following comment).

sophiebits commented 7 years ago

@jmar777 To be clear, #35 would only change that example to have <button onClick=handleClick>text</button> with no braces, and you'd need explicit quotes when adding string text.

If people have more concerns about any particular issue, please comment on that issue's page instead of here.

jmar777 commented 7 years ago

@spicyj 💡 doh! I did misunderstand the proposal. Thanks.

xpagesbeast commented 7 years ago

JavaServer Faces (JSF) Expression language has stood the test of time, JSX is a very similar new kid on the block, here are some specs: https://docs.oracle.com/javaee/7/tutorial/jsf-el.htm. It would be nice to copy and paste between the two with minimal adjustments.

jordwalke commented 7 years ago

@gaearon / @sebmarkbage I love a good bike-shed! :D

Please don't feel the need to incorporate all/any of Reason's JSX features/difference. Some of what we implemented, we did simply because:

  1. It was expedient.
  2. I requested that we try out a couple of features to see how they feel (for example, no {} around expressions that are already "simple" enough). I think I like it.
  3. Reason isn't just targeting web, but web and native compilation so embedding string text by default inside of <tag>here</tag> is less important, and maintaining compat with HTML is not as high of a priority. Therefore we were able to treat everything inside of tags as a space separated list of identifiers <tag>any list of componentIdentifiers here</tag> without needing interpolation {} (although interpolation is still valid if you want it because {x} is always equivalent to x in Reason!). Maybe we should reconsider this one though, so that people can copy/paste markup from the web into JSX more easily. How important is this?
  4. Because we know we can rapidly iterate without churning a bunch of developers. It's a laboratory where we can quickly refmt forward without breaking anyone since refmt is deeply integrated into Reason.

Frags seem like a nice addition though.

piotr-oles commented 7 years ago

I think we can make JSX more readable on conditions with current implementation. What do you think about implementing your custom If component? `<If condition={ someCondition }>

`
LucaColonnello commented 7 years ago

Nice to have something like https://www.npmjs.com/package/jsx-control-statements

mvolkmann commented 7 years ago

As someone who teaches React, I hate having to say "JSX is kind of like HTML which you already know, but it differs in these ways." Making JSX as similar to HTML as possible will appeal to newcomers. That means supporting comments and handling class and for attributes. One of the benefits of JSX is that it uses JavaScript for all conditional and iteration logic. Please do not add custom syntax for those! IMHO, that is a bad feature of other frameworks ... having to learn and use two syntaxes.

tomascharad commented 7 years ago

I haven't think about it thoroughly but I think it would be nice to have a way to conditionally render a component inside JSX 'html'.

I don't know if it would be better to have it as a Component's native prop (Which I believe it would be out of the scope of this thread):

<div>
  Hi!
  <MyComponent renderIf={bool} />
</div>

Or something more related to JSX sintax (haven't think about it thoroughly):

<div>
  Hi!
  {renderIf(bool) => <MyComponent />}
</div>

This would make my code much cleaner prior the return statement of the render function. I hate doing this kind of stuff:

  let myComponent = <div></div>;
  if(bool) {
    myComponent = <MyComponent />
  }
  return <div>
    Hi!
    {myComponent}
  </div>;

I think that reading the rendering if condition inside the HTML, is far more readable/maintainable.

tweeres04 commented 7 years ago

@tomascharad You should just do

return (
    <div>
        Hi!
        {bool ? myComponent : null}
    </div>
);
klimashkin commented 7 years ago

@tweeres04 since bool sounds like boolean :)

return (
    <div>
        Hi!
        {bool && myComponent}
    </div>
);
tomascharad commented 7 years ago

Thanks @tweeres04, @klimashkin, That implies assigning the component in a variable. What I'm suggesting is to try to avoid that assignation so the code doesn't get messy.

tweeres04 commented 7 years ago

So then do

return (
    <div>
        Hi!
        {bool ? <MyComponent /> : null}
    </div>
);

My point is that it's already clean and simple to do what you're trying to do with jsx

mrberggg commented 7 years ago

I hope whatever changes are considered will prioritize simplicity, readability, an HTML-like syntax, and using plain old JavaScript. I think these are what make React so great.

sebmarkbage commented 7 years ago

This thread has turned into a bit of back and forth on particular proposals. It might be better if we move them out as separate GitHub issues for each proposal.

Since a lot of people are asking for control flow. May I suggest that you first consider what #39 will enable before adding new control flow. For example this is perfectly valid if we supported do expressions by default:

return <div>
  Hi!
  {if (bool) <MyComponent /> else <div></div>}
</div>;
tracker1 commented 7 years ago

Most of these conditions could be handled with additional components/methods.

Possible solution for #4, add a raw() method on react-dom that returns a String object with a doNotEncode property... this can be checked for as part of the rendering path(s).

function raw(input) {
  const str = new String(input || '');
  str.doNotEncode = true;
  return str;
}

Then all you need to do is check for string object with said property and not html encode.... That would be additive and there shouldn't be any instances in the wild where this would break things.


Another is the desire for more conditional syntax, I'm against doing this via special props, and would rather see people either use ternary, the do expression default, or custom component wrappers for the purpose.

<If
  condition={ condition }
  then={ <component /> }
  else={ <component /> }
/>

function If(props) {
  const condition = props.condition || false;
  const positive = props['then'] || null;
  const negative = props['else'] || null;
  return condition ? positive : negative;
}
Noitidart commented 7 years ago

I don't get why we use JSX. It just saves a bit of typing but adds a whole bunch of complexity and new rules. I love plain React.createElement, that way I am leveraging javascript, and any questions I have can be answered by anyone that knows javascript.

arendjr commented 7 years ago

I think we should restrict any changes to JSX to ones that will not (or barely) impact existing codebases using JSX. This will also help people with large codebases (myself included) who'll have to migrate gradually to the next version, rather than convert everything overnight.

I don't like the argument of "you don't need to upgrade to JSX 2, you can keeping using the old version", because it creates friction in the community. It'll be harder to share components among projects that use different JSX versions, it creates ambiguity in online documentation/tutorials regarding the version they're using, and if you have to work across projects that involve different incompatible versions, it creates mental overhead and increases the chance of mistakes. To put it differently: I like ES6 because all ES5 is valid ES6 code. I will like JSX 2 if all JSX 1 code is also valid JSX 2 code.

So looking at the discussion above, I see definitely some features that I like:

I'd say dropping HTML encoding is already tricky because it might break existing codebases, but its impact might be limited enough to be defensible. If an ESLint rule could be created for it (including a fixer, which should be doable) so that the issues can be spotted (and fixed) easily, I'd say it's not a deal breaker.

But I'd be strongly against #35 and also against the other proposals originally mentioned as controversial, for the reasons mentioned above.

sebmarkbage commented 7 years ago

@arendjr Could expand a bit on why you wouldn't be able to convert overnight? E.g. with an automatic codemod. We can convert any caller automatically.

What are the problems you're expecting? This would be useful information.

arendjr commented 7 years ago

@sebmarkbage Well, personally I tend to be a bit hesitant towards codemods. For one thing, if I were to convert 350 components overnight using a codemod, that would generate a Pull Request none of my team mates will be happy to review. It also creates noise in version control, making git blame for example less useful. Also, what guarantee do I have the code it generates will satisfy my ESLint rules? For me, a codemod should be a last resort rather than something I'd happily apply. I'd much prefer to keep the old code around for as long as it works.

But more specifically, if we're talking about #35, let's suppose I have the following snippet of code:

<div>
    <a onClick={this._like}>Like</a> &bull; <a onClick={this._comment}>Comment</a>
</div>

How will the codemod convert this code? Will it automatically wrap the " &bull; " part in a <span>? Currently there's no <span> there in the DOM, so needing one where it wasn't needed before could at least be seen as a slight regression.

arendjr commented 7 years ago

Something more just came to mind: When you (as in: Facebook) released React v15, you published a blog post about the new versioning scheme: https://facebook.github.io/react/blog/2016/02/19/new-versioning-scheme.html

What I found very comforting back then was this quote:

Therefore, we're committed to making it easy for most components and libraries built on top of React to be compatible with two major versions at the same time. We will do this by introducing new APIs before completely removing the old ones, thereby avoiding those cliffs.

This gives me a lot of confidence my projects built on React will be able to smoothly move forward without hitting a huge stumbling block where suddenly a lot of code has to be converted, because there will usually be a grace period during which code will work both in the old and the preferred new way.

Now while I understand the React API and the JSX syntax are strictly separate things, as a React-using developer I'd also argue JSX is a vital extension of the React API. After all, it's through JSX that I consume an important part of the React API. And as such I believe it would be a shame if the stability of the React API and its policy of avoiding sudden breaking changes were not respected similarly by JSX as well.

sebmarkbage commented 7 years ago

@arendjr We generally assume that npm packages are published with their Babel extensions compiled out to a lowest common denominator. A lot of things break without that anyway. So any syntax changes wouldn't break already compiled packages upstream.

However, any breaking transition to new syntax would likely be using an opt-in header for a while so that you can keep having both in the same codebase at once, but that's a thing you wanted to avoid. I don't see any other way to make breaking changes other than doing them all at once - like codemods - or incrementally package by package or file by file. Not making breaking changes at all is way too restrictive with something like JSX at this stage. Just like many other Babel plugins like decorators, field initializers, classes etc. have all gone through breaking changes. If you're not willing to upgrade all at once or incrementally, I wouldn't recommend using non-standard syntax extensions at all.

Your example could be converted to this:

<div>
    <a onClick=(this._like)>"Like"</a>" \u2022 "<a onClick=(this._comment)>"Comment"</a>
</div>

Or if you're fine with unicode we can just put the " • " in your code.

There are no semantic differences needed. All the proposals are purely syntactic sugar.

arendjr commented 7 years ago

@sebmarkbage Thanks for your detailed reply. Seems I was at least mistaken about #35, as I thought it implied having text nodes between other nodes was simply no longer possible. Glad that's not the case :)

What would such an opt-in header look like? Because if it does allow me to transition to a new version file by file, that would actually alleviate most of my personal complaints.

Regarding Babel plugins, I actually do stay away from experimental language extensions, exactly to avoid the risk of breaking changes. It's also why I have neither adopted Flow nor TypeScript, because I'd hate it to adopt a language extension only to find out a year later I bet on the wrong horse. Maybe I'm erring on the side of caution, but well, at least you know where I'm coming from :)

The thing is, I never thought of JSX as an experimental language feature. Non-standard, yes, but the React API is considered stable and it clearly advocates the use of JSX, which is why I had an expectancy of the same level of stability when I adopted JSX. Now when you say, "Not making breaking changes at all is way too restrictive with something like JSX at this stage." I think it makes it clear you're seeing it in a different light. That's not necessarily bad, I cannot judge that, but I've given my opinion which is that I think it might not be wise to introduce friction in the community at this point. And I think the reason I think it's unwise (but others might judge differently) boils down to how you answer the question: Is it worth it to obsolete JSX tutorials currently out there and to have the community perform migrations and have people relearn the details of how JSX works, in order to have some syntactic sugar?

sebmarkbage commented 7 years ago

Is it worth it to obsolete JSX tutorials currently out there and to have the community perform migrations and have people relearn the details of how JSX works, in order to have some syntactic sugar?

I'm not really sure. I think that most of these are not worth while on their own but maybe as a unit it might be worth while.

However, I've learned that it is not really up to me - or anyone else. If these things are considered valuable, then someone will build it and people will adopt it. In that case, the community will diverge and there will be things to learn anyway. I'm just trying to get out in front of it so that we can do it in a structured way instead of ad-hoc.

That said, I don't think anything in this issue means that this will actually happen. It is more about figuring out what it would be if we were to go down this route and getting feedback from the community so that we don't end up with disconnected ad-hoc forks.

syranide commented 7 years ago

@sebmarkbage Going off on a tangent, there's always the possibility of actually making it JSX1 vs JSX2 and simply requiring people to use both transformations if you want to keep old code around. Or if you will, JSXML vs JSX2 and officially keeping a JSX-version around that's tailored to HTML-style frontends, although that may also fracture the community.

But I feel like there is some merit to having a JSXML type of thing as JSX2 may be a bit cumbersome for small-scale purposes and not as friendly to the hobbyist crowd that JS has an excess of. So having JSXML as an introduction to React for hobbyist/small-scale apps and then JSX2 for when you generally intend to tackle business/larger problems. It would allow JSXML to make some trade-offs that aren't necessarily 100% technically sound, but which may appeal to that crowd. Just like jQuery is in many ways flawed, it's a perfect tool for those who are unfamiliar because it's simple and it works well enough. One could even imagine a ReactDOM-fork to go with it, with everything being compatible with HTML, lower-case props, non-document event listeners, non-pooled events, etc, but again, fracturing the community may be a problem.

That being said, I would probably never use JSXML myself, but it may be relevant for React to keep traction with newcomers and not seem like a frightening framework or one that it is exclusively for "large business needs".

fibo commented 7 years ago

it would be nice to have something like

render () {
  const {
    prop1,
    prop2,
    prop3
  } = this.props

  return (
    <MyComponent prop1 prop2 prop3 />
  )
}

where prop1, prop2 and prop3 are not booleans, but any kind of prop.

Instead of

render () {
  const {
    prop1,
    prop2,
    prop3
  } = this.props

  return (
    <MyComponent prop1={prop1} prop2={prop2} prop3={prop3} />
  )
}

or maybe it could be easier to implement this, to avoid breaking changes with current syntax

render () {
  const {
    prop1,
    prop2,
    prop3
  } = this.props

  return (
    <MyComponent {prop1} {prop2} {prop3} />
  )
}
fibo commented 7 years ago

It would be great to have more tools for indentation. On Codepen and vim, jsx indentation does not work.

fibo commented 7 years ago

Please add HTML style comments like this

<!--div>
  <MyComponent />
</div-->
fibo commented 7 years ago

support <!doctype>: this will make server side rendering a lot easier.

There are also a lot of other tags not supported, like <style>, <script>, etc.

if my jsx is like

<body>
  <h1>Hello JSX2</h1>
<body>

React should understand and mount it automatically, after body.... well this is brainstorimng, right?