Skip to content
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

Coffee Tags #4346

Closed
wants to merge 46 commits into from
Closed

Coffee Tags #4346

wants to merge 46 commits into from

Conversation

shreeve
Copy link
Contributor

@shreeve shreeve commented Oct 23, 2016

CoffeeTags

  • tiny addition (just 27 lines total) to the native CoffeeScript compiler for rendering HTML
  • most useful with projects like Vuejs or React, but can be used by many others
  • no new language or syntax to learn, just use all the power of normal CoffeeScript
  • resembles HTML, but is actually active/executable code
  • doesn't mess with CoffeeScript syntax, takes a previously invalid syntax and makes it legal
  • doesn't require tags to be closed, uses indentation level to determine
  • doesn't require tags to be predeclared to use (no more {div, table, th, tr} = React.DOM)
  • allows selector-like syntax for tags (e.g. - <div.active@menu> yields <div class="active" id="menu">
  • allows naked tags (e.g. - you don't have to type div(), you can just use <div>)
  • allows raw HTML using a trailing exclamation point in the tag name (e.g. - <span.note!> "Note: #{text}")
  • allows default tag name of div (i.e. - <>, <.note>, and <.active@sidebar!> are all valid)
  • allows reserved words to be used as attributes (no more using className: 'foo')
  • allows attributes to be skipped (no more using div null, "Hello")
  • allows children to be passed as a list without having to pass them as an explicit array
  • allows the first argument to indicate if the tag should be skipped (via truthy Boolean or Number)
  • invokes a render function (by convention, 'h') to process tags for the target framework (React, Vue, etc.)
  • a companion helper for Vuejs is found at Allow tag selectors in render functions vuejs/vue#4016
  • TODO: allows dynamic tags (e.g. - <"h#{ level }"> can produce <h3>)
  • TODO: allows closing tags (they are ignored, but might help some with readability)

Example

CoffeeScript source

render: (h) ->
  <table.active.fixed@inbox>
    <tr>
      <th> 'From'
      <th> 'Subject'
      <th> 'Received'
    for item in @list
      <tr> class:item.tags,
        <td> <p> item.from
        <td> item.subj
        <td> <p> item.date

CoffeeScript output

render: function(h) {
  var item;
  return h('table.active.fixed@inbox', h('tr', h('th', 'From'), h('th', 'Subject'), h('th', 'Received')), (function() {
    var i, len, ref, results;
    ref = this.list;
    results = [];
    for (i = 0, len = ref.length; i < len; i++) {
      item = ref[i];
      results.push(h('tr', {
        "class": item.tags
      }, h('td', h('p', item.from)), h('td', item.subj), h('td', h('p', item.date))));
    }
    return results;
  }).call(this));
}

An example component (this one uses VueJS)

It's important to note that, just by adding a new legal token to CoffeeScript, this is totally valid CoffeeScript code (ie - there is no "transformation" step, it's just "normal" CoffeeScript source):

messages coffee lite 2016-10-23 00-48-13

adjustCoffeeTags: ->
@scanTokens (token, i, tokens) ->
if token[0] is 'CT'
f = generate 'IDENTIFIER', 'h'; f.spaced = true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can’t merge in this PR if what it does is replace this new coffee tag literal with calls to a mysterious h function that is not defined within CoffeeScript. That makes coffee tags dependant on some external library to supply this h function in the global scope.

@GeoffreyBooth
Copy link
Collaborator

@shreeve please comment when the h is addressed and Coffee Tags are compatible with not just Vue but also React, for starters, and I’ll reopen this PR.

…unction

$ coffee -s -p <<< '<!createRenderer!><a>"Hello..."'

(function() {
  createRenderer('a', "Hello...");

}).call(this);

$ coffee -s -p <<< '<a>"Hello..."'

(function() {
  h('a', "Hello...");

}).call(this);
@shreeve
Copy link
Contributor Author

shreeve commented Feb 16, 2017

@GeoffreyBooth -- I've updated my local branch to include a new custom render function, using syntax like <!myRenderer!> to set the custom function to call (the default function is just h). I've updated my branch at https://github.com/shreeve/coffeescript/tree/coffee-tags but wasn't sure how to make this older pull request update to see the new changes.

@shreeve shreeve mentioned this pull request Feb 16, 2017
@GeoffreyBooth GeoffreyBooth reopened this Feb 17, 2017
@GeoffreyBooth
Copy link
Collaborator

@shreeve Does this work with React? Can you show an example of how this could be used to implement a Coffee-style JSX?

@shreeve
Copy link
Contributor Author

shreeve commented Feb 17, 2017

@GeoffreyBooth - Yes, this should work with React also. The code will simply call a function (by default h, but you can set a custom createElement function by using the <!myCustomCreateElement!> syntax) that has the parameters (tags,data,children...).

@shreeve
Copy link
Contributor Author

shreeve commented Feb 17, 2017

Here is an example:

coffee-tags

@shreeve
Copy link
Contributor Author

shreeve commented Feb 17, 2017

In the above image, I'm actually showing the coffee-tags syntax (which allows the inline tags) and also something I called 'easytags' (which is a way to easily specify tags, classes, ids, etc.). So, <@conversations> yields <div id="conversations">. Also, the <.messages> will yield <div class="messages">.

@GeoffreyBooth
Copy link
Collaborator

Can you please show how to achieve the examples in https://facebook.github.io/react/docs/react-without-jsx.html? Those are basically hello-world level.

@mrmowgli
Copy link
Contributor

mrmowgli commented Feb 17, 2017 via email

@tiye
Copy link

tiye commented Feb 18, 2017

Adding some personal opinion here as a ClojureScript programmer, who previously wrote CoffeeScript for medium-size React projects.

I think it's not very wise to add the feature into the compiler, although the idea itself is quite cool. Here's the reasons.

When I was using writing React, I can write virtual DOM in CoffeeScript, i.e. the React.createElement way:

{div = React.DOM}

render: ->
  div {},
    div {}, "text"

That is valid JavaScript code in CoffeeScript, nothing more.

And in ClojureScript, there is a similar situation that people want to code HTML DSLs like div#foo, not that JSX-style but just aliasing ids and classes, called Hiccup:

(html [:div#foo.bar.baz "bang"])

The thing is it's just normal Clojure syntax that the library can transform the DSL at runtime. So no compiler is involved.

I mean this feature is totally new syntax sugar for CoffeeScript, which involves some HTML knowledges. That will make CoffeeScript syntax depend on HTML stuffs. As it turned out it's actually a language extension, rather than core syntax. I would be worried if CoffeeScript ships such features. Well, being a ClojureScript guy.

@GeoffreyBooth
Copy link
Collaborator

I’m not sure that this is something that should become part of the CoffeeScript compiler. On the one hand I like something that feels like an equivalent to JSX, to help prevent loss of mindshare for people who want to use React and feel like they should use JSX to do so; but on the other hand I feel like if something is going to be baked into the compiler, it needs to be abstract and able to handle any templating language. Not just JSX or HTML/React or Vue, but also Handlebars and Underscore templates, or anything else people might want to use. Not all of these things will have a render function with a (tags, data, children…) signature.

Perhaps the real way that this should be implemented is via a plugin infrastructure, similar to how JSX is processed as a plugin for Babel. Then if @shreeve really just wants Vue templating integration, he can make just that; and someone using React or Handlebars or something else full-time on another project can tackle that integration as a plugin of its own. The compiler isn’t really designed in a very modular way, so I’m not sure how a plugin architecture would even look; or maybe there could be some kind of preprocessor, similar to how marked is parsing the markdown in .litcoffee files before leaving the code for the regular compiler to parse. Either way, the templating support becomes an optional add-on, not something baked into the compiler that requires configuration.

This also needs to be built on top of the 2 branch, not master. The 1.x line is closed to new features at this point.

@lydell
Copy link
Collaborator

lydell commented Feb 21, 2017

As far as I know, all experimental or non-standard JavaScript syntax (including JSX) is hard-coded into Babylon, and just have on/off switches (I think the same is true for "all" JavaScript parsers). There are no "parser plugins": babel/babel#1351. Just sayin'.

@jashkenas
Copy link
Owner

I think I've said this when it was proposed before, but...

CoffeeScript should not be a language that mixes two different alien syntaxes together.

JSX is useful, and compelling conceptually, but hideous syntactically. It would be extremely unCoffeeScripty to munge and HTML-ish syntax into what we already have.

@GeoffreyBooth
Copy link
Collaborator

Closing for now. I think if this can be reimplemented as a plugin that sits atop CoffeeScript, similar to how marked processes Markdown in .litcoffee files, that would be the preferred approach.

@emilebosch
Copy link

@jashkenas I know what you're saying but things like typescript also have native JSX support. Its an absolute shame that implementing something like JSX as a pluginis kinda hard for none parser/grammer experts. And since CSJX is deprecated this really really forces me finally to move to babel, but i still feel completely silly writing so much babel code just because i want to use JSX. Is there really no option to include JSX?

@GeoffreyBooth
Copy link
Collaborator

See https://facebook.github.io/react/docs/react-without-jsx.html:

e = React.createElement

ReactDOM.render(
  e('div', null, 'Hello World'),
  document.getElementById('root')
)

I’ve also seen people make shortcuts for each element they want to use:

div = (props, children) ->
  React.createElement 'div', props, children

ReactDOM.render(
  div(null, 'Hello World'),
  document.getElementById('root')
)

If anything, this looks more Coffee-like than interspersing HTML tags in your code. And it’s fully supported by Facebook and React.

@emilebosch
Copy link

emilebosch commented Apr 10, 2017 via email

@GeoffreyBooth
Copy link
Collaborator

I would maybe approach this the way Illiterate does. Illiterate is an implementation of “literate anything”, inspired by Literate CoffeeScript. So Literate CoffeeScript treats indented code blocks as CoffeeScript and non-indented lines as Markdown comments. Illiterate takes this one step further, treating indented code blocks as code (language agnostic) and non-indented lines as Markdown comments. Illiterate doesn’t try to transpile the code blocks (i.e. convert CoffeeScript code to JavaScript code); it just outputs a new file that has all the “comment” lines removed, so that some other tool can compile the “code” lines.

If you can figure out how to recognize the “JSX” lines separate from the CoffeeScript lines, you could create a fork of Illiterate that works similarly. For example, any line that begins with < can probably be safely assumed to be JSX. I’m sure it’s more complicated than that, that you’ll need to catch return < and the child lines within open/close tags, but it should be doable. Then you wrap all JSX sections in backticks, pass the revised source through the CoffeeScript compiler, then pass that output through Babel with the JSX plugin.

A dumb way to separate the JSX lines from the Coffee lines is to require open/close comments, like # JSX START and # JSX END, or ```jsx and ``` like Markdown. This is quite ugly, but would be easier to implement. Though if you’re going to do this, you should probably just use the triple-backtick operator that has been part of CoffeeScript since 1.12.0.

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

Successfully merging this pull request may close these issues.

8 participants