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

Crazy Idea: Deprecate JSXText? #35

Open sebmarkbage opened 9 years ago

sebmarkbage commented 9 years ago

Currently we allow arbitrary text content to flow amongst tags. However, we have noticed that while building UIs, you actually don't utilize this very much. @syranide can speak more to this.

You usually want to wrap your text content in some other container. E.g. instead of writing it like this:

<div> Foo <Button /> </div>

You should write something like this:

<div> <Label text="Foo" /> <Button /> </div>

At the very least you want to wrap it for internationalization purposes:

<div> <Intl message="Foo" desc="A label" /> <Button /> </div>

What if we only treated it as a sequence of expressions and got rid of the curlies? We don't really need curlies around attributes since they can disambiguated with parenthesis if necessary.

That way the JSX example would become:

var box =
  <Box>
      shouldShowAnswer(user) ?
      <Answer value=false>"no"</Answer> :
      <Box.Comment>
         "Text Content"
      </Box.Comment>
  </Box>;

No curlies!

Multiple JSXChildren have to be delimitated by ,. A JSX elements doesn't need to be followed by a comma.

var box =
  <Box>
      "Leading text",
      functionCall(),
      <Answer value={'this is': 'an object'}>"yes"</Answer>
      <Box.Comment><Child /></Box.Comment>
      anotherFunctionCall(),
      <OptionalTrailingComma />,
      (expr + ession)
  </Box>;

This moves the design further away from HTML generation and towards UI composition.

syranide commented 9 years ago

Sorry for the tl;dr and loosly structured thoughts, but I hope most of what I'm trying to convey/play with is understandable.

8 sums up some of my thoughts on it I think :+1:

I think it's great for many reasons, including being more transparent about strings not being concatenated up-front and encouraging users to do it explicitly rather than always go for the "beautiful syntax". Also no more arbitrary whitespace! Although #28 is another possibility for that it's not nearly as intuitive or as explicit.

Multiple JSXChildren have to be delimitated by ,. A JSX elements doesn't need to be followed by a comma.

I'm really not a fan of this, I'm kind of OK with comma, but it's really weird that it's not used between JSX elements (but somewhat understandable). I wonder if commas between JSX elements really is such a bad idea, there are benefits and perhaps the aversion to it has more to do with familiarity with HTML than practicality. Else, for commas themselves, I feel like it's commas unavoidable to an extent even if syntax wasn't an issue.

Writing <Box>varA varB varC</Box> makes sense but it I imagine it makes you hesitant to improve and rewrite it as <Box>(varA + varB) varC</Box> then, whereas <Box>varA, varB, varC</Box> vs <Box>varA + varB, varC</Box> feels less jarring.

An alternative to commas is to make newline be implicit commas (kind of like semicolons in JS). That way you largely avoid requiring commas, but you can still use them to stack multiple values per line. Still not a huge fan and doesn't translate nicely to other languages.


JSX elements are pretty much named-argument + variable-argument functions in a technical sense:

// example syntax taken from Jonathan Blow's JAI
Box(width=10, height=10, children="Text", value, func(1,2,3), ...)

I think it could make a lot of sense to move more towards such a model, but not for the syntax itself. It needs to be easily nested without causing an aneurysm. But I think it could make sense to rethink and derive JSX from that rather than HTML. Especially because JSX as it is right now tricks people into sending strings for static arguments.

So just to run blindly ahead without any critical thinking:

<Box number=0 string="" boolean=true variable=myvar expression={1 + A} />

The idea being that what's on the right-side of argument= is not an expression but a subset (although technically it could be an expression so long as we disallow assignment without parenthesis, to avoid being error-prone).

And perhaps the same idea should be applied to children as well so that we end up with:

var box =
  <Box>
      variable
      0
      "Leading text"
      {functionCall()}
      <Answer value={'this is': 'an object'}>"yes"</Answer>
      <Box.Comment><Child /></Box.Comment>
      {anotherFunctionCall()}
      <OptionalTrailingComma />
      {expr + ession}
  </Box>;

Which I'm probably a more OK with (but it's not really encouraging concat though). Substituting {} for () could be nice and intuitive but perhaps kind of weird?

Instinctively I feel like there should be a consistent rule, either we separate everything by , or everything not-JSX needs to be inside {} (including even primitives perhaps).

Tangentially I still think https://github.com/facebook/react/issues/3473#issuecomment-90481322 is the way to go eventually, i.e. renderable values must be elements too (frontends without style inheritance are unaffected, so it's not as bad as it sounds). In that world you would never have primitives in your children which simplifies the situation.


So taking it all the way and then some, this is what I envision it could look like (including trying to be nice with HTML):

