Skip to content

Instantly share code, notes, and snippets.

@jeffwhelpley
Last active August 29, 2015 13:58
Show Gist options
  • Save jeffwhelpley/10041806 to your computer and use it in GitHub Desktop.
Save jeffwhelpley/10041806 to your computer and use it in GitHub Desktop.
Isomorphic JavaScript Templates

I am in the process of building an isomorphic JavaScript framework and I need some help deciding between two options for our templates.

I have two different strategies I am trying to choose between. I have both working and functionally either will be fine, but thing in particular I am trying to think about now is which one would be easier to grok and more accepted by developers/contractors we hire or community members we get to help out on open source pieces.

Note: both options using our jeff.js templating lib which is essentially JavaScript/JSON instead of HTML.

Option 1 - Node Style

The basic idea here is to have Node functions that are used to either bind data on the server side or generate HTML for the client side. For ex:

repeat(model.questions, function (question) {
  return div({ class: 'blah'}, question.title);
})

Which would generate this on the server:

<div class="blah">something 1</div>
<div class="blah">something else 2</div>
<div class="blah">one more thing 3</div>

Or this on the client:

<div class="blah" ng-repeat="question in questions">{{ question.title }}</div>

The way this works is that essentially the server side runtime and client side build process have different implementations of the repeat() function.

Option 2 - Angular Style

We can actually use the Angular Style witin our jeff.js templates like this:

div({ 'ng-repeat': 'question in questions' }, '{{ question.title }}')

which will generate the exact same HTML on the server and client as Option 1 above. For the client generation, the code is very simple since the markup is a match to the final client HTML. For the server, the jeff.js template engine has rendering handlers for all Angular tags that are binding in nature (i.e. ng-repeat, ng-bind, ng-show, etc.) and the server values are dynamically injected through a series of JavaScript evals. So, sort of cool...but sort of evil as well ;)

@lefnire
Copy link

lefnire commented Apr 7, 2014

I think I like #2 better. More of an overall unified experience, meaning less learning required and more expertise gained in a given area. Take my opinion as a smaller vote than others, I'm less versed in the evil of eval other than it's security implications (eg, eval performed on user data rather than as part of the codebase), to which your situation seems exempt... TMK?

@jeffwhelpley
Copy link
Author

Yeah, I am joking about the evil thing. It doesn't apply here since it is all server side and doesn't use any injected values from user submissions. Just a means to an end.

@bahmutov
Copy link

bahmutov commented Apr 7, 2014

#2 is nice because it matches angular code closely. But, #1 seems to be testable, and be able to handle better sortBy / filter extensions.

@michaelcox
Copy link

Just out of curiosity, what does a non-trivial example look like in these two formats (server-side)? It's not immediately obvious to me how you're handling nesting. For example, how would you output something like this:

<div class="blah">
  <ol>
    <li><a href="#question1">question 1</a></li>
    <li><a href="#question2">question 2</a></li>
  </ol>
</div>

Assuming that the href and text values there were passed in from your JSON model.

I'm leaning towards #2 as well either way. It seems to keep both client and server closer contextually.

Also, you can probably implement it without an eval statement if you didn't trust the template source - it'd just be harder. You would need still to eval if you expect to support any possible statement, similar to what angular gives you with {{ 2 + 2}}. But if you're just looking to access objects in your model you could do some fancy string parsing along these lines:
http://stackoverflow.com/questions/6393943/convert-javascript-string-in-dot-notation-into-an-object-reference

@jeffwhelpley
Copy link
Author

Nesting for basic tags is handled by jeff.js (without the isomorphic stuff) like this:

div({ class: 'blah' }, [
  ol([
    li([ a({ href: '#question1' }, 'question 1') ]),
    li([ a({ href: '#question2' }, 'question 2') ]),
  ])
])

Basically each HTML element is a function. To each function you can pass an object (attributes), array (sub-elements) or string (inner text).

Your example of {{ 2 + 2 }} is exactly why we do need to eval. Something like ng-bind is easy since it is just a variable assignment. The harder thing is when you have to compute values. Angular does their own eval on the client side, so we sort of are matching that on the server.

Btw, it is sort of funny that it seems like everyone commenting here on the gist is leaning toward #2 but everyone that emailed me back is leaning toward #1.

@GeorgeErickson
Copy link

Interesting, I had been playing around for a while with a similar idea to number #2 using a modified version of jade, but it became hairy very quickly.

It started out as a set of syntax helpers for angular and jade static sites, and then turned into a way to dynamically bootstrap initial http requests into the page.

The biggest issues I ran into were:

  1. Maintaining feature parity/consistency with the client side angular templates. (Something that would probably be easier with #1)
  2. Not wanting to maintain two versions of controllers/services etc. (angular.ui.router was a particular pain).
  3. Rendering data heavy pages where I want to limit the number of watch statements.

If you can get #2 to work well, then that would be my vote.

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