miki-template 2.3.0 → 2.3.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 (160) hide show
  1. package/README.md +17 -5
  2. package/package.json +45 -1
  3. package/.eslintrc.json +0 -16
  4. package/.github/release-notes/v1.3.1.md +0 -55
  5. package/.github/release-notes/v1.3.3.md +0 -77
  6. package/.github/release-notes/v1.3.7.md +0 -20
  7. package/.github/workflows/ci.yml +0 -42
  8. package/.github/workflows/docs.yml +0 -107
  9. package/.github/workflows/release.yml +0 -107
  10. package/AGENT.md +0 -71
  11. package/API_REFERENCE.md +0 -341
  12. package/CHANGELOG.md +0 -173
  13. package/CODE_OF_CONDUCT.md +0 -14
  14. package/CONTRIBUTING.md +0 -27
  15. package/ROADMAP.md +0 -40
  16. package/assets/banner.png +0 -0
  17. package/assets/logo.png +0 -0
  18. package/benchmarks/ejs-results.json +0 -17
  19. package/benchmarks/ejs.js +0 -38
  20. package/benchmarks/handlebars-results.json +0 -17
  21. package/benchmarks/handlebars.js +0 -45
  22. package/benchmarks/miki-results.json +0 -17
  23. package/benchmarks/miki.js +0 -39
  24. package/benchmarks/pug-results.json +0 -17
  25. package/benchmarks/pug.js +0 -38
  26. package/benchmarks/report.json +0 -17
  27. package/benchmarks/run.js +0 -81
  28. package/benchmarks/stress.mjs +0 -647
  29. package/benchmarks/templates/large.dtpl +0 -7
  30. package/benchmarks/templates/medium.dtpl +0 -3
  31. package/benchmarks/templates/small.dtpl +0 -7
  32. package/context/component.md +0 -109
  33. package/context/prd.md +0 -131
  34. package/context/project-structure.md +0 -33
  35. package/dir/base.html +0 -23
  36. package/dir/cmpnt.html +0 -11
  37. package/dir/footer.html +0 -3
  38. package/dir/home.html +0 -80
  39. package/dir/index.html +0 -80
  40. package/dir/navbar.html +0 -9
  41. package/docs/api/async-render.md +0 -170
  42. package/docs/api/cache.md +0 -174
  43. package/docs/api/compile.md +0 -256
  44. package/docs/api/context-processors.md +0 -154
  45. package/docs/api/filters.md +0 -437
  46. package/docs/api/finder.md +0 -188
  47. package/docs/api/helpers.md +0 -106
  48. package/docs/api/i18n.md +0 -314
  49. package/docs/api/index.md +0 -108
  50. package/docs/api/libraries.md +0 -414
  51. package/docs/api/render-partial.md +0 -162
  52. package/docs/api/render.md +0 -184
  53. package/docs/api/security.md +0 -290
  54. package/docs/api/setup-express.md +0 -152
  55. package/docs/api/tags.md +0 -268
  56. package/docs/assets/banner.png +0 -0
  57. package/docs/assets/logo.png +0 -0
  58. package/docs/filter.md +0 -0
  59. package/docs/guide/advanced-usage.md +0 -794
  60. package/docs/guide/async-rendering.md +0 -616
  61. package/docs/guide/context-processors.md +0 -514
  62. package/docs/guide/custom-filters.md +0 -622
  63. package/docs/guide/custom-tags.md +0 -542
  64. package/docs/guide/filters.md +0 -1314
  65. package/docs/guide/getting-started.md +0 -204
  66. package/docs/guide/installation.md +0 -190
  67. package/docs/guide/partial-templates.md +0 -734
  68. package/docs/guide/quick-start.md +0 -444
  69. package/docs/guide/security.md +0 -690
  70. package/docs/guide/tags.md +0 -1566
  71. package/docs/guide/template-discovery.md +0 -340
  72. package/docs/guide/template-inheritance.md +0 -546
  73. package/docs/guide/what-is-miki-template.md +0 -28
  74. package/docs/guide/why-miki-template.md +0 -75
  75. package/docs/index.md +0 -86
  76. package/docs/integrations/elysia.md +0 -80
  77. package/docs/integrations/express.md +0 -219
  78. package/docs/integrations/fastify.md +0 -79
  79. package/docs/integrations/hono.md +0 -80
  80. package/docs/integrations/index.md +0 -68
  81. package/docs/integrations/koa.md +0 -90
  82. package/docs/integrations/nestjs.md +0 -80
  83. package/docs/integrations/tsed.md +0 -83
  84. package/docs/performance.md +0 -74
  85. package/eslint.config.mjs +0 -42
  86. package/ex.mjs +0 -36
  87. package/live-test/integrations/elysia-example.js +0 -16
  88. package/live-test/integrations/express-example.js +0 -24
  89. package/live-test/integrations/fastify-example.js +0 -20
  90. package/live-test/integrations/hono-example.js +0 -16
  91. package/live-test/integrations/koa-example.js +0 -30
  92. package/live-test/integrations/nestjs-example.js +0 -25
  93. package/live-test/integrations/smoke-test.js +0 -166
  94. package/live-test/integrations/tsed-example.js +0 -23
  95. package/live-test/package-lock.json +0 -1150
  96. package/live-test/package.json +0 -13
  97. package/live-test/packages/product/templates/product/detail.html +0 -7
  98. package/live-test/server.js +0 -38
  99. package/live-test/templates/app_templates/detail.html +0 -6
  100. package/live-test/views/base.html +0 -8
  101. package/live-test/views/child.html +0 -7
  102. package/live-test/views/home.html +0 -17
  103. package/live-test/views/index.html +0 -1
  104. package/miki-template-extension/.github/workflows/ci.yml +0 -116
  105. package/miki-template-extension/.vscodeignore +0 -7
  106. package/miki-template-extension/CHANGELOG.md +0 -99
  107. package/miki-template-extension/LICENSE +0 -21
  108. package/miki-template-extension/README.md +0 -273
  109. package/miki-template-extension/extension.js +0 -1013
  110. package/miki-template-extension/icon.png +0 -0
  111. package/miki-template-extension/icon.svg +0 -10
  112. package/miki-template-extension/miki-template-1.7.1.vsix +0 -0
  113. package/miki-template-extension/package.json +0 -280
  114. package/miki-template-extension/snippets/miki-template.json +0 -717
  115. package/miki-template-extension/syntaxes/language-configuration.json +0 -114
  116. package/miki-template-extension/syntaxes/miki-template.tmLanguage.json +0 -355
  117. package/miki-template-extension/tests/grammar-tests.json +0 -162
  118. package/miki-template-extension/tests/run-grammar-tests.js +0 -82
  119. package/mkdocs.yml +0 -195
  120. package/overrides/main.html +0 -26
  121. package/requirements-docs.txt +0 -2
  122. package/sample-app/package-lock.json +0 -901
  123. package/sample-app/package.json +0 -9
  124. package/sample-app/server.js +0 -14
  125. package/sample-app/views/index.html +0 -1
  126. package/scripts/build-vsix.js +0 -129
  127. package/scripts/build-vsix.ps1 +0 -15
  128. package/snippets/miki-template.json +0 -177
  129. package/src/asyncRender.js +0 -21
  130. package/src/types.d.ts +0 -107
  131. package/syntaxes/language-configuration.json +0 -26
  132. package/syntaxes/miki-template.tmLanguage.json +0 -146
  133. package/tests/asyncRender.test.js +0 -17
  134. package/tests/base.html +0 -6
  135. package/tests/child.html +0 -3
  136. package/tests/context_processors.test.js +0 -13
  137. package/tests/esm.test.mjs +0 -61
  138. package/tests/filters.test.js +0 -263
  139. package/tests/finder-appdirs.test.js +0 -19
  140. package/tests/finder.test.js +0 -17
  141. package/tests/fixtures/views/nested/index.html +0 -1
  142. package/tests/fixtures/views/partial.html +0 -1
  143. package/tests/fixtures/views/sub/deepfile.html +0 -1
  144. package/tests/fixtures/views-appdirs/product/site/detail.html +0 -1
  145. package/tests/include_security.test.js +0 -9
  146. package/tests/integration/README.md +0 -32
  147. package/tests/integration/features.test.cjs +0 -1681
  148. package/tests/integration/features.test.mjs +0 -1697
  149. package/tests/integration/finder.esm.test.mjs +0 -13
  150. package/tests/integration/partial-render.test.cjs +0 -13
  151. package/tests/integration/templates/base.miki +0 -6
  152. package/tests/integration/templates/child.miki +0 -6
  153. package/tests/integration/templates/index.html +0 -17
  154. package/tests/lexer.test.js +0 -45
  155. package/tests/parser.test.js +0 -57
  156. package/tests/partial.html +0 -1
  157. package/tests/partialdef.test.js +0 -79
  158. package/tests/production_checks.js +0 -57
  159. package/tests/security.test.js +0 -28
  160. 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
-