miki-template 2.0.0 → 2.2.2

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 (87) hide show
  1. package/.github/workflows/ci.yml +14 -10
  2. package/.github/workflows/docs.yml +105 -0
  3. package/.github/workflows/npm-publish-github-packages.yml +36 -0
  4. package/README.md +142 -26
  5. package/assets/logo.png +0 -0
  6. package/benchmarks/ejs-results.json +17 -0
  7. package/benchmarks/ejs.js +36 -0
  8. package/benchmarks/handlebars-results.json +17 -0
  9. package/benchmarks/handlebars.js +48 -0
  10. package/benchmarks/miki-results.json +17 -0
  11. package/benchmarks/miki.js +36 -0
  12. package/benchmarks/pug-results.json +17 -0
  13. package/benchmarks/pug.js +36 -0
  14. package/benchmarks/run.js +69 -37
  15. package/benchmarks/stress.mjs +1 -1
  16. package/docs/api/async-render.md +85 -0
  17. package/docs/api/cache.md +87 -0
  18. package/docs/api/compile.md +128 -0
  19. package/docs/api/context-processors.md +77 -0
  20. package/docs/api/filters.md +217 -0
  21. package/docs/api/finder.md +94 -0
  22. package/docs/api/helpers.md +53 -0
  23. package/docs/api/i18n.md +157 -0
  24. package/docs/api/index.md +54 -0
  25. package/docs/api/libraries.md +207 -0
  26. package/docs/api/render-partial.md +81 -0
  27. package/docs/api/render.md +92 -0
  28. package/docs/api/security.md +145 -0
  29. package/docs/api/setup-express.md +76 -0
  30. package/docs/api/tags.md +134 -0
  31. package/docs/assets/banner.png +0 -0
  32. package/docs/assets/logo.png +0 -0
  33. package/docs/guide/advanced-usage.md +397 -0
  34. package/docs/guide/async-rendering.md +308 -0
  35. package/docs/guide/context-processors.md +257 -0
  36. package/docs/guide/custom-filters.md +311 -0
  37. package/docs/guide/custom-tags.md +271 -0
  38. package/docs/guide/filters.md +642 -0
  39. package/docs/guide/getting-started.md +102 -0
  40. package/docs/guide/installation.md +95 -0
  41. package/docs/guide/partial-templates.md +367 -0
  42. package/docs/guide/quick-start.md +222 -0
  43. package/docs/guide/security.md +345 -0
  44. package/docs/guide/tags.md +783 -0
  45. package/docs/guide/template-discovery.md +170 -0
  46. package/docs/guide/template-inheritance.md +273 -0
  47. package/docs/guide/what-is-miki-template.md +28 -0
  48. package/docs/guide/why-miki-template.md +75 -0
  49. package/docs/index.md +104 -0
  50. package/docs/integrations/elysia.md +78 -0
  51. package/docs/integrations/express.md +219 -0
  52. package/docs/integrations/fastify.md +77 -0
  53. package/docs/integrations/hono.md +78 -0
  54. package/docs/integrations/index.md +68 -0
  55. package/docs/integrations/koa.md +88 -0
  56. package/docs/integrations/nestjs.md +78 -0
  57. package/docs/integrations/tsed.md +81 -0
  58. package/docs/javascripts/extra.js +174 -0
  59. package/docs/performance.md +37 -0
  60. package/docs/stylesheets/extra.css +819 -0
  61. package/live-test/integrations/elysia-example.js +16 -0
  62. package/live-test/integrations/express-example.js +24 -0
  63. package/live-test/integrations/fastify-example.js +20 -0
  64. package/live-test/integrations/hono-example.js +16 -0
  65. package/live-test/integrations/koa-example.js +30 -0
  66. package/live-test/integrations/nestjs-example.js +25 -0
  67. package/live-test/integrations/smoke-test.js +166 -0
  68. package/live-test/integrations/tsed-example.js +23 -0
  69. package/live-test/package-lock.json +235 -0
  70. package/live-test/package.json +4 -0
  71. package/live-test/views/home.html +17 -0
  72. package/mkdocs.yml +217 -0
  73. package/overrides/main.html +26 -0
  74. package/overrides/partials/footer.html +9 -0
  75. package/package.json +16 -6
  76. package/requirements-docs.txt +1 -0
  77. package/tests/integration/partial-render.test.cjs +13 -0
  78. package/docs/README.md +0 -18
  79. package/docs/advanced_usage.md +0 -71
  80. package/docs/api.md +0 -122
  81. package/docs/filters.md +0 -708
  82. package/docs/installation.md +0 -106
  83. package/docs/overview.md +0 -79
  84. package/docs/partialdef.md +0 -70
  85. package/docs/security.md +0 -27
  86. package/docs/tags.md +0 -673
  87. package/docs/usage.md +0 -646
