miki-template 1.2.0 → 1.3.6

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 (105) hide show
  1. package/.github/release-notes/v1.3.1.md +55 -0
  2. package/.github/release-notes/v1.3.3.md +77 -0
  3. package/.github/workflows/ci.yml +38 -54
  4. package/.github/workflows/release.yml +106 -0
  5. package/AGENT.md +71 -71
  6. package/API_REFERENCE.md +314 -314
  7. package/CHANGELOG.md +173 -97
  8. package/CODE_OF_CONDUCT.md +14 -14
  9. package/CONTRIBUTING.md +27 -27
  10. package/README.md +342 -304
  11. package/ROADMAP.md +40 -40
  12. package/assets/banner.png +0 -0
  13. package/benchmarks/report.json +16 -16
  14. package/benchmarks/run.js +49 -49
  15. package/benchmarks/stress.mjs +647 -0
  16. package/benchmarks/templates/large.dtpl +7 -7
  17. package/benchmarks/templates/medium.dtpl +3 -3
  18. package/benchmarks/templates/small.dtpl +7 -7
  19. package/context/component.md +109 -109
  20. package/context/prd.md +131 -131
  21. package/context/project-structure.md +33 -33
  22. package/dir/base.html +23 -0
  23. package/dir/cmpnt.html +11 -0
  24. package/dir/footer.html +3 -0
  25. package/dir/home.html +80 -0
  26. package/dir/index.html +80 -0
  27. package/dir/navbar.html +9 -0
  28. package/docs/README.md +18 -18
  29. package/docs/advanced_usage.md +71 -71
  30. package/docs/api.md +119 -102
  31. package/docs/filters.md +708 -540
  32. package/docs/installation.md +106 -106
  33. package/docs/overview.md +57 -57
  34. package/docs/partialdef.md +70 -41
  35. package/docs/security.md +27 -27
  36. package/docs/tags.md +673 -610
  37. package/docs/usage.md +646 -599
  38. package/eslint.config.mjs +42 -34
  39. package/ex.mjs +33 -0
  40. package/miki-template-extension/.github/workflows/ci.yml +116 -0
  41. package/miki-template-extension/.vscodeignore +7 -0
  42. package/miki-template-extension/CHANGELOG.md +99 -0
  43. package/miki-template-extension/LICENSE +21 -21
  44. package/miki-template-extension/README.md +273 -82
  45. package/miki-template-extension/extension.js +1013 -0
  46. package/miki-template-extension/icon.png +0 -0
  47. package/miki-template-extension/icon.svg +10 -10
  48. package/miki-template-extension/miki-template-1.7.1.vsix +0 -0
  49. package/miki-template-extension/package.json +280 -46
  50. package/miki-template-extension/snippets/miki-template.json +717 -177
  51. package/miki-template-extension/syntaxes/language-configuration.json +114 -26
  52. package/miki-template-extension/syntaxes/miki-template.tmLanguage.json +355 -146
  53. package/miki-template-extension/tests/grammar-tests.json +162 -0
  54. package/miki-template-extension/tests/run-grammar-tests.js +82 -0
  55. package/package.json +37 -31
  56. package/sample-app/package-lock.json +901 -0
  57. package/sample-app/package.json +9 -0
  58. package/sample-app/server.js +14 -0
  59. package/sample-app/views/index.html +1 -0
  60. package/scripts/build-vsix.js +129 -0
  61. package/scripts/build-vsix.ps1 +15 -0
  62. package/snippets/miki-template.json +177 -177
  63. package/src/asyncRender.js +20 -20
  64. package/src/cache.js +80 -41
  65. package/src/context.js +126 -122
  66. package/src/context_processors.js +48 -41
  67. package/src/esm.mjs +84 -72
  68. package/src/filters.js +975 -527
  69. package/src/i18n.js +171 -171
  70. package/src/index.js +974 -454
  71. package/src/lexer.js +114 -92
  72. package/src/libraries.js +371 -240
  73. package/src/parser.js +270 -250
  74. package/src/security.js +53 -51
  75. package/src/tags/control.js +719 -590
  76. package/src/tags/extra.js +154 -0
  77. package/src/tags/helpers.js +26 -26
  78. package/src/tags/i18n.js +256 -230
  79. package/src/tags/inheritance.js +335 -216
  80. package/src/tags/registry.js +18 -18
  81. package/src/tags/util.js +400 -322
  82. package/src/types.d.ts +107 -107
  83. package/syntaxes/language-configuration.json +26 -26
  84. package/syntaxes/miki-template.tmLanguage.json +146 -146
  85. package/tests/asyncRender.test.js +17 -17
  86. package/tests/base.html +6 -6
  87. package/tests/child.html +3 -3
  88. package/tests/context_processors.test.js +13 -13
  89. package/tests/esm.test.mjs +61 -26
  90. package/tests/filters.test.js +254 -99
  91. package/tests/include_security.test.js +9 -9
  92. package/tests/integration/README.md +32 -0
  93. package/tests/integration/features.test.cjs +1681 -0
  94. package/tests/integration/features.test.mjs +1697 -0
  95. package/tests/integration/templates/base.miki +6 -0
  96. package/tests/integration/templates/child.miki +6 -0
  97. package/tests/integration/templates/index.html +17 -0
  98. package/tests/lexer.test.js +45 -45
  99. package/tests/parser.test.js +57 -55
  100. package/tests/partial.html +1 -1
  101. package/tests/partialdef.test.js +79 -40
  102. package/tests/production_checks.js +57 -57
  103. package/tests/security.test.js +28 -28
  104. package/tests/tags.test.js +233 -203
  105. package/miki-template-1.2.0.vsix +0 -0
