UNPKG

14.5 kBMarkdownView Raw
1[![npm][npm]][npm-url]
2[![node][node]][node-url]
3[![tests][tests]][tests-url]
4[![coverage][cover]][cover-url]
5[![code style][style]][style-url]
6
7<div align="center">
8 <img width="110" height="100" title="PostHTML Plugin" vspace="50" src="http://michael-ciniawsky.github.io/postcss-load-plugins/logo.svg">
9 <img width="220" height="200" title="PostHTML" src="http://posthtml.github.io/posthtml/logo.svg">
10 <h1>Expressions Plugin</h1>
11</div>
12
13<h2 align="center">Install</h2>
14
15```bash
16npm i -D posthtml-expressions
17```
18
19<h2 align="center">Usage</h2>
20
21```js
22const { readFileSync } = require('fs')
23
24const posthtml = require('posthtml')
25const expressions = require('posthtml-expressions')
26
27posthtml(expressions({ locals: { foo: 'bar' } }))
28 .process(readFileSync('index.html', 'utf8'))
29 .then((result) => console.log(result.html))
30```
31
32This plugin provides a syntax for including local variables and expressions in your templates, and also extends custom tags to act as helpers for conditionals and looping.
33
34You have full control over the delimiters used for injecting locals, as well as the tag names for the conditional and loop helpers, if you need them. All options that can be passed to the `expressions` plugin are shown below:
35
36<h2 align="center">Options</h2>
37
38|Option|Default|Description|
39|:----:|:-----:|:----------|
40| **delimiters** | `['{{', '}}']` | Array containing beginning and ending delimiters for escaped locals |
41| **unescapeDelimiters** | `['{{{', '}}}']` | Array containing beginning and ending delimiters for unescaped locals |
42| **locals** | `{}` | Object containing any local variables you want to be available inside your expressions |
43| **localsAttr** | `locals` | Attribute name for the tag `script` which contains ***[locals](#locals)***|
44| **removeScriptLocals** | `false` | Will remove tag `script` which contains ***[locals](#locals)***|
45| **conditionalTags** | `['if', 'elseif', 'else']` | Array containing names for tags used for `if/else if/else` statements |
46| **switchTags** | `['switch', 'case', 'default']` | Array containing names for tags used for `switch/case/default` statements |
47| **loopTags** | `['each']` | Array containing names for `for` loops |
48| **scopeTags** | `['scope']` | Array containing names for scopes |
49| **ignoredTag** | `'raw'` | String containing name of tag inside which parsing is disabled |
50| **strictMode** | `true` | Boolean value set to `false` will not throw an exception |
51
52### Locals
53
54You can inject locals into any piece of content in your html templates, other than overwriting tag names. For example, if you passed the following config to the expressions plugin:
55
56```js
57locals: { className: 'intro', name: 'Marlo', 'status': 'checked' }
58```
59
60```html
61<div class="{{ className }}">
62 <input type="radio" {{ status }}>
63 My name is {{ name }}
64</div>
65```
66
67```html
68<div class="intro">
69 <input type="radio" checked="">
70 My name is Marlo
71</div>
72```
73
74You can also use the script tag with the attribute `locals` or you custome attribute containing data to interpolate in the template.
75
76```html
77<script locals>
78 module.exports = {
79 name: 'Scrum'
80 }
81</script>
82
83<div>My name: {{name}}</div>
84```
85
86```html
87<script locals>
88 module.exports = {
89 name: 'Scrum'
90 }
91</script>
92
93<div>My name: Scrum</div>
94```
95
96In addition, the use of script tag allow you to use `locals` defined globally to assign data to variables.
97
98```js
99posthtml(expressions({ locals: { foo: 'bar' } }))
100 .process(readFileSync('index.html', 'utf8'))
101 .then((result) => console.log(result.html))
102```
103
104```html
105<script locals>
106 module.exports = {
107 name: 'Scrum',
108 foo: locals.foo || 'empty'
109 }
110</script>
111
112<div>My name: {{name}}</div>
113<div>Foo: {{foo}}</div>
114```
115
116```html
117<script locals>
118 module.exports = {
119 name: 'Scrum',
120 foo: locals.foo || 'empty'
121 }
122</script>
123
124<div>My name: {{name}}</div>
125<div>Foo: bar</div>
126```
127
128### Unescaped Locals
129
130By default, special characters will be escaped so that they show up as text, rather than html code. For example, if you had a local containing valid html as such:
131
132```js
133locals: { statement: '<strong>wow!</strong>' }
134```
135
136```html
137<p>The fox said, {{ statement }}</p>
138```
139
140```html
141<p>The fox said, &lt;strong&gt;wow!&lt;strong&gt;</p>
142```
143
144In your browser, you would see the angle brackets, and it would appear as intended. However, if you wanted it instead to be parsed as html, you would need to use the `unescapeDelimiters`, which by default are three curly brackets, like this:
145
146```html
147<p>The fox said, {{{ strongStatement }}}</p>
148```
149
150In this case, your code would render as html:
151
152```html
153<p>The fox said, <strong>wow!<strong></p>
154```
155
156### Expressions
157
158You are not limited to just directly rendering local variables either, you can include any type of javascript expressions and it will be evaluated, with the result rendered. For example:
159
160```html
161<p class="{{ env === 'production' ? 'active' : 'hidden' }}">in production!</p>
162```
163
164With this in mind, it is strongly recommended to limit the number and complexity of expressions that are run directly in your template. You can always move the logic back to your config file and provide a function to the locals object for a smoother and easier result. For example:
165
166```js
167locals: {
168 isProduction: (env) => env === 'production' ? 'active' : 'hidden'
169}
170```
171
172```html
173<p class="{{ isProduction(env) }}">in production!</p>
174```
175
176#### Ignoring Expressions
177
178Many JavaScript frameworks use `{{` and `}}` as expression delimiters. It can even happen that another framework uses the same _custom_ delimiters you have defined in this plugin.
179
180You can tell the plugin to completely ignore an expression by prepending `@` to the delimiters:
181
182```html
183<p>The @{{ foo }} is strong with this one.</p>
184```
185
186Result:
187
188```html
189<p>The {{ foo }} is strong with this one.</p>
190```
191
192### Conditionals
193
194Conditional logic uses normal html tags, and modifies/replaces them with the results of the logic. If there is any chance of a conflict with other custom tag names, you are welcome to change the tag names this plugin looks for in the options. For example, given the following config:
195
196```js
197locals: { foo: 'foo' }
198```
199
200```html
201<if condition="foo === 'bar'">
202 <p>Foo really is bar! Revolutionary!</p>
203</if>
204
205<elseif condition="foo === 'wow'">
206 <p>Foo is wow, oh man.</p>
207</elseif>
208
209<else>
210 <p>Foo is probably just foo in the end.</p>
211</else>
212```
213
214```html
215<p>Foo is probably just foo in the end.</p>
216```
217
218Anything in the `condition` attribute is evaluated directly as an expressions.
219
220It should be noted that this is slightly cleaner-looking if you are using the [SugarML parser](https://github.com/posthtml/sugarml). But then again so is every other part of html.
221
222```sml
223if(condition="foo === 'bar'")
224 p Foo really is bar! Revolutionary!
225
226elseif(condition="foo === 'wow'")
227 p Foo is wow, oh man.
228
229else
230 p Foo is probably just foo in the end.
231```
232
233#### `conditionalTags`
234
235Type: `array`\
236Default: `['if', 'elseif', 'else']`
237
238You can define custom tag names to use for creating a conditional.
239
240Example:
241
242```js
243conditionalTags: ['when', 'elsewhen', 'otherwise']
244```
245
246```html
247<when condition="foo === 'bar'">
248 <p>Foo really is bar! Revolutionary!</p>
249</when>
250
251<elsewhen condition="foo === 'wow'">
252 <p>Foo is wow, oh man.</p>
253</elsewhen>
254
255<otherwise>
256 <p>Foo is probably just foo in the end.</p>
257</otherwise>
258```
259
260Note: tag names must be in the exact order as the default ones.
261
262### Switch statement
263
264Switch statements act like streamline conditionals. They are useful for when you want to compare a single variable against a series of constants.
265
266```js
267locals: { foo: 'foo' }
268```
269
270```html
271<switch expression="foo">
272 <case n="'bar'">
273 <p>Foo really is bar! Revolutionary!</p>
274 </case>
275 <case n="'wow'">
276 <p>Foo is wow, oh man.</p>
277 </case>
278 <default>
279 <p>Foo is probably just foo in the end.</p>
280 </default>
281</switch>
282```
283
284```html
285<p>Foo is probably just foo in the end.</p>
286```
287
288Anything in the `expression` attribute is evaluated directly as an expressions.
289
290#### `switchTags`
291
292Type: `array`\
293Default: `['switch', 'case', 'default']`
294
295You can define custom tag names to use when creating a switch.
296
297Example:
298
299```js
300switchTags: ['clause', 'when', 'fallback']
301```
302
303```html
304<clause expression="foo">
305 <when n="'bar'">
306 <p>Foo really is bar! Revolutionary!</p>
307 </when>
308 <when n="'wow'">
309 <p>Foo is wow, oh man.</p>
310 </when>
311 <fallback>
312 <p>Foo is probably just foo in the end.</p>
313 </fallback>
314</clause>
315```
316
317Note: tag names must be in the exact order as the default ones.
318
319### Loops
320
321You can use the `each` tag to build loops. It works with both arrays and objects. For example:
322
323```js
324locals: {
325 array: ['foo', 'bar'],
326 object: { foo: 'bar' }
327}
328```
329
330**Array**
331```html
332<each loop="item, index in array">
333 <p>{{ index }}: {{ item }}</p>
334</each>
335```
336
337```html
338<p>1: foo</p>
339<p>2: bar</p>
340```
341
342**Object**
343```html
344<each loop="value, key in anObject">
345 <p>{{ key }}: {{ value }}</p>
346</each>
347```
348
349```html
350<p>foo: bar</p>
351```
352
353The value of the `loop` attribute is not a pure expressions evaluation, and it does have a tiny and simple custom parser. Essentially, it starts with one or more variable declarations, comma-separated, followed by the word `in`, followed by an expressions.
354
355
356```html
357<each loop="item in [1,2,3]">
358 <p>{{ item }}</p>
359</each>
360```
361
362So you don't need to declare all the available variables (in this case, the index is skipped), and the expressions after `in` doesn't need to be a local variable, it can be any expressions.
363
364#### `loopTags`
365
366Type: `array`\
367Default: `['each']`
368
369You can define custom tag names to use for creating loops:
370
371Example:
372
373```js
374loopTags: ['each', 'for']
375```
376
377You can now also use the `<for>` tag when writing a loop:
378
379```html
380<for loop="item in [1,2,3]">
381 <p>{{ item }}</p>
382</for>
383```
384
385#### Loop meta
386
387Inside a loop, you have access to a special `loop` object, which contains information about the loop currently being executed:
388
389- `loop.index` - the current iteration of the loop (0 indexed)
390- `loop.remaining` - number of iterations until the end (0 indexed)
391- `loop.first` - boolean indicating if it's the first iteration
392- `loop.last` - boolean indicating if it's the last iteration
393- `loop.length` - total number of items
394
395Example:
396
397```html
398<each loop='item in [1,2,3]'>
399 <li>Item value: {{ item }}</li>
400 <li>Current iteration of the loop: {{ loop.index }}</li>
401 <li>Number of iterations until the end: {{ loop.remaining }} </li>
402 <li>This {{ loop.first ? 'is' : 'is not' }} the first iteration</li>
403 <li>This {{ loop.last ? 'is' : 'is not' }} the last iteration</li>
404 <li>Total number of items: {{ loop.length }}</li>
405</each>
406```
407
408### Scopes
409
410You can replace locals inside certain area wrapped in a `<scope>` tag. For example you can use it after [posthtml-include](https://github.com/posthtml/posthtml-include)
411
412```js
413locals: {
414 author: { name: 'John'},
415 editor: { name: 'Jeff'}
416}
417```
418
419```html
420<scope with="author">
421 <include src="components/profile.html"></include>
422</scope>
423<scope with="editor">
424 <include src="components/profile.html"></include>
425</scope>
426```
427
428```html
429<div class="profile">
430 <div class="profile__name">{{ name }}</div>
431 <img class="profile__avatar" src="{{ image }}" alt="{{ name }}'s avatar" />
432 <a class="profile__link" href="{{ link }}">more info</a>
433</div>
434```
435
436#### `scopeTags`
437
438Type: `array`\
439Default: `['scope']`
440
441You can define a custom tag name to use for creating scopes:
442
443Example:
444
445```js
446scopeTags: ['context']
447```
448
449You can now also use the `<context>` tag when writing a scope:
450
451```html
452<context with="author">
453 <include src="components/profile.html"></include>
454</context>
455```
456
457### Ignored tag
458
459Anything inside this tag will not be parsed, allowing you to output delimiters and anything the plugin would normally parse, in their original form.
460
461```html
462<raw>
463 <if condition="foo === 'bar'">
464 <p>Output {{ foo }} as-is</p>
465 </if>
466</raw>
467```
468
469```html
470<if condition="foo === 'bar'">
471 <p>Output {{ foo }} as-is</p>
472</if>
473```
474
475You can customize the name of the tag:
476
477```js
478var opts = {
479 ignoredTag: 'verbatim',
480 locals: { foo: 'bar' } }
481}
482
483posthtml(expressions(opts))
484 .process(readFileSync('index.html', 'utf8'))
485 .then((result) => console.log(result.html))
486```
487
488```html
489<verbatim>
490 <if condition="foo === 'bar'">
491 <p>Output {{ foo }} as-is</p>
492 </if>
493</verbatim>
494```
495
496```html
497<if condition="foo === 'bar'">
498 <p>Output {{ foo }} as-is</p>
499</if>
500```
501
502<h2 align="center">Maintainers</h2>
503
504<table>
505 <tbody>
506 <tr>
507 <td align="center">
508 <img width="150 height="150"
509 src="https://avatars.githubusercontent.com/u/556932?v=3&s=150">
510 <br>
511 <a href="https://github.com/jescalan">Jeff Escalante</a>
512 </td>
513 <td align="center">
514 <img width="150 height="150"
515 src="https://avatars.githubusercontent.com/u/7034281?v=3&s=150">
516 <br>
517 <a href="https://github.com/mrmlnc">Denis Malinochkin</a>
518 </td>
519 </tr>
520 <tbody>
521</table>
522
523<h2 align="center">Contributors</h2>
524
525<table>
526 <tbody>
527 <tr>
528 <td align="center">
529 <img width="150 height="150"
530 src="https://avatars.githubusercontent.com/u/5419992?v=3&s=150">
531 <br>
532 <a href="https://github.com/michael-ciniawsky">Michael Ciniawsky</a>
533 </td>
534 <td align="center">
535 <img width="150 height="150"
536 src="https://avatars.githubusercontent.com/u/17473315?v=3&s=150">
537 <br>
538 <a href="https://github.com/xakdog">Krillin</a>
539 </td>
540 <td align="center">
541 <img width="150 height="150"
542 src="https://avatars0.githubusercontent.com/u/1656595?s=150&v=4">
543 <br>
544 <a href="https://github.com/cossssmin">Cosmin Popovici</a>
545 </td>
546 </tr>
547 <tbody>
548</table>
549
550
551[npm]: https://img.shields.io/npm/v/posthtml-expressions.svg
552[npm-url]: https://npmjs.com/package/posthtml-expressions
553
554[node]: https://img.shields.io/node/v/posthtml-expressions.svg
555[node-url]: https://nodejs.org/
556
557[deps]: https://david-dm.org/posthtml/posthtml-expressions.svg
558[deps-url]: https://david-dm.org/posthtml/posthtml-expressions
559
560[tests]: https://github.com/posthtml/posthtml-expressions/workflows/Actions%20Status/badge.svg?style=flat-square
561[tests-url]: https://github.com/posthtml/posthtml-expressions/actions?query=workflow%3A%22CI+tests%22
562
563[cover]: https://coveralls.io/repos/github/posthtml/posthtml-expressions/badge.svg
564[cover-url]: https://coveralls.io/github/posthtml/posthtml-expressions
565
566[style]: https://img.shields.io/badge/code%20style-standard-yellow.svg
567[style-url]: http://standardjs.com/