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.
Files changed (4) hide show
  1. package/README.md +149 -22
  2. package/index.d.ts +5 -5
  3. package/index.js +20 -20
  4. package/package.json +2 -2
package/README.md CHANGED
@@ -6,17 +6,8 @@
6
6
  [![npm bundle size](https://img.shields.io/bundlephobia/minzip/zzz-template)](https://bundlephobia.com/package/zzz-template)
7
7
  [![license](https://img.shields.io/npm/l/zzz-template.svg)](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. A fast, hackable alternative to EJS, Handlebars, and Mustache that works in both Node.js and browsers.
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, ~500 bytes minified + gzipped
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('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;'), '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 24,478,910 ops/sec ±1.23% (91 runs sampled)
405
- zzz render x 24,256,470 ops/sec ±1.25% (90 runs sampled)
406
- literal render x 16,843,920 ops/sec ±1.63% (89 runs sampled)
407
- zup render x 2,738,409 ops/sec ±1.43% (91 runs sampled)
408
- ejs render x 247,632 ops/sec ±2.10% (91 runs sampled)
409
- dot render x 1,096,741 ops/sec ±0.58% (93 runs sampled)
410
- edge render x 8,037 ops/sec ±1.80% (90 runs sampled)
411
- Fastest is vanilla render, zzz render
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: 2025-12-01T07:31:03.778Z
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?: string | false): void;
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, alias?: string): void;
18
- export function useLayout(zzz: ZzzTemplateBase, alias?: string): void;
19
- export function useLocal(zzz: ZzzTemplateBase, aliasSet?: string, aliasSeta?: string): void;
20
- export function useIfMap(zzz: ZzzTemplateBase, aliases?: boolean): void;
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.$[fn.name] = fn
23
- if (alias) zzz.s.push(`let ${alias} = this.${fn.name}.bind(this);`)
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, alias = 'INCLUDE') {
29
- useFn(zzz, function include(file, data) {return zzz.compile(zzz.read(file), this.local)(data)}, alias)
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, alias = 'LAYOUT') {
32
- zzz.$['include'] || useInclude(zzz)
33
- zzz.e.push('if(this._layout)return this.include(this._layout.t,{...this._layout.d,content});')
34
- useFn(zzz, function layout(t, d) {this._layout={t,d};return ''}, alias)
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, aliasSet = 'SET', aliasSeta = 'SETA') {
36
+ export function useLocal(zzz) {
37
37
  zzz.s.push('let local = this.local;')
38
38
  zzz.$.local = {}
39
- useFn(zzz, function set(key, values) {this.local[key] = values;return ''}, aliasSet)
40
- useFn(zzz, function seta(key, ...values) {this.local[key] = [(this.local[key] ?? []), ...values].flat();return ''}, aliasSeta)
41
- }
42
- export function useIfMap(zzz, aliases = true) {
43
- zzz.$['include'] || useInclude(zzz)
44
- useFn(zzz, function template(str, data) {return zzz.compile(str, this.local)(data)}, aliases && 'TEMPLATE')
45
- useFn(zzz, function if_template(cond, str, data) {return cond ? zzz.compile(str, this.local)(data) : ''}, aliases && 'IF')
46
- useFn(zzz, function if_include(cond, file, data) {return cond ? this.include(file, data) : ''}, aliases && 'IFI')
47
- useFn(zzz, function map_template(arr, str) {return arr.map(x => {return zzz.compile(str, this.local)(x)}).join('')}, aliases && 'MAP')
48
- useFn(zzz, function map_include(arr, file) {return arr.map(x => {return this.include(file, x)}).join('')}, aliases && 'MAPI')
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": "1.0.1",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
- "description": "Fastest JavaScript template engine using native template literals. Zero dependencies, ~500 bytes, works in Node.js and browser. EJS/Handlebars alternative.",
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"