@@ -0,0 +1,207 @@
1
+ # Libraries API
2
+
3
+ ## registerLibrary
4
+
5
+ Register a library — a named bundle of filters, tags, and helpers.
6
+
7
+ === "CommonJS"
8
+
9
+ ```javascript
10
+ const { registerLibrary } = require('miki-template');
11
+
12
+ registerLibrary('mylib', {
13
+ filters: {
14
+ shout: (val) => String(val).toUpperCase() + '!'
15
+ },
16
+ tags: {
17
+ hello: (tagContent, parser) => ({
18
+ render: (context) => 'Hello!'
19
+ })
20
+ },
21
+ helpers: {
22
+ bold: (inner, context) => `<b>${inner}</b>`
23
+ }
24
+ });
25
+ ```
26
+
27
+ === "ES Modules"
28
+
29
+ ```javascript
30
+ import { registerLibrary } from 'miki-template';
31
+
32
+ registerLibrary('mylib', {
33
+ filters: {
34
+ shout: (val) => String(val).toUpperCase() + '!'
35
+ },
36
+ tags: {
37
+ hello: (tagContent, parser) => ({
38
+ render: (context) => 'Hello!'
39
+ })
40
+ },
41
+ helpers: {
42
+ bold: (inner, context) => `<b>${inner}</b>`
43
+ }
44
+ });
45
+ ```
46
+
47
+ ## activateLibrary
48
+
49
+ Activate a library so its filters and tags become available.
50
+
51
+ === "CommonJS"
52
+
53
+ ```javascript
54
+ const { activateLibrary } = require('miki-template');
55
+
56
+ activateLibrary('mylib');
57
+ ```
58
+
59
+ === "ES Modules"
60
+
61
+ ```javascript
62
+ import { activateLibrary } from 'miki-template';
63
+
64
+ activateLibrary('mylib');
65
+ ```
66
+
67
+ Built-in libraries (`humanize`, `cache`, `lorem`, `markdown`, `i18n`) are auto-activated on import.
68
+
69
+ ## unregisterLibrary
70
+
71
+ === "CommonJS"
72
+
73
+ ```javascript
74
+ const { unregisterLibrary } = require('miki-template');
75
+
76
+ // Unregister specific library
77
+ unregisterLibrary('mylib');
78
+
79
+ // Unregister all libraries
80
+ unregisterLibrary();
81
+ ```
82
+
83
+ === "ES Modules"
84
+
85
+ ```javascript
86
+ import { unregisterLibrary } from 'miki-template';
87
+
88
+ unregisterLibrary('mylib');
89
+ ```
90
+
91
+ ## hasLibrary
92
+
93
+ === "CommonJS"
94
+
95
+ ```javascript
96
+ const { hasLibrary } = require('miki-template');
97
+
98
+ if (hasLibrary('humanize')) {
99
+ // library is registered
100
+ }
101
+ ```
102
+
103
+ === "ES Modules"
104
+
105
+ ```javascript
106
+ import { hasLibrary } from 'miki-template';
107
+
108
+ if (hasLibrary('humanize')) {
109
+ // library is registered
110
+ }
111
+ ```
112
+
113
+ ## getLibrary
114
+
115
+ Retrieve a library by name.
116
+
117
+ === "CommonJS"
118
+
119
+ ```javascript
120
+ const { getLibrary } = require('miki-template');
121
+
122
+ const lib = getLibrary('humanize');
123
+ console.log(Object.keys(lib.filters));
124
+ ```
125
+
126
+ === "ES Modules"
127
+
128
+ ```javascript
129
+ import { getLibrary } from 'miki-template';
130
+
131
+ const lib = getLibrary('humanize');
132
+ ```
133
+
134
+ ## getLibraryNames
135
+
136
+ === "CommonJS"
137
+
138
+ ```javascript
139
+ const { getLibraryNames } = require('miki-template');
140
+
141
+ console.log(getLibraryNames());
142
+ // ['humanize', 'cache', 'lorem']
143
+ ```
144
+
145
+ === "ES Modules"
146
+
147
+ ```javascript
148
+ import { getLibraryNames } from 'miki-template';
149
+
150
+ console.log(getLibraryNames());
151
+ ```
152
+
153
+ ## registerLibraryFromPath
154
+
155
+ Load a library from a JavaScript file on disk. The file must export `{ tags, filters, helpers }`.
156
+
157
+ === "CommonJS"
158
+
159
+ ```javascript
160
+ const { registerLibraryFromPath } = require('miki-template');
161
+
162
+ registerLibraryFromPath('mylib', './libs/mylib.js');
163
+ ```
164
+
165
+ === "ES Modules"
166
+
167
+ ```javascript
168
+ import { registerLibraryFromPath } from 'miki-template';
169
+
170
+ await registerLibraryFromPath('mylib', './libs/mylib.mjs');
171
+ ```
172
+
173
+ ## Built-in Libraries
174
+
175
+ ### humanize
176
+
177
+ Provides natural formatting filters: `intcomma`, `intword`, `ordinal`, `naturalday`, `naturaltime`.
178
+
179
+ | Filter | Description |
180
+ |--------|-------------|
181
+ | `intcomma` | `1234567` → `1,234,567` |
182
+ | `intword` | `1234567` → `1.2M` |
183
+ | `ordinal` | `1` → `1st`, `2` → `2nd` |
184
+ | `naturalday` | Format dates as "today", "yesterday" |
185
+ | `naturaltime` | Format times as "just now", "2 hours ago" |
186
+ | `ordinal` | Convert numbers to ordinal (1st, 2nd, 3rd) |
187
+
188
+ ### cache
189
+
190
+ Provides `{% cache timeout key %}...{% endcache %}` tag for caching template fragments.
191
+
192
+ ### lorem
193
+
194
+ Provides `{% lorem count random_words %}` tag and `lorem` filter for placeholder text.
195
+
196
+ ### markdown
197
+
198
+ Provides `markdown` filter for Markdown→HTML conversion.
199
+
200
+ ### i18n
201
+
202
+ Provides `{% trans %}`, `{% blocktrans %}`, `{% language %}` tags and translation functions.
203
+
204
+ ## Next Steps
205
+
206
+ - [Advanced Usage: Libraries](../guide/advanced-usage#library-system)
207
+ - [API Reference](../)
@@ -0,0 +1,81 @@
1
+ # renderPartialFromFile / renderPartialFromSource
2
+
3
+ Render a named partial from a template file or source string.
4
+
5
+ ## renderPartialFromFile
6
+
7
+ Render a named partial from a template file.
8
+
9
+ ```javascript
10
+ renderPartialFromFile(fileName, partialName, contextObj, options)
11
+ ```
12
+
13
+ ### Parameters
14
+
15
+ | Parameter | Type | Description |
16
+ |-----------|------|-------------|
17
+ | `fileName` | `string` | Template file name (without extension) |
18
+ | `partialName` | `string` | Name of the partial to render |
19
+ | `contextObj` | `object` | Variables to inject |
20
+ | `options` | `object` | Options including `views` directories |
21
+
22
+ ### Example
23
+
24
+ === "CommonJS"
25
+
26
+ ```javascript
27
+ const { renderPartialFromFile } = require('miki-template');
28
+
29
+ const html = renderPartialFromFile('home', 'card', { title: 'Hello' }, { views: './views' });
30
+ ```
31
+
32
+ === "ES Modules"
33
+
34
+ ```javascript
35
+ import { renderPartialFromFile } from 'miki-template';
36
+
37
+ const html = renderPartialFromFile('home', 'card', { title: 'Hello' }, { views: './views' });
38
+ ```
39
+
40
+ ## renderPartialFromSource
41
+
42
+ Render a named partial from a template source string.
43
+
44
+ ```javascript
45
+ renderPartialFromSource(fileContent, partialName, contextObj, options, filePath?)
46
+ ```
47
+
48
+ ### Parameters
49
+
50
+ | Parameter | Type | Description |
51
+ |-----------|------|-------------|
52
+ | `fileContent` | `string` | Template source string |
53
+ | `partialName` | `string` | Name of the partial to render |
54
+ | `contextObj` | `object` | Variables to inject |
55
+ | `options` | `object` | Options |
56
+ | `filePath` | `string?` | Optional file path for error messages |
57
+
58
+ ### Example
59
+
60
+ === "CommonJS"
61
+
62
+ ```javascript
63
+ const { renderPartialFromSource } = require('miki-template');
64
+
65
+ const source = `{% partialdef card %}<div>{{ title }}</div>{% endpartialdef %}`;
66
+ const html = renderPartialFromSource(source, 'card', { title: 'Hello' });
67
+ ```
68
+
69
+ === "ES Modules"
70
+
71
+ ```javascript
72
+ import { renderPartialFromSource } from 'miki-template';
73
+
74
+ const source = `{% partialdef card %}<div>{{ title }}</div>{% endpartialdef %}`;
75
+ const html = renderPartialFromSource(source, 'card', { title: 'Hello' });
76
+ ```
77
+
78
+ ## Related
79
+
80
+ - [render()](./render)
81
+ - [compile()](./compile)
@@ -0,0 +1,92 @@
1
+ # render()
2
+
3
+ Render a template string or file partial.
4
+
5
+ ## Signature
6
+
7
+ ```javascript
8
+ render(templateStr, contextObj = {}, options = {})
9
+ ```
10
+
11
+ ## Parameters
12
+
13
+ | Parameter | Type | Description |
14
+ |-----------|------|-------------|
15
+ | `templateStr` | `string` | Template string or file path with `#partial` suffix |
16
+ | `contextObj` | `object` | Variables to inject into the template |
17
+ | `options` | `object` | Options including `views` directories and custom settings |
18
+
19
+ ## Returns
20
+
21
+ `string` — The rendered HTML.
22
+
23
+ ## Examples
24
+
25
+ ### Render a template string
26
+
27
+ === "CommonJS"
28
+
29
+ ```javascript
30
+ const { render } = require('miki-template');
31
+
32
+ const html = render('Hello {{ name }}!', { name: 'World' });
33
+ // Output: Hello World!
34
+ ```
35
+
36
+ === "ES Modules"
37
+
38
+ ```javascript
39
+ import { render } from 'miki-template';
40
+
41
+ const html = render('Hello {{ name }}!', { name: 'World' });
42
+ // Output: Hello World!
43
+ ```
44
+
45
+ ### Render a partial from file
46
+
47
+ === "CommonJS"
48
+
49
+ ```javascript
50
+ const { render } = require('miki-template');
51
+
52
+ const html = render('home#card', { title: 'Hello' }, { views: './views' });
53
+ ```
54
+
55
+ === "ES Modules"
56
+
57
+ ```javascript
58
+ import { render } from 'miki-template';
59
+
60
+ const html = render('home#card', { title: 'Hello' }, { views: './views' });
61
+ ```
62
+
63
+ ### Render with options
64
+
65
+ === "CommonJS"
66
+
67
+ ```javascript
68
+ const { render } = require('miki-template');
69
+
70
+ const html = render(template, context, {
71
+ views: ['./views', './app/templates'],
72
+ staticUrl: '/static',
73
+ urlHelper: (name, ...args) => '/' + name + '/' + args.join('/')
74
+ });
75
+ ```
76
+
77
+ === "ES Modules"
78
+
79
+ ```javascript
80
+ import { render } from 'miki-template';
81
+
82
+ const html = render(template, context, {
83
+ views: ['./views', './app/templates'],
84
+ staticUrl: '/static',
85
+ urlHelper: (name, ...args) => '/' + name + '/' + args.join('/')
86
+ });
87
+ ```
88
+
89
+ ## Related
90
+
91
+ - [compile()](./compile)
92
+ - [asyncRender()](./async-render)
@@ -0,0 +1,145 @@
1
+ # Security API
2
+
3
+ ## markSafe
4
+
5
+ Mark a string as safe (bypass auto-escaping).
6
+
7
+ === "CommonJS"
8
+
9
+ ```javascript
10
+ const { markSafe } = require('miki-template');
11
+
12
+ const html = markSafe('<b>ok</b>');
13
+ // Will not be escaped
14
+ ```
15
+
16
+ === "ES Modules"
17
+
18
+ ```javascript
19
+ import { markSafe } from 'miki-template';
20
+
21
+ const html = markSafe('<b>ok</b>');
22
+ ```
23
+
24
+ ## isSafe
25
+
26
+ Check if a value is marked safe.
27
+
28
+ === "CommonJS"
29
+
30
+ ```javascript
31
+ const { isSafe } = require('miki-template');
32
+
33
+ if (isSafe(value)) {
34
+ // value is marked safe
35
+ }
36
+ ```
37
+
38
+ === "ES Modules"
39
+
40
+ ```javascript
41
+ import { isSafe } from 'miki-template';
42
+
43
+ if (isSafe(value)) {
44
+ // value is marked safe
45
+ }
46
+ ```
47
+
48
+ ## escapeHtml
49
+
50
+ Escape HTML special characters (`&`, `<`, `>`, `"`, `'`, `` ` ``).
51
+
52
+ === "CommonJS"
53
+
54
+ ```javascript
55
+ const { escapeHtml } = require('miki-template');
56
+
57
+ const escaped = escapeHtml('<script>');
58
+ // Output: &lt;script&gt;
59
+ ```
60
+
61
+ === "ES Modules"
62
+
63
+ ```javascript
64
+ import { escapeHtml } from 'miki-template';
65
+
66
+ const escaped = escapeHtml('<script>');
67
+ ```
68
+
69
+ ### Force-Escape SafeString
70
+
71
+ Pass `true` as the second argument to force-escape a `SafeString` (matching Django's `|escape` filter behavior):
72
+
73
+ === "CommonJS"
74
+
75
+ ```javascript
76
+ const { escapeHtml, SafeString } = require('miki-template');
77
+
78
+ const safe = new SafeString('<b>bold</b>');
79
+ const forced = escapeHtml(safe, true);
80
+ // Output: &lt;b&gt;bold&lt;/b&gt;
81
+ ```
82
+
83
+ === "ES Modules"
84
+
85
+ ```javascript
86
+ import { escapeHtml, SafeString } from 'miki-template';
87
+
88
+ const safe = new SafeString('<b>bold</b>');
89
+ const forced = escapeHtml(safe, true);
90
+ ```
91
+
92
+ ## stripExpressContext
93
+
94
+ Strip Express-specific framework keys (`_`, `settings`, `cache`) from a context object.
95
+
96
+ === "CommonJS"
97
+
98
+ ```javascript
99
+ const { stripExpressContext } = require('miki-template');
100
+
101
+ const cleanCtx = stripExpressContext(expressOptions);
102
+ // Removes: _locals, settings, cache, and other _ prefixed keys
103
+ ```
104
+
105
+ === "ES Modules"
106
+
107
+ ```javascript
108
+ import { stripExpressContext } from 'miki-template';
109
+
110
+ const cleanCtx = stripExpressContext(expressOptions);
111
+ ```
112
+
113
+ ## SafeString Class
114
+
115
+ Create a SafeString instance directly.
116
+
117
+ === "CommonJS"
118
+
119
+ ```javascript
120
+ const { SafeString } = require('miki-template');
121
+
122
+ const safe = new SafeString('<b>ok</b>');
123
+ ```
124
+
125
+ === "ES Modules"
126
+
127
+ ```javascript
128
+ import { SafeString } from 'miki-template';
129
+
130
+ const safe = new SafeString('<b>ok</b>');
131
+ ```
132
+
133
+ ## Path Traversal Protection
134
+
135
+ The `extends` and `include` tags validate that resolved template paths stay within configured views directories. Attempting to traverse outside throws an error:
136
+
137
+ ```html
138
+ {% extends "../../etc/passwd" %} <!-- throws -->
139
+ {% include "../../secrets" %} <!-- throws -->
140
+ ```
141
+
142
+ ## Next Steps
143
+
144
+ - [Security Guide](../guide/security)
145
+ - [API Reference](../)
@@ -0,0 +1,76 @@
1
+ # setupExpress()
2
+
3
+ One-line Express integration that wires the view engine, views directory, and partial responses.
4
+
5
+ ## Signature
6
+
7
+ ```javascript
8
+ setupExpress(app, options = {})
9
+ ```
10
+
11
+ ## Options
12
+
13
+ | Option | Type | Description |
14
+ |--------|------|-------------|
15
+ | `extension` | `string` | View file extension, default `'html'` |
16
+ | `views` | `string\|string[]` | Views directory path(s) |
17
+ | `async` | `boolean` | Use async engine (`__expressAsync`) for Express 5+ |
18
+
19
+ ## What It Does
20
+
21
+ - Calls `app.engine()` with the miki view engine.
22
+ - Sets `app.set('view engine', extension)` if not already set.
23
+ - Sets `app.set('views', views)` if `options.views` is provided.
24
+ - Expands `views` to include nested template directories (app-style `templates/` folders).
25
+ - Patches `res.render` to support `view#partial` syntax for HTMX responses.
26
+
27
+ ## Example
28
+
29
+ === "CommonJS"
30
+
31
+ ```javascript
32
+ const express = require('express');
33
+ const miki = require('miki-template');
34
+
35
+ const app = express();
36
+ miki.setupExpress(app, { extension: 'html', views: './views' });
37
+
38
+ app.get('/', (req, res) => res.render('home', { user: req.user }));
39
+ app.get('/card/:id', (req, res) =>
40
+ res.render(`home#card`, { title: 'Hello' })
41
+ );
42
+
43
+ app.listen(3000);
44
+ ```
45
+
46
+ === "ES Modules"
47
+
48
+ ```javascript
49
+ import express from 'express';
50
+ import miki from 'miki-template';
51
+
52
+ const app = express();
53
+ miki.setupExpress(app, { extension: 'html', views: './views' });
54
+
55
+ app.get('/', (req, res) => res.render('home', { user: req.user }));
56
+ app.get('/card/:id', (req, res) =>
57
+ res.render(`home#card`, { title: 'Hello' })
58
+ );
59
+
60
+ app.listen(3000);
61
+ ```
62
+
63
+ ## Partial Responses
64
+
65
+ `setupExpress()` patches `res.render` so that any view name containing `#` renders only the named partial:
66
+
67
+ ```javascript
68
+ // Renders only the "card" partialdef from home.html
69
+ res.render('home#card', { title: 'Hello' });
70
+ ```
71
+
72
+ This is ideal for HTMX where you only need to update a portion of the page.
73
+
74
+ ## Related
75
+
76
+ - [Integrations: Express](../integrations/express)