goodeggs / teacup

Teacup is templates in CoffeeScript
MIT License
122 stars 18 forks source link

Huge import lists #16

Closed 00dani closed 11 years ago

00dani commented 11 years ago

I have what I think is a fairly simple Teacup template file, defining an HTML5 page with a table. However, because tables involve lots of tags and I also need stuff like doctype, the beginning of my template file looks like this:

{
  renderable
  raw

  doctype
  html
  head
  body

  table
  thead
  tbody
  tr
  th
  td
} = require 'teacup'

I understand why Teacup works this way, but that's fourteen lines (plus two blank ones) and is almost as long as the template itself. Is there some way to cut down on the length of these import listings? (Putting all the needed tag names on a single line with commas doesn't count, because then there's just one ridiculously long line and then has problems such as not working well with version-control diffing.)

What's the idiomatic way to write Teacup templates that use a lot of tags?

00dani commented 11 years ago

Having written a few more Teacup templates, I think I've decided that doing this works very well:

{ renderable, raw
  doctype, html, head, body
  table, thead, tbody, tr, th, td
} = require 'teacup'

i.e., importing related elements on a single line with commas, using newlines to separate different semantic "groups" of elements. This is significantly more compact than giving each element a single line, but doesn't lead to big version-control diff problems since related tags are grouped on a line.

asalant commented 11 years ago

Ha! Nice. I've been using teacup for primarily client-side view templates (e.g. backbone views) so rarely run in to a situation where there are that many tags to wrangle. When writing full views, it can get unwieldy. I like your approach.

Other alternatives that come to mind, some not for the faint-of-heart:

  1. Import teacup as 't' and reference tags with that prefix.
t = require 'teacup'

template = t.renderable ->
  t.div ->
    ...
  1. Import some or all of the teacup tags into GLOBAL. (In some situations this could be just fine).
  2. Bind a template function to teacup so that you can refer to tags as @div, @p, ... Not much better that t.div, t.p.
  3. Use CoffeeCup (https://github.com/gradus/coffeecup). We wrote Teacup because we didn't like the weird eval hoops that CoffeeCup jumps through to avoid importing tags and that destroys useful things like closure scope.
  4. You could use node's vm (http://nodejs.org/api/vm.html) to create a context with teacup at the root and evaluate a template in that context. This is like importing teacup tags into GLOBAL but without polluting the global scope. For example,
vm = require 'vm'
teacup = require 'teacup'
coffeescript = require 'coffee-script'
context = vm.createContext teacup

template = 'render -> div "foo"'
vm.runInContext context, template

evaluates to

'<div>foo</div>'
00dani commented 11 years ago

Simply importing teacup as a short name like t works well. I also really like the templateFunc.bind teacup solution; the only problem there is that it seems you have to remember to use => for tag-content callbacks instead of ->.

I didn't much like the eval magic used in CoffeeCup, especially the way it breaks closure scope, and because of that significantly prefer Teacup. I haven't actually tried using the vm module but suspect it may have the same problems with scope as CoffeeCup.

There's one more solution I thought of. It works, but it's not a technique I'd want to use regularly, especially since it can't actually be done in normal CoffeeScript.

teacup = require 'teacup'
myTemplate = renderable ->
  `with (teacup) {`
  html ->
    body ->
      p "some content"
  `}`
asalant commented 11 years ago

Yep, I didn't mention with because coffee-script doesn't support it but I forgot this style.

Another option and one that teacup makes easy, is to encapsulate common markup modules into helper functions.

{ renderable, doctype, html, head, body} = require 'teacup'

module.exports = helpers = 
  layout: renderable ({headContent, bodyContent}) ->
    doctype
    html ->
      head ->
        headContent()
      body ->
        bodyContent()

and then

{renderable, title, h1, p} = require 'teacup'
{layout} = require './helpers'

template = renderable ->
  layout
    headContent: ->
      title 'My Page'
    bodyContent: ->
      h1 'My Page'
      p 'My content.