miki-template 2.0.1 → 2.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/.github/workflows/ci.yml +13 -37
  2. package/.github/workflows/docs.yml +105 -0
  3. package/.github/workflows/release.yml +6 -0
  4. package/README.md +69 -14
  5. package/assets/logo.png +0 -0
  6. package/benchmarks/ejs-results.json +4 -4
  7. package/benchmarks/handlebars-results.json +6 -6
  8. package/benchmarks/miki-results.json +4 -4
  9. package/benchmarks/pug-results.json +4 -4
  10. package/benchmarks/stress.mjs +1 -1
  11. package/docs/api/async-render.md +85 -0
  12. package/docs/api/cache.md +87 -0
  13. package/docs/api/compile.md +128 -0
  14. package/docs/api/context-processors.md +77 -0
  15. package/docs/api/filters.md +217 -0
  16. package/docs/api/finder.md +94 -0
  17. package/docs/api/helpers.md +53 -0
  18. package/docs/api/i18n.md +157 -0
  19. package/docs/api/index.md +54 -0
  20. package/docs/api/libraries.md +207 -0
  21. package/docs/api/render-partial.md +81 -0
  22. package/docs/api/render.md +92 -0
  23. package/docs/api/security.md +145 -0
  24. package/docs/api/setup-express.md +76 -0
  25. package/docs/api/tags.md +134 -0
  26. package/docs/assets/banner.png +0 -0
  27. package/docs/assets/logo.png +0 -0
  28. package/docs/guide/advanced-usage.md +397 -0
  29. package/docs/guide/async-rendering.md +308 -0
  30. package/docs/guide/context-processors.md +257 -0
  31. package/docs/guide/custom-filters.md +311 -0
  32. package/docs/guide/custom-tags.md +271 -0
  33. package/docs/guide/filters.md +642 -0
  34. package/docs/guide/getting-started.md +102 -0
  35. package/docs/guide/installation.md +95 -0
  36. package/docs/guide/partial-templates.md +367 -0
  37. package/docs/guide/quick-start.md +222 -0
  38. package/docs/guide/security.md +345 -0
  39. package/docs/guide/tags.md +783 -0
  40. package/docs/guide/template-discovery.md +170 -0
  41. package/docs/guide/template-inheritance.md +273 -0
  42. package/docs/guide/what-is-miki-template.md +28 -0
  43. package/docs/guide/why-miki-template.md +75 -0
  44. package/docs/index.md +104 -0
  45. package/docs/integrations/elysia.md +78 -0
  46. package/docs/integrations/express.md +219 -0
  47. package/docs/integrations/fastify.md +77 -0
  48. package/docs/integrations/hono.md +78 -0
  49. package/docs/integrations/index.md +68 -0
  50. package/docs/integrations/koa.md +88 -0
  51. package/docs/integrations/nestjs.md +78 -0
  52. package/docs/integrations/tsed.md +81 -0
  53. package/docs/javascripts/extra.js +174 -0
  54. package/docs/performance.md +37 -0
  55. package/docs/stylesheets/extra.css +819 -0
  56. package/mkdocs.yml +217 -0
  57. package/overrides/main.html +26 -0
  58. package/overrides/partials/footer.html +9 -0
  59. package/package.json +4 -2
  60. package/requirements-docs.txt +1 -0
  61. package/docs/README.md +0 -18
  62. package/docs/advanced_usage.md +0 -71
  63. package/docs/api.md +0 -122
  64. package/docs/filters.md +0 -708
  65. package/docs/installation.md +0 -106
  66. package/docs/integrations.md +0 -214
  67. package/docs/overview.md +0 -79
  68. package/docs/partialdef.md +0 -70
  69. package/docs/security.md +0 -27
  70. package/docs/tags.md +0 -673
  71. package/docs/usage.md +0 -646
@@ -7,60 +7,36 @@ on:
7
7
  branches: [ main ]
8
8
  workflow_dispatch:
9
9
 
10
- jobs:
11
- test:
12
- runs-on: ubuntu-latest
13
- strategy:
14
- matrix:
15
- node-version: [18.x, 20.x]
16
- steps:
17
- - uses: actions/checkout@v4
18
- - name: Use Node.js
19
- uses: actions/setup-node@v4
20
- with:
21
- node-version: ${{ matrix.node-version }}
22
- - name: Install
23
- run: npm ci
24
- - name: Run unit tests
25
- run: npm test
26
- - name: Run integration smoke-test
27
- if: github.event_name == 'workflow_dispatch'
28
- run: node live-test/integrations/smoke-test.js
29
- # CI Workflow — lint + test on every push and PR.
30
- #
31
- # Releases are handled by .github/workflows/release.yml, which triggers
32
- # automatically when a `chore(release):` commit lands on main.
33
-
34
- name: CI
35
-
36
- on:
37
- push:
38
- branches: [ main ]
39
- pull_request:
40
- branches: [ main ]
41
-
42
10
  jobs:
43
11
  lint:
44
- runs-on: windows-latest
12
+ runs-on: ubuntu-latest
45
13
  steps:
46
14
  - uses: actions/checkout@v4
47
15
  - name: Setup Node.js
48
16
  uses: actions/setup-node@v4
49
17
  with:
50
18
  node-version: '20'
19
+ cache: 'npm'
51
20
  - run: npm ci
52
21
  - name: Lint code
53
22
  run: npx eslint src/**/*.js
54
23
 
55
24
  test:
56
- runs-on: windows-latest
25
+ runs-on: ubuntu-latest
57
26
  needs: lint
27
+ strategy:
28
+ matrix:
29
+ node-version: [18.x, 20.x]
58
30
  steps:
59
31
  - uses: actions/checkout@v4
60
- - name: Setup Node.js
32
+ - name: Use Node.js
61
33
  uses: actions/setup-node@v4
62
34
  with:
63
- node-version: '20'
35
+ node-version: ${{ matrix.node-version }}
36
+ cache: 'npm'
64
37
  - run: npm ci
65
- - name: Run test suite
38
+ - name: Run unit tests
66
39
  run: npm test
40
+ - name: Run integration smoke-test
41
+ if: github.event_name == 'workflow_dispatch'
42
+ run: node live-test/integrations/smoke-test.js
@@ -0,0 +1,105 @@
1
+ name: Deploy Documentation
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: write
12
+ pages: write
13
+ id-token: write
14
+
15
+ concurrency:
16
+ group: "pages"
17
+ cancel-in-progress: false
18
+
19
+ jobs:
20
+ deploy-docs:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - name: Checkout repository
24
+ uses: actions/checkout@v4
25
+
26
+ - name: Setup Python
27
+ uses: actions/setup-python@v5
28
+ with:
29
+ python-version: '3.12'
30
+ cache: 'pip'
31
+ cache-dependency-path: 'requirements-docs.txt'
32
+
33
+ - name: Install MkDocs Material with imaging support
34
+ run: |
35
+ python -m pip install --upgrade pip
36
+ pip install -r requirements-docs.txt
37
+
38
+ - name: Verify docs build
39
+ run: mkdocs build --strict
40
+
41
+ - name: Deploy docs to GitHub Pages
42
+ run: 'mkdocs gh-deploy --force --message "chore: deploy docs to GitHub Pages "'
43
+
44
+ - name: Output Pages URL
45
+ run: echo "Docs deployed to https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/"
46
+
47
+ sync-wiki:
48
+ runs-on: ubuntu-latest
49
+ needs: deploy-docs
50
+ steps:
51
+ - name: Checkout repository
52
+ uses: actions/checkout@v4
53
+ with:
54
+ fetch-depth: 0
55
+
56
+ - name: Setup Git config
57
+ run: |
58
+ git config --global user.name "github-actions[bot]"
59
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
60
+
61
+ - name: Create wiki clone directory
62
+ run: mkdir -p wiki-repo
63
+
64
+ - name: Clone wiki repository
65
+ run: |
66
+ git clone https://github.com/${{ github.repository }}.wiki.git wiki-repo || true
67
+
68
+ - name: Sync docs to wiki
69
+ if: hashFiles('wiki-repo/.git/config') != ''
70
+ run: |
71
+ cd wiki-repo
72
+
73
+ # Copy all markdown docs preserving directory structure
74
+ cp -r ../docs/*.md .
75
+ cp -r ../docs/guide/*.md ./guide/ 2>/dev/null || true
76
+ cp -r ../docs/api/*.md ./api/ 2>/dev/null || true
77
+ cp -r ../docs/integrations/*.md ./integrations/ 2>/dev/null || true
78
+
79
+ # Remove index.md if it exists (GitHub wiki uses README.md as home page)
80
+ if [ -f index.md ]; then
81
+ if [ -f README.md ]; then
82
+ rm index.md
83
+ else
84
+ mv index.md README.md
85
+ fi
86
+ fi
87
+
88
+ # Create Home.md from index.md if README.md doesn't exist
89
+ if [ ! -f README.md ] && [ ! -f Home.md ] && [ -f docs/index.md ]; then
90
+ cp docs/index.md Home.md
91
+ fi
92
+
93
+ # Commit and push if there are changes
94
+ git add -A
95
+ git diff --cached --quiet || {
96
+ git commit -m "docs: sync documentation to wiki"
97
+ git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.wiki.git master
98
+ }
99
+
100
+ - name: Wiki initialization hint
101
+ if: hashFiles('wiki-repo/.git/config') == ''
102
+ run: |
103
+ echo "::warning::Wiki repository not found. Enable the wiki in repository settings first."
104
+ echo "Go to https://github.com/${{ github.repository }}/settings/wiki and click 'Create the first page'"
105
+ continue-on-error: true
@@ -26,6 +26,7 @@ on:
26
26
 
27
27
  permissions:
28
28
  contents: write
29
+ packages: write
29
30
 
30
31
  jobs:
31
32
  release:
@@ -104,3 +105,8 @@ jobs:
104
105
  run: npm publish --access public
105
106
  env:
106
107
  NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
108
+
109
+ - name: Publish to GitHub Packages
110
+ run: npm publish
111
+ env:
112
+ NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
package/README.md CHANGED
@@ -75,14 +75,13 @@ miki-template is built for real-world apps. Its compiled-AST engine is especiall
75
75
 
76
76
  ## 📚 Documentation
77
77
 
78
- - [Installation](docs/installation.md)
79
- - [Usage Guide](docs/usage.md)
80
- - [Tags Reference](docs/tags.md)
81
- - [Filters Reference](docs/filters.md)
82
- - [Security](docs/security.md)
83
- - [API Reference](docs/api.md)
84
- - [Partial Definitions](docs/partialdef.md)
85
- - [Contributing](docs/advanced_usage.md)
78
+ - **[Documentation](https://alainmiki.github.io/miki-template/)** — Full docs site
79
+ - [Getting Started](https://alainmiki.github.io/miki-template/guide/getting-started)
80
+ - [Installation](https://alainmiki.github.io/miki-template/guide/installation)
81
+ - [Quick Start](https://alainmiki.github.io/miki-template/guide/quick-start)
82
+ - [API Reference](https://alainmiki.github.io/miki-template/api/)
83
+ - [Integrations](https://alainmiki.github.io/miki-template/integrations/)
84
+ - [Performance](https://alainmiki.github.io/miki-template/performance)
86
85
 
87
86
  ---
88
87
 
@@ -252,13 +251,14 @@ Resolve properties dynamically on nested objects or arrays. If the resolved valu
252
251
 
253
252
  ### Built-in Filters
254
253
  Apply filters using pipes (`|`). Arguments are passed after a colon (`:`).
255
- - **Text**: `upper`, `lower`, `title`, `capfirst`, `slugify`, `wordcount`, `striptags`, `linebreaks`, `linebreaksbr`, `truncatewords:N`, `truncatechars:N`.
254
+ - **Text**: `upper`, `lower`, `title`, `capfirst`, `slugify`, `wordcount`, `striptags`, `linebreaks`, `linebreaksbr`, `truncatewords:N`, `truncatechars:N`, `truncatechars_html:N`.
256
255
  - **HTML**: `safe`, `escape`.
257
- - **List**: `length`, `join:","`, `slice:"start:end"`, `dictsort:"key"`, `dictsortreversed:"key"`.
258
- - **Default**: `default:"fallback"`, `default_if_none:"fallback"`.
259
- - **Date/Time**: `date:"Y-m-d"`, `time:"H:i"`, `timesince`, `timeuntil`.
260
- - **Numeric**: `add:5`, `divisibleby:2`, `floatformat:2`.
261
- - **Misc**: `yesno:"yes,no,maybe"`, `pluralize:"suffix"`, `filesizeformat`.
256
+ - **List**: `length`, `join:","`, `slice:"start:end"`, `dictsort:"key"`, `dictsortreversed:"key"`, `sort`, `unique`, `random`, `reverse`, `split:","`, `replace:"old,new"`.
257
+ - **Default**: `default:"fallback"`, `default_if_none:"fallback"`, `firstof:v1 v2 v3`.
258
+ - **Date/Time**: `date:"Y-m-d"`, `time:"H:i"`, `date_format:"yyyy-MM-dd"`, `strftime:"PPpp"`, `timesince`, `timeuntil`, `ago`, `until`, `time_diff:other_date`.
259
+ - **Numeric**: `add:5`, `sub:3`, `mult:2`, `divisibleby:2`, `mod:3`, `floatformat:2`, `square`, `sqrt`, `abs`, `round:2`, `floor`, `ceil`, `min:10`, `max:100`, `sum`, `average`.
260
+ - **Currency/Data**: `currency:"$"`, `phone_number`, `email`, `url`, `mask:"*"`, `whatsapp_link:"msg"`, `credit_card`, `ssn`, `ip_address`, `uuid`, `filesizeformat`, `yesno:"yes,no,maybe"`, `pluralize:"s"`, `urlencode`, `escapeuri`, `stringformat:"%s"`, `cut:"text"`, `addslashes`, `removetags:"p,div"`, `trans`, `regroup:"attr"`, `json`, `urlize`.
261
+ - **Encoding**: `base64_encode`, `base64_decode`.
262
262
 
263
263
  ### Built-in Control Tags
264
264
  - **if / elif / else / endif**: Supports conditional expressions with operators: `==`, `!=`, `<`, `<=`, `>`, `>=`, `in`, `not in`, `and`, `or`, `not`.
@@ -291,9 +291,64 @@ Apply filters using pipes (`|`). Arguments are passed after a colon (`:`).
291
291
  <tr class="{% cycle 'row-odd' 'row-even' %}">...</tr>
292
292
  {% endfor %}
293
293
  ```
294
+ - **firstof**: Return the first truthy value.
295
+ ```html
296
+ {% firstof var1 var2 var3 "fallback" %}
297
+ ```
298
+ - **set**: Assign variables.
299
+ ```html
300
+ {% set total = price * quantity %}
301
+ {% set greeting %}Hello {{ name }}{% endset %}
302
+ ```
303
+ - **ifchanged / endifchanged**: Render only when value changes.
304
+ ```html
305
+ {% for item in items %}
306
+ {% ifchanged item.category %}
307
+ <h2>{{ item.category }}</h2>
308
+ {% endifchanged %}
309
+ {% endfor %}
310
+ ```
311
+ - **now**: Output current date/time.
312
+ ```html
313
+ {% now "Y-m-d H:i:s" %}
314
+ ```
315
+ - **static**: Generate static file URLs.
316
+ ```html
317
+ {% static "css/style.css" %}
318
+ ```
319
+ - **url**: Build URLs from route names.
320
+ ```html
321
+ {% url 'user.profile' user.id %}
322
+ ```
323
+ - **regroup**: Group lists by attribute.
324
+ ```html
325
+ {% for group in items|regroup:"category" %}
326
+ <h3>{{ group.grouper }}</h3>
327
+ {% endfor %}
328
+ ```
329
+ - **spaceless**: Remove whitespace between tags.
330
+ ```html
331
+ {% spaceless %}<div> <span>hi</span> </div>{% endspaceless %}
332
+ ```
333
+ - **widthratio**: Calculate ratios.
334
+ ```html
335
+ {% widthratio value max max_width %}
336
+ ```
337
+ - **debug**: Dump template context.
338
+ ```html
339
+ {% debug %}
340
+ ```
294
341
  - **autoescape on/off**: Control auto-escaping block behavior.
295
342
  - **verbatim / endverbatim**: Treat raw text inside literally.
296
343
  - **comment / endcomment**: Block comment ignored during parse.
344
+ - **load**: Activate template libraries.
345
+ ```html
346
+ {% load lorem humanize %}
347
+ ```
348
+ - **templatetag**: Output literal template tag tokens.
349
+ ```html
350
+ {% templatetag openblock %} if user.is_admin {% templatetag closeblock %}
351
+ ```
297
352
 
298
353
  ### Security Tags
299
354
  - **csrf_token**: Automatically outputs a hidden input carrying the CSRF token from the context variable `csrf_token`.
Binary file
@@ -6,12 +6,12 @@
6
6
  },
7
7
  {
8
8
  "name": "ejs:medium",
9
- "medianMs": 0.03449999999997999,
10
- "rps": 28986
9
+ "medianMs": 0.033799999999985175,
10
+ "rps": 29586
11
11
  },
12
12
  {
13
13
  "name": "ejs:large",
14
- "medianMs": 3.445900000000165,
15
- "rps": 290
14
+ "medianMs": 3.5704000000000633,
15
+ "rps": 280
16
16
  }
17
17
  ]
@@ -1,17 +1,17 @@
1
1
  [
2
2
  {
3
3
  "name": "handlebars:small",
4
- "medianMs": 0.0024000000000086175,
5
- "rps": 416667
4
+ "medianMs": 0.0024999999999977263,
5
+ "rps": 400000
6
6
  },
7
7
  {
8
8
  "name": "handlebars:medium",
9
- "medianMs": 0.020599999999973306,
10
- "rps": 48544
9
+ "medianMs": 0.019200000000012096,
10
+ "rps": 52083
11
11
  },
12
12
  {
13
13
  "name": "handlebars:large",
14
- "medianMs": 1.512900000000002,
15
- "rps": 661
14
+ "medianMs": 1.5285000000001219,
15
+ "rps": 654
16
16
  }
17
17
  ]
@@ -1,8 +1,8 @@
1
1
  [
2
2
  {
3
3
  "name": "miki:small",
4
- "medianMs": 0.008700000000004593,
5
- "rps": 114943
4
+ "medianMs": 0.00899999999998613,
5
+ "rps": 111111
6
6
  },
7
7
  {
8
8
  "name": "miki:medium",
@@ -11,7 +11,7 @@
11
11
  },
12
12
  {
13
13
  "name": "miki:large",
14
- "medianMs": 0.0020999999999844476,
15
- "rps": 476190
14
+ "medianMs": 0.002200000000016189,
15
+ "rps": 454545
16
16
  }
17
17
  ]
@@ -1,8 +1,8 @@
1
1
  [
2
2
  {
3
3
  "name": "pug:small",
4
- "medianMs": 0.0006000000000199179,
5
- "rps": 1666667
4
+ "medianMs": 0.0006999999999948159,
5
+ "rps": 1428571
6
6
  },
7
7
  {
8
8
  "name": "pug:medium",
@@ -11,7 +11,7 @@
11
11
  },
12
12
  {
13
13
  "name": "pug:large",
14
- "medianMs": 0.3201000000000249,
15
- "rps": 3124
14
+ "medianMs": 0.33320000000003347,
15
+ "rps": 3001
16
16
  }
17
17
  ]
@@ -528,7 +528,7 @@ const endRps = 100000 / dur;
528
528
 
529
529
  record('endurance: 100k renders complete in < 30s', dur < 30000,
530
530
  `${dur.toFixed(0)} ms total, ${Math.round(endRps).toLocaleString()} rps avg`);
531
- record('endurance: RSS growth < 50 MB', rssDelta < 50,
531
+ record('endurance: RSS growth < 80 MB', rssDelta < 80,
532
532
  `before ${beforeRss.toFixed(1)} MB, peak ${endRssMax.toFixed(1)} MB (Δ +${rssDelta.toFixed(1)} MB)`);
533
533
  record('endurance: output is still correct at end', (() => {
534
534
  const expected = endC.render(enduranceData);
@@ -0,0 +1,85 @@
1
+ # asyncRender()
2
+
3
+ Async version of `render()`. Returns a Promise. Use this when your templates contain async filters, async custom tags, or async library components.
4
+
5
+ ## Signature
6
+
7
+ ```javascript
8
+ asyncRender(templateStr, contextObj = {}, options = {})
9
+ ```
10
+
11
+ ## Returns
12
+
13
+ `Promise<string>` — The rendered HTML.
14
+
15
+ ## When to Use
16
+
17
+ Use `asyncRender()` when your templates contain:
18
+
19
+ - Async filters (returning Promises)
20
+ - Async custom tags (render return a Promise)
21
+ - Async library helpers
22
+ - `{% load %}` libraries with async components
23
+
24
+ Using async features with `render()` throws: `Async node encountered during sync render. Use asyncRender() instead.`
25
+
26
+ ## Examples
27
+
28
+ ### Basic async render
29
+
30
+ === "CommonJS"
31
+
32
+ ```javascript
33
+ const { asyncRender } = require('miki-template');
34
+
35
+ const html = await asyncRender('Hello {{ name }}!', { name: 'World' });
36
+ ```
37
+
38
+ === "ES Modules"
39
+
40
+ ```javascript
41
+ import { asyncRender } from 'miki-template';
42
+
43
+ const html = await asyncRender('Hello {{ name }}!', { name: 'World' });
44
+ ```
45
+
46
+ ### With async tags/filters
47
+
48
+ === "CommonJS"
49
+
50
+ ```javascript
51
+ const { asyncRender } = require('miki-template');
52
+
53
+ const html = await asyncRender(templateWithAsyncHelpers, context, options);
54
+ ```
55
+
56
+ === "ES Modules"
57
+
58
+ ```javascript
59
+ import { asyncRender } from 'miki-template';
60
+
61
+ const html = await asyncRender(templateWithAsyncHelpers, context, options);
62
+ ```
63
+
64
+ ### Async partial from file
65
+
66
+ === "CommonJS"
67
+
68
+ ```javascript
69
+ const { asyncRender } = require('miki-template');
70
+
71
+ const html = await asyncRender('home#card', context, { views: './views' });
72
+ ```
73
+
74
+ === "ES Modules"
75
+
76
+ ```javascript
77
+ import { asyncRender } from 'miki-template';
78
+
79
+ const html = await asyncRender('home#card', context, { views: './views' });
80
+ ```
81
+
82
+ ## Related
83
+
84
+ - [render()](./render)
85
+ - [compile()](./compile)
@@ -0,0 +1,87 @@
1
+ # Cache API
2
+
3
+ ## clearCache
4
+
5
+ Clear the compiled template cache. Templates are cached in-memory (LRU, 100 entries max). Call this when templates change on disk during development, in tests, or when dynamically registering tags/filters.
6
+
7
+ === "CommonJS"
8
+
9
+ ```javascript
10
+ const { clearCache } = require('miki-template');
11
+
12
+ clearCache();
13
+ ```
14
+
15
+ === "ES Modules"
16
+
17
+ ```javascript
18
+ import { clearCache } from 'miki-template';
19
+
20
+ clearCache();
21
+ ```
22
+
23
+ ## How Caching Works
24
+
25
+ - Templates are cached by a key combining the source string and compile options (views, custom settings).
26
+ - The cache is an in-memory LRU cache limited to 100 entries.
27
+ - Cached compiled templates are reused across renders, improving performance for repeated templates.
28
+ - Partials defined via `{% partialdef %}` are cached along with their parent template.
29
+
30
+ ## When to Clear Cache
31
+
32
+ - During development when templates change frequently on disk
33
+ - In tests to ensure fresh compilation
34
+ - When dynamically registering custom tags/filters at runtime
35
+
36
+ ### Development File Watcher
37
+
38
+ === "CommonJS"
39
+
40
+ ```javascript
41
+ const fs = require('fs');
42
+ const { clearCache } = require('miki-template');
43
+
44
+ if (process.env.NODE_ENV !== 'production') {
45
+ fs.watch('./views', () => {
46
+ clearCache();
47
+ console.log('Template cache cleared');
48
+ });
49
+ }
50
+ ```
51
+
52
+ === "ES Modules"
53
+
54
+ ```javascript
55
+ import fs from 'node:fs';
56
+ import { clearCache } from 'miki-template';
57
+
58
+ if (process.env.NODE_ENV !== 'production') {
59
+ fs.watch('./views', () => {
60
+ clearCache();
61
+ console.log('Template cache cleared');
62
+ });
63
+ }
64
+ ```
65
+
66
+ ## cache Library
67
+
68
+ The built-in `cache` library (auto-activated) provides a template tag for caching fragments:
69
+
70
+ ```html
71
+ {% load cache %}
72
+
73
+ {% cache 300 sidebar_key %}
74
+ <div class="sidebar">
75
+ {% for item in sidebar_items %}
76
+ <a href="{{ item.url }}">{{ item.title }}</a>
77
+ {% endfor %}
78
+ </div>
79
+ {% endcache %}
80
+ ```
81
+
82
+ The first argument is the TTL in seconds. The second is a cache key. Additional arguments serve as key components.
83
+
84
+ ## Next Steps
85
+
86
+ - [Advanced Usage: Caching](../guide/advanced-usage#caching)
87
+ - [API Reference](../)