Work with compile-to-JS languages (like CoffeeScript) #47

Closed
edc opened this Issue Jun 3, 2013 · 33 comments

Projects

None yet
@edc
edc commented Jun 3, 2013

JSX is nice, but those using compile-to-JS language have to change the transpiler to connect to the JSX compiler. Is there any chance of adding alternative DOM syntax to JSX so it does not break every compile-to-JS language? Something gettext-style would be nice. So in addition to:

var HelloMessage = React.createClass({
  render: function() {
    return <div>{'Hello ' + this.props.name}</div>;
  }
});

it would be nice to support

var HelloMessage = React.createClass({
  render: function() {
    return _dom_("<div>{'Hello ' + this.props.name}</div>");
  }
});
@petehunt
Contributor
petehunt commented Jun 3, 2013

@edc what do you think of this?

var HelloMessage = React.createClass({
  render: function() {
    return React.DOM.div(null, 'Hello ' + this.props.name);
  }
});

This works out of the box (and is what JSX actually compiles down to).

I fear a solution that parses strings because of performance implications, having to include a parser in the core (which is more bytes down the wire) and that it becomes easier to introduce XSS vulnerabilities.

@zpao
Member
zpao commented Jun 3, 2013

Well, that's not really better, it's just writing the JSX transform yourself. Which sucks (otherwise we wouldn't have JSX at all). And if we ever decide to change the output of the transform things are going to fail weirdly.

What @edc is proposing is nice in that since it's just JS, things like coffeescript can parse it and not choke.

What we could maybe do is have the JSX transform step look for this _dom_ method and parse the contents, turning it into Reacty code. The only additional bytes are then in the transformer, which isn't bad.

@edc
edc commented Jun 3, 2013

I like @zpao's proposal. The _dom_ could simply be a syntactical "escape", and the JSX transform can treat the argument of _dom_ as a string literal instead of an expression and therefore bypass the performance implications and XSS issues. The JSX transform can simply abort if the argument of _dom_ is not a string literal.

@petehunt
Contributor
petehunt commented Jun 3, 2013

Ah, now I get what you meant by gettext-style. That sounds like a pretty good idea to me.

@holmsand
holmsand commented Jun 3, 2013

Another way to support at least CoffeeScript could be to make the component constructor a little more lenient. I've been playing a little with this, and my favorite syntax so far is something like this:

L = React.DOM

HelloComponent = React.createClass
  render: ->
    L.div class: 'myClass',
      L.p 'Hi there, ', @props.name
      L.input type: 'button', onClick: @onClick,
        value: 'Click me'

That is:

  • Allow props to be omitted.
  • Allow more than one child.

And, since CoffeeScript is a bit weird, combine multiple "props" objects into one. (CoffeeScript turns that last line into:

L.input({
  type: 'button',
  onClick: this.onClick
}, {
  value: 'Click me'
});

).

It may not be HTML, but it is quite readable IMHO.

@petehunt
Contributor
petehunt commented Jun 3, 2013

@holmsand you may want to check out what coffeekup does: coffeekup.org

You can pass arrays as children today. We try to avoid them for single-child nodes so we can save an array allocation. I bet we could build a jsx-coffee package that wraps up all the ReactNativeComponents in React.DOM to expose this interface pretty easily. What do you think about that approach?

@holmsand
holmsand commented Jun 3, 2013

@petehunt I'm not that fond of coffeekup (eval evilness, and whatnot).

About arrays of children: I just found it confusing a couple of times that multiple children just disappeared without any warning... And the CoffeeScript gets a little bit prettier without the []s.

Anyway, wrapping is exactly what I did when playing with React. I ended up with something like the code below (with some auto-autoBinding thrown in). But it sure would be nice to have a common, human-usable API for us preprocessor-challenged users...

Reactor = @Reactor or= {}

isProps = (val) ->
  typeof val is 'object' and not 
    (React.isValidComponent(val) or Array.isArray(val))

copyObject = (from, to) ->
  for own key, val of from
    to[key] = val
  to

wrapComponent = (compFn) ->
  (children...) ->
    # Coffeescript breaks objects into more than one with this syntax:
    #     L.input type: 'button', onClick: @onClick,
    #       value: 'Click this button'
    # So combine second object with first.
    # Also allow props to be omitted, and class instead of className.
    props = null
    while isProps children[0]
      props = copyObject children.shift(), props or {}
    if props and 'class' of props
      props.className = props['class']
      delete props['class']
    if children.length
      children = children[0] if children.length is 1
      compFn props, children
    else
      compFn props

Reactor.DOM = {}
for own key, val of React.DOM
  Reactor.DOM[key] = wrapComponent val

Reactor.createClass = (comp) ->
  newc = {}
  # autoBind all handlers starting with 'on' or 'handle'
  for own key, val of comp
    if key.match /^(on|handle)/
      val = React.autoBind val
    newc[key] = val
  wrapComponent React.createClass newc
@vjeux
Contributor
vjeux commented Jun 3, 2013

One of the common use case of JSX is to embed complex Javascript code within the XML like

<ul>
  {this.props.children.map(function(child) {
    return <li>{child}</li>;
  })}
</ul>

How would you write that in Coffeescript with your suggested method? Would the code inside of the {} be written in Javascript? Wouldn't that be weird to mix coffeescript and Javascript?

@edc
edc commented Jun 3, 2013

I still prefer JSX since it is HTML. To quote the React's own document:

  • It's easier to visualize the structure of the DOM.
  • Designers are more comfortable making changes.
  • It's familiar for those who have used MXML or XAML.

IMHO, DSL based on CoffeeScript can easily become a mess because of the mixture of optional parenthesis/braces and significant whitespace. For instance, if you forget a trailing comma and instead of

L.input onClick: @onClick,
    value: 1

you have

L.input onClick: @onClick
    value: 1

it will continue to compile, but the semantic is totally different.

@vjeux
Contributor
vjeux commented Jun 3, 2013

I think the following can be made to work:

<form onSubmit={this.handleSubmit.bind(this)}>
  <input type="text" ref="textInput" />
  <button>Add</button>
</form>
(form onSubmit: @handleSubmit,
  (input type: 'Text', ref: 'textInput'),
  (button 'Add')
)

I think the best course of action is to make a CoffeeScript dedicated wrapper that has a nicer syntax than the constructor we have now.

@vjeux
Contributor
vjeux commented Jun 3, 2013

With {}

(form {onSubmit: @handleSubmit},
  (input {type: 'Text', ref: 'textInput'}),
  (button {}, 'Add')
)

With {} and []: {} means attributes, [] means children

(form {onSubmit: @handleSubmit}, [
  (input {type: 'Text', ref: 'textInput'}),
  (button ['Add'])
])

Where the general form is (name {attributes}, [children]). Only children is optinal

@edc
edc commented Jun 3, 2013

@vjeux It would be really nice to treat CoffeeScript as a first-class citizen and have CSX in addition to JSX ๐Ÿ˜ƒ. We might even be able to get rid of react.createClass and use class MyComponent extends react.BaseComponent. Am I being too unreasonable? ๐Ÿ˜„

@vjeux
Contributor
vjeux commented Jun 3, 2013

If you feel like making JSX work on CoffeeScript, that would be awesome :) I don't think that anyone is working on it right now.

