miki-template 2.0.1 → 2.2.3

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 (71) hide show
  1. package/.github/workflows/ci.yml +13 -37
  2. package/.github/workflows/docs.yml +105 -0
  3. package/.github/workflows/release.yml +6 -0
  4. package/README.md +69 -14
  5. package/assets/logo.png +0 -0
  6. package/benchmarks/ejs-results.json +4 -4
  7. package/benchmarks/handlebars-results.json +6 -6
  8. package/benchmarks/miki-results.json +4 -4
  9. package/benchmarks/pug-results.json +4 -4
  10. package/benchmarks/stress.mjs +1 -1
  11. package/docs/api/async-render.md +85 -0
  12. package/docs/api/cache.md +87 -0
  13. package/docs/api/compile.md +128 -0
  14. package/docs/api/context-processors.md +77 -0
  15. package/docs/api/filters.md +217 -0
  16. package/docs/api/finder.md +94 -0
  17. package/docs/api/helpers.md +53 -0
  18. package/docs/api/i18n.md +157 -0
  19. package/docs/api/index.md +54 -0
  20. package/docs/api/libraries.md +207 -0
  21. package/docs/api/render-partial.md +81 -0
  22. package/docs/api/render.md +92 -0
  23. package/docs/api/security.md +145 -0
  24. package/docs/api/setup-express.md +76 -0
  25. package/docs/api/tags.md +134 -0
  26. package/docs/assets/banner.png +0 -0
  27. package/docs/assets/logo.png +0 -0
  28. package/docs/guide/advanced-usage.md +397 -0
  29. package/docs/guide/async-rendering.md +308 -0
  30. package/docs/guide/context-processors.md +257 -0
  31. package/docs/guide/custom-filters.md +311 -0
  32. package/docs/guide/custom-tags.md +271 -0
  33. package/docs/guide/filters.md +642 -0
  34. package/docs/guide/getting-started.md +102 -0
  35. package/docs/guide/installation.md +95 -0
  36. package/docs/guide/partial-templates.md +367 -0
  37. package/docs/guide/quick-start.md +222 -0
  38. package/docs/guide/security.md +345 -0
  39. package/docs/guide/tags.md +783 -0
  40. package/docs/guide/template-discovery.md +170 -0
  41. package/docs/guide/template-inheritance.md +273 -0
  42. package/docs/guide/what-is-miki-template.md +28 -0
  43. package/docs/guide/why-miki-template.md +75 -0
  44. package/docs/index.md +104 -0
  45. package/docs/integrations/elysia.md +78 -0
  46. package/docs/integrations/express.md +219 -0
  47. package/docs/integrations/fastify.md +77 -0
  48. package/docs/integrations/hono.md +78 -0
  49. package/docs/integrations/index.md +68 -0
  50. package/docs/integrations/koa.md +88 -0
  51. package/docs/integrations/nestjs.md +78 -0
  52. package/docs/integrations/tsed.md +81 -0
  53. package/docs/javascripts/extra.js +174 -0
  54. package/docs/performance.md +37 -0
  55. package/docs/stylesheets/extra.css +819 -0
  56. package/mkdocs.yml +217 -0
  57. package/overrides/main.html +26 -0
  58. package/overrides/partials/footer.html +9 -0
  59. package/package.json +4 -2
  60. package/requirements-docs.txt +1 -0
  61. package/docs/README.md +0 -18
  62. package/docs/advanced_usage.md +0 -71
  63. package/docs/api.md +0 -122
  64. package/docs/filters.md +0 -708
  65. package/docs/installation.md +0 -106
  66. package/docs/integrations.md +0 -214
  67. package/docs/overview.md +0 -79
  68. package/docs/partialdef.md +0 -70
  69. package/docs/security.md +0 -27
  70. package/docs/tags.md +0 -673
  71. package/docs/usage.md +0 -646
