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.
Files changed (66) hide show
  1. package/.github/workflows/docs.yml +3 -1
  2. package/.github/workflows/release.yml +1 -0
  3. package/benchmarks/ejs-results.json +6 -6
  4. package/benchmarks/ejs.js +5 -3
  5. package/benchmarks/handlebars-results.json +6 -6
  6. package/benchmarks/handlebars.js +5 -8
  7. package/benchmarks/miki-results.json +6 -6
  8. package/benchmarks/miki.js +6 -3
  9. package/benchmarks/pug-results.json +6 -6
  10. package/benchmarks/pug.js +5 -3
  11. package/docs/api/async-render.md +88 -3
  12. package/docs/api/cache.md +90 -3
  13. package/docs/api/compile.md +131 -3
  14. package/docs/api/context-processors.md +80 -3
  15. package/docs/api/filters.md +223 -3
  16. package/docs/api/finder.md +97 -3
  17. package/docs/api/helpers.md +56 -3
  18. package/docs/api/i18n.md +160 -3
  19. package/docs/api/index.md +82 -28
  20. package/docs/api/libraries.md +210 -3
  21. package/docs/api/render-partial.md +84 -3
  22. package/docs/api/render.md +95 -3
  23. package/docs/api/security.md +148 -3
  24. package/docs/api/setup-express.md +78 -2
  25. package/docs/api/tags.md +138 -4
  26. package/docs/filter.md +0 -0
  27. package/docs/guide/advanced-usage.md +403 -6
  28. package/docs/guide/async-rendering.md +312 -4
  29. package/docs/guide/context-processors.md +261 -4
  30. package/docs/guide/custom-filters.md +315 -4
  31. package/docs/guide/custom-tags.md +275 -4
  32. package/docs/guide/filters.md +675 -3
  33. package/docs/guide/getting-started.md +109 -7
  34. package/docs/guide/installation.md +99 -4
  35. package/docs/guide/partial-templates.md +371 -4
  36. package/docs/guide/quick-start.md +228 -6
  37. package/docs/guide/security.md +348 -3
  38. package/docs/guide/tags.md +789 -6
  39. package/docs/guide/template-discovery.md +174 -4
  40. package/docs/guide/template-inheritance.md +277 -4
  41. package/docs/index.md +24 -42
  42. package/docs/integrations/elysia.md +4 -2
  43. package/docs/integrations/express.md +219 -219
  44. package/docs/integrations/fastify.md +4 -2
  45. package/docs/integrations/hono.md +4 -2
  46. package/docs/integrations/index.md +68 -68
  47. package/docs/integrations/koa.md +4 -2
  48. package/docs/integrations/nestjs.md +4 -2
  49. package/docs/integrations/tsed.md +4 -2
  50. package/docs/performance.md +45 -8
  51. package/ex.mjs +1 -1
  52. package/mkdocs.yml +0 -22
  53. package/overrides/main.html +1 -1
  54. package/package.json +1 -1
  55. package/requirements-docs.txt +2 -1
  56. package/src/codegen.js +905 -0
  57. package/src/context.js +42 -30
  58. package/src/filters.js +16 -0
  59. package/src/index.js +66 -61
  60. package/src/tags/control.js +15 -12
  61. package/src/utils.js +60 -0
  62. package/tests/filters.test.js +9 -0
  63. package/.github/workflows/npm-publish-github-packages.yml +0 -36
  64. package/docs/javascripts/extra.js +0 -174
  65. package/docs/stylesheets/extra.css +0 -819
  66. 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
- - [Custom Filters: Async Filters](./custom-filters#async-filters)
307
- - [Custom Tags: Async Custom Tags](./custom-tags#async-custom-tags)
308
- - [API Reference: asyncRender](../api/async-render)
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
+