miki-template 2.3.1 → 2.3.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 (167) hide show
  1. package/README.md +1 -1
  2. package/package.json +24 -2
  3. package/src/codegen.js +140 -9
  4. package/src/parser.js +69 -1
  5. package/src/tags/control.js +112 -51
  6. package/src/tags/extra.js +4 -17
  7. package/src/tags/i18n.js +6 -17
  8. package/src/tags/inheritance.js +6 -20
  9. package/src/tags/util.js +5 -20
  10. package/.eslintrc.json +0 -16
  11. package/.github/release-notes/v1.3.1.md +0 -55
  12. package/.github/release-notes/v1.3.3.md +0 -77
  13. package/.github/release-notes/v1.3.7.md +0 -20
  14. package/.github/workflows/ci.yml +0 -42
  15. package/.github/workflows/docs.yml +0 -107
  16. package/.github/workflows/release.yml +0 -107
  17. package/AGENT.md +0 -71
  18. package/API_REFERENCE.md +0 -341
  19. package/CHANGELOG.md +0 -173
  20. package/CODE_OF_CONDUCT.md +0 -14
  21. package/CONTRIBUTING.md +0 -27
  22. package/ROADMAP.md +0 -40
  23. package/assets/banner.png +0 -0
  24. package/assets/logo.png +0 -0
  25. package/benchmarks/ejs-results.json +0 -17
  26. package/benchmarks/ejs.js +0 -38
  27. package/benchmarks/handlebars-results.json +0 -17
  28. package/benchmarks/handlebars.js +0 -45
  29. package/benchmarks/miki-results.json +0 -17
  30. package/benchmarks/miki.js +0 -39
  31. package/benchmarks/pug-results.json +0 -17
  32. package/benchmarks/pug.js +0 -38
  33. package/benchmarks/report.json +0 -17
  34. package/benchmarks/run.js +0 -81
  35. package/benchmarks/stress.mjs +0 -647
  36. package/benchmarks/templates/large.dtpl +0 -7
  37. package/benchmarks/templates/medium.dtpl +0 -3
  38. package/benchmarks/templates/small.dtpl +0 -7
  39. package/context/component.md +0 -109
  40. package/context/prd.md +0 -131
  41. package/context/project-structure.md +0 -33
  42. package/dir/base.html +0 -23
  43. package/dir/cmpnt.html +0 -11
  44. package/dir/footer.html +0 -3
  45. package/dir/home.html +0 -80
  46. package/dir/index.html +0 -80
  47. package/dir/navbar.html +0 -9
  48. package/docs/api/async-render.md +0 -170
  49. package/docs/api/cache.md +0 -174
  50. package/docs/api/compile.md +0 -256
  51. package/docs/api/context-processors.md +0 -154
  52. package/docs/api/filters.md +0 -437
  53. package/docs/api/finder.md +0 -188
  54. package/docs/api/helpers.md +0 -106
  55. package/docs/api/i18n.md +0 -314
  56. package/docs/api/index.md +0 -108
  57. package/docs/api/libraries.md +0 -414
  58. package/docs/api/render-partial.md +0 -162
  59. package/docs/api/render.md +0 -184
  60. package/docs/api/security.md +0 -290
  61. package/docs/api/setup-express.md +0 -152
  62. package/docs/api/tags.md +0 -268
  63. package/docs/assets/banner.png +0 -0
  64. package/docs/assets/logo.png +0 -0
  65. package/docs/filter.md +0 -0
  66. package/docs/guide/advanced-usage.md +0 -794
  67. package/docs/guide/async-rendering.md +0 -616
  68. package/docs/guide/context-processors.md +0 -514
  69. package/docs/guide/custom-filters.md +0 -622
  70. package/docs/guide/custom-tags.md +0 -542
  71. package/docs/guide/filters.md +0 -1314
  72. package/docs/guide/getting-started.md +0 -204
  73. package/docs/guide/installation.md +0 -190
  74. package/docs/guide/partial-templates.md +0 -734
  75. package/docs/guide/quick-start.md +0 -444
  76. package/docs/guide/security.md +0 -690
  77. package/docs/guide/tags.md +0 -1566
  78. package/docs/guide/template-discovery.md +0 -340
  79. package/docs/guide/template-inheritance.md +0 -546
  80. package/docs/guide/what-is-miki-template.md +0 -28
  81. package/docs/guide/why-miki-template.md +0 -75
  82. package/docs/index.md +0 -86
  83. package/docs/integrations/elysia.md +0 -80
  84. package/docs/integrations/express.md +0 -219
  85. package/docs/integrations/fastify.md +0 -79
  86. package/docs/integrations/hono.md +0 -80
  87. package/docs/integrations/index.md +0 -68
  88. package/docs/integrations/koa.md +0 -90
  89. package/docs/integrations/nestjs.md +0 -80
  90. package/docs/integrations/tsed.md +0 -83
  91. package/docs/performance.md +0 -74
  92. package/eslint.config.mjs +0 -42
  93. package/ex.mjs +0 -36
  94. package/live-test/integrations/elysia-example.js +0 -16
  95. package/live-test/integrations/express-example.js +0 -24
  96. package/live-test/integrations/fastify-example.js +0 -20
  97. package/live-test/integrations/hono-example.js +0 -16
  98. package/live-test/integrations/koa-example.js +0 -30
  99. package/live-test/integrations/nestjs-example.js +0 -25
  100. package/live-test/integrations/smoke-test.js +0 -166
  101. package/live-test/integrations/tsed-example.js +0 -23
  102. package/live-test/package-lock.json +0 -1150
  103. package/live-test/package.json +0 -13
  104. package/live-test/packages/product/templates/product/detail.html +0 -7
  105. package/live-test/server.js +0 -38
  106. package/live-test/templates/app_templates/detail.html +0 -6
  107. package/live-test/views/base.html +0 -8
  108. package/live-test/views/child.html +0 -7
  109. package/live-test/views/home.html +0 -17
  110. package/live-test/views/index.html +0 -1
  111. package/miki-template-extension/.github/workflows/ci.yml +0 -116
  112. package/miki-template-extension/.vscodeignore +0 -7
  113. package/miki-template-extension/CHANGELOG.md +0 -99
  114. package/miki-template-extension/LICENSE +0 -21
  115. package/miki-template-extension/README.md +0 -273
  116. package/miki-template-extension/extension.js +0 -1013
  117. package/miki-template-extension/icon.png +0 -0
  118. package/miki-template-extension/icon.svg +0 -10
  119. package/miki-template-extension/miki-template-1.7.1.vsix +0 -0
  120. package/miki-template-extension/package.json +0 -280
  121. package/miki-template-extension/snippets/miki-template.json +0 -717
  122. package/miki-template-extension/syntaxes/language-configuration.json +0 -114
  123. package/miki-template-extension/syntaxes/miki-template.tmLanguage.json +0 -355
  124. package/miki-template-extension/tests/grammar-tests.json +0 -162
  125. package/miki-template-extension/tests/run-grammar-tests.js +0 -82
  126. package/mkdocs.yml +0 -195
  127. package/overrides/main.html +0 -26
  128. package/requirements-docs.txt +0 -2
  129. package/sample-app/package-lock.json +0 -901
  130. package/sample-app/package.json +0 -9
  131. package/sample-app/server.js +0 -14
  132. package/sample-app/views/index.html +0 -1
  133. package/scripts/build-vsix.js +0 -129
  134. package/scripts/build-vsix.ps1 +0 -15
  135. package/snippets/miki-template.json +0 -177
  136. package/src/asyncRender.js +0 -21
  137. package/src/types.d.ts +0 -107
  138. package/syntaxes/language-configuration.json +0 -26
  139. package/syntaxes/miki-template.tmLanguage.json +0 -146
  140. package/tests/asyncRender.test.js +0 -17
  141. package/tests/base.html +0 -6
  142. package/tests/child.html +0 -3
  143. package/tests/context_processors.test.js +0 -13
  144. package/tests/esm.test.mjs +0 -61
  145. package/tests/filters.test.js +0 -263
  146. package/tests/finder-appdirs.test.js +0 -19
  147. package/tests/finder.test.js +0 -17
  148. package/tests/fixtures/views/nested/index.html +0 -1
  149. package/tests/fixtures/views/partial.html +0 -1
  150. package/tests/fixtures/views/sub/deepfile.html +0 -1
  151. package/tests/fixtures/views-appdirs/product/site/detail.html +0 -1
  152. package/tests/include_security.test.js +0 -9
  153. package/tests/integration/README.md +0 -32
  154. package/tests/integration/features.test.cjs +0 -1681
  155. package/tests/integration/features.test.mjs +0 -1697
  156. package/tests/integration/finder.esm.test.mjs +0 -13
  157. package/tests/integration/partial-render.test.cjs +0 -13
  158. package/tests/integration/templates/base.miki +0 -6
  159. package/tests/integration/templates/child.miki +0 -6
  160. package/tests/integration/templates/index.html +0 -17
  161. package/tests/lexer.test.js +0 -45
  162. package/tests/parser.test.js +0 -57
  163. package/tests/partial.html +0 -1
  164. package/tests/partialdef.test.js +0 -79
  165. package/tests/production_checks.js +0 -57
  166. package/tests/security.test.js +0 -28
  167. package/tests/tags.test.js +0 -233
