miki-template 2.2.2 → 2.3.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.
- package/.github/workflows/docs.yml +3 -1
- package/.github/workflows/release.yml +1 -0
- package/benchmarks/ejs-results.json +6 -6
- package/benchmarks/ejs.js +5 -3
- package/benchmarks/handlebars-results.json +6 -6
- package/benchmarks/handlebars.js +5 -8
- package/benchmarks/miki-results.json +6 -6
- package/benchmarks/miki.js +6 -3
- package/benchmarks/pug-results.json +6 -6
- package/benchmarks/pug.js +5 -3
- package/docs/api/async-render.md +88 -3
- package/docs/api/cache.md +90 -3
- package/docs/api/compile.md +131 -3
- package/docs/api/context-processors.md +80 -3
- package/docs/api/filters.md +223 -3
- package/docs/api/finder.md +97 -3
- package/docs/api/helpers.md +56 -3
- package/docs/api/i18n.md +160 -3
- package/docs/api/index.md +82 -28
- package/docs/api/libraries.md +210 -3
- package/docs/api/render-partial.md +84 -3
- package/docs/api/render.md +95 -3
- package/docs/api/security.md +148 -3
- package/docs/api/setup-express.md +78 -2
- package/docs/api/tags.md +138 -4
- package/docs/filter.md +0 -0
- package/docs/guide/advanced-usage.md +403 -6
- package/docs/guide/async-rendering.md +312 -4
- package/docs/guide/context-processors.md +261 -4
- package/docs/guide/custom-filters.md +315 -4
- package/docs/guide/custom-tags.md +275 -4
- package/docs/guide/filters.md +675 -3
- package/docs/guide/getting-started.md +109 -7
- package/docs/guide/installation.md +99 -4
- package/docs/guide/partial-templates.md +371 -4
- package/docs/guide/quick-start.md +228 -6
- package/docs/guide/security.md +348 -3
- package/docs/guide/tags.md +789 -6
- package/docs/guide/template-discovery.md +174 -4
- package/docs/guide/template-inheritance.md +277 -4
- package/docs/index.md +24 -42
- package/docs/integrations/elysia.md +4 -2
- package/docs/integrations/express.md +219 -219
- package/docs/integrations/fastify.md +4 -2
- package/docs/integrations/hono.md +4 -2
- package/docs/integrations/index.md +68 -68
- package/docs/integrations/koa.md +4 -2
- package/docs/integrations/nestjs.md +4 -2
- package/docs/integrations/tsed.md +4 -2
- package/docs/performance.md +45 -8
- package/ex.mjs +1 -1
- package/mkdocs.yml +0 -22
- package/overrides/main.html +1 -1
- package/package.json +1 -1
- package/requirements-docs.txt +2 -1
- package/src/codegen.js +905 -0
- package/src/context.js +42 -30
- package/src/filters.js +16 -0
- package/src/index.js +66 -61
- package/src/tags/control.js +15 -12
- package/src/utils.js +60 -0
- package/tests/filters.test.js +9 -0
- package/.github/workflows/npm-publish-github-packages.yml +0 -36
- package/docs/javascripts/extra.js +0 -174
- package/docs/stylesheets/extra.css +0 -819
- package/overrides/partials/footer.html +0 -9
|
@@ -1,308 +1,616 @@
|
|
|
1
|
-
# Async Rendering
|
|
1
|
+
# Async Rendering
|
|
2
|
+
|
|
3
|
+
|
|
2
4
|
|
|
3
5
|
miki-template supports async rendering for templates that use async filters, async custom tags, or async library components. Use `asyncRender()` instead of `render()` to await these operations.
|
|
4
6
|
|
|
7
|
+
|
|
8
|
+
|
|
5
9
|
## Table of Contents
|
|
6
10
|
|
|
11
|
+
|
|
12
|
+
|
|
7
13
|
- [When to Use Async Rendering](#when-to-use-async-rendering)
|
|
14
|
+
|
|
8
15
|
- [asyncRender()](#asyncrender)
|
|
16
|
+
|
|
9
17
|
- [compiled.asyncRender()](#compiledasyncrender)
|
|
18
|
+
|
|
10
19
|
- [Async Filters](#async-filters)
|
|
20
|
+
|
|
11
21
|
- [Async Custom Tags](#async-custom-tags)
|
|
22
|
+
|
|
12
23
|
- [Express Async Engine](#express-async-engine)
|
|
13
24
|
|
|
25
|
+
|
|
26
|
+
|
|
14
27
|
---
|
|
15
28
|
|
|
29
|
+
|
|
30
|
+
|
|
16
31
|
## When to Use Async Rendering
|
|
17
32
|
|
|
33
|
+
|
|
34
|
+
|
|
18
35
|
Use `asyncRender()` when your templates contain any of the following:
|
|
19
36
|
|
|
37
|
+
|
|
38
|
+
|
|
20
39
|
- **Async filters** — filters that return Promises
|
|
40
|
+
|
|
21
41
|
- **Async custom tags** — custom tags whose `render()` returns a Promise
|
|
42
|
+
|
|
22
43
|
- **Async library components** — i18n translations loaded dynamically
|
|
44
|
+
|
|
23
45
|
- **Async helpers** — helpers that perform I/O
|
|
24
46
|
|
|
47
|
+
|
|
48
|
+
|
|
25
49
|
If you use async features with `render()` or `compiled.render()`, the engine throws:
|
|
26
50
|
|
|
51
|
+
|
|
52
|
+
|
|
27
53
|
> Async node encountered during sync render. Use asyncRender() instead.
|
|
28
54
|
|
|
55
|
+
|
|
56
|
+
|
|
29
57
|
## asyncRender()
|
|
30
58
|
|
|
59
|
+
|
|
60
|
+
|
|
31
61
|
=== "CommonJS"
|
|
32
62
|
|
|
63
|
+
|
|
64
|
+
|
|
33
65
|
```javascript
|
|
66
|
+
|
|
34
67
|
const { asyncRender } = require('miki-template');
|
|
35
68
|
|
|
69
|
+
|
|
70
|
+
|
|
36
71
|
const html = await asyncRender('Hello, {{ name }}!', { name: 'World' });
|
|
72
|
+
|
|
37
73
|
console.log(html);
|
|
74
|
+
|
|
38
75
|
// → "Hello, World!"
|
|
76
|
+
|
|
39
77
|
```
|
|
40
78
|
|
|
79
|
+
|
|
80
|
+
|
|
41
81
|
=== "ES Modules"
|
|
42
82
|
|
|
83
|
+
|
|
84
|
+
|
|
43
85
|
```javascript
|
|
86
|
+
|
|
44
87
|
import { asyncRender } from 'miki-template';
|
|
45
88
|
|
|
89
|
+
|
|
90
|
+
|
|
46
91
|
const html = await asyncRender('Hello, {{ name }}!', { name: 'World' });
|
|
92
|
+
|
|
47
93
|
console.log(html);
|
|
94
|
+
|
|
48
95
|
// → "Hello, World!"
|
|
96
|
+
|
|
49
97
|
```
|
|
50
98
|
|
|
99
|
+
|
|
100
|
+
|
|
51
101
|
### With Views and Partials
|
|
52
102
|
|
|
103
|
+
|
|
104
|
+
|
|
53
105
|
=== "CommonJS"
|
|
54
106
|
|
|
107
|
+
|
|
108
|
+
|
|
55
109
|
```javascript
|
|
110
|
+
|
|
56
111
|
const { asyncRender } = require('miki-template');
|
|
57
112
|
|
|
113
|
+
|
|
114
|
+
|
|
58
115
|
// Render a single partial from a file
|
|
116
|
+
|
|
59
117
|
const html = await asyncRender('home#card', { user: userData }, {
|
|
118
|
+
|
|
60
119
|
views: './templates'
|
|
120
|
+
|
|
61
121
|
});
|
|
122
|
+
|
|
62
123
|
```
|
|
63
124
|
|
|
125
|
+
|
|
126
|
+
|
|
64
127
|
=== "ES Modules"
|
|
65
128
|
|
|
129
|
+
|
|
130
|
+
|
|
66
131
|
```javascript
|
|
132
|
+
|
|
67
133
|
import { asyncRender } from 'miki-template';
|
|
68
134
|
|
|
135
|
+
|
|
136
|
+
|
|
69
137
|
const html = await asyncRender('home#card', { user: userData }, {
|
|
138
|
+
|
|
70
139
|
views: './templates'
|
|
140
|
+
|
|
71
141
|
});
|
|
142
|
+
|
|
72
143
|
```
|
|
73
144
|
|
|
145
|
+
|
|
146
|
+
|
|
74
147
|
## compiled.asyncRender()
|
|
75
148
|
|
|
149
|
+
|
|
150
|
+
|
|
76
151
|
When you pre-compile a template, the returned object has `asyncRender()` and `asyncRenderWith()` methods:
|
|
77
152
|
|
|
153
|
+
|
|
154
|
+
|
|
78
155
|
=== "CommonJS"
|
|
79
156
|
|
|
157
|
+
|
|
158
|
+
|
|
80
159
|
```javascript
|
|
160
|
+
|
|
81
161
|
const { compile } = require('miki-template');
|
|
82
162
|
|
|
163
|
+
|
|
164
|
+
|
|
83
165
|
const template = `
|
|
166
|
+
|
|
84
167
|
{% load markdown %}
|
|
168
|
+
|
|
85
169
|
{{ content|markdown }}
|
|
170
|
+
|
|
86
171
|
`;
|
|
87
172
|
|
|
173
|
+
|
|
174
|
+
|
|
88
175
|
const compiled = compile(template);
|
|
176
|
+
|
|
89
177
|
const html = await compiled.asyncRender({ content: '# Hello World' });
|
|
178
|
+
|
|
90
179
|
```
|
|
91
180
|
|
|
181
|
+
|
|
182
|
+
|
|
92
183
|
=== "ES Modules"
|
|
93
184
|
|
|
185
|
+
|
|
186
|
+
|
|
94
187
|
```javascript
|
|
188
|
+
|
|
95
189
|
import { compile } from 'miki-template';
|
|
96
190
|
|
|
191
|
+
|
|
192
|
+
|
|
97
193
|
const template = `
|
|
194
|
+
|
|
98
195
|
{% load markdown %}
|
|
196
|
+
|
|
99
197
|
{{ content|markdown }}
|
|
198
|
+
|
|
100
199
|
`;
|
|
101
200
|
|
|
201
|
+
|
|
202
|
+
|
|
102
203
|
const compiled = compile(template);
|
|
204
|
+
|
|
103
205
|
const html = await compiled.asyncRender({ content: '# Hello World' });
|
|
206
|
+
|
|
104
207
|
```
|
|
105
208
|
|
|
209
|
+
|
|
210
|
+
|
|
106
211
|
## Async Filters
|
|
107
212
|
|
|
213
|
+
|
|
214
|
+
|
|
108
215
|
Filters that return Promises are automatically awaited when using `asyncRender()`:
|
|
109
216
|
|
|
217
|
+
|
|
218
|
+
|
|
110
219
|
=== "CommonJS"
|
|
111
220
|
|
|
221
|
+
|
|
222
|
+
|
|
112
223
|
```javascript
|
|
224
|
+
|
|
113
225
|
const { registerFilter, asyncRender } = require('miki-template');
|
|
114
226
|
|
|
227
|
+
|
|
228
|
+
|
|
115
229
|
registerFilter('to_upper', (val) => val.toUpperCase());
|
|
230
|
+
|
|
116
231
|
registerFilter('fetch_url', async (url) => {
|
|
232
|
+
|
|
117
233
|
const res = await fetch(url);
|
|
234
|
+
|
|
118
235
|
return res.text();
|
|
236
|
+
|
|
119
237
|
});
|
|
238
|
+
|
|
120
239
|
```
|
|
121
240
|
|
|
241
|
+
|
|
242
|
+
|
|
122
243
|
=== "ES Modules"
|
|
123
244
|
|
|
245
|
+
|
|
246
|
+
|
|
124
247
|
```javascript
|
|
248
|
+
|
|
125
249
|
import { registerFilter, asyncRender } from 'miki-template';
|
|
126
250
|
|
|
251
|
+
|
|
252
|
+
|
|
127
253
|
registerFilter('to_upper', (val) => val.toUpperCase());
|
|
254
|
+
|
|
128
255
|
registerFilter('fetch_url', async (url) => {
|
|
256
|
+
|
|
129
257
|
const res = await fetch(url);
|
|
258
|
+
|
|
130
259
|
return res.text();
|
|
260
|
+
|
|
131
261
|
});
|
|
262
|
+
|
|
132
263
|
```
|
|
133
264
|
|
|
265
|
+
|
|
266
|
+
|
|
134
267
|
Usage:
|
|
135
268
|
|
|
269
|
+
|
|
270
|
+
|
|
136
271
|
```html
|
|
272
|
+
|
|
137
273
|
{{ api_endpoint|fetch_url }}
|
|
274
|
+
|
|
138
275
|
```
|
|
139
276
|
|
|
277
|
+
|
|
278
|
+
|
|
140
279
|
**Real-world CMS content fetch:**
|
|
141
280
|
|
|
281
|
+
|
|
282
|
+
|
|
142
283
|
=== "CommonJS"
|
|
143
284
|
|
|
285
|
+
|
|
286
|
+
|
|
144
287
|
```javascript
|
|
288
|
+
|
|
145
289
|
const { registerFilter, asyncRender } = require('miki-template');
|
|
146
290
|
|
|
291
|
+
|
|
292
|
+
|
|
147
293
|
registerFilter('cms_content', async (id) => {
|
|
294
|
+
|
|
148
295
|
const res = await fetch(`https://cms.example.com/api/content/${id}`);
|
|
296
|
+
|
|
149
297
|
const data = await res.json();
|
|
298
|
+
|
|
150
299
|
return data.html;
|
|
300
|
+
|
|
151
301
|
});
|
|
152
302
|
|
|
303
|
+
|
|
304
|
+
|
|
153
305
|
const html = await asyncRender(
|
|
306
|
+
|
|
154
307
|
'{% autoescape off %}{{ page_id|cms_content }}{% endautoescape %}',
|
|
308
|
+
|
|
155
309
|
{ page_id: 'about' }
|
|
310
|
+
|
|
156
311
|
);
|
|
312
|
+
|
|
157
313
|
```
|
|
158
314
|
|
|
315
|
+
|
|
316
|
+
|
|
159
317
|
=== "ES Modules"
|
|
160
318
|
|
|
319
|
+
|
|
320
|
+
|
|
161
321
|
```javascript
|
|
322
|
+
|
|
162
323
|
import { registerFilter, asyncRender } from 'miki-template';
|
|
163
324
|
|
|
325
|
+
|
|
326
|
+
|
|
164
327
|
registerFilter('cms_content', async (id) => {
|
|
328
|
+
|
|
165
329
|
const res = await fetch(`https://cms.example.com/api/content/${id}`);
|
|
330
|
+
|
|
166
331
|
const data = await res.json();
|
|
332
|
+
|
|
167
333
|
return data.html;
|
|
334
|
+
|
|
168
335
|
});
|
|
169
336
|
|
|
337
|
+
|
|
338
|
+
|
|
170
339
|
const html = await asyncRender(
|
|
340
|
+
|
|
171
341
|
'{% autoescape off %}{{ page_id|cms_content }}{% endautoescape %}',
|
|
342
|
+
|
|
172
343
|
{ page_id: 'about' }
|
|
344
|
+
|
|
173
345
|
);
|
|
346
|
+
|
|
174
347
|
```
|
|
175
348
|
|
|
349
|
+
|
|
350
|
+
|
|
176
351
|
## Async Custom Tags
|
|
177
352
|
|
|
353
|
+
|
|
354
|
+
|
|
178
355
|
Custom tags whose `render()` returns a Promise work with `asyncRender()`:
|
|
179
356
|
|
|
357
|
+
|
|
358
|
+
|
|
180
359
|
=== "CommonJS"
|
|
181
360
|
|
|
361
|
+
|
|
362
|
+
|
|
182
363
|
```javascript
|
|
364
|
+
|
|
183
365
|
const { registerTag, asyncRender } = require('miki-template');
|
|
184
366
|
|
|
367
|
+
|
|
368
|
+
|
|
185
369
|
registerTag('api_data', (tagContent, parser) => {
|
|
370
|
+
|
|
186
371
|
const endpoint = tagContent.trim();
|
|
372
|
+
|
|
187
373
|
return {
|
|
374
|
+
|
|
188
375
|
async render(context) {
|
|
376
|
+
|
|
189
377
|
const res = await fetch(context.get(endpoint));
|
|
378
|
+
|
|
190
379
|
const data = await res.json();
|
|
380
|
+
|
|
191
381
|
return JSON.stringify(data, null, 2);
|
|
382
|
+
|
|
192
383
|
}
|
|
384
|
+
|
|
193
385
|
};
|
|
386
|
+
|
|
194
387
|
});
|
|
195
388
|
|
|
389
|
+
|
|
390
|
+
|
|
196
391
|
const html = await asyncRender(
|
|
392
|
+
|
|
197
393
|
'{% api_data api_url %}',
|
|
394
|
+
|
|
198
395
|
{ api_url: 'https://api.example.com/users' }
|
|
396
|
+
|
|
199
397
|
);
|
|
398
|
+
|
|
200
399
|
```
|
|
201
400
|
|
|
401
|
+
|
|
402
|
+
|
|
202
403
|
=== "ES Modules"
|
|
203
404
|
|
|
405
|
+
|
|
406
|
+
|
|
204
407
|
```javascript
|
|
408
|
+
|
|
205
409
|
import { registerTag, asyncRender } from 'miki-template';
|
|
206
410
|
|
|
411
|
+
|
|
412
|
+
|
|
207
413
|
registerTag('api_data', (tagContent, parser) => {
|
|
414
|
+
|
|
208
415
|
const endpoint = tagContent.trim();
|
|
416
|
+
|
|
209
417
|
return {
|
|
418
|
+
|
|
210
419
|
async render(context) {
|
|
420
|
+
|
|
211
421
|
const res = await fetch(context.get(endpoint));
|
|
422
|
+
|
|
212
423
|
const data = await res.json();
|
|
424
|
+
|
|
213
425
|
return JSON.stringify(data, null, 2);
|
|
426
|
+
|
|
214
427
|
}
|
|
428
|
+
|
|
215
429
|
};
|
|
430
|
+
|
|
216
431
|
});
|
|
217
432
|
|
|
433
|
+
|
|
434
|
+
|
|
218
435
|
const html = await asyncRender(
|
|
436
|
+
|
|
219
437
|
'{% api_data api_url %}',
|
|
438
|
+
|
|
220
439
|
{ api_url: 'https://api.example.com/users' }
|
|
440
|
+
|
|
221
441
|
);
|
|
442
|
+
|
|
222
443
|
```
|
|
223
444
|
|
|
445
|
+
|
|
446
|
+
|
|
224
447
|
## Express Async Engine
|
|
225
448
|
|
|
449
|
+
|
|
450
|
+
|
|
226
451
|
For Express apps with async templates, use `__expressAsync` or `express({ async: true })`:
|
|
227
452
|
|
|
453
|
+
|
|
454
|
+
|
|
228
455
|
=== "CommonJS"
|
|
229
456
|
|
|
457
|
+
|
|
458
|
+
|
|
230
459
|
```javascript
|
|
460
|
+
|
|
231
461
|
const express = require('express');
|
|
462
|
+
|
|
232
463
|
const miki = require('miki-template');
|
|
233
464
|
|
|
465
|
+
|
|
466
|
+
|
|
234
467
|
const app = express();
|
|
468
|
+
|
|
235
469
|
app.engine('html', miki.express({ async: true }));
|
|
470
|
+
|
|
236
471
|
app.set('view engine', 'html');
|
|
472
|
+
|
|
237
473
|
app.set('views', './views');
|
|
474
|
+
|
|
238
475
|
```
|
|
239
476
|
|
|
477
|
+
|
|
478
|
+
|
|
240
479
|
=== "ES Modules"
|
|
241
480
|
|
|
481
|
+
|
|
482
|
+
|
|
242
483
|
```javascript
|
|
484
|
+
|
|
243
485
|
import express from 'express';
|
|
486
|
+
|
|
244
487
|
import miki from 'miki-template';
|
|
245
488
|
|
|
489
|
+
|
|
490
|
+
|
|
246
491
|
const app = express();
|
|
492
|
+
|
|
247
493
|
app.engine('html', miki.express({ async: true }));
|
|
494
|
+
|
|
248
495
|
app.set('view engine', 'html');
|
|
496
|
+
|
|
249
497
|
app.set('views', './views');
|
|
498
|
+
|
|
250
499
|
```
|
|
251
500
|
|
|
501
|
+
|
|
502
|
+
|
|
252
503
|
### Express 5+ Native Promise Support
|
|
253
504
|
|
|
505
|
+
|
|
506
|
+
|
|
254
507
|
If you're using Express 5 (which supports Promise-based view engines), use `__expressAsync` directly:
|
|
255
508
|
|
|
509
|
+
|
|
510
|
+
|
|
256
511
|
=== "CommonJS"
|
|
257
512
|
|
|
513
|
+
|
|
514
|
+
|
|
258
515
|
```javascript
|
|
516
|
+
|
|
259
517
|
const express = require('express');
|
|
518
|
+
|
|
260
519
|
const miki = require('miki-template');
|
|
261
520
|
|
|
521
|
+
|
|
522
|
+
|
|
262
523
|
app.engine('html', miki.__expressAsync);
|
|
524
|
+
|
|
263
525
|
```
|
|
264
526
|
|
|
527
|
+
|
|
528
|
+
|
|
265
529
|
=== "ES Modules"
|
|
266
530
|
|
|
531
|
+
|
|
532
|
+
|
|
267
533
|
```javascript
|
|
534
|
+
|
|
268
535
|
import express from 'express';
|
|
536
|
+
|
|
269
537
|
import miki from 'miki-template';
|
|
270
538
|
|
|
539
|
+
|
|
540
|
+
|
|
271
541
|
app.engine('html', miki.__expressAsync);
|
|
542
|
+
|
|
272
543
|
```
|
|
273
544
|
|
|
545
|
+
|
|
546
|
+
|
|
274
547
|
## asyncRenderWith()
|
|
275
548
|
|
|
549
|
+
|
|
550
|
+
|
|
276
551
|
Override compile-time options at render time:
|
|
277
552
|
|
|
553
|
+
|
|
554
|
+
|
|
278
555
|
=== "CommonJS"
|
|
279
556
|
|
|
557
|
+
|
|
558
|
+
|
|
280
559
|
```javascript
|
|
560
|
+
|
|
281
561
|
const { compile } = require('miki-template');
|
|
282
562
|
|
|
563
|
+
|
|
564
|
+
|
|
283
565
|
const compiled = compile(template, { views: './views' });
|
|
284
566
|
|
|
567
|
+
|
|
568
|
+
|
|
285
569
|
const html = await compiled.asyncRenderWith(
|
|
570
|
+
|
|
286
571
|
{ user: userData },
|
|
572
|
+
|
|
287
573
|
{ views: './other-views', customOption: true }
|
|
574
|
+
|
|
288
575
|
);
|
|
576
|
+
|
|
289
577
|
```
|
|
290
578
|
|
|
579
|
+
|
|
580
|
+
|
|
291
581
|
=== "ES Modules"
|
|
292
582
|
|
|
583
|
+
|
|
584
|
+
|
|
293
585
|
```javascript
|
|
586
|
+
|
|
294
587
|
import { compile } from 'miki-template';
|
|
295
588
|
|
|
589
|
+
|
|
590
|
+
|
|
296
591
|
const compiled = compile(template, { views: './views' });
|
|
297
592
|
|
|
593
|
+
|
|
594
|
+
|
|
298
595
|
const html = await compiled.asyncRenderWith(
|
|
596
|
+
|
|
299
597
|
{ user: userData },
|
|
598
|
+
|
|
300
599
|
{ views: './other-views', customOption: true }
|
|
600
|
+
|
|
301
601
|
);
|
|
602
|
+
|
|
302
603
|
```
|
|
303
604
|
|
|
605
|
+
|
|
606
|
+
|
|
304
607
|
## Next Steps
|
|
305
608
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
- [
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
- [Custom Filters: Async Filters](./custom-filters.md#async-filters)
|
|
612
|
+
|
|
613
|
+
- [Custom Tags: Async Custom Tags](./custom-tags.md#async-custom-tags)
|
|
614
|
+
|
|
615
|
+
- [API Reference: asyncRender](../api/async-render.md)
|
|
616
|
+
|