/
atom.xml
372 lines (321 loc) · 17.6 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title><![CDATA[Category: handlebars | machty's thoughtz]]></title>
<link href="http://machty.github.com/blog/categories/handlebars/atom.xml" rel="self"/>
<link href="http://machty.github.com/"/>
<updated>2015-08-26T16:33:39-04:00</updated>
<id>http://machty.github.com/</id>
<author>
<name><![CDATA[Alex Matchneer]]></name>
</author>
<generator uri="http://octopress.org/">Octopress</generator>
<entry>
<title type="html"><![CDATA[Emblem.js - Indented templating targeting Handlebars.js and Ember.js]]></title>
<link href="http://machty.github.com/blog/2013/01/27/emblem-dot-js-indented-templating-targeting-handlebars-dot-js-and-ember-dot-js/"/>
<updated>2013-01-27T14:13:00-05:00</updated>
<id>http://machty.github.com/blog/2013/01/27/emblem-dot-js-indented-templating-targeting-handlebars-dot-js-and-ember-dot-js</id>
<content type="html"><![CDATA[<p>I wrote a templating language called
<a href="https://github.com/machty/emblem.js">Emblem</a> which you can use instead
of Handlebars to write templates for your views in Ember.js (and soon,
in any other setting). It's still very new, and I definitely have more
planned as far as syntax enhancements/refinements, but what's there is
pretty solid and I hope you'll take it for a test drive and give me some
feedback, or better yet submit a PR or two.</p>
<p>You can check out a zany little demo <a href="http://jsbin.com/ulegec/17/edit">here</a>, which
gives you an opportunity to try out some syntactical experiments
yourself.</p>
<p>Aaand
<a href="https://speakerdeck.com/machty/emblem-dot-js-ember-targeting-indentation-based-templates">here</a>
are the slides from the presentation at made at the NYC Ember.js meetup
I made this week. The rest of this post goes into some of the
motivation and architecture behind Emblem.</p>
<!-- more -->
<h2>Motivation</h2>
<p>Here's what Handlebars (in an Ember setting) looks like:</p>
<p><div class='bogus-wrapper'><notextile><figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt"></p></span>
</span><span class='line'>
</span><span class='line'><span class="nt"><div</span> <span class="na">id=</span><span class="s">"main-container"</span> <span class="na">class=</span><span class="s">"padded active"</span><span class="nt">></span>
</span><span class='line'> <span class="nt"><h1></span>Greetings, {{name}}<span class="nt"></h1></span>
</span><span class='line'>
</span><span class='line'> {{! comment that doesn't get rendered. }}
</span><span class='line'>
</span><span class='line'> {{#if loggedIn}}
</span><span class='line'> <span class="nt"><button</span> <span class="err">{{</span><span class="na">action</span> <span class="na">logout</span><span class="err">}}</span><span class="nt">></span>Log Out<span class="nt"></button></span>
</span><span class='line'> {{else}}
</span><span class='line'> <span class="nt"><button</span> <span class="err">{{</span><span class="na">action</span> <span class="na">login</span><span class="err">}}</span><span class="nt">></span>Log In<span class="nt"></button></span>
</span><span class='line'> {{/if}}
</span><span class='line'>
</span><span class='line'> <span class="nt"><div</span> <span class="na">class=</span><span class="s">"text-container"</span><span class="nt">></span>
</span><span class='line'> {{#each paragraphs}}
</span><span class='line'> <span class="nt"><p></span>{{{this}}}<span class="nt"></p></span>
</span><span class='line'> {{/each}}
</span><span class='line'> <span class="nt"></div></span>
</span><span class='line'><span class="nt"></div></span>
</span><span class='line'>
</span><span class='line'>
</span><span class='line'><span class="nt"><p></span>
</span></code></pre></td></tr></table></div></figure></notextile></div></p>
<p>and here's the equivalent Emblem.js code:</p>
<p><div class='bogus-wrapper'><notextile><figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
</pre></td><td class='code'><pre><code class=''><span class='line'></p>
</span><span class='line'>
</span><span class='line'><h1>main-container.padded.active</h1>
</span><span class='line'>
</span><span class='line'><p> h1 Greetings, {{name}}</p>
</span><span class='line'>
</span><span class='line'><p> / comment that doesn't get rendered</p>
</span><span class='line'>
</span><span class='line'><p> if loggedIn</p>
</span><span class='line'>
</span><span class='line'><pre><code>button click="logout" Log Out
</span><span class='line'></code></pre>
</span><span class='line'>
</span><span class='line'><p> else</p>
</span><span class='line'>
</span><span class='line'><pre><code>button click="login" Log In
</span><span class='line'></code></pre>
</span><span class='line'>
</span><span class='line'><p> .text-container</p>
</span><span class='line'>
</span><span class='line'><pre><code>each paragraphs
</span><span class='line'> p == this
</span><span class='line'></code></pre>
</span><span class='line'>
</span><span class='line'><p> / Could also write the</p>
</span><span class='line'>
</span><span class='line'><pre><code>.text-container = each paragraphs
</span><span class='line'> p == this
</span><span class='line'></code></pre>
</span><span class='line'>
</span><span class='line'><p></span></code></pre></td></tr></table></div></figure></notextile></div></p>
<p>Note how indentation is used to determine what gets placed inside HTML
elements or block Handlebars helpers. This prevents a lot of unnecessary
typing and keeps your code very neat (note that this certainly isn't a
new idea, see HAML/Slim/Jade/Python/etc).</p>
<p>More importantly, while Ember.js lets you use any templating language
you'd like, if you use something other than Handlebars, you miss out an
all of Ember's lovely data-binding functionality, which allows templates
to re-render themselves when the data they're tied to changes.</p>
<p><img src="/images/onedoesnot.png" alt="one does not simply use data-binding non-handlebars templates" /></p>
<p>One of the hacks I used to allow me to write indentation-based templates
that played nicely with Ember was to use Hamlbars, a light-weight
wrapped around HAML that exposed an <code>hb</code> helper to the HAML code which
could be used to generate the mustache'd Handlebars code, that
Handlebars would then process as if you'd just written the mustache'd
HTML yourself. A Hamlbars version of the above example would go as
follows:</p>
<p><div class='bogus-wrapper'><notextile><figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
</pre></td><td class='code'><pre><code class='haml'><span class='line'></p>
</span><span class='line'>
</span><span class='line'><h1>main-container.padded.active</h1>
</span><span class='line'>
</span><span class='line'><p> %h1 Greetings, {{name}}</p>
</span><span class='line'>
</span><span class='line'><p> -# comment that doesn't get rendered</p>
</span><span class='line'>
</span><span class='line'><p> = hb 'if loggedIn' do</p>
</span><span class='line'>
</span><span class='line'><pre><code>%button{ _action: "logout" } Log Out
</span><span class='line'><span class="p">=</span> <span class="n">hb</span> <span class="s1">'else'</span>
</span><span class='line'><span class="nt">%button</span><span class="p">{</span> <span class="n">_action</span><span class="p">:</span> <span class="s2">"login"</span> <span class="p">}</span> Log In
</span><span class='line'></code></pre>
</span><span class='line'>
</span><span class='line'><p> .text-container</p>
</span><span class='line'>
</span><span class='line'><pre><code>= hb 'each paragraphs' do
</span><span class='line'> <span class="nt">%p</span>
</span><span class='line'> <span class="p">=</span> <span class="n">hbb</span> <span class="s1">'this'</span>
</span><span class='line'></code></pre>
</span><span class='line'>
</span><span class='line'><p>
</span></code></pre></td></tr></table></div></figure></notextile></div></p>
<p>I prefer this over Handlebars, but it's still extremely jank, due to the
<code>hb</code> helper showing up all over the place and the fact that you can't
properly indent the <code>else</code> the way you'd expect without closing the
tag before injecting the <code>else</code>.</p>
<p>So, with all this in mind, I wrote Emblem.js for the following reasons:</p>
<ol>
<li>I hate writing HTML, particularly typing angle brackets and
backslashes and closing tags.</li>
<li>Hamlbars is still mad jank.</li>
<li>Beyond fixing issues inherent in Handle/Hamlbars, I had a lot of
ideas for a clean, flexible, Ember-targeting syntax.</li>
<li>I wanted to learn how to write a parser.</li>
</ol>
<h2>Solution: Emblem.js</h2>
<p>Emblem.js saves the day as follows:</p>
<ol>
<li>It's indentation-based</li>
<li>It internally compiles to Handlebars, and therefore has access to all
of Handlebars' features/helpers, including the ability to bind to
data in an Ember context</li>
<li>It spares you all sorts of ugly markup prefix characters by assuming
that either an html element or handlebars property/helper lookup
starts a line. They're equally first-class citizens, the second-class
citizen being line-starting text, which can be easily specified using
the <code>|</code> prefix.</li>
<li>It's all in JS, so you can compile in the browser.</li>
</ol>
<h3>HTML elements and HB Helpers as equal first-class citizens</h3>
<p>Check out the following valid Emblem:</p>
<p><div class='bogus-wrapper'><notextile><figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>p Hello
</span><span class='line'> foo Hello</span></code></pre></td></tr></table></div></figure></notextile></div></p>
<p>Emblem doesn't intermediately compile to Handlebars input text, but if
it did, the above code would generate something like:</p>
<p><div class='bogus-wrapper'><notextile><figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'> <span class="nt"><p></span>Hello<span class="nt"></p></span>
</span><span class='line'> {{foo Hello}}
</span></code></pre></td></tr></table></div></figure></notextile></div></p>
<p>So, a paragraph tag and helper invocation. How does Emblem know the
difference? Answer: Emblem looks out for the known HTML helpers and
assumes everything else that starts a line is a Handlebars mustache
invocation. This is bound to raise an eyebrow and furrow the other for
some people, but 1) Why would you name your helpers the same things as
HTML tags, and 2) If you really need custom elements (future versions
of) Emblem allow you to escape these elements either by specifying a
whitelist in your build tools or simply by preceding your non-standard
HTML with a <code>%</code>, similar to what HAML makes you do all the time. But
such corner cases should be very rare in the typical front-end dev
workflow.</p>
<h2>How's it built?</h2>
<p>Emblem takes your code and does the following:</p>
<ol>
<li>Runs the code through a preprocessor to strip empty lines and
validate indentation, replacing indentation starts with an <code>INDENT</code>
token and indentation ends with a <code>DEDENT</code> token.</li>
<li>Passes the pre-processed and lightly tokenized code through a PEG.js
parser, which returns an Abstract Syntax Tree (AST) of Handlebars
nodes.</li>
<li>Passes the HB AST to the Handlebars compiler, which generates the
template function that Ember (and other frameworks) can use.</li>
</ol>
<h3>PEG.js</h3>
<p><a href="http://pegjs.majda.cz/">PEG.js</a> is a JavaScript implementation of
a <a href="http://en.wikipedia.org/wiki/Parsing_expression_grammar">PEG compiler</a>.
With PEG.js, you define a parser's grammar in a <code>.pegjs</code> file, then use
PEG.js to generate a compiler based on the provided grammar. You can
return anything you want from a PEG-based compiler, but it's most common
to return some form of tree structure, since those are easy for
compilers to work with. In Emblem's case, the object returned from the
PEG parser is a Handlebars AST (the same that would have been generated
if you'd used Handlebars' Jison compiler to parse Handlebars code).</p>
<p>One thing to note about PEG is that it is a context-free parser, which
basically means that (without cheating) your PEG grammar can't rely on
changes in state to do its parsing. An example of "state"
would be, say, the current level of indentation of an Emblem template;
you can't write grammar code that says, "ok, now that I'm at level 2
indentation, I can handle 1 or 2 de-indentations, but I must throw an error
if I encounter 3 de-indentations." Rather, pure PEG requires that you
must always specify sequences of tokens that you universally expect to
encounter, regardless of the current state of the parser. The solution
to this is to first run your code through a preprocessor, which isn't
context-free, which will convert anything state-dependent to tokens that
PEG can reason about in a context-free setting. In Emblem's case, this
meant taking code like</p>
<p>```
p</p>
<pre><code>span What's up
</code></pre>
<p>```</p>
<p>and pre-processing it into</p>
<p><code>
p<TERM><INDENT>span What's up<TERM><DEDENT>
</code></p>
<p>Now that it's in the tokenized form, you could write PEG grammar like
the following, which will match the above <code>p</code> as an
<code>htmlTagAndOptionalAttributes</code> and detect (due to the indentation) that
the <code>span What's up</code> is the nested content in the (optional) block.</p>
<p><code>
htmlElementMaybeBlock = h:htmlTagAndOptionalAttributes _ TERM c:(INDENT content DEDENT)?
</code></p>
<p>This technique is how Emblem decides whether a certain line of Emblem
code is in block form or not, whether it's a Handlebars helper or an HTML element.</p>
<h2>Conclusion</h2>
<p>Writing Emblem.js was extremely challenging yet extremely rewarding. If
you've never written anything like a parser before, definitely give it a
go.</p>
<p>Emblem's getting there, but it's still not as polished as I would like.
I'd definitely appreciate whatever help I can get, so if you're feeling
saucy, check out the <a href="https://github.com/machty/emblem.js">Github Repo</a>, and submit
a PR. I'll continue to post updates to the syntax over the coming weeks.
Let me know how you think it's shaping up.</p>
]]></content>
</entry>
</feed>