@@ -1,1566 +0,0 @@
1
- # Tags
2
-
3
-
4
-
5
- Tags control template logic and structure. They use `{% %}` syntax.
6
-
7
-
8
-
9
- ## Table of Contents
10
-
11
-
12
-
13
- - [Control Flow](#control-flow)
14
-
15
- - [Variable Assignment](#variable-assignment)
16
-
17
- - [Change Detection](#change-detection)
18
-
19
- - [Date and Time](#date-and-time)
20
-
21
- - [Utility Tags](#utility-tags)
22
-
23
- - [Security Tags](#security-tags)
24
-
25
- - [Comments and Raw Output](#comments-and-raw-output)
26
-
27
- - [Autoescape](#autoescape)
28
-
29
- - [Library Loading](#library-loading)
30
-
31
- - [Template Tags](#template-tags)
32
-
33
- - [Inheritance Tags](#inheritance-tags)
34
-
35
- - [Partial Tags](#partial-tags)
36
-
37
- - [i18n Tags](#i18n-tags)
38
-
39
-
40
-
41
- ---
42
-
43
-
44
-
45
- ## Control Flow
46
-
47
-
48
-
49
- ### if / elif / else / endif
50
-
51
-
52
-
53
- Conditional rendering with a wide range of operators.
54
-
55
-
56
-
57
- === "Basic if"
58
-
59
-
60
-
61
- ```html
62
-
63
- {% if user.is_authenticated %}
64
-
65
- <p>Welcome back, {{ user.name }}!</p>
66
-
67
- {% else %}
68
-
69
- <p>Please <a href="/login">log in</a>.</p>
70
-
71
- {% endif %}
72
-
73
- ```
74
-
75
-
76
-
77
- === "Multiple conditions with elif"
78
-
79
-
80
-
81
- ```html
82
-
83
- {% if user.role == 'admin' %}
84
-
85
- <p>Admin panel</p>
86
-
87
- {% elif user.is_staff %}
88
-
89
- <p>Staff dashboard</p>
90
-
91
- {% else %}
92
-
93
- <p>Guest view</p>
94
-
95
- {% endif %}
96
-
97
- ```
98
-
99
-
100
-
101
- === "Combined conditions with parentheses"
102
-
103
-
104
-
105
- ```html
106
-
107
- {% if (user.role == 'admin' or user.is_staff) and user.is_active %}
108
-
109
- <p>Active staff member</p>
110
-
111
- {% endif %}
112
-
113
-
114
-
115
- {% if item not in cart_items %}
116
-
117
- <button>Add to cart</button>
118
-
119
- {% endif %}
120
-
121
- ```
122
-
123
-
124
-
125
- **Supported operators:** `==`, `!=`, `<`, `<=`, `>`, `>=`, `in`, `not in`, `and`, `or`, `not`
126
-
127
-
128
-
129
- **Operator precedence** (highest to lowest): comparison → `and` → `or`
130
-
131
-
132
-
133
- ### for / empty / endfor
134
-
135
-
136
-
137
- Loop over arrays and objects. Injects `forloop` meta tracking.
138
-
139
-
140
-
141
- === "Basic loop"
142
-
143
-
144
-
145
- ```html
146
-
147
- <ul>
148
-
149
- {% for item in items %}
150
-
151
- <li>{{ forloop.counter }}: {{ item }}</li>
152
-
153
- {% empty %}
154
-
155
- <li>No items found</li>
156
-
157
- {% endfor %}
158
-
159
- </ul>
160
-
161
- ```
162
-
163
-
164
-
165
- === "Loop with filters"
166
-
167
-
168
-
169
- ```html
170
-
171
- {% for group in items|regroup:"category" %}
172
-
173
- <h3>{{ group.grouper }}</h3>
174
-
175
- {% for item in group.list %}- {{ item.name }}
176
-
177
- {% endfor %}
178
-
179
- {% endfor %}
180
-
181
- ```
182
-
183
-
184
-
185
- === "Dictionary iteration"
186
-
187
-
188
-
189
- ```html
190
-
191
- {% for key, value in config %}
192
-
193
- <dt>{{ key }}</dt>
194
-
195
- <dd>{{ value }}</dd>
196
-
197
- {% endfor %}
198
-
199
- ```
200
-
201
-
202
-
203
- === "Nested loops"
204
-
205
-
206
-
207
- ```html
208
-
209
- {% for department in departments %}
210
-
211
- <h2>{{ department.name }}</h2>
212
-
213
- {% for employee in department.employees %}
214
-
215
- <span>#{{ forloop.parentloop.counter }}.{{ forloop.counter }} {{ employee.name }}</span>
216
-
217
- {% endfor %}
218
-
219
- {% endfor %}
220
-
221
- ```
222
-
223
-
224
-
225
- #### forloop Metadata
226
-
227
-
228
-
229
- | Variable | Description |
230
-
231
- |----------|-------------|
232
-
233
- | `forloop.counter` | 1-based index |
234
-
235
- | `forloop.counter0` | 0-based index |
236
-
237
- | `forloop.revcounter` | Reverse 1-based index |
238
-
239
- | `forloop.revcounter0` | Reverse 0-based index |
240
-
241
- | `forloop.first` | `true` on first iteration |
242
-
243
- | `forloop.last` | `true` on last iteration |
244
-
245
- | `forloop.parentloop` | Parent loop context (nested loops) |
246
-
247
-
248
-
249
- **Real-world table with alternating row classes:**
250
-
251
-
252
-
253
- ```html
254
-
255
- <table>
256
-
257
- {% for row in rows %}
258
-
259
- <tr class="{% cycle 'row-odd' 'row-even' %}">
260
-
261
- <td>{{ row.name }}</td>
262
-
263
- <td>{{ row.value }}</td>
264
-
265
- </tr>
266
-
267
- {% endfor %}
268
-
269
- </table>
270
-
271
- ```
272
-
273
-
274
-
275
- ### with / endwith
276
-
277
-
278
-
279
- Scope localized variables.
280
-
281
-
282
-
283
- === "Alias a variable"
284
-
285
-
286
-
287
- ```html
288
-
289
- {% with user.profile.address as addr %}
290
-
291
- <p>{{ addr.city }}, {{ addr.zip }}</p>
292
-
293
- {% endwith %}
294
-
295
- ```
296
-
297
-
298
-
299
- === "Multiple assignments"
300
-
301
-
302
-
303
- ```html
304
-
305
- {% with greeting="Hello", who="World" %}
306
-
307
- <p>{{ greeting }} {{ who }}</p>
308
-
309
- {% endwith %}
310
-
311
- ```
312
-
313
-
314
-
315
- === "Combined alias"
316
-
317
-
318
-
319
- ```html
320
-
321
- {% with a=5, b=10 as total %}
322
-
323
- <p>Total: {{ total }}</p>
324
-
325
- {% endwith %}
326
-
327
- ```
328
-
329
-
330
-
331
- ### cycle
332
-
333
-
334
-
335
- Cycle through values sequentially.
336
-
337
-
338
-
339
- === "Alternating CSS classes"
340
-
341
-
342
-
343
- ```html
344
-
345
- {% for row in rows %}
346
-
347
- <tr class="{% cycle 'row-odd' 'row-even' %}">...</tr>
348
-
349
- {% endfor %}
350
-
351
- ```
352
-
353
-
354
-
355
- === "Store without output (as)"
356
-
357
-
358
-
359
- ```html
360
-
361
- {% cycle 'row-odd' 'row-even' as row_class %}
362
-
363
- <tr class="{{ row_class }}">
364
-
365
- ```
366
-
367
-
368
-
369
- === "Named cycle for resumable state"
370
-
371
-
372
-
373
- ```html
374
-
375
- {% for item in items %}
376
-
377
- {% cycle 'a' 'b' 'c' as marker silent %}
378
-
379
- {% if marker == 'b' %}
380
-
381
- <strong>{{ item }}</strong>
382
-
383
- {% else %}
384
-
385
- {{ item }}
386
-
387
- {% endif %}
388
-
389
- {% endfor %}
390
-
391
- ```
392
-
393
-
394
-
395
- ### firstof
396
-
397
-
398
-
399
- Return the first truthy value.
400
-
401
-
402
-
403
- ```html
404
-
405
- {% firstof user.display_name user.username "Anonymous" %}
406
-
407
- ```
408
-
409
-
410
-
411
- ---
412
-
413
-
414
-
415
- ## Variable Assignment
416
-
417
-
418
-
419
- ### set
420
-
421
-
422
-
423
- Assign a value to a variable.
424
-
425
-
426
-
427
- === "Inline assignment"
428
-
429
-
430
-
431
- ```html
432
-
433
- {% set total = price * quantity %}
434
-
435
- <p>Total: ${{ total|floatformat:2 }}</p>
436
-
437
- ```
438
-
439
-
440
-
441
- === "Block form (captures rendered output)"
442
-
443
-
444
-
445
- ```html
446
-
447
- {% set greeting %}
448
-
449
- Hello {{ user.name|title }}, welcome to {{ site.name }}!
450
-
451
- {% endset %}
452
-
453
-
454
-
455
- <h1>{{ greeting|safe }}</h1>
456
-
457
- ```
458
-
459
-
460
-
461
- === "Multiple variables"
462
-
463
-
464
-
465
- ```html
466
-
467
- {% set tax_rate = 0.08, tax = subtotal|mult:tax_rate %}
468
-
469
- ```
470
-
471
-
472
-
473
- Variables set with `{% set %}` persist in the current scope and can be used after the tag.
474
-
475
-
476
-
477
- ---
478
-
479
-
480
-
481
- ## Change Detection
482
-
483
-
484
-
485
- ### ifchanged / endifchanged
486
-
487
-
488
-
489
- Render the body only when a value changes.
490
-
491
-
492
-
493
- === "Basic (no argument)"
494
-
495
-
496
-
497
- ```html
498
-
499
- {% for item in changelog %}
500
-
501
- {% ifchanged item.timestamp %}
502
-
503
- <h3>{{ item.timestamp|date:"Y-m-d" }}</h3>
504
-
505
- {% endifchanged %}
506
-
507
- <p>{{ item.change }}</p>
508
-
509
- {% endfor %}
510
-
511
- ```
512
-
513
-
514
-
515
- === "With else"
516
-
517
-
518
-
519
- ```html
520
-
521
- {% for item in items %}
522
-
523
- {% ifchanged item.category %}
524
-
525
- <h2>{{ item.category }}</h2>
526
-
527
- {% else %}
528
-
529
- <p>Same category as above</p>
530
-
531
- {% endifchanged %}
532
-
533
- {% endfor %}
534
-
535
- ```
536
-
537
-
538
-
539
- ---
540
-
541
-
542
-
543
- ## Date and Time
544
-
545
-
546
-
547
- ### now
548
-
549
-
550
-
551
- Output the current date/time.
552
-
553
-
554
-
555
- ```html
556
-
557
- <p>Current time: {% now "Y-m-d H:i:s" %}</p>
558
-
559
- <p>Pretty date: {% now "F j, Y" %}</p>
560
-
561
- ```
562
-
563
-
564
-
565
- Uses the same format codes as the `date` filter (Django-style tokens like `Y`, `m`, `d`, `H`, `i`, `s`, `F`).
566
-
567
-
568
-
569
- **Real-world copyright footer:**
570
-
571
-
572
-
573
- ```html
574
-
575
- <footer>
576
-
577
- &copy; {{ "now"|date:"Y" }} {{ site.name }}. All rights reserved.
578
-
579
- </footer>
580
-
581
- ```
582
-
583
-
584
-
585
- ---
586
-
587
-
588
-
589
- ## Utility Tags
590
-
591
-
592
-
593
- ### static
594
-
595
-
596
-
597
- Generate a static file URL.
598
-
599
-
600
-
601
- ```html
602
-
603
- <link rel="stylesheet" href="{% static "css/main.css" %}">
604
-
605
- <script src="{% static "js/app.js" %}"></script>
606
-
607
- <img src="{% static "images/logo.svg" }}" alt="{{ site.name }}">
608
-
609
- ```
610
-
611
-
612
-
613
- Configure the prefix at compile time:
614
-
615
-
616
-
617
- === "CommonJS"
618
-
619
-
620
-
621
- ```javascript
622
-
623
- const { compile } = require('miki-template');
624
-
625
- const template = compile(source, { staticUrl: '/assets/' });
626
-
627
- ```
628
-
629
-
630
-
631
- === "ES Modules"
632
-
633
-
634
-
635
- ```javascript
636
-
637
- import { compile } from 'miki-template';
638
-
639
- const template = compile(source, { staticUrl: '/assets/' });
640
-
641
- ```
642
-
643
-
644
-
645
- ### url
646
-
647
-
648
-
649
- Build a URL from a route name.
650
-
651
-
652
-
653
- === "Basic"
654
-
655
-
656
-
657
- ```html
658
-
659
- <a href="{% url 'user.profile' user.id %}">Profile</a>
660
-
661
- ```
662
-
663
-
664
-
665
- === "With keyword arguments"
666
-
667
-
668
-
669
- ```html
670
-
671
- {% url 'posts.show' post.id tab='comments' %}
672
-
673
- ```
674
-
675
-
676
-
677
- === "Deep routing with dots"
678
-
679
-
680
-
681
- ```html
682
-
683
- {% url 'user.profile.posts.show' user.id post.id %}
684
-
685
- ```
686
-
687
-
688
-
689
- Configure with a custom resolver:
690
-
691
-
692
-
693
- === "CommonJS"
694
-
695
-
696
-
697
- ```javascript
698
-
699
- const { compile } = require('miki-template');
700
-
701
- const template = compile(source, {
702
-
703
- urlHelper: (routeName, ...args) => {
704
-
705
- // Convert "user.profile" + [42] → "/user/profile/42"
706
-
707
- return '/' + routeName.split('.').join('/') + '/' + args.join('/');
708
-
709
- }
710
-
711
- });
712
-
713
- ```
714
-
715
-
716
-
717
- === "ES Modules"
718
-
719
-
720
-
721
- ```javascript
722
-
723
- import { compile } from 'miki-template';
724
-
725
- const template = compile(source, {
726
-
727
- urlHelper: (routeName, ...args) => {
728
-
729
- return '/' + routeName.split('.').join('/') + '/' + args.join('/');
730
-
731
- }
732
-
733
- });
734
-
735
- ```
736
-
737
-
738
-
739
- ### regroup
740
-
741
-
742
-
743
- Group a list by a common attribute.
744
-
745
-
746
-
747
- ```html
748
-
749
- {% regroup people by gender as departments %}
750
-
751
- {% for dept in departments %}
752
-
753
- <h3>{{ dept.grouper }}</h3>
754
-
755
- {% for person in dept.list %}
756
-
757
- <p>{{ person.name }}</p>
758
-
759
- {% endfor %}
760
-
761
- {% endfor %}
762
-
763
- ```
764
-
765
-
766
-
767
- You can also use `regroup` as a filter inside a `{% for %}` loop:
768
-
769
-
770
-
771
- ```html
772
-
773
- {% for group in items|regroup:"category" %}
774
-
775
- <h3>{{ group.grouper }}</h3>
776
-
777
- {% for item in group.list %}
778
-
779
- <p>{{ item.name }}</p>
780
-
781
- {% endfor %}
782
-
783
- {% endfor %}
784
-
785
- ```
786
-
787
-
788
-
789
- ### spaceless
790
-
791
-
792
-
793
- Remove whitespace between HTML tags.
794
-
795
-
796
-
797
- ```html
798
-
799
- {% spaceless %}
800
-
801
- <div>
802
-
803
- <span> hello </span>
804
-
805
- </div>
806
-
807
- {% endspaceless %}
808
-
809
- ```
810
-
811
-
812
-
813
- Output: `<div><span> hello </span></div>`
814
-
815
-
816
-
817
- ### widthratio
818
-
819
-
820
-
821
- Calculate ratios for progress bars or scaling.
822
-
823
-
824
-
825
- ```html
826
-
827
- <!-- Calculate 25 out of 100 scaled to max-width 150 -->
828
-
829
- {% widthratio score 100 150 %}
830
-
831
- <!-- → 37 (floor of 25/100*150) -->
832
-
833
-
834
-
835
- <!-- Progress bar width -->
836
-
837
- <div class="bar" style="width: {% widthratio value max_value 100 %}px;"></div>
838
-
839
- ```
840
-
841
-
842
-
843
- ### debug
844
-
845
-
846
-
847
- Dump the current template context for debugging.
848
-
849
-
850
-
851
- ```html
852
-
853
- {% debug %}
854
-
855
- ```
856
-
857
-
858
-
859
- Outputs a `<pre>` block with all context variables.
860
-
861
-
862
-
863
- ---
864
-
865
-
866
-
867
- ## Security Tags
868
-
869
-
870
-
871
- ### csrf_token
872
-
873
-
874
-
875
- Output a hidden CSRF token input.
876
-
877
-
878
-
879
- ```html
880
-
881
- <form method="post">
882
-
883
- {% csrf_token %}
884
-
885
- <button type="submit">Submit</button>
886
-
887
- </form>
888
-
889
- ```
890
-
891
-
892
-
893
- The token value is HTML-escaped to prevent attribute injection. Requires `csrf_token` to be present in the template context.
894
-
895
-
896
-
897
- === "CommonJS (Express middleware)"
898
-
899
-
900
-
901
- ```javascript
902
-
903
- app.use((req, res, next) => {
904
-
905
- res.locals.csrf_token = req.csrfToken();
906
-
907
- next();
908
-
909
- });
910
-
911
- ```
912
-
913
-
914
-
915
- === "ES Modules"
916
-
917
-
918
-
919
- ```javascript
920
-
921
- app.use((req, res, next) => {
922
-
923
- res.locals.csrf_token = req.csrfToken();
924
-
925
- next();
926
-
927
- });
928
-
929
- ```
930
-
931
-
932
-
933
- ### csp_nonce_attr
934
-
935
-
936
-
937
- Output a `nonce` attribute when `csp_nonce` is in the context.
938
-
939
-
940
-
941
- ```html
942
-
943
- <script {% csp_nonce_attr %} src="/js/app.js"></script>
944
-
945
- ```
946
-
947
-
948
-
949
- If `csp_nonce` is present in context, the output is:
950
-
951
-
952
-
953
- ```html
954
-
955
- <script nonce="abc123" src="/js/app.js"></script>
956
-
957
- ```
958
-
959
-
960
-
961
- If `csp_nonce` is not present, the tag outputs nothing.
962
-
963
-
964
-
965
- ---
966
-
967
-
968
-
969
- ## Comments and Raw Output
970
-
971
-
972
-
973
- ### comment / endcomment
974
-
975
-
976
-
977
- Block comments ignored during parsing.
978
-
979
-
980
-
981
- ```html
982
-
983
- {% comment %}
984
-
985
- This is a comment.
986
-
987
- It can span multiple lines.
988
-
989
- {% endcomment %}
990
-
991
- ```
992
-
993
-
994
-
995
- ### verbatim / endverbatim
996
-
997
-
998
-
999
- Treat content as raw text — template syntax is not parsed.
1000
-
1001
-
1002
-
1003
- ```html
1004
-
1005
- {% verbatim %}
1006
-
1007
- This will NOT be parsed: {{ user.name }}
1008
-
1009
- And this won't either: {% if x %}
1010
-
1011
- {% endverbatim %}
1012
-
1013
- ```
1014
-
1015
-
1016
-
1017
- You can also name a verbatim block:
1018
-
1019
-
1020
-
1021
- ```html
1022
-
1023
- {% verbatim myscript %}
1024
-
1025
- {{ angularExpression }}
1026
-
1027
- {% endverbatim %}
1028
-
1029
- ```
1030
-
1031
-
1032
-
1033
- ---
1034
-
1035
-
1036
-
1037
- ## Autoescape
1038
-
1039
-
1040
-
1041
- Control HTML escaping for a block.
1042
-
1043
-
1044
-
1045
- ```html
1046
-
1047
- {% autoescape on %}
1048
-
1049
- {{ user_input }} {# escaped → &lt;script&gt;... #}
1050
-
1051
- {% endautoescape %}
1052
-
1053
-
1054
-
1055
- {% autoescape off %}
1056
-
1057
- {{ trusted_html }} {# not escaped → raw HTML #}
1058
-
1059
- {% endautoescape %}
1060
-
1061
- ```
1062
-
1063
-
1064
-
1065
- ---
1066
-
1067
-
1068
-
1069
- ## Library Loading
1070
-
1071
-
1072
-
1073
- ### load
1074
-
1075
-
1076
-
1077
- Activate a template library. Built-in libraries (`humanize`, `cache`, `lorem`) are auto-activated — you only need `{% load %}` for custom libraries you've registered.
1078
-
1079
-
1080
-
1081
- ```html
1082
-
1083
- {% load humanize %}
1084
-
1085
- {{ views|intcomma }}
1086
-
1087
- {{ count|ordinal }}
1088
-
1089
- ```
1090
-
1091
-
1092
-
1093
- === "CommonJS (registering a library)"
1094
-
1095
-
1096
-
1097
- ```javascript
1098
-
1099
- const { registerLibrary } = require('miki-template');
1100
-
1101
-
1102
-
1103
- registerLibrary('myutils', {
1104
-
1105
- filters: {
1106
-
1107
- shout: (val) => String(val).toUpperCase() + '!'
1108
-
1109
- }
1110
-
1111
- });
1112
-
1113
- ```
1114
-
1115
-
1116
-
1117
- === "ES Modules"
1118
-
1119
-
1120
-
1121
- ```javascript
1122
-
1123
- import { registerLibrary } from 'miki-template';
1124
-
1125
-
1126
-
1127
- registerLibrary('myutils', {
1128
-
1129
- filters: {
1130
-
1131
- shout: (val) => String(val).toUpperCase() + '!'
1132
-
1133
- }
1134
-
1135
- });
1136
-
1137
- ```
1138
-
1139
-
1140
-
1141
- Then use in templates:
1142
-
1143
-
1144
-
1145
- ```html
1146
-
1147
- {% load myutils %}
1148
-
1149
- {{ name|shout }}
1150
-
1151
- ```
1152
-
1153
-
1154
-
1155
- **Built-in libraries:**
1156
-
1157
-
1158
-
1159
- - `i18n` — `{% trans %}`, `{% blocktrans %}`, `{% language %}`
1160
-
1161
- - `humanize` — `intcomma`, `intword`, `apnumber`, `ordinal`, `naturalday`
1162
-
1163
- - `cache` — `{% cache timeout key %}...{% endcache %}`
1164
-
1165
- - `lorem` — `{% lorem %}` tag and `lorem` filter
1166
-
1167
-
1168
-
1169
- ---
1170
-
1171
-
1172
-
1173
- ## Template Tags
1174
-
1175
-
1176
-
1177
- ### templatetag
1178
-
1179
-
1180
-
1181
- Output literal template tag tokens. Useful when generating documentation or when the template syntax conflicts with another templating layer.
1182
-
1183
-
1184
-
1185
- ```html
1186
-
1187
- {% templatetag openblock %} if user.is_admin {% templatetag closeblock %}
1188
-
1189
- <!-- Renders: {% if user.is_admin %} -->
1190
-
1191
-
1192
-
1193
- {% templatetag openvariable %} name {% templatetag closevariable %}
1194
-
1195
- <!-- Renders: {{ name }} -->
1196
-
1197
- ```
1198
-
1199
-
1200
-
1201
- Available tokens:
1202
-
1203
-
1204
-
1205
- | Token | Output |
1206
-
1207
- |-------|--------|
1208
-
1209
- | `openblock` | `{%` |
1210
-
1211
- | `closeblock` | `%}` |
1212
-
1213
- | `openvariable` | `{{` |
1214
-
1215
- | `closevariable` | `}}` |
1216
-
1217
- | `openbrace` | `{` |
1218
-
1219
- | `closebrace` | `}` |
1220
-
1221
- | `opencomment` | `{#` |
1222
-
1223
- | `closecomment` | `#}` |
1224
-
1225
-
1226
-
1227
- ---
1228
-
1229
-
1230
-
1231
- ## Inheritance Tags
1232
-
1233
-
1234
-
1235
- ### extends
1236
-
1237
-
1238
-
1239
- Inherit from a parent template.
1240
-
1241
-
1242
-
1243
- ```html
1244
-
1245
- {% extends "base.html" %}
1246
-
1247
- ```
1248
-
1249
-
1250
-
1251
- Can use expressions for dynamic parent selection:
1252
-
1253
-
1254
-
1255
- ```html
1256
-
1257
- {% extends device|default:"desktop/base.html" %}
1258
-
1259
- ```
1260
-
1261
-
1262
-
1263
- **Security:** Path traversal is blocked — `{% extends "../../etc/passwd" %}` is rejected.
1264
-
1265
-
1266
-
1267
- ### block / endblock
1268
-
1269
-
1270
-
1271
- Define a block that can be overridden by child templates.
1272
-
1273
-
1274
-
1275
- ```html
1276
-
1277
- <!-- base.html -->
1278
-
1279
- <html>
1280
-
1281
- <body>
1282
-
1283
- {% block content %}Default content{% endblock %}
1284
-
1285
- </body>
1286
-
1287
- </html>
1288
-
1289
- ```
1290
-
1291
-
1292
-
1293
- ```html
1294
-
1295
- <!-- child.html -->
1296
-
1297
- {% extends "base.html" %}
1298
-
1299
- {% block content %}
1300
-
1301
- <h1>Child content</h1>
1302
-
1303
- {{ block.super }}
1304
-
1305
- {% endblock %}
1306
-
1307
- ```
1308
-
1309
-
1310
-
1311
- ### block.super
1312
-
1313
-
1314
-
1315
- A special variable (not a tag). When used inside a `{% block %}`, it renders the parent template's version of that block.
1316
-
1317
-
1318
-
1319
- See [Template Inheritance](template-inheritance.md) for a detailed guide.
1320
-
1321
-
1322
-
1323
- ### include
1324
-
1325
-
1326
-
1327
- Include another template's content inline.
1328
-
1329
-
1330
-
1331
- ```html
1332
-
1333
- {% include "header.html" %}
1334
-
1335
- {% include "header.html" with title="Hello" %}
1336
-
1337
- {% include "header.html#partial_name" %}
1338
-
1339
- {% include "header.html" with title="Hello" %}
1340
-
1341
- ```
1342
-
1343
-
1344
-
1345
- **Security:** Path traversal is blocked.
1346
-
1347
-
1348
-
1349
- ---
1350
-
1351
-
1352
-
1353
- ## Partial Tags
1354
-
1355
-
1356
-
1357
- ### partialdef / endpartialdef
1358
-
1359
-
1360
-
1361
- Define a reusable partial block.
1362
-
1363
-
1364
-
1365
- ```html
1366
-
1367
- {% partialdef card %}
1368
-
1369
- <div class="card">
1370
-
1371
- <h3>{{ title|default:"Untitled" }}</h3>
1372
-
1373
- <p>{{ body|truncatewords:30 }}</p>
1374
-
1375
- {% if featured %}<em>Featured</em>{% endif %}
1376
-
1377
- </div>
1378
-
1379
- {% endpartialdef %}
1380
-
1381
- ```
1382
-
1383
-
1384
-
1385
- **Options:**
1386
-
1387
-
1388
-
1389
- | Option | Description |
1390
-
1391
- |--------|-------------|
1392
-
1393
- | `inline` | Renders the definition inline at its location during parse (the body appears in output AND registers for later use). |
1394
-
1395
-
1396
-
1397
- ```html
1398
-
1399
- {% partialdef greeting inline %}
1400
-
1401
- Hello {{ name }}!
1402
-
1403
- {% endpartialdef %}
1404
-
1405
- <!-- Above line ALSO outputs "Hello World!" when rendered -->
1406
-
1407
- ```
1408
-
1409
-
1410
-
1411
- **Programmatic access:**
1412
-
1413
-
1414
-
1415
- === "CommonJS"
1416
-
1417
-
1418
-
1419
- ```javascript
1420
-
1421
- const { compile } = require('miki-template');
1422
-
1423
- const compiled = compile(template);
1424
-
1425
- compiled.renderPartial('card', { title: 'Hi', body: 'There' });
1426
-
1427
- ```
1428
-
1429
-
1430
-
1431
- === "ES Modules"
1432
-
1433
-
1434
-
1435
- ```javascript
1436
-
1437
- import { compile } from 'miki-template';
1438
-
1439
- const compiled = compile(template);
1440
-
1441
- compiled.renderPartial('card', { title: 'Hi', body: 'There' });
1442
-
1443
- ```
1444
-
1445
-
1446
-
1447
- ### partial
1448
-
1449
-
1450
-
1451
- Render a named partial.
1452
-
1453
-
1454
-
1455
- ```html
1456
-
1457
- {% partial card %}
1458
-
1459
- {% partial card with title="Custom" body="World" %}
1460
-
1461
- {% partial greeting with name=user.name %}
1462
-
1463
- ```
1464
-
1465
-
1466
-
1467
- See [Partial Templates](partial-templates.md) for a detailed guide.
1468
-
1469
-
1470
-
1471
- ---
1472
-
1473
-
1474
-
1475
- ## i18n Tags
1476
-
1477
-
1478
-
1479
- ### trans
1480
-
1481
-
1482
-
1483
- Translate a string.
1484
-
1485
-
1486
-
1487
- ```html
1488
-
1489
- {% trans "Hello, world!" %}
1490
-
1491
- {% trans "Hello, %s!" name=user.name %}
1492
-
1493
- {% trans context "verb" "He runs" %}
1494
-
1495
- ```
1496
-
1497
-
1498
-
1499
- ### blocktrans / endblocktrans
1500
-
1501
-
1502
-
1503
- Translate a block of text with variable interpolation and pluralization.
1504
-
1505
-
1506
-
1507
- ```html
1508
-
1509
- {% blocktrans with name=user.name %}
1510
-
1511
- Hello, {{ name }}!
1512
-
1513
- {% endblocktrans %}
1514
-
1515
-
1516
-
1517
- {% blocktrans count items|length %}
1518
-
1519
- {{ count }} item
1520
-
1521
- {% plural %}
1522
-
1523
- {{ count }} items
1524
-
1525
- {% endblocktrans %}
1526
-
1527
- ```
1528
-
1529
-
1530
-
1531
- ### language / endlanguage
1532
-
1533
-
1534
-
1535
- Switch language temporarily for a block.
1536
-
1537
-
1538
-
1539
- ```html
1540
-
1541
- {% language "fr" %}
1542
-
1543
- {% trans "Hello" %} → renders in French
1544
-
1545
- {% endlanguage %}
1546
-
1547
- ```
1548
-
1549
-
1550
-
1551
- ---
1552
-
1553
-
1554
-
1555
- ## Next Steps
1556
-
1557
-
1558
-
1559
- - [Filters](./filters.md)
1560
-
1561
- - [Template Inheritance](./template-inheritance.md)
1562
-
1563
- - [Partial Templates](./partial-templates.md)
1564
-
1565
- - [Custom Tags](./custom-tags.md)
1566
-