package/docs/tags.md DELETED
@@ -1,673 +0,0 @@
1
- # Tags Reference
2
-
3
- This document provides a detailed reference for every built-in block tag in **miki-template**, grouped by function.
4
-
5
- ---
6
-
7
- ## Control Flow Tags
8
-
9
- ### `{% if %} / {% elif %} / {% else %} / {% endif %}`
10
-
11
- Conditionally renders content based on an expression.
12
-
13
- ```html
14
- {% if user.is_authenticated %}
15
- <p>Hello, {{ user.name }}!</p>
16
- {% elif user.is_guest %}
17
- <p>Welcome, guest!</p>
18
- {% else %}
19
- <p>Please log in.</p>
20
- {% endif %}
21
- ```
22
-
23
- **Supported operators:**
24
-
25
- | Operator | Meaning |
26
- |----------|---------|
27
- | `==` | Equal |
28
- | `!=` | Not equal |
29
- | `<` | Less than |
30
- | `<=` | Less than or equal |
31
- | `>` | Greater than |
32
- | `>=` | Greater than or equal |
33
- | `in` | Membership (item in list) |
34
- | `not in` | Non-membership |
35
- | `and` | Logical AND |
36
- | `or` | Logical OR |
37
- | `not` | Logical NOT |
38
-
39
- **Operator precedence** (highest to lowest): comparison → `and` → `or`
40
-
41
- ```html
42
- {% if user.age >= 18 and user.is_verified %}
43
- <p>Eligible to vote.</p>
44
- {% endif %}
45
-
46
- {% if item not in cart %}
47
- <button>Add to cart</button>
48
- {% endif %}
49
- ```
50
-
51
- ---
52
-
53
- ### `{% for %} / {% empty %} / {% endfor %}`
54
-
55
- Iterates over arrays or objects.
56
-
57
- ```html
58
- <ul>
59
- {% for user in users %}
60
- <li>{{ user.name }}</li>
61
- {% empty %}
62
- <li>No users found.</li>
63
- {% endfor %}
64
- </ul>
65
- ```
66
-
67
- **Object iteration** — unpacks key and value:
68
-
69
- ```html
70
- {% for key, value in config %}
71
- <dt>{{ key }}</dt>
72
- <dd>{{ value }}</dd>
73
- {% endfor %}
74
- ```
75
-
76
- **Tuple unpacking** — unpacks index and item:
77
-
78
- ```html
79
- {% for item, index in items %}
80
- {{ forloop.counter }}. {{ item }}
81
- {% endfor %}
82
- ```
83
-
84
- **Loop metadata** — `forloop` object is available inside the loop:
85
-
86
- | Property | Type | Description |
87
- |----------|------|-------------|
88
- | `forloop.counter` | integer | Current iteration (1-indexed) |
89
- | `forloop.counter0` | integer | Current iteration (0-indexed) |
90
- | `forloop.revcounter` | integer | Iterations remaining (counting down from 1) |
91
- | `forloop.revcounter0` | integer | Iterations remaining (counting down from 0) |
92
- | `forloop.first` | boolean | True on first iteration |
93
- | `forloop.last` | boolean | True on last iteration |
94
- | `forloop.parentloop` | object | Reference to parent loop's `forloop` |
95
-
96
- **Nested loops example:**
97
-
98
- ```html
99
- {% for category in categories %}
100
- <h2>{{ category.name }}</h2>
101
- {% for product in category.products %}
102
- {# forloop.counter = position in category #}
103
- {# forloop.parentloop.counter = position in categories #}
104
- <p>{{ forloop.parentloop.counter }}.{{ forloop.counter }}: {{ product }}</p>
105
- {% endfor %}
106
- {% endfor %}
107
- ```
108
-
109
- ---
110
-
111
- ### `{% with %} / {% endwith %}`
112
-
113
- Creates scoped aliases for variables or expressions.
114
-
115
- ```html
116
- {% with user.profile as profile %}
117
- <img src="{{ profile.avatar }}">
118
- <a href="{{ profile.url }}">{{ profile.display_name }}</a>
119
- {% endwith %}
120
- ```
121
-
122
- **Multiple assignments** (Django-style):
123
-
124
- ```html
125
- {% with a=1 b=items.0.name c="static" %}
126
- {{ a }} | {{ b }} | {{ c }}
127
- {% endwith %}
128
- ```
129
-
130
- You can also unpack tuple-like values:
131
-
132
- ```html
133
- {% with key, value in item %}
134
- <li>{{ key }}: {{ value }}</li>
135
- {% endwith %}
136
- ```
137
-
138
- ---
139
-
140
- ### `{% set %} / {% endset %}`
141
-
142
- Assigns a value to a variable for later use in the template. Supports both inline and block forms.
143
-
144
- **Inline form** — assign a single expression:
145
-
146
- ```html
147
- {% set total = price|add:tax %}
148
- <p>Total: {{ total }}</p>
149
- ```
150
-
151
- **Block form** — capture rendered content:
152
-
153
- ```html
154
- {% set sidebar %}
155
- {% include "sidebar.html" with user=user %}
156
- {% endset %}
157
- {{ sidebar }}
158
- ```
159
-
160
- Variables set with `{% set %}` persist in the current scope and can be used after the tag.
161
-
162
- ---
163
-
164
- ### `{% cycle %}`
165
-
166
- Outputs one of its arguments for each iteration of a loop.
167
-
168
- ```html
169
- {% for row in rows %}
170
- <tr class="{% cycle 'row-even' 'row-odd' %}">
171
- <td>{{ row.name }}</td>
172
- </tr>
173
- {% endfor %}
174
- ```
175
-
176
- Named cycle for resumable state:
177
-
178
- ```html
179
- {% for item in items %}
180
- {% cycle 'a' 'b' 'c' as marker silent %}
181
- {% if marker == 'b' %}
182
- <strong>{{ item }}</strong>
183
- {% else %}
184
- {{ item }}
185
- {% endif %}
186
- {% endfor %}
187
- ```
188
-
189
- ---
190
-
191
- ### `{% firstof %}`
192
-
193
- Outputs the first argument that evaluates to `true`.
194
-
195
- ```html
196
- {% firstof user.display_name user.username "Anonymous" %}
197
- ```
198
-
199
- With `{% else %}` for a fallback:
200
-
201
- ```html
202
- {% firstof user.display_name user.username %}
203
- {{ firstof_output }}
204
- {% else %}
205
- Anonymous
206
- {% endif %}
207
- ```
208
-
209
- ---
210
-
211
- ### `{% ifchanged %}...{% endifchanged %}`
212
-
213
- Renders the body only when the value changes. Useful for detecting changes in loops.
214
-
215
- ```html
216
- {% for item in items %}
217
- {% ifchanged item.category %}
218
- <h2>{{ item.category }}</h2>
219
- {% endifchanged %}
220
- <p>{{ item.name }}</p>
221
- {% endfor %}
222
- ```
223
-
224
- Supports `{% else %}` for when the value does not change:
225
-
226
- ```html
227
- {% for item in items %}
228
- {% ifchanged item.category %}
229
- {{ item.category }}
230
- {% else %}
231
- (same)
232
- {% endifchanged %}
233
- {% endfor %}
234
- ```
235
-
236
- ---
237
-
238
- ## Template Inheritance Tags
239
-
240
- ### `{% extends %}`
241
-
242
- Must be the first tag in a child template. Specifies the parent template.
243
-
244
- ```html
245
- {% extends "base.html" %}
246
- ```
247
-
248
- Can use expressions for dynamic parent (e.g., mobile vs desktop):
249
-
250
- ```html
251
- {% extends device|default:"base.html" %}
252
- ```
253
-
254
- **Security:** Path traversal is blocked — the template name must resolve within the configured `views` directories.
255
-
256
- ---
257
-
258
- ### `{% block %} / {% endblock %}`
259
-
260
- Defines a replaceable section that child templates can override.
261
-
262
- ```html
263
- <!-- base.html -->
264
- {% block content %}
265
- Default content
266
- {% endblock %}
267
- ```
268
-
269
- ```html
270
- <!-- child.html -->
271
- {% extends "base.html" %}
272
- {% block content %}
273
- Overridden content
274
- {% endblock %}
275
- ```
276
-
277
- **`{{ block.super }}`** — renders the parent template's block content within an override:
278
-
279
- ```html
280
- {% block content %}
281
- {{ block.super }}
282
- <p>Additional content from child</p>
283
- {% endblock %}
284
- ```
285
-
286
- ---
287
-
288
- ### `{% block.super %}`
289
-
290
- A special variable, not a tag. When used inside a `{% block %}`, it renders the parent template's version of that block.
291
-
292
- ---
293
-
294
- ## Include and Partial Tags
295
-
296
- ### `{% include %}`
297
-
298
- Includes another template file at render time. The included template gets a copy of the current context.
299
-
300
- ```html
301
- {% include "header.html" %}
302
- {% include "sidebar.html" with active_section="home" %}
303
- {% include "footer.html" without context %}
304
- ```
305
-
306
- **Security:** Path traversal is blocked to prevent reading arbitrary files outside the views directory.
307
-
308
- ---
309
-
310
- ### `{% partialdef %} / {% endpartialdef %}`
311
-
312
- Defines a reusable fragment that can be rendered later via `{% partial %}`.
313
-
314
- ```html
315
- {% partialdef card %}
316
- <div class="card">
317
- <h3>{{ title }}</h3>
318
- <p>{{ description }}</p>
319
- </div>
320
- {% endpartialdef %}
321
-
322
- {% partial card with title="Hello" description="World" %}
323
- ```
324
-
325
- **Options:**
326
-
327
- | Option | Description |
328
- |--------|-------------|
329
- | `inline` | Renders the definition inline at its location during parse. |
330
- | `lazy` | Defers parsing until first use (default is eager parsing). |
331
-
332
- **Programmatic access:**
333
-
334
- ```javascript
335
- const compiled = compile(template);
336
- compiled.renderPartial('card', { title: 'Hi', description: 'There' });
337
- ```
338
-
339
- ---
340
-
341
- ### `{% partial %}`
342
-
343
- Renders a previously defined partial.
344
-
345
- ```html
346
- {% partial card %}
347
- {% partial card with title="Custom" %}
348
- ```
349
-
350
- Supports passing context variables:
351
-
352
- ```html
353
- {% partial greeting with name=user.name %}
354
- ```
355
-
356
- ---
357
-
358
- ## Utility Tags
359
-
360
- ### `{% comment %} / {% endcomment %}`
361
-
362
- Block comment that is stripped from the output entirely.
363
-
364
- ```html
365
- {% comment %}
366
- This section is deprecated.
367
- It will be removed in the next release.
368
- {% endcomment %}
369
- ```
370
-
371
- Short form (single tag, self-closing):
372
-
373
- ```html
374
- {% comment %} This will not appear in output {% endcomment %}
375
- ```
376
-
377
- ---
378
-
379
- ### `{% verbatim %} / {% endverbatim %}`
380
-
381
- Prevents all tag/variable parsing inside the block.
382
-
383
- ```html
384
- {% verbatim %}
385
- {{ this_is_not_a_variable }}
386
- {% if this_is_not_a_tag %}Ignored{% endif %}
387
- {% endverbatim %}
388
- ```
389
-
390
- ---
391
-
392
- ### `{% load %}`
393
-
394
- Loads additional filter libraries (for future extensibility).
395
-
396
- ```html
397
- {% load i18n %}
398
- {% load custom_filters %}
399
- ```
400
-
401
- ---
402
-
403
- ### `{% now "format" %}`
404
-
405
- Outputs the current date/time formatted with the given pattern. Uses the same format codes as the `date` filter.
406
-
407
- ```html
408
- {% now "Y-m-d" %} → "2026-09-03"
409
- {% now "H:i:s" %} → "14:30:45"
410
- {% now "F j, Y" %} → "September 3, 2026"
411
- ```
412
-
413
- ---
414
-
415
- ### `{% spaceless %} / {% endspaceless %}`
416
-
417
- Removes whitespace between HTML tags.
418
-
419
- ```html
420
- {% spaceless %}
421
- <div> <p>Hello</p> </div>
422
- {% endspaceless %}
423
- <!-- → <div><p>Hello</p></div> -->
424
- ```
425
-
426
- ---
427
-
428
- ### `{% static %}`
429
-
430
- Generates the URL for a static asset.
431
-
432
- ```html
433
- <img src="{% static "css/app.css" %}">
434
- <script src="{% static "js/bundle.js" %}"></script>
435
- ```
436
-
437
- Configure the prefix:
438
- ```javascript
439
- compile(template, { staticUrl: '/assets/' });
440
- ```
441
-
442
- ---
443
-
444
- ### `{% url %}`
445
-
446
- Generates a URL for a named route using the provided `urlHelper` function.
447
-
448
- ```html
449
- <a href="{% url "home" %}">Home</a>
450
- <a href="{% url "user-profile" user.id %}">Profile</a>
451
- <a href="{% url "search" query=search_query %}">Search</a>
452
- ```
453
-
454
- Configure:
455
- ```javascript
456
- compile(template, {
457
- urlHelper: (name, params, kwargs) => {
458
- // return resolved URL string
459
- }
460
- });
461
- ```
462
-
463
- ---
464
-
465
- ### `{% csrf_token %}`
466
-
467
- Outputs a CSRF token hidden input for forms.
468
-
469
- ```html
470
- <form method="post">
471
- {% csrf_token %}
472
- <input type="text" name="title">
473
- <button type="submit">Submit</button>
474
- </form>
475
- ```
476
-
477
- Provide `csrf_token` in context:
478
- ```javascript
479
- res.render('form', { csrf_token: req.csrfToken() });
480
- ```
481
-
482
- ---
483
-
484
- ### `{% csp_nonce_attr %}`
485
-
486
- Outputs a `nonce="..."` attribute for Content Security Policy.
487
-
488
- ```html
489
- <script {% csp_nonce_attr %}>
490
- console.log('CSP nonce');
491
- </script>
492
- ```
493
-
494
- Provide `csp_nonce` in context:
495
- ```javascript
496
- res.render('page', { csp_nonce: req.nonce });
497
- ```
498
-
499
- ---
500
-
501
- ### `{% regroup %}`
502
-
503
- Regroups a list by a common attribute.
504
-
505
- ```html
506
- {% regroup users by department as departments %}
507
- {% for dept in departments %}
508
- <h3>{{ dept.grouper }}</h3>
509
- {% for user in dept.list %}
510
- <p>{{ user.name }}</p>
511
- {% endfor %}
512
- {% endfor %}
513
- ```
514
-
515
- ---
516
-
517
- ### `{% trans "key" %}`
518
-
519
- Outputs a translated string from the i18n registry.
520
-
521
- ```html
522
- {% trans "Hello, World!" %}
523
- ```
524
-
525
- With arguments:
526
-
527
- ```html
528
- {% trans "Hello, %s!" name=user.name %}
529
- ```
530
-
531
- With context:
532
-
533
- ```html
534
- {% trans context "verb" "He runs" %}
535
- ```
536
-
537
- ---
538
-
539
- ### `{% blocktrans %}...{% endblocktrans %}`
540
-
541
- Translates a block of text. Supports `{% with name=value %}` and `{% plural count name=value %}`.
542
-
543
- ```html
544
- {% blocktrans with name=user.name count items|length %}
545
- {{ name }} has {{ items|length }} item.
546
- {% plural %}
547
- {{ name }} has {{ items|length }} items.
548
- {% endblocktrans %}
549
- ```
550
-
551
- ---
552
-
553
- ### `{% language "xx" %}...{% endlanguage %}`
554
-
555
- Switches the active language for the enclosed block.
556
-
557
- ```html
558
- {% language "fr" %}
559
- {% trans "Welcome" %}
560
- {% endlanguage %}
561
- ```
562
-
563
- ---
564
-
565
- ### `{% widthratio value max max_width %}`
566
-
567
- Calculates a proportional width, commonly used for bar charts or progress indicators.
568
-
569
- ```html
570
- {% widthratio 25 100 150 %} <!-- → 37 (floor of 25/100*150) -->
571
- ```
572
-
573
- ---
574
-
575
- ### `{% debug %}`
576
-
577
- Dumps the current template context as a formatted HTML `<pre>` block. Useful during development.
578
-
579
- ```html
580
- <pre>
581
- {% debug %}
582
- </pre>
583
- ```
584
-
585
- ---
586
-
587
- ### `{% load library1 library2 %}`
588
-
589
- Loads one or more plugin libraries, making their tags, filters, and helpers available.
590
-
591
- ```html
592
- {% load i18n humanize cache %}
593
- ```
594
-
595
- Built-in libraries:
596
- - `i18n` — `trans`, `blocktrans`, `language`
597
- - `humanize` — `intcomma`, `intword`, `apnumber`, `ordinal`, `naturalday`
598
- - `cache` — `{% cache timeout key %}...{% endcache %}`
599
- - `lorem` — `lorem` filter for placeholder text
600
-
601
- ---
602
-
603
- ## Custom Filters
604
-
605
- Register custom filters with `registerFilter`:
606
-
607
- ```javascript
608
- const { registerFilter } = require('miki-template');
609
-
610
- // Simple filter
611
- registerFilter('reverse', (val) => String(val).split('').reverse().join(''));
612
-
613
- // Filter with argument
614
- registerFilter('truncate', (val, length) => {
615
- const str = String(val);
616
- if (str.length <= length) return str;
617
- return str.slice(0, length) + '...';
618
- });
619
-
620
- // Chaining works automatically:
621
- // {{ name|reverse|truncate:5 }}
622
- ```
623
-
624
- ---
625
-
626
- ## Custom Tags
627
-
628
- Register custom block tags with `registerTag`:
629
-
630
- ```javascript
631
- const { registerTag } = require('miki-template');
632
-
633
- registerTag('markdown', (tagContent, parser) => {
634
- const body = parser.parse(['endmarkdown']);
635
- const next = parser.peek();
636
- if (next && next.type === 'block' && next.content.split(/\s+/)[0] === 'endmarkdown') {
637
- parser.advance();
638
- }
639
- const md = require('markdown-it')();
640
- return {
641
- render(context) {
642
- const html = body.map(n => n.render(context)).join('');
643
- return md.render(html);
644
- }
645
- };
646
- });
647
- ```
648
-
649
- Usage in templates:
650
- ```html
651
- {% markdown %}
652
- # Hello World
653
- {% endmarkdown %}
654
- ```
655
-
656
- ---
657
-
658
- ## Filter Argument Types
659
-
660
- Filters accept the following argument types:
661
-
662
- | Syntax | Type | Example |
663
- |--------|------|---------|
664
- | Unquoted | Variable lookup | `{{ value|filter:count }}` |
665
- | Double-quoted | String literal | `{{ value|filter:"hello" }}` |
666
- | Single-quoted | String literal | `{{ value|filter:'world' }}` |
667
- | Number | Integer literal | `{{ value|truncatewords:10 }}` |
668
-
669
- ```html
670
- {{ user.name|default:"Guest" }} <!-- String default -->
671
- {{ items|slice:"1:3" }} <!-- Slice notation -->
672
- {{ price|floatformat:2 }} <!-- Decimal places -->
673
- ```