miki-template 1.2.0

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 (73) hide show
  1. package/.github/workflows/ci.yml +54 -0
  2. package/AGENT.md +71 -0
  3. package/API_REFERENCE.md +314 -0
  4. package/CHANGELOG.md +97 -0
  5. package/CODE_OF_CONDUCT.md +14 -0
  6. package/CONTRIBUTING.md +27 -0
  7. package/README.md +304 -0
  8. package/ROADMAP.md +40 -0
  9. package/benchmarks/report.json +17 -0
  10. package/benchmarks/run.js +49 -0
  11. package/benchmarks/templates/large.dtpl +7 -0
  12. package/benchmarks/templates/medium.dtpl +3 -0
  13. package/benchmarks/templates/small.dtpl +7 -0
  14. package/context/component.md +109 -0
  15. package/context/prd.md +131 -0
  16. package/context/project-structure.md +33 -0
  17. package/docs/README.md +18 -0
  18. package/docs/advanced_usage.md +71 -0
  19. package/docs/api.md +102 -0
  20. package/docs/filters.md +540 -0
  21. package/docs/installation.md +106 -0
  22. package/docs/overview.md +57 -0
  23. package/docs/partialdef.md +41 -0
  24. package/docs/security.md +27 -0
  25. package/docs/tags.md +610 -0
  26. package/docs/usage.md +599 -0
  27. package/eslint.config.mjs +34 -0
  28. package/miki-template-1.2.0.vsix +0 -0
  29. package/miki-template-extension/LICENSE +21 -0
  30. package/miki-template-extension/README.md +82 -0
  31. package/miki-template-extension/icon.png +0 -0
  32. package/miki-template-extension/icon.svg +10 -0
  33. package/miki-template-extension/package.json +46 -0
  34. package/miki-template-extension/snippets/miki-template.json +177 -0
  35. package/miki-template-extension/syntaxes/language-configuration.json +26 -0
  36. package/miki-template-extension/syntaxes/miki-template.tmLanguage.json +146 -0
  37. package/package.json +31 -0
  38. package/snippets/miki-template.json +177 -0
  39. package/src/asyncRender.js +21 -0
  40. package/src/cache.js +41 -0
  41. package/src/context.js +122 -0
  42. package/src/context_processors.js +41 -0
  43. package/src/esm.mjs +72 -0
  44. package/src/filters.js +527 -0
  45. package/src/i18n.js +171 -0
  46. package/src/index.js +454 -0
  47. package/src/lexer.js +92 -0
  48. package/src/libraries.js +240 -0
  49. package/src/parser.js +250 -0
  50. package/src/security.js +51 -0
  51. package/src/tags/control.js +591 -0
  52. package/src/tags/helpers.js +27 -0
  53. package/src/tags/i18n.js +230 -0
  54. package/src/tags/inheritance.js +216 -0
  55. package/src/tags/registry.js +18 -0
  56. package/src/tags/util.js +322 -0
  57. package/src/types.d.ts +107 -0
  58. package/syntaxes/language-configuration.json +26 -0
  59. package/syntaxes/miki-template.tmLanguage.json +146 -0
  60. package/tests/asyncRender.test.js +17 -0
  61. package/tests/base.html +6 -0
  62. package/tests/child.html +3 -0
  63. package/tests/context_processors.test.js +13 -0
  64. package/tests/esm.test.mjs +26 -0
  65. package/tests/filters.test.js +99 -0
  66. package/tests/include_security.test.js +9 -0
  67. package/tests/lexer.test.js +45 -0
  68. package/tests/parser.test.js +55 -0
  69. package/tests/partial.html +1 -0
  70. package/tests/partialdef.test.js +40 -0
  71. package/tests/production_checks.js +57 -0
  72. package/tests/security.test.js +28 -0
  73. package/tests/tags.test.js +203 -0