About class extension, we could totally do that. We didn't do it because there's no class support on raw Javascript.

@edc
edc commented Jun 3, 2013

@vjeux cool. Will try to put something together. Off topic, but are you guys attending any meetup to get more traction?

@holmsand
holmsand commented Jun 3, 2013

@edc Of course it would be nice to have a preprocessor for CoffeeScript as well - as you say there are some downsides to using a DSL. But there are upsides as well, since the preprocessor is essentially creating an entirely new, and somewhat unfamiliar, language, with gotchas of its own.

@zpao
Member
zpao commented Jun 3, 2013

@edc Yes! Nothing on the books yet but if you have suggestions on particular meetups let me know.

@benjamn
Contributor
benjamn commented Jun 3, 2013

The CoffeeScript grammar appears to be fairly well-defined and extensible. So it might be even easier to write a transformer for XML literals in CoffeeScript than it was to implement JSX using Esprima.

@vjeux
Contributor
vjeux commented Jun 3, 2013

I'm not sure if we can ever make it work but this is valid CoffeeScript :p

_<script>_
_  _<ul>_
_    _<div id="vjeux" /_>_
_  _<_/ul>_
_<_/script>_

If you ignore the _ this is also XML :p

@vjeux
Contributor
vjeux commented Jun 3, 2013

The following could be made to work but doesn't really look like XML anymore and I prefer the version with () {} [] that I talked about earlier

R(
  [ul a:"b", b:"c"]
    @props.map (prop) ->
      R(
        [li]
          R([span], prop, [_span])
        [_li]
      )
  [_ul]
)
@edc
edc commented Jun 3, 2013

I just realized the following works fine:

HelloMessage = React.createClass
  render: -> `<div>{'Hello ' + this.props.name}</div>`

So I guess you could close the issue. :)

@chenglou
Contributor

I'd like to see this issue open. The above solution looks great but defeats the purpose of coffeescript by making a potentially big chunk of code javascript. Plus, it kills the syntax highlighting, which really makes a designer's life hard. And if I'm not mistaken, the whole point of jsx is to write HTML-like code in a js file isn't it?