var box =
  <Box>
      <Label text="Leading text" />
      "Leading text" -- "legacy syntax" (?)
      {"Leading text"} -- non-syntax version of "legacy strings" (?)
      <Text "Leading text" /> -- single-prop syntax (?)
      {functionCall()}
      <Answer value={{'this is': 'an object'}} number=1 text="yes" />
      <Box.Comment><Child /></Box.Comment>
      {anotherFunctionCall()}
      <NoCommasInSight />
      {expr + ession}
  </Box>;

EDIT: I'm not sure I would call "no curlies" a good thing in your example, I feel like you'd need to wrap the ternary operator in parenthesis instead then for it to be humanly readable.

DenisIzmaylov commented 9 years ago

@sebmarkbage I like your solution. It's more clear than handlebars-style below. And it's moving us to js-code from string-templates paradigm.

murashki commented 9 years ago

Multiple JSXChildren have to be delimitated by ,.A JSX elements doesn't need to be followed by a comma.

It seems a bad idea. Why not semicolon? I think if tag content treated as js code, it's should use semicolon after each expression.

It would be nice if jsx components could be use not only for html markup, but as any code parser.

var span = <span className="header">Plain text goes here</span>;

var css = <LessHndr /* IDE code highlighter instruction goes here */>
  @header-font: Georgia;
  h1, h2, h3, h4 {
    font-family: @header-font;
  }
</LessHndr>

var product = <SQLMigration /* IDE code highlighter instruction goes here */>
  id: INT(10), null(true), autoincrement(true)
  price: FLOAT(7,2), null(true), default(null)
</Migration>

What if JSX would support :>, or another sybmols at the closed bracket for js usage inside of tag.

var content = <SomeComponent:>
  console.log("OK!");
</SomeComponent>
sebmck commented 9 years ago

It seems a bad idea. Why not semicolon?

Because semicolons are used to denote statements while commas are used for expressions.

murashki commented 9 years ago

Why not like this:

var box =
  <Box>
      "Leading text";
      functionCall();
      <Answer value={'this is': 'an object'}>"yes"</Answer>;
      <Box.Comment><Child /></Box.Comment>;
      anotherFunctionCall();
      <OptionalTrailingComma />;
      expr + ession;
  </Box>;

?

sebmck commented 9 years ago

As I said, then it'd be assumed that you can put any statement in there when you can't, like:

var box = <Box>
  var foo = "bar";
</Box>;
sebmck commented 9 years ago

Also it conflicts with the semantics of statement lists everywhere else in JavaScript, while with commas you can just reason that it's calling Box with a list of arguments (which is basically is).

DenisIzmaylov commented 9 years ago

I'm agreed that commas is more correct according to semantics. But if we skip commas for JSX-tags we might make possible follow lines:

var box =
  <Box>
      "Leading text"
      functionCall()
      <Answer value={'this is': 'an object'}>"yes"</Answer>
      <Box.Comment><Child /></Box.Comment>
      anotherFunctionCall()
      <OptionalTrailingComma />
      (expr + ession)
  </Box>;

Because in this context commas are using only for one reason - concatenation.

sebmck commented 9 years ago

Introduces too much syntax ambiguity. You could maybe get away with making it optional but we all know how well ASI for JS turned out.

On Sunday, 17 May 2015, Denis Izmaylov notifications@github.com wrote:

I'm agreed that commas is more correct according to semantics. But if we skip commas for JSX-tags we can also may to make possible follow lines:

var box =

"Leading text" functionCall() "yes" anotherFunctionCall() (expr + ession) ; Because in this context commas is using only for one reason - concatenation. ## Reply to this email directly or view it on GitHub https://github.com/facebook/jsx/issues/35#issuecomment-102815614.

Sebastian McKenzie

ghost commented 9 years ago

Thank you for reporting this issue and appreciate your patience. We've notified the core team for an update on this issue. We're looking for a response within the next 30 days or the issue may be closed.

sebmarkbage commented 8 years ago

JSX content could be considered do expressions, in which case, semi-colons would make sense and it would make conditionals easier to express.

var box = <Box>
  if (foo)
    foo
  else
    <Placeholder />
</Box>;

The confusing part would be that each statement would be its own block so that you could do multiple return values.

var box = <Box>
  let x = 1;
  let y = 2;
  x + y;
</Box>;

That would be [undefined, undefined, 3] which I guess is intended.

jeffmo commented 8 years ago

If those were three separate blocks, then the x+y block wouldn't have acces to x or y bindings

syranide commented 7 years ago

Might it make sense to relax the delimiting rules so that , is implied for newlines instead (if the expression can be terminated), similar to statements in JS, and then the comma rule can be enforced consistently even for JSX. I.e.