@@ -0,0 +1,540 @@
1
+ # Filters Reference
2
+
3
+ `miki-template` includes a comprehensive set of built-in filters matching Django's filter library. Filters are applied to variables using the pipe `|` character, and can be chained: `{{ value|filter1|filter2:"arg" }}`.
4
+
5
+ ---
6
+
7
+ ## Filter Chaining
8
+
9
+ Filters can be chained — the output of each filter becomes the input of the next:
10
+
11
+ ```html
12
+ {{ name|lower|capfirst }}
13
+ <!-- "miki" → "miki" → "Miki" -->
14
+
15
+ {{ user.bio|striptags|truncatewords:20 }}
16
+ <!-- Strip HTML tags, then truncate to 20 words -->
17
+ ```
18
+
19
+ ---
20
+
21
+ ## Text Filters
22
+
23
+ ### `upper`
24
+ Converts a string to UPPERCASE.
25
+ ```html
26
+ {{ "hello"|upper }} → "HELLO"
27
+ {{ name|upper }} → "JOHN"
28
+ ```
29
+
30
+ ### `lower`
31
+ Converts a string to lowercase.
32
+ ```html
33
+ {{ "HELLO"|lower }} → "hello"
34
+ ```
35
+
36
+ ### `title`
37
+ Converts to Title Case (first letter of each word capitalized).
38
+ ```html
39
+ {{ "hello world"|title }} → "Hello World"
40
+ ```
41
+
42
+ ### `capfirst`
43
+ Capitalizes only the first character.
44
+ ```html
45
+ {{ "hello"|capfirst }} → "Hello"
46
+ {{ "h"|capfirst }} → "H"
47
+ ```
48
+
49
+ ### `slugify`
50
+ Converts to a URL-safe slug by lowercasing, removing accents, and replacing spaces with hyphens.
51
+ ```html
52
+ {{ "Hello World!"|slugify }} → "hello-world"
53
+ {{ "Café con Leche"|slugify }} → "cafe-con-leche"
54
+ {{ " Multiple Spaces "|slugify }} → "multiple-spaces"
55
+ ```
56
+
57
+ ### `wordcount`
58
+ Returns the number of words (whitespace-separated tokens).
59
+ ```html
60
+ {{ "one two three"|wordcount }} → 3
61
+ {{ " "|wordcount }} → 0
62
+ ```
63
+
64
+ ### `striptags`
65
+ Removes all HTML/XML tags from the string.
66
+ ```html
67
+ {{ "<p>Hello <b>World</b></p>"|striptags }} → "Hello World"
68
+ ```
69
+
70
+ ### `truncatewords:N`
71
+ Truncates the string to approximately N words, appending `...`.
72
+ ```html
73
+ {{ "one two three four five"|truncatewords:3 }} → "one two three ..."
74
+ ```
75
+
76
+ ### `truncatechars:N`
77
+ Truncates to N characters (including the `...` suffix if truncation occurs).
78
+ ```html
79
+ {{ "Hello World"|truncatechars:8 }} → "Hello..."
80
+ {{ "Hi"|truncatechars:5 }} → "Hi" (no truncation needed)
81
+ ```
82
+
83
+ ### `linebreaks`
84
+ Converts newlines into paragraphs (`<p>`) and standalone line breaks into `<br>`.
85
+ ```html
86
+ {{ "Line one\n\nLine two\nLine three"|linebreaks }}
87
+ <!-- Output: <p>Line one</p><p>Line two<br>Line three</p> -->
88
+ ```
89
+
90
+ ### `linebreaksbr`
91
+ Converts all newlines to `<br>` tags. Does **not** wrap in `<p>` tags.
92
+ ```html
93
+ {{ "Line one\nLine two"|linebreaksbr }}
94
+ <!-- Output: Line one<br>Line two -->
95
+ ```
96
+
97
+ ### `cut:value`
98
+ Removes all occurrences of the specified value from the string.
99
+ ```html
100
+ {{ "Hello World"|cut:" " }} → "HelloWorld"
101
+ {{ "a|b|c"|cut:"|" }} → "abc"
102
+ ```
103
+
104
+ ### `addslashes`
105
+ Adds backslashes before single quotes, double quotes, and backslashes (for use in JavaScript strings).
106
+ ```html
107
+ {{ 'He said "Hello"|addslashes }} → 'He said \"Hello\"'
108
+ ```
109
+
110
+ ### `removetags:tag1,tag2,...`
111
+ Removes the named HTML tags (and their contents).
112
+ ```html
113
+ {{ "<p>Hello</p><b>World</b>"|removetags:"p,b" }} → "HelloWorld"
114
+ ```
115
+
116
+ ---
117
+
118
+ ## HTML / Security Filters
119
+
120
+ ### `safe`
121
+ Marks the value as **HTML-safe**, bypassing auto-escaping. Use with caution — never pass unsanitized user input through `|safe`.
122
+ ```html
123
+ {{ "<b>Bold</b>"|safe }} → <b>Bold</b> (NOT &lt;b&gt;Bold&lt;/b&gt;)
124
+ ```
125
+
126
+ ### `escape`
127
+ Explicitly escapes HTML entities. Useful when `autoescape` is `off`.
128
+ ```html
129
+ {% autoescape off %}
130
+ {{ user_input|escape }} → &lt;script&gt;alert()&lt;/script&gt;
131
+ {% endautoescape %}
132
+ ```
133
+
134
+ ### `escapejs`
135
+ Escapes characters for safe use inside JavaScript string literals.
136
+ ```html
137
+ {{ 'Test "quotes" and \backs'|escapejs }}
138
+ ```
139
+
140
+ ---
141
+
142
+ ## URL / Encoding Filters
143
+
144
+ ### `urlencode`
145
+ URL-encodes the string. By default, uses query-string encoding (spaces → `+`).
146
+ ```html
147
+ {{ "Hello World"|urlencode }} → "Hello+World"
148
+ {{ "a/b c"|urlencode }} → "a%2Fb+c"
149
+ ```
150
+
151
+ With `path` modifier, preserves slashes:
152
+ ```html
153
+ {{ "images/logo.png"|urlencode }} → "images%2Flogo.png" (standard encoding)
154
+ ```
155
+
156
+ ### `escapeurl` (alias: `urlize`)
157
+ Percent-encodes all special characters in a URL.
158
+ ```html
159
+ {{ "https://example.com?q=hello world"|escapeurl }}
160
+ → "https%3A%2F%2Fexample.com%3Fq%3Dhello%20world"
161
+ ```
162
+
163
+ ---
164
+
165
+ ## String Formatting Filters
166
+
167
+ ### `stringformat:"fmt"`
168
+ Formats the value using Python-style format strings (`%s`, `%d`, etc.).
169
+ ```html
170
+ {{ 42|stringformat:"d" }} → "42"
171
+ {{ 3.14159|stringformat:"2f" }} → "3.14"
172
+ {{ "x"|stringformat:"s" }} → "x"
173
+ ```
174
+
175
+ ### `center:N`
176
+ Centers the string in a field of width N (padding with spaces).
177
+ ```html
178
+ {{ "Hi"|center:10 }} → " Hi "
179
+ ```
180
+
181
+ ### `ljust:N` / `rjust:N`
182
+ Left/right-justifies the string in a field of width N.
183
+ ```html
184
+ {{ "Hi"|ljust:10 }} → "Hi "
185
+ {{ "Hi"|rjust:10 }} → " Hi"
186
+ ```
187
+
188
+ ---
189
+
190
+ ## List / Sequence Filters
191
+
192
+ ### `length`
193
+ Returns the length of an array, object, string, or any object with a `.length` property.
194
+ ```html
195
+ {{ items|length }} → 5 (for arrays)
196
+ {{ "hello"|length }} → 5
197
+ {{ object|length }} → number of keys
198
+ {{ undefined|length }} → 0
199
+ ```
200
+
201
+ ### `length_is:N`
202
+ Returns `true` if the value's length equals N, otherwise `false`.
203
+ ```html
204
+ {{ "hello"|length_is:5 }} → true
205
+ {{ items|length_is:3 }} → true/false
206
+ ```
207
+
208
+ ### `join:separator`
209
+ Joins an array with the specified separator.
210
+ ```html
211
+ {{ tags|join:", " }} → "js, python, rust"
212
+ {{ items|join:" + " }} → "a + b + c"
213
+ ```
214
+
215
+ ### `slice:"start:end"`
216
+ Slices an array or string like Python (`[start:end]`). Supports negative indices.
217
+ ```html
218
+ {{ items|slice:"1:3" }} → items[1], items[2]
219
+ {{ items|slice:":2" }} → first 2 items
220
+ {{ items|slice:"1:" }} → items from index 1 onwards
221
+ {{ "hello"|slice:"1:4" }} → "ell"
222
+ ```
223
+
224
+ ### `first`
225
+ Returns the first element of a sequence.
226
+ ```html
227
+ {{ items|first }} → first item
228
+ ```
229
+
230
+ ### `last`
231
+ Returns the last element of a sequence.
232
+ ```html
233
+ {{ items|last }} → last item
234
+ ```
235
+
236
+ ### `dictsort:"key"`
237
+ Sorts an array of objects by the specified attribute (ascending).
238
+ ```html
239
+ {% for item in items|dictsort:"name" %}
240
+ {{ item.name }}
241
+ {% endfor %}
242
+ ```
243
+
244
+ ### `dictsortreversed:"key"`
245
+ Sorts an array of objects by the specified attribute (descending).
246
+ ```html
247
+ {% for item in items|dictsortreversed:"price" %}
248
+ {{ item.name }} - ${{ item.price }}
249
+ {% endfor %}
250
+ ```
251
+
252
+ ---
253
+
254
+ ## Default / Fallback Filters
255
+
256
+ ### `default:fallback`
257
+ Uses the fallback value if the original value is falsy (`null`, `undefined`, or empty string `""`).
258
+ ```html
259
+ {{ user.name|default:"Anonymous" }} → "Anonymous" if name is missing
260
+ {{ ""|default:"empty" }} → "empty"
261
+ {{ 0|default:"zero" }} → "zero"
262
+ ```
263
+
264
+ ### `default_if_none:fallback`
265
+ Uses the fallback value only if the original value is `null` or `undefined` (not empty string).
266
+ ```html
267
+ {{ value|default_if_none:"N/A" }} → "N/A" if value === null or value === undefined
268
+ {{ ""|default_if_none:"N/A" }} → "" (empty string is not none)
269
+ ```
270
+
271
+ ### `firstof`
272
+ Returns the first truthy value from the arguments (filter form).
273
+ ```html
274
+ {{ ""|firstof:user.name:guest:default }} → user.name or "guest" or "default"
275
+ ```
276
+
277
+ ---
278
+
279
+ ## Date / Time Filters
280
+
281
+ All date filters accept `Date` objects, ISO strings, or timestamps.
282
+
283
+ ### `date:"format"`
284
+ Formats a date using Django-style format codes:
285
+ | Code | Meaning | Example |
286
+ |------|---------|---------|
287
+ | `d` | Day with leading zero | `01–31` |
288
+ | `j` | Day without leading zero | `1–31` |
289
+ | `m` | Month with leading zero | `01–12` |
290
+ | `n` | Month without leading zero | `1–12` |
291
+ | `Y` | Full year | `2026` |
292
+ | `y` | 2-digit year | `26` |
293
+ | `H` | 24-hour with leading zero | `00–23` |
294
+ | `i` | Minutes | `00–59` |
295
+ | `s` | Seconds | `00–59` |
296
+ | `F` | Full month name | `January` |
297
+ | `M` | Short month name | `Jan` |
298
+
299
+ ```html
300
+ {{ post.published|date:"Y-m-d" }} → "2026-08-31"
301
+ {{ post.published|date:"F j, Y" }} → "August 31, 2026"
302
+ {{ post.published|date:"H:i" }} → "14:30"
303
+ ```
304
+
305
+ ### `time:"format"`
306
+ Same as `date` but only outputs time portion.
307
+
308
+ ### `strftime:"format"`
309
+ Uses `date-fns` format patterns (PPpp, yyyy-MM-dd, etc.) for full locale support.
310
+ ```html
311
+ {{ now|strftime:"PPpp" }} → "Aug 31, 2026 at 2:30 PM"
312
+ {{ now|strftime:"yyyy-MM-dd" }} → "2026-08-31"
313
+ ```
314
+
315
+ ### `date_format:"format"`
316
+ Alias for `strftime` with additional custom patterns.
317
+
318
+ ### `timesince`
319
+ Returns a human-readable "time ago" string (e.g., "4 minutes", "2 hours", "3 days").
320
+ ```html
321
+ {{ post.created|timesince }} → "2 hours"
322
+ {{ post.created|timesince:other_date }} → "3 days" (relative to other_date)
323
+ ```
324
+
325
+ ### `timeuntil`
326
+ Returns a human-readable "time until" string.
327
+ ```html
328
+ {{ event.date|timeuntil }} → "5 days"
329
+ ```
330
+
331
+ ---
332
+
333
+ ## Numeric Filters
334
+
335
+ ### `add:N`
336
+ Adds N to the value. Also works for string concatenation.
337
+ ```html
338
+ {{ count|add:5 }} → count + 5
339
+ {{ items|add:other }} → number or concatenated
340
+ ```
341
+
342
+ ### `divisibleby:N`
343
+ Returns `true` if the value is divisible by N.
344
+ ```html
345
+ {{ 10|divisibleby:5 }} → true
346
+ {{ 7|divisibleby:2 }} → false
347
+ ```
348
+
349
+ ### `floatformat:N`
350
+ Formats a number to N decimal places. Django default is 1 decimal.
351
+
352
+ | arg | behavior |
353
+ |-----|----------|
354
+ | (none) | 1 decimal (`3.4`) |
355
+ | `0` | 0 decimals (`3`) |
356
+ | `1` | 1 decimal (`3.4`) |
357
+ | `2` | 2 decimals (`3.40`) |
358
+ | `-1` | all decimals, trimmed |
359
+
360
+ ```html
361
+ {{ 3.14159|floatformat }} → "3.1"
362
+ {{ 3.14159|floatformat:2 }} → "3.14"
363
+ {{ 3.000|floatformat:0 }} → "3"
364
+ ```
365
+
366
+ ---
367
+
368
+ ## Miscellaneous Filters
369
+
370
+ ### `yesno:"yes,no,maybe"`
371
+ Maps truthy / falsy / null values to custom strings.
372
+ ```html
373
+ {{ flag|yesno:"yes,no,maybe" }} → "yes" if true, "no" if false, "maybe" if null
374
+ {{ active|yesno:"Active,Inactive" }} → "Active" or "Inactive"
375
+ ```
376
+
377
+ ### `pluralize:"s,plural"`
378
+ Returns the singular or plural suffix based on the value. Supports custom suffixes.
379
+ ```html
380
+ {{ items|length }} {{ items|pluralize }} item{{ items|pluralize }}
381
+ <!-- 1 item | 5 items -->
382
+
383
+ {{ count|pluralize:"y,ies" }} → "1 candy" | "2 candies"
384
+ ```
385
+
386
+ ### `filesizeformat`
387
+ Formats a byte count as a human-readable file size.
388
+ ```html
389
+ {{ 1024|filesizeformat }} → "1.0 KB"
390
+ {{ 1048576|filesizeformat }} → "1.0 MB"
391
+ {{ 0|filesizeformat }} → "0 bytes"
392
+ {{ 1536|filesizeformat }} → "1.5 KB"
393
+ ```
394
+
395
+ ---
396
+
397
+ ## Custom Filters
398
+
399
+ Register custom filters with `registerFilter`:
400
+
401
+ ```javascript
402
+ const { registerFilter } = require('miki-template');
403
+
404
+ // Simple filter
405
+ registerFilter('reverse', (val) => String(val).split('').reverse().join(''));
406
+
407
+ // Filter with argument
408
+ registerFilter('truncate', (val, length) => {
409
+ const str = String(val);
410
+ if (str.length <= length) return str;
411
+ return str.slice(0, length) + '...';
412
+ });
413
+
414
+ // Chaining works automatically:
415
+ // {{ name|reverse|truncate:5 }}
416
+ ```
417
+
418
+ ---
419
+
420
+ ## Filter Argument Types
421
+
422
+ Filters accept the following argument types:
423
+
424
+ | Syntax | Type | Example |
425
+ |--------|------|---------|
426
+ | Unquoted | Variable lookup | `{{ value|filter:count }}` |
427
+ | Double-quoted | String literal | `{{ value|filter:"hello" }}` |
428
+ | Single-quoted | String literal | `{{ value|filter:'world' }}` |
429
+ | Number | Integer literal | `{{ value|truncatewords:10 }}` |
430
+
431
+ ```html
432
+ {{ user.name|default:"Guest" }} <!-- String default -->
433
+ {{ items|slice:"1:3" }} <!-- Slice notation -->
434
+ {{ price|floatformat:2 }} <!-- Decimal places -->
435
+ ```
436
+
437
+ ---
438
+
439
+ ## i18n Filters
440
+
441
+ ### `trans:"fallback"`
442
+ Translate a string using the i18n registry. Falls back to the original value if no translation is found.
443
+
444
+ ```html
445
+ {{ "Hello, World!"|trans }}
446
+ {{ greeting|trans:"Hello, %s!" }}
447
+ ```
448
+
449
+ ---
450
+
451
+ ## Regroup Filter
452
+
453
+ ### `regroup:"key"`
454
+ Group an array of objects by a common attribute. Returns an array of `{ grouper, list }` objects.
455
+
456
+ ```html
457
+ {% for group in items|regroup:"category" %}
458
+ <h3>{{ group.grouper }}</h3>
459
+ {% for item in group.list %}
460
+ <p>{{ item.name }}</p>
461
+ {% endfor %}
462
+ {% endfor %}
463
+ ```
464
+
465
+ ---
466
+
467
+ ## String Formatting Filters
468
+
469
+ ### `stringformat:"fmt"`
470
+ Format a value using Python-style format strings (`%s`, `%d`, `%.2f`, `%x`, etc.).
471
+
472
+ ```html
473
+ {{ 42|stringformat:"d" }} → "42"
474
+ {{ 3.14159|stringformat:".2f" }} → "3.14"
475
+ {{ "hello"|stringformat:"s" }} → "hello"
476
+ ```
477
+
478
+ ---
479
+
480
+ ## URL / Encoding Filters
481
+
482
+ ### `urlencode`
483
+ URL-encode a string. Supports `query`, `path`, and `utf-8` modes.
484
+
485
+ ```html
486
+ {{ "Hello World"|urlencode }} → "Hello+World"
487
+ {{ "a/b c"|urlencode }} → "a%2Fb+c"
488
+ ```
489
+
490
+ ### `escapeuri`
491
+ Percent-encode a URI.
492
+
493
+ ```html
494
+ {{ "http://example.com/path"|escapeuri }}
495
+ ```
496
+
497
+ ---
498
+
499
+ ## Text Filters
500
+
501
+ ### `cut:value`
502
+ Remove all occurrences of a substring.
503
+
504
+ ```html
505
+ {{ "hello hello"|cut:" " }} → "hellohello"
506
+ ```
507
+
508
+ ### `addslashes`
509
+ Add backslashes before quotes and backslashes.
510
+
511
+ ```html
512
+ {{ 'He said "Hi"|addslashes }} → He said \"Hi\"
513
+ ```
514
+
515
+ ### `removetags:tag1,tag2,...`
516
+ Remove named HTML tags and their contents.
517
+
518
+ ```html
519
+ {{ "<p>Hello</p><b>World</b>"|removetags:"p,b" }} → "HelloWorld"
520
+ ```
521
+
522
+ ### `length_is:N`
523
+ Return `true` if the value's length equals N.
524
+
525
+ ```html
526
+ {{ "hello"|length_is:5 }} → true
527
+ ```
528
+
529
+ ---
530
+
531
+ ## Date / Time Filters
532
+
533
+ ### `strftime:"format"`
534
+ Format a Date using `date-fns` format strings. More powerful than the built-in `date` filter.
535
+
536
+ ```html
537
+ {{ now|strftime:"PPpp" }} → "Aug 31, 2026 at 10:24 PM"
538
+ {{ now|strftime:"yyyy-MM-dd" }} → "2026-08-31"
539
+ {{ now|strftime:"HH:mm:ss" }} → "22:24:56"
540
+ ```
@@ -0,0 +1,106 @@
1
+ # Installation
2
+
3
+ ## npm
4
+ ```bash
5
+ npm install miki-template
6
+ ```
7
+
8
+ ## Prerequisites
9
+ - **Node.js** >= 14 (ES6+ support)
10
+ - **npm** (or **yarn**) for package management
11
+
12
+ ## Optional dependencies
13
+ - **express** – for server‑side rendering integration (recommended).
14
+ - **eslint** – for linting your project (dev dependency).
15
+
16
+ ## Module System Support
17
+
18
+ `miki-template` supports both **CommonJS** (`require`) and **ESM** (`import`).
19
+
20
+ ### CommonJS (CJS)
21
+
22
+ ```js
23
+ const { render, compile, __express, SafeString, markSafe } = require('miki-template');
24
+ ```
25
+
26
+ ### ES Modules (ESM)
27
+
28
+ ```js
29
+ // Named imports
30
+ import { render, compile, __express, SafeString, markSafe } from 'miki-template';
31
+
32
+ // Default import (all exports)
33
+ import miki from 'miki-template';
34
+ const result = miki.render('Hello {{ name }}', { name: 'World' });
35
+ ```
36
+
37
+ > **Note:** When using ESM in Node.js, either name your files `.mjs` or set `"type": "module"` in your `package.json`.
38
+
39
+ ## Publishing to npm
40
+
41
+ This project is configured for automatic npm publishing via GitHub Actions. When you push to `main`, the CI workflow runs tests and, if they pass, publishes the package to npm.
42
+
43
+ ### Prerequisites for publishing
44
+
45
+ 1. You must have an npm account and be a maintainer of the `miki-template` package on npm.
46
+ 2. In your GitHub repository, go to **Settings → Secrets and variables → Actions**.
47
+ 3. Add a new repository secret named `NPM_TOKEN` with your npm automation token.
48
+ - Generate it at https://www.npmjs.com/settings/YOUR_USERNAME/tokens
49
+ - Select **Automation** as the token type.
50
+
51
+ The CI workflow will then automatically publish on every push to `main`.
52
+
53
+ ### Manual publishing
54
+
55
+ ```bash
56
+ npm version patch # or minor/major
57
+ npm publish --access public
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Quick Start
63
+
64
+ ### 1. Add the engine to your project
65
+
66
+ **CJS:**
67
+ ```js
68
+ const { render, compile } = require('miki-template');
69
+ ```
70
+
71
+ **ESM:**
72
+ ```js
73
+ import { render, compile } from 'miki-template';
74
+ ```
75
+
76
+ ### 2. (Express) Register the view engine
77
+
78
+ **CJS:**
79
+ ```js
80
+ const express = require('express');
81
+ const { __express: renderDtpl } = require('miki-template');
82
+ const app = express();
83
+ app.engine('html', renderDtpl);
84
+ app.set('view engine', 'html');
85
+ app.set('views', './views');
86
+ ```
87
+
88
+ **ESM:**
89
+ ```js
90
+ import express from 'express';
91
+ import { __express as renderDtpl } from 'miki-template';
92
+
93
+ const app = express();
94
+ app.engine('html', renderDtpl);
95
+ app.set('view engine', 'html');
96
+ app.set('views', './views');
97
+ ```
98
+
99
+ ### 3. Run the test suite to verify
100
+
101
+ ```bash
102
+ npm test
103
+ ```
104
+
105
+ ---
106
+
@@ -0,0 +1,57 @@
1
+ # Overview
2
+
3
+ Welcome to **miki-template** – a production‑ready, Django‑style template engine for Node.js and Express. This documentation mirrors the layout of popular open‑source libraries (e.g., Django, Jinja2, Mustache) and provides a clear, hierarchical guide for developers of all skill levels.
4
+
5
+ - **Project structure** – quick glance at the repository layout.
6
+ - **Feature list** – exhaustive rundown of supported tags, filters, security helpers, and the new `partialdef` system.
7
+ - **Getting started** – installation, basic rendering, and Express integration.
8
+ - **Advanced usage** – inheritance, block rendering, custom tags/filters, and performance tips.
9
+
10
+ ---
11
+
12
+ ## Repository layout
13
+
14
+ ```
15
+ 📦 miki-template/
16
+ ├─ 📁 src/ # Core engine source files
17
+ │ ├─ index.js # Entry point, compile/render APIs
18
+ │ ├─ lexer.js # Tokenizer
19
+ │ ├─ parser.js # AST builder
20
+ │ ├─ context.js # Scope & partial registry
21
+ │ └─ tags/ # Built‑in tag parsers (control, inheritance, util)
22
+ │ ├─ control.js # if, for, with, cycle, partialdef, …
23
+ │ ├─ inheritance.js # extends, block, super
24
+ │ └─ util.js # comment, verbatim, etc.
25
+ ├─ 📁 filters/ # Built‑in filter implementations
26
+ ├─ 📁 tests/ # Jest‑style test suite
27
+ ├─ 📁 docs/ # 📖 Documentation (this folder)
28
+ ├─ README.md # Project landing page (high‑level intro)
29
+ ├─ AGENT.md # Agent guardrails (internal)
30
+ ├─ ROADMAP.md # Future roadmap & milestones
31
+ └─ package.json # npm package definition
32
+ ```
33
+
34
+ Each module is deliberately **single‑responsibility** and fully typed via JSDoc comments, making it easy to extend.
35
+
36
+ ---
37
+
38
+ ## Where to start
39
+
40
+ - **Installation** – see `docs/installation.md`.
41
+ - **Basic rendering** – see `docs/usage.md`.
42
+ - **Tag reference** – see `docs/tags.md`.
43
+ - **Filter reference** – see `docs/filters.md`.
44
+ - **Partial definitions** – see `docs/partialdef.md`.
45
+ - **Security considerations** – see `docs/security.md`.
46
+
47
+ For API‑level details (e.g., `compile().renderPartial`) check `docs/api.md`.
48
+
49
+ ---
50
+
51
+ ## Contributing
52
+
53
+ We follow the standard open‑source workflow. Details are in `docs/contributing.md`.
54
+
55
+ ---
56
+
57
+ > **Tip**: All documentation files are located under `c:/Users/Coder Miki/Desktop/miki-template/docs/`.