package/README.md CHANGED
@@ -1,304 +1,342 @@
1
- # miki-template
2
- ![npm version](https://img.shields.io/npm/v/miki-template.svg) ![CI](https://github.com/your-repo/miki-template/workflows/ci.yml/badge.svg)
3
- A robust, production-ready template engine that brings **Django's template language** features and syntax to Node.js and Express, fully compliant with modern JavaScript (ES6+), CommonJS, and **ESM** (`import`) support.
4
-
5
- ---
6
-
7
- ## 🚀 Features
8
-
9
- - **Full Syntax Parity**: Supports variables, dotted lookups, filters (`|`), and block tags (`{% %}`).
10
- - **Template Inheritance**: Multi-level inheritance with `extends`, block overrides, and `{{ block.super }}` support.
11
- - **Express Integration**: Simple, zero-config integration via `app.engine()`.
12
- - **ESM & CommonJS**: Works seamlessly with both `import` and `require` syntax.
13
- - **Security by Default**: Auto-escaping enabled by default with a `SafeString` wrapper.
14
- - **CSRF & CSP Support**: Native tags for `{% csrf_token %}` and `{% csp_nonce_attr %}` to keep apps secure out-of-the-box.
15
- - **Block Partials**: Render a single block from a compiled template via `compiled.renderBlock('block_name')`.
16
- - **Async Rendering**: Support for async filters/tags with `asyncRender()`.
17
- - **Extensible API**: Easy registration for custom tags and filters.
18
- - **No Unsafe Code Execution**: Evaluates expressions securely without using `eval()`.
19
- - **Editor Support**: First-class syntax highlighting and snippets for VS Code, Sublime Text, Atom, and TextMate-compatible editors.
20
-
21
- ### VS Code
22
-
23
- #### Option A: Install the official extension (recommended)
24
-
25
- Search for **miki-template** in the VS Code Marketplace, or install from the command line:
26
-
27
- ```bash
28
- code --install-extension miki-template
29
- ```
30
-
31
- #### Option B: Manual install from this repo
32
-
33
- 1. Copy the `syntaxes/` and `snippets/` folders from this repo.
34
- 2. In VS Code, run **Preferences: Configure File Associations** and associate `*.miki` with `miki-template`.
35
- 3. Or add a workspace-level `.vscode/settings.json`:
36
-
37
- ```json
38
- {
39
- "files.associations": {
40
- "*.miki": "miki-template"
41
- }
42
- }
43
- ```
44
-
45
- ### Sublime Text / Atom / TextMate
46
-
47
- Drop the `syntaxes/miki-template.tmLanguage.json` file into your editor’s `Packages/User/` folder and associate it with the `.miki` extension.
48
-
49
- ---
50
-
51
- ## 📚 Documentation
52
-
53
- - [Installation](docs/installation.md)
54
- - [Usage Guide](docs/usage.md)
55
- - [Tags Reference](docs/tags.md)
56
- - [Filters Reference](docs/filters.md)
57
- - [Security](docs/security.md)
58
- - [API Reference](docs/api.md)
59
- - [Partial Definitions](docs/partialdef.md)
60
- - [Contributing](docs/advanced_usage.md)
61
-
62
- ---
63
-
64
- ## 📦 Installation
65
-
66
- ```bash
67
- npm install miki-template
68
- ```
69
-
70
- ---
71
-
72
- ## 🛠️ Quick Start
73
-
74
- ### CommonJS (require)
75
-
76
- ```javascript
77
- const { render, compile, __express, SafeString, markSafe } = require('miki-template');
78
-
79
- const template = 'Hello {{ user.name|title }}! Roles: {{ user.roles|join:", " }}';
80
- const context = {
81
- user: {
82
- name: 'miki coder',
83
- roles: ['admin', 'developer']
84
- }
85
- };
86
-
87
- const result = render(template, context);
88
- console.log(result); // Output: "Hello Miki Coder! Roles: admin, developer"
89
- ```
90
-
91
- ### ES Modules (import)
92
-
93
- ```javascript
94
- // Named imports
95
- import { render, compile, __express, SafeString, markSafe } from 'miki-template';
96
-
97
- // Or default import (gets all exports)
98
- import miki from 'miki-template';
99
- const { render: mikiRender } = miki;
100
-
101
- const template = 'Hello {{ user.name|title }}!';
102
- const result = render(template, { user: { name: 'world' } });
103
- console.log(result); // Output: "Hello World!"
104
- ```
105
-
106
- > **Note:** For ESM in Node.js, either name your files `.mjs` or add `"type": "module"` to your `package.json`.
107
-
108
- ### Express Integration
109
-
110
- **CommonJS:**
111
- ```javascript
112
- const express = require('express');
113
- const { __express: renderDtpl } = require('miki-template');
114
-
115
- const app = express();
116
-
117
- // Register both .html and .miki extensions
118
- app.engine('html', renderDtpl);
119
- app.engine('miki', renderDtpl);
120
- app.set('view engine', 'miki');
121
- app.set('views', './views');
122
-
123
- app.get('/', (req, res) => {
124
- res.render('home', {
125
- title: 'Django Templates in Node!',
126
- items: ['Apple', 'Banana', 'Orange']
127
- });
128
- });
129
-
130
- app.listen(3000, () => console.log('App listening on port 3000'));
131
- ```
132
-
133
- **ESM:**
134
- ```javascript
135
- import express from 'express';
136
- import { __express as renderDtpl } from 'miki-template';
137
-
138
- const app = express();
139
- app.engine('html', renderDtpl);
140
- app.set('view engine', 'html');
141
- app.set('views', './views');
142
-
143
- app.get('/', (req, res) => {
144
- res.render('home', {
145
- title: 'Django Templates with ESM!',
146
- items: ['Apple', 'Banana', 'Orange']
147
- });
148
- });
149
-
150
- app.listen(3000, () => console.log('App listening on port 3000'));
151
- ```
152
-
153
- ---
154
-
155
- ## 📖 Template Syntax & Parity
156
-
157
- ### Variables & Dotted Lookups
158
- Resolve properties dynamically on nested objects or arrays. If the resolved value is a callable/function, it is automatically executed with zero arguments.
159
- ```html
160
- {{ user.profile.name }}
161
- {{ items.0 }} <!-- Array indexing -->
162
- {{ user.getFullName }} <!-- Function resolution -->
163
- ```
164
-
165
- ### Built-in Filters
166
- Apply filters using pipes (`|`). Arguments are passed after a colon (`:`).
167
- - **Text**: `upper`, `lower`, `title`, `capfirst`, `slugify`, `wordcount`, `striptags`, `linebreaks`, `linebreaksbr`, `truncatewords:N`, `truncatechars:N`.
168
- - **HTML**: `safe`, `escape`.
169
- - **List**: `length`, `join:","`, `slice:"start:end"`, `dictsort:"key"`, `dictsortreversed:"key"`.
170
- - **Default**: `default:"fallback"`, `default_if_none:"fallback"`.
171
- - **Date/Time**: `date:"Y-m-d"`, `time:"H:i"`, `timesince`, `timeuntil`.
172
- - **Numeric**: `add:5`, `divisibleby:2`, `floatformat:2`.
173
- - **Misc**: `yesno:"yes,no,maybe"`, `pluralize:"suffix"`, `filesizeformat`.
174
-
175
- ### Built-in Control Tags
176
- - **if / elif / else / endif**: Supports conditional expressions with operators: `==`, `!=`, `<`, `<=`, `>`, `>=`, `in`, `not in`, `and`, `or`, `not`.
177
- ```html
178
- {% if user.role == 'admin' or user.is_staff %}
179
- <p>Access Granted</p>
180
- {% elif user.age >= 18 %}
181
- <p>Standard Access</p>
182
- {% else %}
183
- <p>Access Denied</p>
184
- {% endif %}
185
- ```
186
- - **for / empty / endfor**: Loop over arrays and objects. Injects `forloop` meta tracking.
187
- ```html
188
- {% for item in items %}
189
- <li>{{ forloop.counter }}: {{ item }}</li>
190
- {% empty %}
191
- <li>No items found</li>
192
- {% endfor %}
193
- ```
194
- - **with / endwith**: Scopes localized variables.
195
- ```html
196
- {% with user.profile.address as addr %}
197
- <p>{{ addr.city }}, {{ addr.zip }}</p>
198
- {% endwith %}
199
- ```
200
- - **cycle**: Cycle through values sequentially.
201
- ```html
202
- {% for row in rows %}
203
- <tr class="{% cycle 'row-odd' 'row-even' %}">...</tr>
204
- {% endfor %}
205
- ```
206
- - **autoescape on/off**: Control auto-escaping block behavior.
207
- - **verbatim / endverbatim**: Treat raw text inside literally.
208
- - **comment / endcomment**: Block comment ignored during parse.
209
-
210
- ### Security Tags
211
- - **csrf_token**: Automatically outputs a hidden input carrying the CSRF token from the context variable `csrf_token`.
212
- ```html
213
- <form method="post">
214
- {% csrf_token %}
215
- ...
216
- </form>
217
- ```
218
- - **csp_nonce_attr**: Dynamically outputs `nonce="value"` if the variable `csp_nonce` is in the context.
219
- ```html
220
- <script {% csp_nonce_attr %} src="app.js"></script>
221
- ```
222
-
223
- ### Inheritance & Block Rendering
224
- Inherit structure from parent templates.
225
- - `base.html`:
226
- ```html
227
- <html>
228
- <body>
229
- {% block content %}Default Content{% endblock %}
230
- </body>
231
- </html>
232
- ```
233
- - `child.html`:
234
- ```html
235
- {% extends "base.html" %}
236
- {% block content %}
237
- <h1>Child Content</h1>
238
- {{ block.super }} <!-- Renders parent's default content -->
239
- {% endblock %}
240
- ```
241
-
242
- #### Rendering a Block-Level Partial (Django 5.1+ / HTMX Style)
243
- You can compile a template and choose to render *only a specific block* (useful for AJAX or HTMX requests):
244
- ```javascript
245
- const compiled = compile(childTemplateStr, { views: './templates' });
246
- const partialHtml = compiled.renderBlock('content', context);
247
- console.log(partialHtml); // Output: "<h1>Child Content</h1> Default Content"
248
- ```
249
-
250
- ---
251
-
252
- ## 🔧 Extensibility API
253
-
254
- ### Register a Custom Filter
255
-
256
- **CommonJS:**
257
- ```javascript
258
- const { registerFilter } = require('miki-template');
259
-
260
- registerFilter('reverse', (val) => {
261
- return String(val).split('').reverse().join('');
262
- });
263
- ```
264
-
265
- **ESM:**
266
- ```javascript
267
- import { registerFilter } from 'miki-template';
268
-
269
- registerFilter('reverse', (val) => {
270
- return String(val).split('').reverse().join('');
271
- });
272
- ```
273
-
274
- ### Register a Custom Tag
275
-
276
- **CommonJS:**
277
- ```javascript
278
- const { registerTag } = require('miki-template');
279
-
280
- // Custom tag parser returning an AST Node
281
- registerTag('hello', (tagContent, parser) => {
282
- return {
283
- render: (context) => 'Hello World!'
284
- };
285
- });
286
- ```
287
-
288
- **ESM:**
289
- ```javascript
290
- import { registerTag } from 'miki-template';
291
-
292
- registerTag('hello', (tagContent, parser) => {
293
- return {
294
- render: (context) => 'Hello World!'
295
- };
296
- });
297
- ```
298
-
299
- ---
300
-
301
- ## 🔒 Security
302
- - **HTML Auto-escaping**: Enabled by default to guard against Cross-Site Scripting (XSS).
303
- - **SafeString Wrapper**: Explicitly bypass escaping using the `|safe` filter or marking variables via `markSafe(val)`.
304
- - **No eval() Execution**: Parser evaluates logic statements securely using standard tokens mapping.
1
+ # miki-template
2
+ ![miki-template banner](assets/banner.png)
3
+ ![npm version](https://img.shields.io/npm/v/miki-template.svg) ![CI](https://github.com/your-repo/miki-template/workflows/ci.yml/badge.svg)
4
+ A robust, production-ready template engine that brings **Django's template language** features and syntax to Node.js and Express, fully compliant with modern JavaScript (ES6+), CommonJS, and **ESM** (`import`) support.
5
+
6
+ ---
7
+
8
+ ## 🚀 Features
9
+
10
+ - **One-line Express integration**: `miki.setupExpress(app, { extension: 'html', views: dir })` — wires the engine, views directory, and a `res.render` shim that makes `res.render('home#card', ...)` Just Work for HTMX-style partial responses. **No boilerplate, no extra middleware.**
11
+ - **Partial responses out of the box**: `{% partialdef %}` blocks can be rendered by name with `res.render('view#partial', ...)`, `miki.expressPartialRenderer()` middleware (`res.renderPartial(...)`), or `renderPartialFromSource(...)`.
12
+ - **Full Syntax Parity**: Supports variables, dotted lookups, filters (`|`), and block tags (`{% %}`).
13
+ - **Template Inheritance**: Multi-level inheritance with `extends`, block overrides, and `{{ block.super }}` support.
14
+ - **Built-in libraries**: `humanize`, `cache`, and `lorem` ship pre-activated. `{% lorem 5 p %}` works without `{% load lorem %}`.
15
+ - **ESM & CommonJS**: Works seamlessly with both `import` and `require` syntax.
16
+ - **Security by Default**: Auto-escaping enabled by default with a `SafeString` wrapper.
17
+ - **CSRF & CSP Support**: Native tags for `{% csrf_token %}` and `{% csp_nonce_attr %}` to keep apps secure out-of-the-box.
18
+ - **Block Partials**: Render a single block from a compiled template via `compiled.renderBlock('block_name')`.
19
+ - **Async Rendering**: Support for async filters/tags with `asyncRender()`.
20
+ - **Extensible API**: Easy registration for custom tags and filters.
21
+ - **No Unsafe Code Execution**: Evaluates expressions securely without using `eval()`.
22
+ - **Editor Support**: First-class syntax highlighting and snippets for VS Code, Sublime Text, Atom, and TextMate-compatible editors.
23
+
24
+ ### VS Code
25
+
26
+ #### Option A: Install the official extension (recommended)
27
+
28
+ Search for **miki-template** in the VS Code Marketplace, or install from the command line:
29
+
30
+ ```bash
31
+ code --install-extension miki-template
32
+ ```
33
+
34
+ #### Option B: Manual install from this repo
35
+
36
+ 1. Copy the `syntaxes/` and `snippets/` folders from this repo.
37
+ 2. In VS Code, run **Preferences: Configure File Associations** and associate `*.miki` with `miki-template`.
38
+ 3. Or add a workspace-level `.vscode/settings.json`:
39
+
40
+ ```json
41
+ {
42
+ "files.associations": {
43
+ "*.miki": "miki-template"
44
+ }
45
+ }
46
+ ```
47
+
48
+ ### Sublime Text / Atom / TextMate
49
+
50
+ Drop the `syntaxes/miki-template.tmLanguage.json` file into your editor’s `Packages/User/` folder and associate it with the `.miki` extension.
51
+
52
+ ---
53
+
54
+ ## 📚 Documentation
55
+
56
+ - [Installation](docs/installation.md)
57
+ - [Usage Guide](docs/usage.md)
58
+ - [Tags Reference](docs/tags.md)
59
+ - [Filters Reference](docs/filters.md)
60
+ - [Security](docs/security.md)
61
+ - [API Reference](docs/api.md)
62
+ - [Partial Definitions](docs/partialdef.md)
63
+ - [Contributing](docs/advanced_usage.md)
64
+
65
+ ---
66
+
67
+ ## 📦 Installation
68
+
69
+ ```bash
70
+ npm install miki-template
71
+ ```
72
+
73
+ ---
74
+
75
+ ## 🛠️ Quick Start
76
+
77
+ ### CommonJS (require)
78
+
79
+ ```javascript
80
+ const { render, compile, __express, SafeString, markSafe } = require('miki-template');
81
+
82
+ const template = 'Hello {{ user.name|title }}! Roles: {{ user.roles|join:", " }}';
83
+ const context = {
84
+ user: {
85
+ name: 'miki coder',
86
+ roles: ['admin', 'developer']
87
+ }
88
+ };
89
+
90
+ const result = render(template, context);
91
+ console.log(result); // Output: "Hello Miki Coder! Roles: admin, developer"
92
+ ```
93
+
94
+ ### ES Modules (import)
95
+
96
+ ```javascript
97
+ // Named imports
98
+ import { render, compile, __express, SafeString, markSafe } from 'miki-template';
99
+
100
+ // Or default import (gets all exports)
101
+ import miki from 'miki-template';
102
+ const { render: mikiRender } = miki;
103
+
104
+ const template = 'Hello {{ user.name|title }}!';
105
+ const result = render(template, { user: { name: 'world' } });
106
+ console.log(result); // Output: "Hello World!"
107
+ ```
108
+
109
+ > **Note:** For ESM in Node.js, either name your files `.mjs` or add `"type": "module"` to your `package.json`.
110
+
111
+ ### Express Integration
112
+
113
+ **The recommended, one-line setup** — wires the view engine, views directory, and partial responses in a single call:
114
+
115
+ ```javascript
116
+ const express = require('express');
117
+ const miki = require('miki-template');
118
+
119
+ const app = express();
120
+ miki.setupExpress(app, { extension: 'html', views: './views' });
121
+
122
+ // Full page
123
+ app.get('/', (req, res) => res.render('home', { user: req.user }));
124
+
125
+ // HTMX / partial response — just append `#partialName` to the view name
126
+ app.get('/partials/:name', (req, res) =>
127
+ res.render(`home#${req.params.name}`, { user: req.user })
128
+ );
129
+
130
+ app.listen(3000);
131
+ ```
132
+
133
+ > `setupExpress` calls `app.engine()`, `app.set('views')`, and `app.set('view engine')` for you, and patches `res.render` so `view#partial` is dispatched to the partial renderer (not the file system). It works equally well for `.miki` files — just pass `extension: 'miki'`.
134
+
135
+ **The classic, fully manual setup still works** if you prefer it:
136
+
137
+ ```javascript
138
+ const express = require('express');
139
+ const { __express } = require('miki-template');
140
+
141
+ const app = express();
142
+ app.engine('html', __express);
143
+ app.set('view engine', 'html');
144
+ app.set('views', './views');
145
+ ```
146
+
147
+ **ESM:**
148
+
149
+ ```javascript
150
+ import express from 'express';
151
+ import miki from 'miki-template';
152
+
153
+ const app = express();
154
+ miki.setupExpress(app, { extension: 'html', views: './views' });
155
+ ```
156
+
157
+ **Async Express 5+:**
158
+ ```javascript
159
+ miki.setupExpress(app, { extension: 'html', views: './views', async: true });
160
+ ```
161
+
162
+ **Or, if you only want partial responses** without changing your engine registration, add the middleware:
163
+
164
+ ```javascript
165
+ app.use(miki.expressPartialRenderer());
166
+
167
+ app.get('/card', (req, res) => res.renderPartial('home#card', { user: req.user }));
168
+ ```
169
+
170
+ ---
171
+
172
+ ## 📖 Template Syntax & Parity
173
+
174
+ ### Variables & Dotted Lookups
175
+ Resolve properties dynamically on nested objects or arrays. If the resolved value is a callable/function, it is automatically executed with zero arguments.
176
+ ```html
177
+ {{ user.profile.name }}
178
+ {{ items.0 }} <!-- Array indexing -->
179
+ {{ user.getFullName }} <!-- Function resolution -->
180
+ ```
181
+
182
+ ### Built-in Filters
183
+ Apply filters using pipes (`|`). Arguments are passed after a colon (`:`).
184
+ - **Text**: `upper`, `lower`, `title`, `capfirst`, `slugify`, `wordcount`, `striptags`, `linebreaks`, `linebreaksbr`, `truncatewords:N`, `truncatechars:N`.
185
+ - **HTML**: `safe`, `escape`.
186
+ - **List**: `length`, `join:","`, `slice:"start:end"`, `dictsort:"key"`, `dictsortreversed:"key"`.
187
+ - **Default**: `default:"fallback"`, `default_if_none:"fallback"`.
188
+ - **Date/Time**: `date:"Y-m-d"`, `time:"H:i"`, `timesince`, `timeuntil`.
189
+ - **Numeric**: `add:5`, `divisibleby:2`, `floatformat:2`.
190
+ - **Misc**: `yesno:"yes,no,maybe"`, `pluralize:"suffix"`, `filesizeformat`.
191
+
192
+ ### Built-in Control Tags
193
+ - **if / elif / else / endif**: Supports conditional expressions with operators: `==`, `!=`, `<`, `<=`, `>`, `>=`, `in`, `not in`, `and`, `or`, `not`.
194
+ ```html
195
+ {% if user.role == 'admin' or user.is_staff %}
196
+ <p>Access Granted</p>
197
+ {% elif user.age >= 18 %}
198
+ <p>Standard Access</p>
199
+ {% else %}
200
+ <p>Access Denied</p>
201
+ {% endif %}
202
+ ```
203
+ - **for / empty / endfor**: Loop over arrays and objects. Injects `forloop` meta tracking.
204
+ ```html
205
+ {% for item in items %}
206
+ <li>{{ forloop.counter }}: {{ item }}</li>
207
+ {% empty %}
208
+ <li>No items found</li>
209
+ {% endfor %}
210
+ ```
211
+ - **with / endwith**: Scopes localized variables.
212
+ ```html
213
+ {% with user.profile.address as addr %}
214
+ <p>{{ addr.city }}, {{ addr.zip }}</p>
215
+ {% endwith %}
216
+ ```
217
+ - **cycle**: Cycle through values sequentially.
218
+ ```html
219
+ {% for row in rows %}
220
+ <tr class="{% cycle 'row-odd' 'row-even' %}">...</tr>
221
+ {% endfor %}
222
+ ```
223
+ - **autoescape on/off**: Control auto-escaping block behavior.
224
+ - **verbatim / endverbatim**: Treat raw text inside literally.
225
+ - **comment / endcomment**: Block comment ignored during parse.
226
+
227
+ ### Security Tags
228
+ - **csrf_token**: Automatically outputs a hidden input carrying the CSRF token from the context variable `csrf_token`.
229
+ ```html
230
+ <form method="post">
231
+ {% csrf_token %}
232
+ ...
233
+ </form>
234
+ ```
235
+ - **csp_nonce_attr**: Dynamically outputs `nonce="value"` if the variable `csp_nonce` is in the context.
236
+ ```html
237
+ <script {% csp_nonce_attr %} src="app.js"></script>
238
+ ```
239
+
240
+ ### Inheritance & Block Rendering
241
+ Inherit structure from parent templates.
242
+ - `base.html`:
243
+ ```html
244
+ <html>
245
+ <body>
246
+ {% block content %}Default Content{% endblock %}
247
+ </body>
248
+ </html>
249
+ ```
250
+ - `child.html`:
251
+ ```html
252
+ {% extends "base.html" %}
253
+ {% block content %}
254
+ <h1>Child Content</h1>
255
+ {{ block.super }} <!-- Renders parent's default content -->
256
+ {% endblock %}
257
+ ```
258
+
259
+ #### Rendering a Block-Level Partial (Django 5.1+ / HTMX Style)
260
+ You can compile a template and choose to render *only a specific block* (useful for AJAX or HTMX requests):
261
+ ```javascript
262
+ const compiled = compile(childTemplateStr, { views: './templates' });
263
+ const partialHtml = compiled.renderBlock('content', context);
264
+ console.log(partialHtml); // Output: "<h1>Child Content</h1> Default Content"
265
+ ```
266
+
267
+ ---
268
+
269
+ ## 🔧 Extensibility API
270
+
271
+ ### Register a Custom Filter
272
+
273
+ **CommonJS:**
274
+ ```javascript
275
+ const { registerFilter } = require('miki-template');
276
+
277
+ registerFilter('reverse', (val) => {
278
+ return String(val).split('').reverse().join('');
279
+ });
280
+ ```
281
+
282
+ **ESM:**
283
+ ```javascript
284
+ import { registerFilter } from 'miki-template';
285
+
286
+ registerFilter('reverse', (val) => {
287
+ return String(val).split('').reverse().join('');
288
+ });
289
+ ```
290
+
291
+ ### Register a Custom Tag
292
+
293
+ **CommonJS:**
294
+ ```javascript
295
+ const { registerTag } = require('miki-template');
296
+
297
+ // Custom tag parser returning an AST Node
298
+ registerTag('hello', (tagContent, parser) => {
299
+ return {
300
+ render: (context) => 'Hello World!'
301
+ };
302
+ });
303
+ ```
304
+
305
+ **ESM:**
306
+ ```javascript
307
+ import { registerTag } from 'miki-template';
308
+
309
+ registerTag('hello', (tagContent, parser) => {
310
+ return {
311
+ render: (context) => 'Hello World!'
312
+ };
313
+ });
314
+ ```
315
+
316
+ ---
317
+
318
+ ## 🚀 Releasing
319
+
320
+ Releases are fully automatic. Pick the bump you want and run one command:
321
+
322
+ ```bash
323
+ npm run release:patch # 1.3.3 → 1.3.4
324
+ npm run release:minor # 1.3.3 → 1.4.0
325
+ npm run release:major # 1.3.3 → 2.0.0
326
+ ```
327
+
328
+ That bumps `package.json`, creates a `chore(release): vX.Y.Z` commit, and pushes to `main`. The CI then:
329
+
330
+ 1. Runs lint + test + the strict benchmark (must pass)
331
+ 2. Creates an annotated `vX.Y.Z` git tag and pushes it
332
+ 3. Creates a GitHub Release with notes from `.github/release-notes/vX.Y.Z.md` (optional)
333
+ 4. Publishes to npm
334
+
335
+ Nothing else to click. The whole pipeline is in `.github/workflows/release.yml`.
336
+
337
+ ---
338
+
339
+ ## 🔒 Security
340
+ - **HTML Auto-escaping**: Enabled by default to guard against Cross-Site Scripting (XSS).
341
+ - **SafeString Wrapper**: Explicitly bypass escaping using the `|safe` filter or marking variables via `markSafe(val)`.
342
+ - **No eval() Execution**: Parser evaluates logic statements securely using standard tokens mapping.