miki-template 2.0.0 → 2.2.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.
- package/.github/workflows/ci.yml +14 -10
- package/.github/workflows/docs.yml +105 -0
- package/.github/workflows/npm-publish-github-packages.yml +36 -0
- package/README.md +142 -26
- package/assets/logo.png +0 -0
- package/benchmarks/ejs-results.json +17 -0
- package/benchmarks/ejs.js +36 -0
- package/benchmarks/handlebars-results.json +17 -0
- package/benchmarks/handlebars.js +48 -0
- package/benchmarks/miki-results.json +17 -0
- package/benchmarks/miki.js +36 -0
- package/benchmarks/pug-results.json +17 -0
- package/benchmarks/pug.js +36 -0
- package/benchmarks/run.js +69 -37
- package/benchmarks/stress.mjs +1 -1
- package/docs/api/async-render.md +85 -0
- package/docs/api/cache.md +87 -0
- package/docs/api/compile.md +128 -0
- package/docs/api/context-processors.md +77 -0
- package/docs/api/filters.md +217 -0
- package/docs/api/finder.md +94 -0
- package/docs/api/helpers.md +53 -0
- package/docs/api/i18n.md +157 -0
- package/docs/api/index.md +54 -0
- package/docs/api/libraries.md +207 -0
- package/docs/api/render-partial.md +81 -0
- package/docs/api/render.md +92 -0
- package/docs/api/security.md +145 -0
- package/docs/api/setup-express.md +76 -0
- package/docs/api/tags.md +134 -0
- package/docs/assets/banner.png +0 -0
- package/docs/assets/logo.png +0 -0
- package/docs/guide/advanced-usage.md +397 -0
- package/docs/guide/async-rendering.md +308 -0
- package/docs/guide/context-processors.md +257 -0
- package/docs/guide/custom-filters.md +311 -0
- package/docs/guide/custom-tags.md +271 -0
- package/docs/guide/filters.md +642 -0
- package/docs/guide/getting-started.md +102 -0
- package/docs/guide/installation.md +95 -0
- package/docs/guide/partial-templates.md +367 -0
- package/docs/guide/quick-start.md +222 -0
- package/docs/guide/security.md +345 -0
- package/docs/guide/tags.md +783 -0
- package/docs/guide/template-discovery.md +170 -0
- package/docs/guide/template-inheritance.md +273 -0
- package/docs/guide/what-is-miki-template.md +28 -0
- package/docs/guide/why-miki-template.md +75 -0
- package/docs/index.md +104 -0
- package/docs/integrations/elysia.md +78 -0
- package/docs/integrations/express.md +219 -0
- package/docs/integrations/fastify.md +77 -0
- package/docs/integrations/hono.md +78 -0
- package/docs/integrations/index.md +68 -0
- package/docs/integrations/koa.md +88 -0
- package/docs/integrations/nestjs.md +78 -0
- package/docs/integrations/tsed.md +81 -0
- package/docs/javascripts/extra.js +174 -0
- package/docs/performance.md +37 -0
- package/docs/stylesheets/extra.css +819 -0
- package/live-test/integrations/elysia-example.js +16 -0
- package/live-test/integrations/express-example.js +24 -0
- package/live-test/integrations/fastify-example.js +20 -0
- package/live-test/integrations/hono-example.js +16 -0
- package/live-test/integrations/koa-example.js +30 -0
- package/live-test/integrations/nestjs-example.js +25 -0
- package/live-test/integrations/smoke-test.js +166 -0
- package/live-test/integrations/tsed-example.js +23 -0
- package/live-test/package-lock.json +235 -0
- package/live-test/package.json +4 -0
- package/live-test/views/home.html +17 -0
- package/mkdocs.yml +217 -0
- package/overrides/main.html +26 -0
- package/overrides/partials/footer.html +9 -0
- package/package.json +16 -6
- package/requirements-docs.txt +1 -0
- package/tests/integration/partial-render.test.cjs +13 -0
- package/docs/README.md +0 -18
- package/docs/advanced_usage.md +0 -71
- package/docs/api.md +0 -122
- package/docs/filters.md +0 -708
- package/docs/installation.md +0 -106
- package/docs/overview.md +0 -79
- package/docs/partialdef.md +0 -70
- package/docs/security.md +0 -27
- package/docs/tags.md +0 -673
- package/docs/usage.md +0 -646
package/benchmarks/run.js
CHANGED
|
@@ -1,49 +1,81 @@
|
|
|
1
|
-
|
|
2
|
-
// Simple benchmark for sync vs async rendering
|
|
3
|
-
const { compile, asyncRender } = require('../src');
|
|
1
|
+
const { execSync } = require('child_process');
|
|
4
2
|
const fs = require('fs');
|
|
5
3
|
const path = require('path');
|
|
6
|
-
const { performance } = require('perf_hooks');
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
const ENGINES = ['miki', 'pug', 'ejs', 'handlebars'];
|
|
6
|
+
const RESULTS_DIR = __dirname;
|
|
7
|
+
|
|
8
|
+
function runBench(name) {
|
|
9
|
+
const file = path.join(RESULTS_DIR, `${name}.js`);
|
|
10
|
+
if (!fs.existsSync(file)) {
|
|
11
|
+
console.log(`Skipping ${name} (file not found)`);
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
console.log(`\n--- Running ${name} benchmark ---`);
|
|
15
|
+
const out = execSync(`node "${file}"`, { encoding: 'utf8', stdio: 'pipe' });
|
|
16
|
+
console.log(out);
|
|
17
|
+
const resultFile = path.join(RESULTS_DIR, `${name}-results.json`);
|
|
18
|
+
if (fs.existsSync(resultFile)) {
|
|
19
|
+
return JSON.parse(fs.readFileSync(resultFile, 'utf8'));
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
11
22
|
}
|
|
12
23
|
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
function printComparison(allResults) {
|
|
25
|
+
console.log('\n========================================');
|
|
26
|
+
console.log(' BENCHMARK COMPARISON (lower is better)');
|
|
27
|
+
console.log('========================================\n');
|
|
28
|
+
|
|
29
|
+
const categories = ['small', 'medium', 'large'];
|
|
30
|
+
for (const cat of categories) {
|
|
31
|
+
console.log(`-- ${cat.toUpperCase()} --`);
|
|
32
|
+
const rows = [];
|
|
33
|
+
for (const [engine, results] of Object.entries(allResults)) {
|
|
34
|
+
const r = results.find(x => x.name === `${engine}:${cat}`);
|
|
35
|
+
if (r) rows.push({ engine, rps: r.rps, ms: r.medianMs });
|
|
36
|
+
}
|
|
37
|
+
rows.sort((a, b) => b.rps - a.rps);
|
|
38
|
+
const bestRps = rows[0]?.rps || 1;
|
|
39
|
+
for (const row of rows) {
|
|
40
|
+
const pct = ((bestRps / row.rps) * 100).toFixed(0);
|
|
41
|
+
const marker = row.engine === 'miki' ? '★' : ' ';
|
|
42
|
+
console.log(` ${marker}${row.engine.padEnd(12)} ${row.ms.toFixed(3).padStart(8)} ms ${row.rps.toString().padStart(8)} rps (${pct}%)`);
|
|
43
|
+
}
|
|
44
|
+
console.log('');
|
|
30
45
|
}
|
|
31
|
-
const avg = arr => arr.reduce((a,b)=>a+b,0)/arr.length;
|
|
32
|
-
return {
|
|
33
|
-
name,
|
|
34
|
-
syncAvgMs: avg(syncTimes).toFixed(2),
|
|
35
|
-
asyncAvgMs: avg(asyncTimes).toFixed(2)
|
|
36
|
-
};
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
function main() {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
results
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
const allResults = {};
|
|
50
|
+
for (const engine of ENGINES) {
|
|
51
|
+
const results = runBench(engine);
|
|
52
|
+
if (results) allResults[engine] = results;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (Object.keys(allResults).length === 0) {
|
|
56
|
+
console.log('No benchmark results collected.');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
printComparison(allResults);
|
|
61
|
+
|
|
62
|
+
const mikiResults = allResults['miki'] || [];
|
|
63
|
+
const mikiAvgRps = mikiResults.reduce((a, r) => a + r.rps, 0) / mikiResults.length;
|
|
64
|
+
const mikiLarge = mikiResults.find(r => r.name === 'miki:large');
|
|
65
|
+
const othersSlowOnLarge = Object.entries(allResults)
|
|
66
|
+
.filter(([engine]) => engine !== 'miki')
|
|
67
|
+
.every(([, results]) => {
|
|
68
|
+
const large = results.find(r => r.name === `${Object.keys(allResults).find(k => allResults[k] === results)}:large`);
|
|
69
|
+
return !large || (mikiLarge && mikiLarge.rps >= large.rps);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (othersSlowOnLarge) {
|
|
73
|
+
console.log('★ miki-template dominates on large/real-world workloads.\n');
|
|
74
|
+
} else if (mikiAvgRps >= 100000) {
|
|
75
|
+
console.log('★ miki-template delivers strong performance across workloads.\n');
|
|
76
|
+
} else {
|
|
77
|
+
console.log('Note: miki-template performance may vary by workload.\n');
|
|
78
|
+
}
|
|
47
79
|
}
|
|
48
80
|
|
|
49
81
|
main();
|
package/benchmarks/stress.mjs
CHANGED
|
@@ -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 <
|
|
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](../)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# compile()
|
|
2
|
+
|
|
3
|
+
Compile a template string into a reusable renderable object.
|
|
4
|
+
|
|
5
|
+
## Signature
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
compile(templateStr, options = {})
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Parameters
|
|
12
|
+
|
|
13
|
+
| Parameter | Type | Description |
|
|
14
|
+
|-----------|------|-------------|
|
|
15
|
+
| `templateStr` | `string` | Template source string |
|
|
16
|
+
| `options` | `object` | Options including `views` directories |
|
|
17
|
+
|
|
18
|
+
## Returns
|
|
19
|
+
|
|
20
|
+
An object with these render methods:
|
|
21
|
+
|
|
22
|
+
| Method | Description |
|
|
23
|
+
|--------|-------------|
|
|
24
|
+
| `render(contextObj, callOptions)` | Synchronous render |
|
|
25
|
+
| `renderWith(contextObj, callOptions)` | Render with options override |
|
|
26
|
+
| `asyncRender(contextObj)` | Asynchronous render (supports async filters/tags) |
|
|
27
|
+
| `asyncRenderWith(contextObj, callOptions)` | Async render with options override |
|
|
28
|
+
| `renderBlock(blockName, contextObj)` | Render a single `{% block %}` |
|
|
29
|
+
| `renderPartial(partialName, contextObj)` | Render a named `{% partialdef %}` |
|
|
30
|
+
|
|
31
|
+
## Examples
|
|
32
|
+
|
|
33
|
+
### Basic compile
|
|
34
|
+
|
|
35
|
+
=== "CommonJS"
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
const { compile } = require('miki-template');
|
|
39
|
+
|
|
40
|
+
const compiled = compile('<h1>{{ title }}</h1>');
|
|
41
|
+
|
|
42
|
+
const html = compiled.render({ title: 'Hello' });
|
|
43
|
+
// Output: <h1>Hello</h1>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
=== "ES Modules"
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
import { compile } from 'miki-template';
|
|
50
|
+
|
|
51
|
+
const compiled = compile('<h1>{{ title }}</h1>');
|
|
52
|
+
|
|
53
|
+
const html = compiled.render({ title: 'Hello' });
|
|
54
|
+
// Output: <h1>Hello</h1>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Render with options override
|
|
58
|
+
|
|
59
|
+
=== "CommonJS"
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
const { compile } = require('miki-template');
|
|
63
|
+
|
|
64
|
+
const compiled = compile(template, { views: './templates' });
|
|
65
|
+
const html = compiled.renderWith({ title: 'Hello' }, { views: './other-views' });
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
=== "ES Modules"
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
import { compile } from 'miki-template';
|
|
72
|
+
|
|
73
|
+
const compiled = compile(template, { views: './templates' });
|
|
74
|
+
const html = compiled.renderWith({ title: 'Hello' }, { views: './other-views' });
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Render a Block (template inheritance)
|
|
78
|
+
|
|
79
|
+
=== "CommonJS"
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
const { compile } = require('miki-template');
|
|
83
|
+
|
|
84
|
+
const compiled = compile(childTemplate, { views: './templates' });
|
|
85
|
+
const html = compiled.renderBlock('content', context);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
=== "ES Modules"
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
import { compile } from 'miki-template';
|
|
92
|
+
|
|
93
|
+
const compiled = compile(childTemplate, { views: './templates' });
|
|
94
|
+
const html = compiled.renderBlock('content', context);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Render a Partial
|
|
98
|
+
|
|
99
|
+
=== "CommonJS"
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
const { compile } = require('miki-template');
|
|
103
|
+
|
|
104
|
+
const compiled = compile(`
|
|
105
|
+
{% partialdef card %}
|
|
106
|
+
<div class="card">{{ title }}</div>
|
|
107
|
+
{% endpartialdef %}
|
|
108
|
+
`);
|
|
109
|
+
const html = compiled.renderPartial('card', { title: 'Hello' });
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
=== "ES Modules"
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
import { compile } from 'miki-template';
|
|
116
|
+
|
|
117
|
+
const compiled = compile(`
|
|
118
|
+
{% partialdef card %}
|
|
119
|
+
<div class="card">{{ title }}</div>
|
|
120
|
+
{% endpartialdef %}
|
|
121
|
+
`);
|
|
122
|
+
const html = compiled.renderPartial('card', { title: 'Hello' });
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Related
|
|
126
|
+
|
|
127
|
+
- [render()](./render)
|
|
128
|
+
- [asyncRender()](./async-render)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Context Processors API
|
|
2
|
+
|
|
3
|
+
## registerContextProcessor
|
|
4
|
+
|
|
5
|
+
Register a context processor function that runs before every render. The returned object is merged into the rendering context, with explicit context values always winning.
|
|
6
|
+
|
|
7
|
+
=== "CommonJS"
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
const { registerContextProcessor } = require('miki-template');
|
|
11
|
+
|
|
12
|
+
registerContextProcessor((context) => {
|
|
13
|
+
return {
|
|
14
|
+
siteName: 'My App',
|
|
15
|
+
currentYear: new Date().getFullYear()
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
=== "ES Modules"
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
import { registerContextProcessor } from 'miki-template';
|
|
24
|
+
|
|
25
|
+
registerContextProcessor((context) => {
|
|
26
|
+
return {
|
|
27
|
+
siteName: 'My App',
|
|
28
|
+
currentYear: new Date().getFullYear()
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Signature
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
type ContextProcessor = (context: Context) => Record<string, any> | null
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- Receives the `Context` object, allowing inspection of existing values via `context.get('key')`.
|
|
40
|
+
- Must return a plain object. Returning `null` or `undefined` is treated as `{}`.
|
|
41
|
+
- Must be synchronous — no async/await or Promises.
|
|
42
|
+
|
|
43
|
+
## clearContextProcessors
|
|
44
|
+
|
|
45
|
+
Clear all registered context processors. Useful in tests or when re-configuring.
|
|
46
|
+
|
|
47
|
+
=== "CommonJS"
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
const { clearContextProcessors } = require('miki-template');
|
|
51
|
+
|
|
52
|
+
clearContextProcessors();
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
=== "ES Modules"
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
import { clearContextProcessors } from 'miki-template';
|
|
59
|
+
|
|
60
|
+
clearContextProcessors();
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Precedence Rules
|
|
64
|
+
|
|
65
|
+
1. **Context processors run first** — their key/value pairs are added to the context.
|
|
66
|
+
2. **Your explicit context is applied last** — explicit values always override processor values.
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
// Processor sets: { siteName: 'My App', theme: 'dark' }
|
|
70
|
+
// You render with: { theme: 'light' }
|
|
71
|
+
// Result: { siteName: 'My App', theme: 'light' }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Next Steps
|
|
75
|
+
|
|
76
|
+
- [Context Processors Guide](../guide/context-processors)
|
|
77
|
+
- [API Reference](../)
|