(Disclaimer: I'm still checking out react and am by no mean fluent in it. Please point out any false assumption I'm making)
I have many thoughts on this matter. First, I happen to have written a 21 lines, fully functional templating engine in coffeescript not so long ago. It has mostly coffeekup's sweet, clean syntax but without the "eval evilness" @holmsand mentioned: https://gist.github.com/chenglou/5819861 (please also ignore the freakish mess code, it was more of a successful code challenge than anything else). Since all the tags are functions, I'd imagine it's pretty easy to convert them into the current React.DOM calls. Plus, it looks like a cleaner HTML one would actually want to write in. AND! Natural syntax highlighting, code checking and all in editors.

The downside of this is that just like many solutions proposed here, this looks clean at the expense of regular javascript. I have some neat solutions in mind but, another time.

Second, just like lots of the newcomers, I'm still a bit uncomfortable at the idea of writing my markup, especially markup that needs compiling, in my js files. It's mostly a mental barrier. I have an idea for the best of both worlds but now that I'm writing, I think I need a bit more reflection on this. I'll open a separate issue once I get more acquainted with this library.

@zpao
Member
zpao commented Jun 26, 2013

So if you wanted, you can just use React.DOM directly and forget about JSX. @vjeux has done that - check out http://blog.vjeux.com/2013/javascript/react-coffeescript.html

It's the integration of JSX into coffeescript that's hard. coffeescript probably chokes on <markup> and our JSX transform works with a JS AST, so it can't parse coffeescript. In order of this to just work, support needs to be added at one end of this. We are not currently in a position where we can spend time adding coffeescript support to our transform. Maybe we could add support for a suggestion like _markup_, but that would likely end up in a different esprima fork, and we would only be adding this to work around coffeescript.

As to your last point, I'll reiterate that you don't have to use JSX. You can write the React.DOM() or MyComponent() calls yourself. We use JSX and we think it makes life easier. We've started thinking about some solutions where you can write your render function separately from your component, so your markup could live separately but we haven't gone far down that path. I'm happy to hear your thoughts!

@petehunt
Contributor

@chenglou how about something like this? http://jsfiddle.net/hdrV3/1/

Just a thin wrapper around React.DOM to make it more DSLy in CoffeeScript.

@chenglou
Contributor

Yeah.
But to reply to @zpao, I do want to use JSX though, because it's the only thing that makes sense for designers. I was actually thinking about isolating the JSX (and maybe even css) in another file, this way people will be able to create react components that contain a js, a jsx and a css file. And instead of compiling JSX, you kind of insert it into the js file. Or maybe (as much as I appreciate what you guys are doing with JSX) dropping it completely and make it a mustache-like template. But like I said, I haven't experimented with react enough to provide concrete examples, so I'll comment once I get more familiar with it in a short while.

@petehunt
Contributor
@holmsand

@chenglou Just to add another datapoint:

It turns out that the idea of @edc works quite well in practice, at least for me (i.e. escaping JSX in Coffeescript, and then compiling the resulting .js file using React's compiler). I've just rewritten something like 1500 lines of code using that method, and so far it's been an absolute pleasure. I.e. using code like this

Dropdown = React.createClass
  render: ->
    @transferPropsTo `
      <div class="btn-group">
        <button tabIndex='-1' type='button' class="btn dropdown-toggle"
            data-toggle="dropdown">
          <span class="caret"></span>
        </button>
        <ul class="dropdown-menu">
          {this.props.children}
        </ul>
      </div>`

I actually like the 'look' of JSX in Coffeescript better than in JS, since the escaping clearly demarks what is JSX and what is not.

@yang
yang commented Jul 16, 2013

Shameless but pretty relevant plug: some of the syntax ideas being discussed here are actually close to something I've been working on called reactive.coffee, an embedded DSL for building reactive UIs (in a scalable manner).

@pconerly pconerly referenced this issue in Xavura/CoffeeScript-Sublime-Plugin Jul 16, 2013
Closed

HTML templating of JSX inside Coffee-script backticks #47

@damassi
damassi commented May 5, 2014

I just want to quickly add that if you're using GitHub's Atom, it (kind of) highlights syntax within backticks.

@zpao
Member
zpao commented May 5, 2014

Ok, I don't think we are going to extend the transformer in ways that have been discussed, so I'm just going to close this out. People have found solutions that are working for them in different languages.

@zpao zpao closed this May 5, 2014
@spicyj
Member
spicyj commented May 19, 2014

I just found https://github.com/jsdf/coffee-react-transform which appears to be a JSX transformer for CoffeeScript. I haven't tried it but it looks promising -- if anyone here tries it out, it would be great if you could report back with your results.

@KyleAMathews
Contributor

I'm working on CJSX quickstart. Has a Gulpfile to build coffeescript/sass as well as integration with Jest. WIP but coming along.

https://github.com/KyleAMathews/coffee-react-quickstart

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment