-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Meta: Support JSX similar to Literate CoffeeScript? #4529
Comments
Personally I’m not in favor of it simply from the standpoint of having to
make changes to the language just to apease a small segment of the overall
javascript ecosystem. It’s definitely not stage 4 ES2015+ ;)
While react may be trendy now, it certainly shouldn’t have more merrit than
say handlebars and Ember. Especially when it doesn’t add anything usefull
to any other workflows.
I could get behind a hybrid of pug and coffeescript if I could use it
generically with any framework, but even then I wouldn’t want to make
changes just for that particular workflow.
I would think JSX specific workflows should be another project or possibly
another reason to create a plugin system for coffeescript.
|
@mrmowgli What I’m proposing would work with Handlebars or Ember. That’s the point of passing through HTML blocks as is: it’s independent of any particular templating system. greet = ->
<h1>Hello, world!</h1> would become var greet;
greet = function() {
return <h1>Hello, world!</h1>;
}; JSX is just a file format, that defines HTML blocks with JavaScript; similar to how Literate CoffeeScript defines CoffeeScript blocks within Markdown. We would leave it to other steps in the build chain (i.e. Babel with various transforms) to parse the HTML blocks into specific precompiled templates, whether they be React or Vue or Handlebars or something else. |
If we're going to support JSX, we should parse it for real. No "passing strings through" hacks. I think it would make sense to output JSX as well, and let users pass that on to other compilers. Here's a common example of more complicated JS embedded inside JSX: <Table>
{items.map(item => {
const itemId = item.is_accessory ? item.parentId : item.id;
const hidden = !allHidden && removedItemIds.indexOf(itemId) >= 0;
return (
<ItemRow
key={item.id}
item={item}
quantityOverride={itemQuantityOverrides[itemId] || null}
className={hidden ? 'js-Hiding is-hidden' : undefined}
/>
);
})}
</Table> |
This has come up before. And I think that we definitely shouldn't add halfassed JSX support to CoffeeScript. Both because if you're going to parse it you should be parsing it for real; and because JSX, as a syntax, is almost diametrically opposed to the spirit of CoffeeScript. |
@jashkenas I think you’re a little too harsh. This proposal is “halfassed JSX support” in the same way that backticks are halfassed JavaScript support or that Literate CoffeeScript is halfassed Markdown support. We already have established patterns, in Literate CoffeeScript and backticks, of passing through code to be compiled by other components of a build chain. Just because XML syntax is ugly isn’t a great reason to exclude it as an option. These XML blocks don’t necessarily have to include JavaScript. Vue uses JSX to embed Vue templates: new Vue({
el: '#app',
data: {
msg: 'Click to see the message.'
},
methods: {
hello () {
alert('This is the message.')
}
},
render: function render(h) {
return (
<span
class={{ 'my-class-3': true }}
style={{ cursor: 'pointer' }}
on-click={ this.hello }
>
{ this.msg }
</span>
)
}
}); From this precedent, I can easily see someone extending the Babel JSX transform to support Handlebars and other similar templates. JSX at this point is becoming another hybrid passthrough format like Literate CoffeeScript. |
Backticks are halfassed JavaScript support, and you're not supposed to use them. They're an emergency escape hatch in times of need. Literate CoffeeScript is on slightly firmer ground insofar as that it's not trying to mix two different programming languages, with the inter-nested back and forth that requires. It's just top-level prose and code. Let's call it 2/3 assed. |
Can't we add hooks to the compilation stage to add flavors AKA babel presets? @jashkenas isn't there some way that we can extend coffee script with JSX. It has always been we're not going to do it, but what IS possible/acceptable for you? |
Another approach could be to have a preprocessor like @billymoon’s Illiterate, that detects XML blocks and just wraps them in backticks before passing that to the CoffeeScript compiler, which would then pass to Babel with a JSX transform. Such a preprocessor could even theoretically try to compile CoffeeScript to JavaScript within the XML blocks, if it were ambitious. I don’t think the “wrap XML blocks in backticks” workaround is so bad, personally. If we’re not going to do any other alternatives I think we should at least document it. More to the point, though, if we want people to use CoffeeScript it needs to integrate well with popular JavaScript frameworks and the JavaScript ecosystem. Say what you will about JSX, and I agree its very concept is a somewhat hideous idea, but like it or not it’s a significant part of the JavaScript ecosystem now. It’s a reasonable question for people to ask how to use JSX with CoffeeScript, especially since coffee-react has been deprecated. I’m not saying the answer needs to be this proposal, or backticks, or some as-yet-unbuilt preprocessor, but the answer shouldn’t be to just diss JSX as stupid and walk away. |
Or how about making a keyword with an external plugin like:
extern jsx
<input name="etc">
Anything indented under that tag will get parsed by the named external
plugin :)
|
The power of JSX is that it is a very thin layer on top JavaScript, and you can use full power JS within it and around it. This would only make sense if the JSX support in CoffeeScript was proper: return
<span
class={'my-class-3': true}
style={cursor: 'pointer'}
onClick={@hello}>
{@msg}
</span> from your example. I agree that it is very difficult to write a valid CoffeeScript expression that would include JSX, so this could be a non-breaking change. I also agree that the output should be JSX for another preprocessor. I would also trade I get that JSX seems against CoffeeScript philosophy. It's unnecessarily verbose. But it has evolved to more than it was a couple years ago. It's a compatible syntax which allows different compilation+execution strategy (in React it's essentially a lazy function call). As such it doesn't really matter that it looks like XML, and I wouldn't mind dropping that (if there was a tool ala js2coffee that would allow easy migration between the two). But you still need syntax that allows "special function calls with named arguments and one unnamed You could try for example: return
<div a: 3 b: "x" d: {a: 3} e: [1 2 3]>
"we might not need the {} around CS expressions"
<div>
<Component /> (note that CSX can be simpler because we can rely on indentation, which JSX cannot) All this said, it would be awesome to have some JSX/CSX support in CoffeeScript. React is hugely influential for client-side (compare Github stars for Ember or handlerbars) and most of today's JS (ECMAScript) transpilers support JSX. |
Can anyone familiar with the lexer explain why this doesn’t compile? a ` = ` 1 [stdin]:1:9: error: unexpected number
a ` = ` 1
^ I ask because it would make possible code like this: greet = ->
`<h1>Hello, ` @user `!</h1>` |
At my company we write code in CJSX pretty much all day every day (in https://github.com/sagemathinc/smc). So huge +1 to this proposal. We'll surely use what you do; I'm obviously nervous about relying on the deprecated current cjsx project... |
@williamstein anyone at your company willing to help code this? 😉 I have an idea for how to approach it, I’m hoping someone answers my question above . . . |
@GeoffreyBooth I don't know the particulars of why the above doesn't work, but as an interesting data point it does work if you wrap the backticks in parens, but it compiles to |
Yeah, this doesn’t work either: a ### = ### 1 [stdin]:1:3: error: unexpected =
a ### = ### 1
^^^^^^^^^^ Very odd. |
Unfortunately not; none of us have much expertise in parsers, etc... We're obviously interesting in testing. |
Because bacticks aren't implicitly callable.
|
@vendethiel I don’t understand your comment. I also just came across this, which might be related: #4290. |
Your
Can't work because the space after |
Okay, but then how do you explain this? a` = `1 [stdin]:1:2: error: unexpected =
a` = `1
^^^^^ It’s the same error as when putting an inline block comment here. |
The lexer gives It's the same reason why we don't allow comments everywhere -- we have a few issues about this: we lex block comments (because we need to re-output them) as |
So I guess the issue is that really my example is too simple. It seems obvious how the compiler should output |
Getting back to the original goal of this thread, I do still think that there needs to be a way for CoffeeScript to interact with JSX in a manner that’s robust enough for average developers to use. I think there’s no question that many CoffeeScripters hate JSX, both the very idea of its mixing of markup and logic and also the ugliness of its XML-based syntax; but that’s beside the point. The mission statement of CoffeeScript is “it’s just JavaScript.” For CoffeeScript 2 we elaborated on that to point out that nowadays, JavaScript is ES2015+, and so if CoffeeScript is to remain “just JavaScript” it needs to also be ES2015 and its successors. But an even broader point is that being “just JavaScript” means interoperating with the overall JavaScript ecosystem. Whatever we may think of the monstrosity that is JSX, it has grown from a hacky part of React into an emerging standard that is used by multiple frameworks. If CoffeeScript is still “just JavaScript,” it should work with that standard and those frameworks as well as standard ES2015+ JavaScript does. The specific implementation of how this can be achieved is well worth debating. Seeing what was done in #4551, it seems obvious to me that the shortcut approaches I was hoping for in the ideas above would probably have proven unworkable. Building hooks for a plugin architecture, as discussed in #4540, might make the CSX proposal in #4551 possible, if we add enough hooks throughout the lexer and elsewhere; but I think that that would be adding even more complexity than just building support into the compiler. If the only breaking change is that people need to use spaces around |
I think part of it is long term support, putting this as it is now commits this project to maintaining it. I get that this is attractive at the moment, but what is the likelyhood that this will be kept up to date as JSX changes? Or as CoffeeScript changes?
I still personally think that the effort of putting in plugin hooks will pay off in the long run, as it seems like something that has come up over and over, and probably will keep coming up for some time.
|
@mrmowgli Read the code in the CSX PR. That would take a lot of hooks. Not that I'm against adding the hooks, but JSX might be too complicated for them. We could also add CSX now and refactor it out into hooks later, like I was hoping to do with Literate CoffeeScript. JSX is just XML, and as such, isn't really changing. We would have to worry about changes if we were doing what coffee-react-transform was doing, compiling XML tags into React function calls, but we would just be compiling XML tags to XML tags and letting Babel handle the React (or Vue or whatever) part. That seems pretty safe. |
As per request from @GeoffreyBooth, I'm moving some comments I made over on [CS2] Support for CSX - equivalent of JSX. In short, JSX via template template literals could be the way to go: implemented as a self-contained library, rather than adding support to the core compiler. Copied comments.. @greghuc said... For the future of coffeescript, I think having a clear solution for handling jsx is good. However, I'm not convinced that adding compiler-level support is the way to go: it increases complexity, makes future change to the compiler harder, and biases coffeescript towards jsx over other templating approaches. As a pointer for another direction, a clean jsx solution might be possible using tagged template strings. Consider:
With bel/hyperx an "html template" is specified as the tagged template "string", which is supplied to the tagged template "function" for processing into a dom (or react?) element. I don't use jsx, but I do use bel to construct html dom elements like this in Coffeescript: element = bel"""
<div>
<h3>Contact Us</h3>
<form>
<label for="email">From</label>
<input type="text" oninput=#{handleEmailInput} value="#{model.email}">
<textarea placeholder="Some text" oninput=#{handleContentInput}>{model.content}</textarea>
</form>
</div>
""" I find this a very clean approach: the html template is specified as a templated-string, which is post-processed into a dom element tree using the prefixing template-function (bel in this case). This is what motivated me to propose and add tagged template string support to CS2. So I would suggest that a cleaner solution to adding JSX support to Coffeescript is to find/create a small library that uses tagged template strings ala hyperx/bel. There might even be a library that does this already. Given the current popularity of JSX, the coffeescript docs could then have a section showing use of JSX with Coffeescript using this library. But we wouldn't add first-class support at the compiler level. Quick addendums:
Then @GeoffreyBooth asked... @greghuc how would you implement @lydell’s JSX example using tagged template literals? <Table>
{items.map(item => {
const itemId = item.is_accessory ? item.parentId : item.id;
const hidden = !allHidden && removedItemIds.indexOf(itemId) >= 0;
return (
<ItemRow
key={item.id}
item={item}
quantityOverride={itemQuantityOverrides[itemId] || null}
className={hidden ? 'js-Hiding is-hidden' : undefined}
/>
);
})}
</Table> and @greghuc replied... @GeoffreyBooth the example would probably look like: bel"""
<Table>
#{items.map (item) ->
itemId = if item.is_accessory then item.parentId else item.id
hidden = !allHidden && removedItemIds.indexOf(itemId) >= 0
bel"""
<ItemRow key=#{item.id}
item=#{item}
quantityOverride=#{itemQuantityOverrides[itemId] || null}
className=#{if hidden then 'js-Hiding is-hidden' else undefined}
/>
"""
}
</Table>
""" I've never done this in jsx, but I do regularly use bel with nested (real) dom components to create complicated dom trees. It works very nicely:
|
@mrmowgli The fact that this now needs to be maintained as part of the language is also advantage of this approach, because it prevents it from getting out of sync the way coffee-react-transform inevitably would have and indeed did. (I do agree it's also a cost and downside) I don't see a way to do this via hooks. But someone super smart might come up with a way. But I would bet against the odds of that happening. Our syntax and parser are just too complicated.
True, but it's not too bad. As @GeoffreyBooth pointed out, a plugin system would have been much more complex. Plus CSX gets parsed into normal CS AST.
I think we can still support template literals. What other approaches are there? For the rest of your discussion: All those arguments can be applied against JSX. They are not specific to CoffeeScript. Given that people prefer JSX over template literals (it's a bit unfair since later came after former), I would bet that people will prefer CSX over using template CoffeeScript literals. But again, we can have those, and you can use those instead of CSX if you prefer. But even from your example it seems clear that for the particular syntax that CSX provides they are clunkier, so pragmatically I would use them whenever I need something more/different than CSX. |
@xixixao I'm all in favour of Coffeescript having support for JSX :-) However, I'm not in favour of the Coffeescript compiler having first class support for JSX (requiring compiler changes). The approach I've sketched out is JSX support in the form of a self-contained library called via tagged template literals. So I don't think the comparison is between JSX and template literals. It's between JSX support:
JSX support at the compiler level will always have the cleanest syntax vs a standalone library. However, there's not much in it vs hyperx/bel with tagged template literals: you have some extra tagged template literal syntax like:
Interesting opinion on JSX from the original author of hyperx: choojs/hyperx#2 (comment) |
@greghuc Again, it is argument on whether to use JSX at all, instead of template strings (in JS). "some extra" is a huge deal if you are writing thousands upon thousands of lines of JSX. Ergonomics matter. |
To address @jashkenas's comment, this is the story I've been pondering: Once upon a time there was a great language with shitty syntax called JavaScript. People used it a lot, in various ways. Over time, people started considering some of those ways as the bad parts. Then CoffeeScript came along, and allowed you to write JavaScript with better syntax, avoiding the bad parts. This is still CoffeeScript today, and it still has value. It runs everywhere. Bunch of pretty smart people realized that some of the wins from CoffeeScript could be brought to JavaScript, plus some other things that were out of scope for CoffeeScript or we never got around to them. Now we are in a new age, where more and more people write in this futuristic dialect of JavaScript, with some additions on top (JSX). This dialect is DEFINITELY much better than ES3/5. So is CoffeeScript classic. But that sucks, because now people have to choose between two variations of JavaScript which are both better than the old JavaScript with bad parts, but each have its pros and cons. Now consider that many shied away from CoffeeScript regardless of its quality. Unless CoffeeScript gets ahead of the curve, and becomes to ES7+ what it originally was for ES3/5, it will die a quick death. This is CoffeeScript 2. It needs to support all widely used features as they get rolled out in JavaScript and anything else that is popular and easy to do in the ecosystem, like JSX. Because if I am a dev, who is deciding between Babel and CoffeeScript, CoffeeScript needs to be better. Now this doesn't mean that CSX in the form I implemented it is the way to go. But the language needs some simple way of outputting JSX, otherwise it will never be adopted by the masses of React/Vue/etc. devs. Imho. |
@xixixao what you describe is essentially the CoffeeScript 2 mission statement. To quote from that:
At the time I was only thinking in terms of ES2015 features that CoffeeScript needed to support ASAP, especially modules and ES classes, with the priority driven by whichever features impeded CoffeeScript’s compatibility with popular frameworks and build tools. But one could just as easily think of JSX as a feature of the JavaScript community, like any ES feature, that CoffeeScript needs to support in order to remain viable. There are some ES features that we don’t need to support in order to preserve compatibility, and among those features |
This is a bit more of a general thing than JSX-specific, but: If we're tying the success of CS to 'keeping up' with the JS people can use, our target is effectively babel and all its plugins, not ECMAScript itself. We see this already with things like public class fields and JSX (which are not in the ECMAScript standard), and even If our success is to be matching babel like-for-like, investing in a pluggable parser (whatever that even means 😄), or at the very least attempting to produce a standard AST that can be consumed by babel backends, is probably the only way we can hope to do that sustainably. |
(The latest Edge and Safari previews have support for |
I also think it's worth bearing in mind what we already support without additional syntax, e.g.: el = React.createElement
render: ->
{ event, latestLog } = @state
el 'li', onClick: @handleClick,
el 'span', className: 'event-name', event?.name
if latestLog?
el 'span', className: 'latest-log',
el 'span', className: 'log-date', latestLog.date.toDateString()
el 'span', className: 'log-duration', latestLog.duration
el 'span', className: 'log-percent', "#{latestLog.color}%"
else
el 'span', className: 'latest-log empty', 'No logs' |
@greghuc @GeoffreyBooth here's one way you could write @lydell's example in the HAML-inspired syntax I have working (on %Table
= for item in items
{is_accessory, parentId, id} = item
itemId = if is_accessory then parentId else id
hidden = not allHidden and removedItemIds.indexOf(itemId) >= 0
%ItemRow{
key: id
item
quantityOverride: itemQuantityOverrides[itemId] or null
className: 'js-Hiding is-hidden' if hidden
} I believe this would work the same as the given example. Will open a pull request with all the details but wanted to give a taste and join the discussion on a stylistic level |
How about a syntax like: render = (todos) =>
%ul
todos.map (todo) =>
%li
%input type='checkbox', checked=todo.done
todo.title which would be equivalent to: function render(todos) {
return <ul>
{todos.map(todo =>
<li><input type="checkbox" checked={todo.done}/> {todo.title}</li>
)}
</ul>
} |
@connec I'm not sure what the big win of outputting to Babel AST would be. The trick is in coming up with CoffeeScript syntax for ES6+ features, then implementing the parsing phase for those. Printing out JS at the end is rather trivial (so lexer, rewriter, grammar are the tricky bits, nodes is pretty easy). |
I was being a little facetious tbh. I wouldn't want to see CS become a frontend for babel, but if we did plan to 'compete' with babel feature by feature we could make that intention clearer and the implementation more robust by going through babel's API. Some people want indentation sensitive JS with a few shortcuts, and would feel a bit more inclined if CS was basically a babel parser plugin. I agree it wouldn't be saving a whole lot of work though. Definitely a bit off topic for a conversation about JSX though, so sorry about that 😅 I was pretty much just trying to extrapolate from the perspective that JSX is a popular babel plugin. |
Man check this out: https://github.com/jsdf/coffee-react-transform JSX + CoffeeSctipt = Awesome! |
I've been writing a lot of (production) code using https://github.com/jsdf/coffee-react-transform for the last two years. For example, here is a re-implementation of Juptyer notebooks from scratch, in case you want to see some nontrivial CJSX code (so not just demo examples): https://github.com/sagemathinc/cocalc/tree/master/src/smc-webapp/jupyter It is very nice to write.... |
@williamstein Do you mind trying to compile your project via #4551? It’s a PR that adds JSX support to CoffeeScript 2, and I think it’s ready to merge in. You’ll have to rework your build chain, to run both It would be helpful to us to see how that PR fares on a real-world project, to see if there are any bugs that need to be addressed. The one caveat we’re already aware of is that sometimes the compiler can be confused by |
Hi @GeoffreyBooth, I would like to try it too, but how do I install it? With |
That branch isn't even merged into git clone -b csx [email protected]:xixixao/coffee-script.git Then copy the You'll also need to do the build chain refactoring I mentioned above, as this new compiler outputs JSX, not ES5 ready-to-run JavaScript. |
But with the above command it's suposed to install not from npm, but from xixixao's repo. In fact, I tested it in my react project this component and this other with this webpack config and works like a charm by now. I'm trying to turn my ES6 project to CS. |
Without spread support existing projects are probably not gonna compile, fyi. |
Wow, |
Resolved via #4551. |
I’ve been thinking about JSX, and a way that CoffeeScript could perhaps support it. In particular, I’ve been thinking about how it’s actually somewhat similar to Literate CoffeeScript:
I haven’t used JSX in a project, so if I’m oversimplifying here, please correct me.
What if the CoffeeScript compiler detected XML open and closing tags and treated them the same as backticks? In other words, everything from the first
<
of the first opening tag through to the last>
of the last closing tag would be passed through unmodified, similar to embedded JavaScript. The compiler would therefore be outputting JSX, and then the next step in the build chain would be Babel with the JSX transform.This wouldn’t even be a breaking change. Currently
<div>
throwsunexpected <
, and</div>
throwsmissing / (unclosed regex)
.We also wouldn’t be favoring one framework over another, because we’re just outputting the straight HTML, not converting it into code like
React.createElement
. Vue has added support for JSX, and the Babel transform is now generic to allow future frameworks to adopt it, so it’s not a React-specific thing anymore.How is this different from coffee-react or coffee-react-transform or CJSX, you might ask?
React.createElement
output, and therefore was dependant on React; the last version supports “React >= 0.14.x”.That last point is important. In my proposal, within an XML block you would need to use
this.foo
, not@foo
. You couldn’t write code like coffee-react‘s example{<p key={n}>This line has been printed {n} times</p> for n in [1..5]}
—you would need to write the JavaScript equivalent. But I don’t think this is so terrible. In my experience, most of the JavaScript inside a template is very minimal, generally just references to objects or basic constructs likeif
statements or loops. (Again, please correct me if I’m wrong.) It would be a formidable challenge to parse CoffeeScript within XML tags, which is probably whycoffee-react
was deprecated; but I don’t think we need to take on that task.I can certainly see people objecting to this proposal on the grounds that people shouldn’t be forced to use JavaScript, or even snippets of it, within a CoffeeScript file. Fair enough. Another perfectly valid objection is that JSX isn’t hard to achieve right now using plain backticks:
See CoffeeScript and Babel conversions. If we don’t implement this proposal, at the very least we should perhaps at this example to the docs, either in the backticks section or in a new section discussing JSX.
But anyway, now that JSX is growing into a framework-independent standard, and we can support it in a minimal, framework-independent way, should we? And if we should, does this seem like the way to add support for it?
The text was updated successfully, but these errors were encountered: