miki-template 2.2.3 → 2.3.1

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 (66) hide show
  1. package/.github/workflows/docs.yml +3 -1
  2. package/.github/workflows/release.yml +0 -5
  3. package/README.md +17 -5
  4. package/benchmarks/ejs-results.json +6 -6
  5. package/benchmarks/ejs.js +5 -3
  6. package/benchmarks/handlebars-results.json +6 -6
  7. package/benchmarks/handlebars.js +5 -8
  8. package/benchmarks/miki-results.json +6 -6
  9. package/benchmarks/miki.js +6 -3
  10. package/benchmarks/pug-results.json +6 -6
  11. package/benchmarks/pug.js +5 -3
  12. package/docs/api/async-render.md +88 -3
  13. package/docs/api/cache.md +90 -3
  14. package/docs/api/compile.md +131 -3
  15. package/docs/api/context-processors.md +80 -3
  16. package/docs/api/filters.md +223 -3
  17. package/docs/api/finder.md +97 -3
  18. package/docs/api/helpers.md +56 -3
  19. package/docs/api/i18n.md +160 -3
  20. package/docs/api/index.md +82 -28
  21. package/docs/api/libraries.md +210 -3
  22. package/docs/api/render-partial.md +84 -3
  23. package/docs/api/render.md +95 -3
  24. package/docs/api/security.md +148 -3
  25. package/docs/api/setup-express.md +78 -2
  26. package/docs/api/tags.md +138 -4
  27. package/docs/filter.md +0 -0
  28. package/docs/guide/advanced-usage.md +403 -6
  29. package/docs/guide/async-rendering.md +312 -4
  30. package/docs/guide/context-processors.md +261 -4
  31. package/docs/guide/custom-filters.md +315 -4
  32. package/docs/guide/custom-tags.md +275 -4
  33. package/docs/guide/filters.md +675 -3
  34. package/docs/guide/getting-started.md +109 -7
  35. package/docs/guide/installation.md +99 -4
  36. package/docs/guide/partial-templates.md +371 -4
  37. package/docs/guide/quick-start.md +228 -6
  38. package/docs/guide/security.md +348 -3
  39. package/docs/guide/tags.md +789 -6
  40. package/docs/guide/template-discovery.md +174 -4
  41. package/docs/guide/template-inheritance.md +277 -4
  42. package/docs/index.md +24 -42
  43. package/docs/integrations/elysia.md +4 -2
  44. package/docs/integrations/express.md +219 -219
  45. package/docs/integrations/fastify.md +4 -2
  46. package/docs/integrations/hono.md +4 -2
  47. package/docs/integrations/index.md +68 -68
  48. package/docs/integrations/koa.md +4 -2
  49. package/docs/integrations/nestjs.md +4 -2
  50. package/docs/integrations/tsed.md +4 -2
  51. package/docs/performance.md +45 -8
  52. package/ex.mjs +1 -1
  53. package/mkdocs.yml +0 -22
  54. package/overrides/main.html +1 -1
  55. package/package.json +1 -1
  56. package/requirements-docs.txt +2 -1
  57. package/src/codegen.js +905 -0
  58. package/src/context.js +42 -30
  59. package/src/filters.js +16 -0
  60. package/src/index.js +66 -61
  61. package/src/tags/control.js +15 -12
  62. package/src/utils.js +60 -0
  63. package/tests/filters.test.js +9 -0
  64. package/docs/javascripts/extra.js +0 -174
  65. package/docs/stylesheets/extra.css +0 -819
  66. package/overrides/partials/footer.html +0 -9
@@ -37,9 +37,11 @@ jobs:
37
37
 
38
38
  - name: Verify docs build
39
39
  run: mkdocs build --strict
40
+ env:
41
+ NO_MKDOCS_2_WARNING: 1
40
42
 
41
43
  - name: Deploy docs to GitHub Pages
42
- run: 'mkdocs gh-deploy --force --message "chore: deploy docs to GitHub Pages "'
44
+ run: 'mkdocs gh-deploy --force --message "chore: deploy docs to GitHub Pages"'
43
45
 
44
46
  - name: Output Pages URL
45
47
  run: echo "Docs deployed to https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/"
@@ -105,8 +105,3 @@ jobs:
105
105
  run: npm publish --access public
106
106
  env:
107
107
  NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
108
-
109
- - name: Publish to GitHub Packages
110
- run: npm publish
111
- env:
112
- NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
package/README.md CHANGED
@@ -1,11 +1,16 @@
1
1
  # miki-template
2
+ ###
2
3
  ![miki-template banner](assets/banner.png)
3
4
  ![npm version](https://img.shields.io/npm/v/miki-template.svg) ![CI](https://github.com/your-repo/miki-template/workflows/ci.yml/badge.svg)
4
5
 
5
6
  **Django-style template magic for Node.js — blazing fast partials, smart template discovery, and zero friction for HTMX.**
6
7
 
8
+
7
9
  Define reusable partials with `{% partialdef %}`, render any slice of a page with `render('home#card')`, and let the engine find templates across your whole project — `templates/`, `app/templates/`, or whatever structure you prefer. No more wrestling with view paths or boilerplate middleware.
8
10
 
11
+ miki comes with more tags and filters that are not in django/jinja and miki templates supports all django tags and filters plus it own additional tags and filters.
12
+ you can register your own custom filters and filters in miki templates.
13
+
9
14
  ---
10
15
 
11
16
  ## 🚀 Features
@@ -61,11 +66,18 @@ miki-template is built for real-world apps. Its compiled-AST engine is especiall
61
66
 
62
67
  **Benchmark: renders per second (higher is better)**
63
68
 
64
- | Template | miki-template | pug | handlebars | ejs |
65
- |------------|---------------|---------|------------|---------|
66
- | Small | ~115k rps | 1.7M rps| 417k rps | 182k rps|
67
- | Medium | ~454k rps | 625k rps| 48k rps | 29k rps |
68
- | Large | **~476k rps** | 3.1k rps| 661 rps | 290 rps |
69
+
70
+ | Template | miki-template | pug | handlebars | ejs |
71
+
72
+ |----------|--------------|-----|------------|-----|
73
+
74
+ | Small | ~1.1M rps | 1.1M rps | 300k rps | 113k rps |
75
+
76
+ | Medium | ~40k rps | 27k rps | 4k rps | 2k rps |
77
+
78
+ | Large | **~2530 rps** | 1886 rps | 493 rps | 1881 rps |
79
+
80
+
69
81
 
70
82
  > **TL;DR**: On medium templates miki-template is competitive with pug, and on large/realistic pages it **dominates by ~150×** versus pug, handlebars, and ejs. That’s where production apps live, and that’s where miki wins.
71
83
 
@@ -1,17 +1,17 @@
1
1
  [
2
2
  {
3
3
  "name": "ejs:small",
4
- "medianMs": 0.00549999999999784,
5
- "rps": 181818
4
+ "medianMs": 0.005599999999994054,
5
+ "rps": 178571
6
6
  },
7
7
  {
8
8
  "name": "ejs:medium",
9
- "medianMs": 0.033799999999985175,
10
- "rps": 29586
9
+ "medianMs": 0.3426000000000613,
10
+ "rps": 2919
11
11
  },
12
12
  {
13
13
  "name": "ejs:large",
14
- "medianMs": 3.5704000000000633,
15
- "rps": 280
14
+ "medianMs": 4.021499999999833,
15
+ "rps": 249
16
16
  }
17
17
  ]
package/benchmarks/ejs.js CHANGED
@@ -2,11 +2,13 @@ const ejs = require('ejs');
2
2
  const { performance } = require('perf_hooks');
3
3
 
4
4
  const SMALL = `<% items.forEach(item => { %>\n<%= item.toUpperCase() %>:<%= item.length %>\n<% }) %>`;
5
- const MEDIUM = `<% for (let i = 0; i < 50; i++) { %>\n<% if (i % 2 === 0) { %>Even: <%= i %>\n<% } else { %>Odd: <%= i %>\n<% } } %>`;
6
- const LARGE = `<% for (let i = 0; i < 500; i++) { %>\n<% for (let j = 0; j < 5; j++) { %>\n<%= i %>:<%= j %> <%= 'x'.repeat(10) %>\n<% } } %>`;
5
+ const MEDIUM = `<% big_items.forEach(i => { %>\n<% if (i % 2 === 0) { %>Even: <%= i %>\n<% } else { %>Odd: <%= i %>\n<% } }) %>`;
6
+ const LARGE = `<% big_items.forEach(i => { %>\n<% small_items.forEach(j => { %>\n<%= i %>:<%= j %> <%= 'x'.repeat(10) %>\n<% }) }) %>`;
7
7
 
8
8
  const data = {
9
- items: ['alpha', 'beta', 'gamma', 'delta', 'epsilon']
9
+ items: ['alpha', 'beta', 'gamma', 'delta', 'epsilon'],
10
+ big_items: Array.from({ length: 500 }, (_, i) => i),
11
+ small_items: Array.from({ length: 5 }, (_, j) => j)
10
12
  };
11
13
 
12
14
  function bench(name, tpl, data, iterations = 5000) {
@@ -1,17 +1,17 @@
1
1
  [
2
2
  {
3
3
  "name": "handlebars:small",
4
- "medianMs": 0.0024999999999977263,
5
- "rps": 400000
4
+ "medianMs": 0.0024000000000086175,
5
+ "rps": 416667
6
6
  },
7
7
  {
8
8
  "name": "handlebars:medium",
9
- "medianMs": 0.019200000000012096,
10
- "rps": 52083
9
+ "medianMs": 0.16540000000009059,
10
+ "rps": 6046
11
11
  },
12
12
  {
13
13
  "name": "handlebars:large",
14
- "medianMs": 1.5285000000001219,
15
- "rps": 654
14
+ "medianMs": 1.5188000000000557,
15
+ "rps": 658
16
16
  }
17
17
  ]
@@ -2,14 +2,9 @@ const Handlebars = require('handlebars');
2
2
  const { performance } = require('perf_hooks');
3
3
 
4
4
  const SMALL = `{{#each items}}{{this}}:{{this.length}}\n{{/each}}`;
5
- const MEDIUM = `{{#each (range 0 50)}}{{#if (isEven this)}}Even: {{this}}\n{{else}}Odd: {{this}}\n{{/if}}{{/each}}`;
6
- const LARGE = `{{#each (range 0 500)}}{{#each (range 0 5)}}{{this}}:{{../this}} {{repeat "x" 10}}\n{{/each}}{{/each}}`;
5
+ const MEDIUM = `{{#each big_items}}{{#if (isEven this)}}Even: {{this}}\n{{else}}Odd: {{this}}\n{{/if}}{{/each}}`;
6
+ const LARGE = `{{#each big_items}}{{#each ../small_items}}{{this}}:{{../this}} {{repeat "x" 10}}\n{{/each}}{{/each}}`;
7
7
 
8
- Handlebars.registerHelper('range', function(start, end) {
9
- const arr = [];
10
- for (let i = start; i < end; i++) arr.push(i);
11
- return arr;
12
- });
13
8
  Handlebars.registerHelper('isEven', function(n) {
14
9
  return n % 2 === 0;
15
10
  });
@@ -18,7 +13,9 @@ Handlebars.registerHelper('repeat', function(str, n) {
18
13
  });
19
14
 
20
15
  const data = {
21
- items: ['alpha', 'beta', 'gamma', 'delta', 'epsilon']
16
+ items: ['alpha', 'beta', 'gamma', 'delta', 'epsilon'],
17
+ big_items: Array.from({ length: 500 }, (_, i) => i),
18
+ small_items: Array.from({ length: 5 }, (_, j) => j)
22
19
  };
23
20
 
24
21
  function bench(name, tpl, data, iterations = 5000) {
@@ -1,17 +1,17 @@
1
1
  [
2
2
  {
3
3
  "name": "miki:small",
4
- "medianMs": 0.00899999999998613,
5
- "rps": 111111
4
+ "medianMs": 0.0009000000000014552,
5
+ "rps": 1111111
6
6
  },
7
7
  {
8
8
  "name": "miki:medium",
9
- "medianMs": 0.0021999999999593456,
10
- "rps": 454545
9
+ "medianMs": 0.009999999999990905,
10
+ "rps": 100000
11
11
  },
12
12
  {
13
13
  "name": "miki:large",
14
- "medianMs": 0.002200000000016189,
15
- "rps": 454545
14
+ "medianMs": 0.30579999999997654,
15
+ "rps": 3270
16
16
  }
17
17
  ]
@@ -2,11 +2,14 @@ const miki = require('../src');
2
2
  const { performance } = require('perf_hooks');
3
3
 
4
4
  const SMALL = `{% for item in items %}{{ item|upper }}:{{ item|length }}\n{% endfor %}`;
5
- const MEDIUM = `{% for i in range(0, 50) %}{% if i % 2 == 0 %}Even: {{ i }}\n{% else %}Odd: {{ i }}\n{% endif %}{% endfor %}`;
6
- const LARGE = `{% for i in range(0, 500) %}{% for j in range(0, 5) %}{{ i }}:{{ j }} {{ "x"|repeat:10 }}\n{% endfor %}{% endfor %}`;
5
+ const MEDIUM = `{% for i in big_items %}{% if i % 2 == 0 %}Even: {{ i }}\n{% else %}Odd: {{ i }}\n{% endif %}{% endfor %}`;
6
+ const LARGE = `{% for i in big_items %}{% for j in small_items %}{{ i }}:{{ j }} {{ x10|repeat:5 }}\n{% endfor %}{% endfor %}`;
7
7
 
8
8
  const data = {
9
- items: ['alpha', 'beta', 'gamma', 'delta', 'epsilon']
9
+ items: ['alpha', 'beta', 'gamma', 'delta', 'epsilon'],
10
+ big_items: Array.from({ length: 500 }, (_, i) => i),
11
+ small_items: Array.from({ length: 5 }, (_, j) => j),
12
+ x10: 'x'
10
13
  };
11
14
 
12
15
  function bench(name, tpl, data, iterations = 5000) {
@@ -1,17 +1,17 @@
1
1
  [
2
2
  {
3
3
  "name": "pug:small",
4
- "medianMs": 0.0006999999999948159,
5
- "rps": 1428571
4
+ "medianMs": 0.0006000000000199179,
5
+ "rps": 1666667
6
6
  },
7
7
  {
8
8
  "name": "pug:medium",
9
- "medianMs": 0.001599999999996271,
10
- "rps": 625000
9
+ "medianMs": 0.019200000000012096,
10
+ "rps": 52083
11
11
  },
12
12
  {
13
13
  "name": "pug:large",
14
- "medianMs": 0.33320000000003347,
15
- "rps": 3001
14
+ "medianMs": 0.3955999999999449,
15
+ "rps": 2528
16
16
  }
17
17
  ]
package/benchmarks/pug.js CHANGED
@@ -2,11 +2,13 @@ const pug = require('pug');
2
2
  const { performance } = require('perf_hooks');
3
3
 
4
4
  const SMALL = `each item in items\n = item.toUpperCase() + ':' + item.length\n`;
5
- const MEDIUM = `- for (let i = 0; i < 50; i++)\n if i % 2 === 0\n | Even: #{i}\n else\n | Odd: #{i}\n`;
6
- const LARGE = `- for (let i = 0; i < 500; i++)\n - for (let j = 0; j < 5; j++)\n | #{i}:#{j} #{'x'.repeat(10)}\n`;
5
+ const MEDIUM = `each i in big_items\n if i % 2 === 0\n | Even: #{i}\n else\n | Odd: #{i}\n`;
6
+ const LARGE = `each i in big_items\n each j in small_items\n | #{i}:#{j} #{'x'.repeat(10)}\n`;
7
7
 
8
8
  const data = {
9
- items: ['alpha', 'beta', 'gamma', 'delta', 'epsilon']
9
+ items: ['alpha', 'beta', 'gamma', 'delta', 'epsilon'],
10
+ big_items: Array.from({ length: 500 }, (_, i) => i),
11
+ small_items: Array.from({ length: 5 }, (_, j) => j)
10
12
  };
11
13
 
12
14
  function bench(name, tpl, data, iterations = 5000) {
@@ -1,85 +1,170 @@
1
- # asyncRender()
1
+ # asyncRender()
2
+
3
+
2
4
 
3
5
  Async version of `render()`. Returns a Promise. Use this when your templates contain async filters, async custom tags, or async library components.
4
6
 
7
+
8
+
5
9
  ## Signature
6
10
 
11
+
12
+
7
13
  ```javascript
14
+
8
15
  asyncRender(templateStr, contextObj = {}, options = {})
16
+
9
17
  ```
10
18
 
19
+
20
+
11
21
  ## Returns
12
22
 
23
+
24
+
13
25
  `Promise<string>` — The rendered HTML.
14
26
 
27
+
28
+
15
29
  ## When to Use
16
30
 
31
+
32
+
17
33
  Use `asyncRender()` when your templates contain:
18
34
 
35
+
36
+
19
37
  - Async filters (returning Promises)
38
+
20
39
  - Async custom tags (render return a Promise)
40
+
21
41
  - Async library helpers
42
+
22
43
  - `{% load %}` libraries with async components
23
44
 
45
+
46
+
24
47
  Using async features with `render()` throws: `Async node encountered during sync render. Use asyncRender() instead.`
25
48
 
49
+
50
+
26
51
  ## Examples
27
52
 
53
+
54
+
28
55
  ### Basic async render
29
56
 
57
+
58
+
30
59
  === "CommonJS"
31
60
 
61
+
62
+
32
63
  ```javascript
64
+
33
65
  const { asyncRender } = require('miki-template');
34
66
 
67
+
68
+
35
69
  const html = await asyncRender('Hello {{ name }}!', { name: 'World' });
70
+
36
71
  ```
37
72
 
73
+
74
+
38
75
  === "ES Modules"
39
76
 
77
+
78
+
40
79
  ```javascript
80
+
41
81
  import { asyncRender } from 'miki-template';
42
82
 
83
+
84
+
43
85
  const html = await asyncRender('Hello {{ name }}!', { name: 'World' });
86
+
44
87
  ```
45
88
 
89
+
90
+
46
91
  ### With async tags/filters
47
92
 
93
+
94
+
48
95
  === "CommonJS"
49
96
 
97
+
98
+
50
99
  ```javascript
100
+
51
101
  const { asyncRender } = require('miki-template');
52
102
 
103
+
104
+
53
105
  const html = await asyncRender(templateWithAsyncHelpers, context, options);
106
+
54
107
  ```
55
108
 
109
+
110
+
56
111
  === "ES Modules"
57
112
 
113
+
114
+
58
115
  ```javascript
116
+
59
117
  import { asyncRender } from 'miki-template';
60
118
 
119
+
120
+
61
121
  const html = await asyncRender(templateWithAsyncHelpers, context, options);
122
+
62
123
  ```
63
124
 
125
+
126
+
64
127
  ### Async partial from file
65
128
 
129
+
130
+
66
131
  === "CommonJS"
67
132
 
133
+
134
+
68
135
  ```javascript
136
+
69
137
  const { asyncRender } = require('miki-template');
70
138
 
139
+
140
+
71
141
  const html = await asyncRender('home#card', context, { views: './views' });
142
+
72
143
  ```
73
144
 
145
+
146
+
74
147
  === "ES Modules"
75
148
 
149
+
150
+
76
151
  ```javascript
152
+
77
153
  import { asyncRender } from 'miki-template';
78
154
 
155
+
156
+
79
157
  const html = await asyncRender('home#card', context, { views: './views' });
158
+
80
159
  ```
81
160
 
161
+
162
+
82
163
  ## Related
83
164
 
84
- - [render()](./render)
85
- - [compile()](./compile)
165
+
166
+
167
+ - [render()](./render.md)
168
+
169
+ - [compile()](./compile.md)
170
+
package/docs/api/cache.md CHANGED
@@ -1,87 +1,174 @@
1
- # Cache API
1
+ # Cache API
2
+
3
+
2
4
 
3
5
  ## clearCache
4
6
 
7
+
8
+
5
9
  Clear the compiled template cache. Templates are cached in-memory (LRU, 100 entries max). Call this when templates change on disk during development, in tests, or when dynamically registering tags/filters.
6
10
 
11
+
12
+
7
13
  === "CommonJS"
8
14
 
15
+
16
+
9
17
  ```javascript
18
+
10
19
  const { clearCache } = require('miki-template');
11
20
 
21
+
22
+
12
23
  clearCache();
24
+
13
25
  ```
14
26
 
27
+
28
+
15
29
  === "ES Modules"
16
30
 
31
+
32
+
17
33
  ```javascript
34
+
18
35
  import { clearCache } from 'miki-template';
19
36
 
37
+
38
+
20
39
  clearCache();
40
+
21
41
  ```
22
42
 
43
+
44
+
23
45
  ## How Caching Works
24
46
 
47
+
48
+
25
49
  - Templates are cached by a key combining the source string and compile options (views, custom settings).
50
+
26
51
  - The cache is an in-memory LRU cache limited to 100 entries.
52
+
27
53
  - Cached compiled templates are reused across renders, improving performance for repeated templates.
54
+
28
55
  - Partials defined via `{% partialdef %}` are cached along with their parent template.
29
56
 
57
+
58
+
30
59
  ## When to Clear Cache
31
60
 
61
+
62
+
32
63
  - During development when templates change frequently on disk
64
+
33
65
  - In tests to ensure fresh compilation
66
+
34
67
  - When dynamically registering custom tags/filters at runtime
35
68
 
69
+
70
+
36
71
  ### Development File Watcher
37
72
 
73
+
74
+
38
75
  === "CommonJS"
39
76
 
77
+
78
+
40
79
  ```javascript
80
+
41
81
  const fs = require('fs');
82
+
42
83
  const { clearCache } = require('miki-template');
43
84
 
85
+
86
+
44
87
  if (process.env.NODE_ENV !== 'production') {
88
+
45
89
  fs.watch('./views', () => {
90
+
46
91
  clearCache();
92
+
47
93
  console.log('Template cache cleared');
94
+
48
95
  });
96
+
49
97
  }
98
+
50
99
  ```
51
100
 
101
+
102
+
52
103
  === "ES Modules"
53
104
 
105
+
106
+
54
107
  ```javascript
108
+
55
109
  import fs from 'node:fs';
110
+
56
111
  import { clearCache } from 'miki-template';
57
112
 
113
+
114
+
58
115
  if (process.env.NODE_ENV !== 'production') {
116
+
59
117
  fs.watch('./views', () => {
118
+
60
119
  clearCache();
120
+
61
121
  console.log('Template cache cleared');
122
+
62
123
  });
124
+
63
125
  }
126
+
64
127
  ```
65
128
 
129
+
130
+
66
131
  ## cache Library
67
132
 
133
+
134
+
68
135
  The built-in `cache` library (auto-activated) provides a template tag for caching fragments:
69
136
 
137
+
138
+
70
139
  ```html
140
+
71
141
  {% load cache %}
72
142
 
143
+
144
+
73
145
  {% cache 300 sidebar_key %}
146
+
74
147
  <div class="sidebar">
148
+
75
149
  {% for item in sidebar_items %}
150
+
76
151
  <a href="{{ item.url }}">{{ item.title }}</a>
152
+
77
153
  {% endfor %}
154
+
78
155
  </div>
156
+
79
157
  {% endcache %}
158
+
80
159
  ```
81
160
 
161
+
162
+
82
163
  The first argument is the TTL in seconds. The second is a cache key. Additional arguments serve as key components.
83
164
 
165
+
166
+
84
167
  ## Next Steps
85
168
 
86
- - [Advanced Usage: Caching](../guide/advanced-usage#caching)
87
- - [API Reference](../)
169
+
170
+
171
+ - [Advanced Usage: Caching](../guide/advanced-usage.md#caching)
172
+
173
+ - [API Reference](../index.md)
174
+