var box =
  <Box>
      "Leading text"
      "foo", "bar"
      <foo />, <bar />
      <foo><bar /></foo>
  </Box>;

It's not consistent with how arrays/objects are in JS, although I would personally enjoy implied commas for those as well actually (if it can be neatly solved), but that is an entirely different battle with ECMA.

tolmasky commented 7 years ago

I feel kind of weird saying this since I am a huge advocate of expanding JSX to general purpose programming, but I feel that this change would shift JSX too far from its HTML roots and simply make it closer to just an alternative function call syntax without some of JSX's unique benefits. Having defended JSX to a fair amount of people who don't understand why we can't just use createElement(), one of my go to arguments is that JSX really shines when content is king. I love JSX precisely because I'm not having to put large paragraphs of text into strings, never worry about escaping quotes, etc. Additionally, I am allowed to escape the strict tree-children realities that functions imply. For example hello, <b>how</b> are you doing feels very lightweight in HTML/JSX and conceptually like 2 children (the text, and an inner bold tag), whereas any real tree (such as the above), forces you to really think of it as the true three children, with the bold tag breaking up the surrounding string "hello, ", <b>, " how are you doing". This of course is exacerbated the more tags you use. It may sound silly, but it starts to become conceptually heavy to want to put a simple bold tag in an existing large paragraph, knowing you have to split up the surrounding string.

syranide commented 7 years ago

@tolmasky Remember that JSX has nothing to do with HTML, XML maybe. HTML is only one possible outcome, you have ReactNative and a host of other renderers which has nothing to do with HTML. That being said your points are valid.

IMHO, I think a large part of the friction comes from the general mindset of HTML, where you mix both the large and complex with the small and simple. I.e. you design both large visual hierarchies (the visual structure) AND documents (i.e. flowing content with images, text, etc) with the same language and primitives. For documents JSX can be kind of heavy-handed and many times markdown or similar syntax could be a better fit, or some alternate syntax. Whereas when you do complex hierarchies you don't care about textual content.

HTML is a document-language and so it's unsurprising that you get a lot of both. But if you look at interfaces generally you'll find a much more rigid structure and even inline text isn't even a thing, there are no inherited styles and thus "text" can't just be drawn anywhere. You then have different components for different purposes, some for plain basic text, some for rich text, some for complex documents. So you naturally get components of the type <BasicFontRender text={...} /> and more complex data-structures surrounding it.

The way JSX currently works also requires special white-space handling rules, but there's no perfect solution https://github.com/facebook/react/issues/4134, unexpected behaviors are bad.

sebmarkbage commented 7 years ago

@tolmasky The main motivator for me is that when you're building large products you often have internationalization requirements. There are essentially three variants that are common:

1) You use external files with all text separate from your code.

2) You use inline text in your code file that can be translated in place or stripped out automatically. With a particular annotation around the translated string or data structure. You also provide additional meta-data beyond just the pure text - such as a description of the context, the project it might belong too, if there are alternative strings for plural, genders etc.

3) You use a magic tool that can just strip out all possible strings seamlessly without further annotation needed.

Option 3 seems to be very rare. The common solutions are either 1 or 2 - which is how Facebook, AirBnB, Yahoo etc. use React.

If your project uses special annotations for text content anyway, then it is actually very rare that you embed text in your regular JSX. In fact, at Facebook we even have a special form of JSX for translations.

The primary motivation is that it's nice to have but not actually used that much in its pure form.

Of course, there are many more smaller companies that build apps that never see internationalization needs, and there are also lots of internal apps at bigger companies that never need translation because they're only using the corporate default language. There are also big companies that build separate apps for each region.

So it becomes a tradeoff between these two worlds. It would be nice to have something that works well in both scenarios though.

sergeysova commented 7 years ago

Please, do not write yoda-ternary-expressions.

Bad:

const ex = (
  <div>
    {checkYourCondition(value, expectedResult) ?
    <First value={value}>Example component</First> :
    <SecondComponent notReadable />}
  </div>
)

That form unintuitive. You can't at the first sight understand that this ternary expression.

Good:

const ex = (
  <div>
    {
      checkYourCondition(value, expectedResult)
      ? <First value={value}>Example component</First>
      : <SecondComponent notReadable />
    }
  </div>
)
zhangenming commented 4 years ago

I L O V E I T !

please make it real When will it happen, do you have a plan? thanks

lukeapage commented 4 years ago

Am I the only one in favor on commas? I would think of the syntax as sugar for arrays and the only thing missing is the ‘[‘ and ‘]’ where as semicolons look like a code block - so it’s further from what’s its sugar for. New lines are ambiguous and likely to confuse.