zzz-template 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -22
- package/index.d.ts +5 -5
- package/index.js +20 -20
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,17 +6,8 @@
|
|
|
6
6
|
[](https://bundlephobia.com/package/zzz-template)
|
|
7
7
|
[](https://github.com/cuhuak/zzz-template/blob/main/LICENSE)
|
|
8
8
|
|
|
9
|
-
**zzz-template** is an ultra-lightweight JavaScript template engine that leverages native template literals for maximum performance.
|
|
10
|
-
|
|
11
|
-
## Why zzz-template?
|
|
12
|
-
|
|
13
|
-
| Feature | zzz-template | EJS | Handlebars |
|
|
14
|
-
|---------|-------------|-----|------------|
|
|
15
|
-
| Size (min+gzip) | ~500 bytes | ~6KB | ~17KB |
|
|
16
|
-
| Dependencies | 0 | 1 | 0 |
|
|
17
|
-
| Performance | 24M ops/sec | 247K ops/sec | - |
|
|
18
|
-
| Browser + Node.js | Yes | Yes | Yes |
|
|
19
|
-
| Template Literals | Native | No | No |
|
|
9
|
+
**zzz-template** is an ultra-lightweight JavaScript template engine that leverages native template literals for maximum performance.
|
|
10
|
+
A fast, hackable alternative to EJS, Handlebars, and Mustache that works in both Node.js and browsers.
|
|
20
11
|
|
|
21
12
|
## Features
|
|
22
13
|
|
|
@@ -26,7 +17,7 @@
|
|
|
26
17
|
- **Local variables**: `${SET('title', 'Hello world')}`, then use it in template: `${local.title}`
|
|
27
18
|
- **Blazing fast**: Matches vanilla JavaScript performance (24M ops/sec)
|
|
28
19
|
- **Zero dependencies**: No bloat, no supply chain risk
|
|
29
|
-
- **Tiny footprint**: ~50 lines of code, ~
|
|
20
|
+
- **Tiny footprint**: ~50 lines of code, ~600 bytes minified + gzipped
|
|
30
21
|
- **Hackable**: Easy to extend with plugins
|
|
31
22
|
- **Isomorphic**: Works on server (Node.js) and browser
|
|
32
23
|
|
|
@@ -89,6 +80,88 @@ console.log(result)
|
|
|
89
80
|
// </p>
|
|
90
81
|
```
|
|
91
82
|
|
|
83
|
+
### All features in one example (see [examples/99-all-features](examples/99-all-features))
|
|
84
|
+
``` html
|
|
85
|
+
<!-- file examples/99-all-features/all.html -->
|
|
86
|
+
<script type="module">
|
|
87
|
+
import { ZzzTemplate, useLayout, useLocal, useIfMap, useFn } from '/zzz-template/index.js'
|
|
88
|
+
|
|
89
|
+
const zzz = new ZzzTemplate()
|
|
90
|
+
|
|
91
|
+
useLayout(zzz) // LAYOUT, INCLUDE
|
|
92
|
+
useLocal(zzz) // SET, SETA, local
|
|
93
|
+
useIfMap(zzz) // IF, IFI, MAP, MAPI, TEMPLATE
|
|
94
|
+
useFn(zzz, str => str.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>'), 'ESCAPE') // custom ESCAPE function
|
|
95
|
+
|
|
96
|
+
// Render
|
|
97
|
+
const result = zzz.render('page', {
|
|
98
|
+
title: 'All Features Demo',
|
|
99
|
+
user: { name: 'Alice', role: 'admin' },
|
|
100
|
+
pets: [
|
|
101
|
+
{ name: 'Cat', say: 'meow' },
|
|
102
|
+
{ name: 'Dog', say: 'woof' }
|
|
103
|
+
],
|
|
104
|
+
html: '<b>bold</b>'
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
document.body.innerHTML = result
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<!-- Main page template -->
|
|
111
|
+
<script id="page" type="plain/text">
|
|
112
|
+
${LAYOUT('layout', {title: data.title})}
|
|
113
|
+
${SET('year', 2024)}
|
|
114
|
+
|
|
115
|
+
<h2>Hello ${data.user.name}!</h2>
|
|
116
|
+
|
|
117
|
+
<!-- IF: conditional inline template -->
|
|
118
|
+
${IF(data.user.role === 'admin', '<p>You have admin access.</p>')}
|
|
119
|
+
|
|
120
|
+
<!-- IFI: conditional include template -->
|
|
121
|
+
${IFI(data.user.role === 'admin', 'admin-badge', data.user)}
|
|
122
|
+
|
|
123
|
+
<!-- Custom function: escape HTML -->
|
|
124
|
+
<p>Escaped: ${ESCAPE(data.html)}</p>
|
|
125
|
+
|
|
126
|
+
<!-- MAP: loop with inline template -->
|
|
127
|
+
<ul>
|
|
128
|
+
${MAP(data.pets, '<li>${data.name} says ${data.say}</li>')}
|
|
129
|
+
</ul>
|
|
130
|
+
|
|
131
|
+
<!-- MAPI: loop with include template -->
|
|
132
|
+
<ul>
|
|
133
|
+
${MAPI(data.pets, 'pet')}
|
|
134
|
+
</ul>
|
|
135
|
+
|
|
136
|
+
<!-- INCLUDE: partial template -->
|
|
137
|
+
${INCLUDE('footer', {text: 'Thanks for visiting!'})}
|
|
138
|
+
</script>
|
|
139
|
+
|
|
140
|
+
<!-- Layout template -->
|
|
141
|
+
<script id="layout" type="plain/text">
|
|
142
|
+
<html>
|
|
143
|
+
<head><title>${data.title}</title></head>
|
|
144
|
+
<body>
|
|
145
|
+
<header>${data.title} - ${local.year}</header>
|
|
146
|
+
${data.content}
|
|
147
|
+
</body>
|
|
148
|
+
</html>
|
|
149
|
+
</script>
|
|
150
|
+
|
|
151
|
+
<!-- Partial templates -->
|
|
152
|
+
<script id="footer" type="plain/text">
|
|
153
|
+
<footer>${data.text}</footer>
|
|
154
|
+
</script>
|
|
155
|
+
|
|
156
|
+
<script id="admin-badge" type="plain/text">
|
|
157
|
+
<span style="color:red">[Admin: ${data.name}]</span>
|
|
158
|
+
</script>
|
|
159
|
+
|
|
160
|
+
<script id="pet" type="plain/text">
|
|
161
|
+
<li>${data.name} says "${data.say}"</li>
|
|
162
|
+
</script>
|
|
163
|
+
```
|
|
164
|
+
|
|
92
165
|
## Include (partial)
|
|
93
166
|
* `useInclude(zzz)` to enable include feature
|
|
94
167
|
* `${INCLUDE('partial', {name: 'universe'})}` to include `partial` template
|
|
@@ -343,7 +416,7 @@ function useContentTrim(zzz) {
|
|
|
343
416
|
zzz.e.push('content = content.trim();')
|
|
344
417
|
}
|
|
345
418
|
```
|
|
346
|
-
This function pushes a code snippet to the end array that will be invoked after the template content is compiled.
|
|
419
|
+
This function pushes a code snippet to the end (`e`) array that will be invoked after the template content is compiled.
|
|
347
420
|
|
|
348
421
|
|
|
349
422
|
Or you may want to introduce a new var in your templates.
|
|
@@ -395,20 +468,54 @@ console.log(result); // > "Hello Tom"
|
|
|
395
468
|
</script>
|
|
396
469
|
```
|
|
397
470
|
|
|
471
|
+
### Example using `with` statement to avoid `data.` prefix (see [examples/10-extend](examples/10-extend))
|
|
472
|
+
``` html
|
|
473
|
+
<!-- file examples/10-extend/with.html -->
|
|
474
|
+
<!-- useWith: use ${name} instead of ${data.name} -->
|
|
475
|
+
<script type="module">
|
|
476
|
+
import { ZzzTemplate, useLayout } from '/zzz-template/index.js'
|
|
477
|
+
|
|
478
|
+
// wraps template in with(data){...} so you can use ${name} instead of ${data.name}
|
|
479
|
+
function useWith(zzz) {
|
|
480
|
+
zzz.s.unshift('with(data){')
|
|
481
|
+
zzz.e.push('return content}')
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const zzz = new ZzzTemplate()
|
|
485
|
+
useLayout(zzz)
|
|
486
|
+
useWith(zzz) // MUST BE LAST
|
|
487
|
+
|
|
488
|
+
const result = zzz.render('page', { title: 'My Page', name: 'Jerry' })
|
|
489
|
+
|
|
490
|
+
document.body.innerHTML = result
|
|
491
|
+
</script>
|
|
492
|
+
|
|
493
|
+
<script id="page" type="plain/text">
|
|
494
|
+
${LAYOUT('layout', {title})}
|
|
495
|
+
<p>Hello ${name}!</p>
|
|
496
|
+
</script>
|
|
497
|
+
|
|
498
|
+
<script id="layout" type="plain/text">
|
|
499
|
+
<h1>${title}</h1>
|
|
500
|
+
${content}
|
|
501
|
+
</script>
|
|
502
|
+
```
|
|
503
|
+
|
|
398
504
|
## Fast
|
|
399
505
|
|
|
400
506
|
Fastest JS engine ever :) That is true, see the benchmark results (ran on the author's old Intel i7):
|
|
401
507
|
|
|
402
508
|
```
|
|
403
509
|
--------- Benchmark Render ---------
|
|
404
|
-
vanilla render x
|
|
405
|
-
zzz render x
|
|
406
|
-
literal render x
|
|
407
|
-
zup render x
|
|
408
|
-
ejs render x
|
|
409
|
-
dot render x 1,
|
|
410
|
-
edge render x 8,
|
|
411
|
-
|
|
510
|
+
vanilla render x 25,887,906 ops/sec ±1.96% (90 runs sampled)
|
|
511
|
+
zzz render x 26,094,676 ops/sec ±2.12% (89 runs sampled)
|
|
512
|
+
literal render x 18,892,337 ops/sec ±1.50% (90 runs sampled)
|
|
513
|
+
zup render x 3,288,075 ops/sec ±1.15% (92 runs sampled)
|
|
514
|
+
ejs render x 272,557 ops/sec ±1.12% (93 runs sampled)
|
|
515
|
+
dot render x 1,121,797 ops/sec ±1.48% (88 runs sampled)
|
|
516
|
+
edge render x 8,030 ops/sec ±1.99% (87 runs sampled)
|
|
517
|
+
handlebars render x 146,572 ops/sec ±1.29% (92 runs sampled)
|
|
518
|
+
Fastest is zzz render, vanilla render
|
|
412
519
|
```
|
|
413
520
|
|
|
414
521
|
Try to run benchmarks
|
|
@@ -429,6 +536,26 @@ And if you do, please make sure you:
|
|
|
429
536
|
- provide a secure `zzz.read` function that reads files from a specified directory, not `../../../secret.passwords`
|
|
430
537
|
- escape all user input to prevent XSS attacks
|
|
431
538
|
|
|
539
|
+
## Why zzz-template?
|
|
540
|
+
|
|
541
|
+
| Feature | zzz-template | EJS | Handlebars | doT | zup |
|
|
542
|
+
|---------|--------------|-----|------------|-----|-----|
|
|
543
|
+
| Size (min+gzip) | ~600 bytes | ~6KB | ~17KB | ~2KB | ~1KB |
|
|
544
|
+
| Dependencies | 0 | 1 | 0 | 0 | 0 |
|
|
545
|
+
| Performance | 26M ops/sec | 273K ops/sec | 147K ops/sec | 1.1M ops/sec | 3.3M ops/sec |
|
|
546
|
+
| Browser + Node.js | Yes | Yes | Yes | Yes | Yes |
|
|
547
|
+
| Template Literals | Native | No | No | No | No |
|
|
548
|
+
|
|
549
|
+
## Changelog
|
|
550
|
+
|
|
551
|
+
### 2.0.0
|
|
552
|
+
|
|
553
|
+
- **Breaking**: `useFn` simplified - now accepts `(zzz, fn, name)` and registers function by ALIAS (not fn.name)
|
|
554
|
+
- **Breaking**: `useInclude`, `useLocal`, `useIfMap` now use built-in aliases (not customizable)
|
|
555
|
+
- **Deprecated**: `ZzzBrowser` - use `ZzzTemplate` instead
|
|
556
|
+
- **Deprecated**: `ZzzFs` - use `ZzzTemplateNode` instead
|
|
557
|
+
- Added `with` statement example for cleaner template syntax
|
|
558
|
+
|
|
432
559
|
## License
|
|
433
560
|
MIT
|
|
434
561
|
|
|
@@ -441,4 +568,4 @@ Looking for JavaScript template engines? Here are some alternatives:
|
|
|
441
568
|
- [doT](https://www.npmjs.com/package/dot) - Fast template engine
|
|
442
569
|
|
|
443
570
|
---
|
|
444
|
-
Docs revision:
|
|
571
|
+
Docs revision: 2026-02-09T09:14:32.390Z
|
package/index.d.ts
CHANGED
|
@@ -12,9 +12,9 @@ export class ZzzTemplate extends ZzzTemplateBase {
|
|
|
12
12
|
read(f: string): string;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function useFn(zzz: ZzzTemplateBase, fn: Function, alias
|
|
15
|
+
export function useFn(zzz: ZzzTemplateBase, fn: Function, alias: string): void;
|
|
16
16
|
export function useContentTrim(zzz: ZzzTemplateBase): void;
|
|
17
|
-
export function useInclude(zzz: ZzzTemplateBase
|
|
18
|
-
export function useLayout(zzz: ZzzTemplateBase
|
|
19
|
-
export function useLocal(zzz: ZzzTemplateBase
|
|
20
|
-
export function useIfMap(zzz: ZzzTemplateBase
|
|
17
|
+
export function useInclude(zzz: ZzzTemplateBase): void;
|
|
18
|
+
export function useLayout(zzz: ZzzTemplateBase): void;
|
|
19
|
+
export function useLocal(zzz: ZzzTemplateBase): void;
|
|
20
|
+
export function useIfMap(zzz: ZzzTemplateBase): void;
|
package/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export class ZzzTemplateBase {
|
|
|
5
5
|
this.$ = $this
|
|
6
6
|
}
|
|
7
7
|
compile(str, local, sign = 'data, parent') {
|
|
8
|
-
const fn = new Function(sign, `${this.s.join(';')}var content=\`${str}\`;${this.e.join(';')}return content;`)
|
|
8
|
+
const fn = new Function(sign, `${this.s.join(';')}var content=\`${str}\`;${this.e.join(';')}return content;`) // var to work "with"
|
|
9
9
|
return fn.bind({...this.$, local}) // shallow copy of $
|
|
10
10
|
}
|
|
11
11
|
render(template, data, local = {}) {
|
|
@@ -19,33 +19,33 @@ export class ZzzTemplate extends ZzzTemplateBase {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
export function useFn(zzz, fn, alias) {
|
|
22
|
-
zzz.$[
|
|
23
|
-
|
|
22
|
+
zzz.$[alias] = fn
|
|
23
|
+
zzz.s.push(`let ${alias} = this.${alias}.bind(this);`)
|
|
24
24
|
}
|
|
25
25
|
export function useContentTrim(zzz) {
|
|
26
26
|
zzz.e.push('content = content.trim();')
|
|
27
27
|
}
|
|
28
|
-
export function useInclude(zzz
|
|
29
|
-
useFn(zzz, function
|
|
28
|
+
export function useInclude(zzz) {
|
|
29
|
+
useFn(zzz, function (file, data) {return zzz.compile(zzz.read(file), this.local)(data)}, 'INCLUDE')
|
|
30
30
|
}
|
|
31
|
-
export function useLayout(zzz
|
|
32
|
-
zzz.$['
|
|
33
|
-
zzz.e.push('if(this._layout)return
|
|
34
|
-
useFn(zzz, function
|
|
31
|
+
export function useLayout(zzz) {
|
|
32
|
+
zzz.$['INCLUDE'] || useInclude(zzz)
|
|
33
|
+
zzz.e.push('if(this._layout)return INCLUDE(this._layout.t,{...this._layout.d,content});')
|
|
34
|
+
useFn(zzz, function (t, d) {this._layout={t,d};return ''}, 'LAYOUT')
|
|
35
35
|
}
|
|
36
|
-
export function useLocal(zzz
|
|
36
|
+
export function useLocal(zzz) {
|
|
37
37
|
zzz.s.push('let local = this.local;')
|
|
38
38
|
zzz.$.local = {}
|
|
39
|
-
useFn(zzz, function
|
|
40
|
-
useFn(zzz, function
|
|
41
|
-
}
|
|
42
|
-
export function useIfMap(zzz
|
|
43
|
-
|
|
44
|
-
useFn(zzz, function
|
|
45
|
-
useFn(zzz, function
|
|
46
|
-
useFn(zzz, function
|
|
47
|
-
useFn(zzz, function
|
|
48
|
-
useFn(zzz, function
|
|
39
|
+
useFn(zzz, function (key, values) {this.local[key] = values;return ''}, 'SET')
|
|
40
|
+
useFn(zzz, function (key, ...values) {this.local[key] = [(this.local[key] ?? []), ...values].flat();return ''}, 'SETA')
|
|
41
|
+
}
|
|
42
|
+
export function useIfMap(zzz) {
|
|
43
|
+
zzz.$['INCLUDE'] || useInclude(zzz)
|
|
44
|
+
useFn(zzz, function (str, data) {return zzz.compile(str, this.local)(data)}, 'TEMPLATE')
|
|
45
|
+
useFn(zzz, function (cond, str, data) {return cond ? zzz.compile(str, this.local)(data) : ''}, 'IF')
|
|
46
|
+
useFn(zzz, function (cond, file, data) {return cond ? this.INCLUDE(file, data) : ''}, 'IFI')
|
|
47
|
+
useFn(zzz, function (arr, str) {return arr.map(x => {return zzz.compile(str, this.local)(x)}).join('')}, 'MAP')
|
|
48
|
+
useFn(zzz, function (arr, file) {return arr.map(x => {return this.INCLUDE(file, x)}).join('')}, 'MAPI')
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
// @deprecated ZzzBrowser, use ZzzTemplate class instead
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zzz-template",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Fastest JavaScript template engine using native template literals. Zero dependencies, ~
|
|
5
|
+
"description": "Fastest JavaScript template engine using native template literals. Zero dependencies, ~600 bytes, works in Node.js and browser. EJS/Handlebars alternative.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Evgeny Sinitsyn",
|
|
8
8
|
"email": "cuhuak@gmail.com"
|