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,134 @@
1
+ # Tags API
2
+
3
+ ## registerTag
4
+
5
+ Register a custom tag callable from templates as `{% tag_name content %}...{% endtag_name %}`.
6
+
7
+ === "CommonJS"
8
+
9
+ ```javascript
10
+ const { registerTag } = require('miki-template');
11
+
12
+ registerTag('hello', (tagContent, parser) => {
13
+ return {
14
+ render: (context) => 'Hello World!'
15
+ };
16
+ });
17
+ ```
18
+
19
+ === "ES Modules"
20
+
21
+ ```javascript
22
+ import { registerTag } from 'miki-template';
23
+
24
+ registerTag('hello', (tagContent, parser) => {
25
+ return {
26
+ render: (context) => 'Hello World!'
27
+ };
28
+ });
29
+ ```
30
+
31
+ ### Tag Parser Signature
32
+
33
+ - `tagContent` — The full text after the tag name, as a string.
34
+ - `parser` — The Parser instance, providing `parser.parse()`, `parser.peek()`, `parser.skipTag()`.
35
+
36
+ Returns a **Node** object with a `render(context)` method. The render method receives the `Context` object and returns a string.
37
+
38
+ ## Built-in Tags
39
+
40
+ ### Control Flow
41
+
42
+ | Tag | Description |
43
+ |-----|-------------|
44
+ | `if / elif / else / endif` | Conditional blocks |
45
+ | `for / empty / endfor` | Loop over arrays/objects |
46
+ | `with / endwith` | Create a scoped context |
47
+ | `cycle` | Cycle through values |
48
+ | `firstof` | Output first non-empty value |
49
+ | `ifchanged / endifchanged` | Only output if value changed |
50
+
51
+ ### Variable Assignment
52
+
53
+ | Tag | Description |
54
+ |-----|-------------|
55
+ | `set var = expr` | Inline variable assignment |
56
+ | `set var %}...{% endset` | Capture block output into a variable |
57
+
58
+ ### Date and Time
59
+
60
+ | Tag | Description |
61
+ |-----|-------------|
62
+ | `now "Y-m-d H:i:s"` | Output current date/time |
63
+
64
+ ### Utility
65
+
66
+ | Tag | Description |
67
+ |-----|-------------|
68
+ | `static "path"` | Resolve static asset path |
69
+ | `url 'route.name' arg1 arg2` | Generate URL by route name |
70
+ | `regroup list by attr as name` | Regroup a list by an attribute |
71
+ | `spaceless / endspaceless` | Remove whitespace between HTML tags |
72
+ | `widthratio value max max_width` | Calculate CSS width ratio |
73
+ | `debug` | Output debugging context information |
74
+
75
+ ### Security
76
+
77
+ | Tag | Description |
78
+ |-----|-------------|
79
+ | `csrf_token` | Output CSRF hidden input |
80
+ | `csp_nonce_attr` | Output `nonce` attribute for CSP |
81
+
82
+ ### Comments and Raw Output
83
+
84
+ | Tag | Description |
85
+ |-----|-------------|
86
+ | `comment / endcomment` | Comment out content |
87
+ | `verbatim / endverbatim` | Disable tag parsing within |
88
+
89
+ ### Autoescape
90
+
91
+ | Tag | Description |
92
+ |-----|-------------|
93
+ | `autoescape on / off / endautoescape` | Toggle HTML escaping |
94
+
95
+ ### Library Loading
96
+
97
+ | Tag | Description |
98
+ |-----|-------------|
99
+ | `load library_name` | Load a registered library |
100
+
101
+ ### Template Tags
102
+
103
+ | Tag | Description |
104
+ |-----|-------------|
105
+ | `templatetag token` | Output a template syntax character (e.g. `{% templatetag openpercentblock %}`) |
106
+
107
+ ### Inheritance
108
+
109
+ | Tag | Description |
110
+ |-----|-------------|
111
+ | `extends "parent.html"` | Inherit from a parent template |
112
+ | `block name / endblock` | Define/overriding inheritable block |
113
+ | `include "file.html"` | Include another template |
114
+
115
+ ### Partials
116
+
117
+ | Tag | Description |
118
+ |-----|-------------|
119
+ | `partialdef name / endpartialdef` | Define a named partial |
120
+ | `partial name with k=v` | Render a defined partial |
121
+
122
+ ### i18n
123
+
124
+ | Tag | Description |
125
+ |-----|-------------|
126
+ | `trans "key"` | Translate a string |
127
+ | `blocktrans / endblocktrans` | Translate with variables |
128
+ | `language "xx" / endlanguage` | Switch language for a block |
129
+
130
+ ## Next Steps
131
+
132
+ - [Tags Guide](../guide/tags)
133
+ - [Custom Tags](../guide/custom-tags)
134
+ - [API Reference](../)
Binary file
Binary file
@@ -0,0 +1,397 @@
1
+ # Advanced Usage
2
+
3
+ This guide covers advanced miki-template features: caching, library system, i18n, and more.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Caching](#caching)
8
+ - [Library System](#library-system)
9
+ - [i18n / Internationalization](#i18n--internationalization)
10
+ - [Template Discovery](#template-discovery)
11
+ - [Partial Templates](#partial-templates)
12
+ - [Extending the Engine](#extending-the-engine)
13
+
14
+ ---
15
+
16
+ ## Caching
17
+
18
+ miki-template caches compiled templates for performance. The cache is an in-memory LRU with a 100-entry limit.
19
+
20
+ ### Clearing the Cache
21
+
22
+ === "CommonJS"
23
+
24
+ ```javascript
25
+ const { clearCache } = require('miki-template');
26
+
27
+ clearCache();
28
+ ```
29
+
30
+ === "ES Modules"
31
+
32
+ ```javascript
33
+ import { clearCache } from 'miki-template';
34
+
35
+ clearCache();
36
+ ```
37
+
38
+ ### When to Clear Cache
39
+
40
+ - **Development** — when templates change frequently on disk
41
+ - **Tests** — to ensure fresh compilation
42
+ - **Runtime filter/tag registration** — when dynamically registering custom tags/filters
43
+
44
+ ```javascript
45
+ // Development middleware that clears cache on file changes
46
+ const { clearCache } = require('miki-template');
47
+
48
+ if (process.env.NODE_ENV !== 'production') {
49
+ fs.watch('./views', () => {
50
+ clearCache();
51
+ console.log('Template cache cleared');
52
+ });
53
+ }
54
+ ```
55
+
56
+ ### How Caching Works
57
+
58
+ - Templates are cached by source string and compile options.
59
+ - The cache key combines the template source and the options object (views, custom settings).
60
+ - Cached templates are reused across renders, improving performance for repeated templates.
61
+
62
+ ## Library System
63
+
64
+ Libraries are bundles of filters, tags, and helpers that can be loaded into templates. Built-in libraries (humanize, cache, lorem, markdown, i18n) are auto-activated.
65
+
66
+ ### Registering a Library
67
+
68
+ === "CommonJS"
69
+
70
+ ```javascript
71
+ const { registerLibrary } = require('miki-template');
72
+
73
+ registerLibrary('myutils', {
74
+ filters: {
75
+ shout: (val) => String(val).toUpperCase() + '!',
76
+ whisper: (val) => String(val).toLowerCase() + '...'
77
+ },
78
+ tags: {
79
+ timestamp: (tagContent, parser) => ({
80
+ render: () => new Date().toISOString()
81
+ })
82
+ },
83
+ helpers: {
84
+ formatPrice: (val) => `$${Number(val).toFixed(2)}`
85
+ }
86
+ });
87
+ ```
88
+
89
+ === "ES Modules"
90
+
91
+ ```javascript
92
+ import { registerLibrary } from 'miki-template';
93
+
94
+ registerLibrary('myutils', {
95
+ filters: {
96
+ shout: (val) => String(val).toUpperCase() + '!',
97
+ whisper: (val) => String(val).toLowerCase() + '...'
98
+ },
99
+ tags: {
100
+ timestamp: (tagContent, parser) => ({
101
+ render: () => new Date().toISOString()
102
+ })
103
+ },
104
+ helpers: {
105
+ formatPrice: (val) => `$${Number(val).toFixed(2)}`
106
+ }
107
+ });
108
+ ```
109
+
110
+ ### Loading Libraries in Templates
111
+
112
+ Once registered, load the library with `{% load %}`:
113
+
114
+ ```html
115
+ {% load myutils %}
116
+
117
+ {{ price|formatPrice }}
118
+ {{ message|shout }}
119
+ {% timestamp %}
120
+ ```
121
+
122
+ ### Built-in Libraries
123
+
124
+ The following libraries are auto-activated (no `{% load %}` needed):
125
+
126
+ | Library | Features |
127
+ |---------|----------|
128
+ | `humanize` | Natural date formatting, number formatting |
129
+ | `cache` | Cache control tags and filters |
130
+ | `lorem` | Lorem ipsum placeholder text |
131
+ | `markdown` | `{{ content|markdown }}` filter for Markdown→HTML |
132
+ | `i18n` | `{% trans %}` and `{% blocktrans %}` for translations |
133
+
134
+ ### Deactivating and Re-registering
135
+
136
+ === "CommonJS"
137
+
138
+ ```javascript
139
+ const { unregisterLibrary, activateLibrary } = require('miki-template');
140
+
141
+ // Remove a library
142
+ unregisterLibrary('lorem');
143
+
144
+ // Re-activate
145
+ activateLibrary('lorem');
146
+ ```
147
+
148
+ === "ES Modules"
149
+
150
+ ```javascript
151
+ import { unregisterLibrary, activateLibrary } from 'miki-template';
152
+
153
+ unregisterLibrary('lorem');
154
+ activateLibrary('lorem');
155
+ ```
156
+
157
+ ## i18n / Internationalization
158
+
159
+ miki-template includes a built-in i18n system supporting `{% trans %}` and `{% blocktrans %}` tags.
160
+
161
+ ### Registering Translations
162
+
163
+ === "CommonJS"
164
+
165
+ ```javascript
166
+ const miki = require('miki-template');
167
+
168
+ miki.setLanguage('fr');
169
+ miki.registerTranslation('fr', {
170
+ 'Hello': 'Bonjour',
171
+ 'Goodbye': 'Au revoir',
172
+ 'Welcome, {name}!': 'Bienvenue, {name} !'
173
+ });
174
+ ```
175
+
176
+ === "ES Modules"
177
+
178
+ ```javascript
179
+ import { setLanguage, registerTranslation } from 'miki-template';
180
+
181
+ setLanguage('fr');
182
+ registerTranslation('fr', {
183
+ 'Hello': 'Bonjour',
184
+ 'Goodbye': 'Au revoir',
185
+ 'Welcome, {name}!': 'Bienvenue, {name} !'
186
+ });
187
+ ```
188
+
189
+ ### Setting Fallback Language
190
+
191
+ === "CommonJS"
192
+
193
+ ```javascript
194
+ const { setLanguage, setFallbackLanguage } = require('miki-template');
195
+
196
+ setLanguage('fr');
197
+ setFallbackLanguage('en');
198
+ ```
199
+
200
+ === "ES Modules"
201
+
202
+ ```javascript
203
+ import { setLanguage, setFallbackLanguage } from 'miki-template';
204
+
205
+ setLanguage('fr');
206
+ setFallbackLanguage('en');
207
+ ```
208
+
209
+ ### Template Usage
210
+
211
+ ```html
212
+ {% trans "Hello" %}
213
+ {% blocktrans %}Welcome, {{ name }}!{% endblocktrans %}
214
+ ```
215
+
216
+ ### Managing Languages
217
+
218
+ === "CommonJS"
219
+
220
+ ```javascript
221
+ const {
222
+ registerTranslation,
223
+ unregisterTranslation,
224
+ setLanguage,
225
+ getLanguage,
226
+ setFallbackLanguage,
227
+ getFallbackLanguage,
228
+ getAvailableLanguages
229
+ } = require('miki-template');
230
+ ```
231
+
232
+ === "ES Modules"
233
+
234
+ ```javascript
235
+ import {
236
+ registerTranslation,
237
+ unregisterTranslation,
238
+ setLanguage,
239
+ getLanguage,
240
+ setFallbackLanguage,
241
+ getFallbackLanguage,
242
+ getAvailableLanguages
243
+ } from 'miki-template';
244
+ ```
245
+
246
+ ## Template Discovery
247
+
248
+ The `findTemplateInViews()` function intelligently locates templates in nested directories.
249
+
250
+ === "CommonJS"
251
+
252
+ ```javascript
253
+ const { findTemplateInViews, setAppTemplateDirNames } = require('miki-template');
254
+
255
+ // Customize which directory names are treated as app template roots
256
+ setAppTemplateDirNames(['templates', 'views', 'pages']);
257
+
258
+ // Search for a template by name
259
+ const found = findTemplateInViews('home', ['./views', './app/templates']);
260
+ console.log(found);
261
+ // → /absolute/path/to/app/templates/home.html
262
+ ```
263
+
264
+ === "ES Modules"
265
+
266
+ ```javascript
267
+ import { findTemplateInViews, setAppTemplateDirNames } from 'miki-template';
268
+
269
+ setAppTemplateDirNames(['templates', 'views', 'pages']);
270
+ const found = findTemplateInViews('home', ['./views', './app/templates']);
271
+ console.log(found);
272
+ ```
273
+
274
+ ## Partial Templates
275
+
276
+ Partials let you define reusable template fragments using `{% partialdef %}` and render them on demand.
277
+
278
+ ### Defining and Rendering Partials
279
+
280
+ ```html
281
+ {% partialdef card %}
282
+ <div class="card">
283
+ <h3>{{ user.name }}</h3>
284
+ <p>{{ user.email }}</p>
285
+ </div>
286
+ {% endpartialdef %}
287
+ ```
288
+
289
+ Render a partial:
290
+
291
+ === "CommonJS"
292
+
293
+ ```javascript
294
+ const { render, compile } = require('miki-template');
295
+
296
+ // Using render() with file partials:
297
+ const html = render('home#card', { user: userData }, { views: './views' });
298
+
299
+ // Using compiled.renderPartial():
300
+ const compiled = compile(templateString);
301
+ const partialHtml = compiled.renderPartial('card', { user: userData });
302
+ ```
303
+
304
+ === "ES Modules"
305
+
306
+ ```javascript
307
+ import { render, compile } from 'miki-template';
308
+
309
+ const html = render('home#card', { user: userData }, { views: './views' });
310
+
311
+ const compiled = compile(templateString);
312
+ const partialHtml = compiled.renderPartial('card', { user: userData });
313
+ ```
314
+
315
+ ### Partial with Context
316
+
317
+ ```html
318
+ {% partialdef greeting %}
319
+ Hello, {{ name }}! You have {{ count }} messages.
320
+ {% endpartialdef %}
321
+
322
+ {% partial greeting with name="Alice" count=3 %}
323
+ ```
324
+
325
+ ## Extending the Engine
326
+
327
+ ### Registering Custom Tags
328
+
329
+ === "CommonJS"
330
+
331
+ ```javascript
332
+ const { registerTag } = require('miki-template');
333
+
334
+ registerTag('markdown', (tagContent, parser) => {
335
+ const nodelist = parser.parse(['endmarkdown']);
336
+ parser.skipTag();
337
+ const { marked } = require('marked');
338
+
339
+ return {
340
+ render: (context) => {
341
+ const body = nodelist.map(n => n.render(context)).join('');
342
+ return markSafe(marked(body));
343
+ }
344
+ };
345
+ });
346
+ ```
347
+
348
+ === "ES Modules"
349
+
350
+ ```javascript
351
+ import { registerTag, markSafe } from 'miki-template';
352
+ import { marked } from 'marked';
353
+
354
+ registerTag('markdown', (tagContent, parser) => {
355
+ const nodelist = parser.parse(['endmarkdown']);
356
+ parser.skipTag();
357
+
358
+ return {
359
+ render: (context) => {
360
+ const body = nodelist.map(n => n.render(context)).join('');
361
+ return markSafe(marked(body));
362
+ }
363
+ };
364
+ });
365
+ ```
366
+
367
+ ### Registering Custom Helpers
368
+
369
+ === "CommonJS"
370
+
371
+ ```javascript
372
+ const { registerHelper } = require('miki-template');
373
+
374
+ registerHelper('truncate_words', (str, count) => {
375
+ const words = String(str).split(/\s+/);
376
+ return words.slice(0, count).join(' ') + (words.length > count ? '...' : '');
377
+ });
378
+ ```
379
+
380
+ === "ES Modules"
381
+
382
+ ```javascript
383
+ import { registerHelper } from 'miki-template';
384
+
385
+ registerHelper('truncate_words', (str, count) => {
386
+ const words = String(str).split(/\s+/);
387
+ return words.slice(0, count).join(' ') + (words.length > count ? '...' : '');
388
+ });
389
+ ```
390
+
391
+ ## Next Steps
392
+
393
+ - [Custom Tags](./custom-tags)
394
+ - [Custom Filters](./custom-filters)
395
+ - [API Reference: Libraries](../api/libraries)
396
+ - [API Reference: Cache](../api/cache)
397
+ - [API Reference: i18n](../api/i18n)