miki-template 2.2.3 → 2.3.1

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 +0 -5
  3. package/README.md +17 -5
  4. package/benchmarks/ejs-results.json +6 -6
  5. package/benchmarks/ejs.js +5 -3
  6. package/benchmarks/handlebars-results.json +6 -6
  7. package/benchmarks/handlebars.js +5 -8
  8. package/benchmarks/miki-results.json +6 -6
  9. package/benchmarks/miki.js +6 -3
  10. package/benchmarks/pug-results.json +6 -6
  11. package/benchmarks/pug.js +5 -3
  12. package/docs/api/async-render.md +88 -3
  13. package/docs/api/cache.md +90 -3
  14. package/docs/api/compile.md +131 -3
  15. package/docs/api/context-processors.md +80 -3
  16. package/docs/api/filters.md +223 -3
  17. package/docs/api/finder.md +97 -3
  18. package/docs/api/helpers.md +56 -3
  19. package/docs/api/i18n.md +160 -3
  20. package/docs/api/index.md +82 -28
  21. package/docs/api/libraries.md +210 -3
  22. package/docs/api/render-partial.md +84 -3
  23. package/docs/api/render.md +95 -3
  24. package/docs/api/security.md +148 -3
  25. package/docs/api/setup-express.md +78 -2
  26. package/docs/api/tags.md +138 -4
  27. package/docs/filter.md +0 -0
  28. package/docs/guide/advanced-usage.md +403 -6
  29. package/docs/guide/async-rendering.md +312 -4
  30. package/docs/guide/context-processors.md +261 -4
  31. package/docs/guide/custom-filters.md +315 -4
  32. package/docs/guide/custom-tags.md +275 -4
  33. package/docs/guide/filters.md +675 -3
  34. package/docs/guide/getting-started.md +109 -7
  35. package/docs/guide/installation.md +99 -4
  36. package/docs/guide/partial-templates.md +371 -4
  37. package/docs/guide/quick-start.md +228 -6
  38. package/docs/guide/security.md +348 -3
  39. package/docs/guide/tags.md +789 -6
  40. package/docs/guide/template-discovery.md +174 -4
  41. package/docs/guide/template-inheritance.md +277 -4
  42. package/docs/index.md +24 -42
  43. package/docs/integrations/elysia.md +4 -2
  44. package/docs/integrations/express.md +219 -219
  45. package/docs/integrations/fastify.md +4 -2
  46. package/docs/integrations/hono.md +4 -2
  47. package/docs/integrations/index.md +68 -68
  48. package/docs/integrations/koa.md +4 -2
  49. package/docs/integrations/nestjs.md +4 -2
  50. package/docs/integrations/tsed.md +4 -2
  51. package/docs/performance.md +45 -8
  52. package/ex.mjs +1 -1
  53. package/mkdocs.yml +0 -22
  54. package/overrides/main.html +1 -1
  55. package/package.json +1 -1
  56. package/requirements-docs.txt +2 -1
  57. package/src/codegen.js +905 -0
  58. package/src/context.js +42 -30
  59. package/src/filters.js +16 -0
  60. package/src/index.js +66 -61
  61. package/src/tags/control.js +15 -12
  62. package/src/utils.js +60 -0
  63. package/tests/filters.test.js +9 -0
  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,217 +1,437 @@
1
1
  # Filters API
2
2
 
3
+
4
+
3
5
  ## registerFilter
4
6
 
7
+
8
+
5
9
  Register a custom filter callable from templates as `{{ value|filter_name:arg }}`.
6
10
 
11
+
12
+
7
13
  === "CommonJS"
8
14
 
15
+
16
+
9
17
  ```javascript
18
+
10
19
  const { registerFilter } = require('miki-template');
11
20
 
21
+
22
+
12
23
  registerFilter('reverse', (val) => {
24
+
13
25
  return String(val).split('').reverse().join('');
26
+
14
27
  });
28
+
15
29
  ```
16
30
 
31
+
32
+
17
33
  === "ES Modules"
18
34
 
35
+
36
+
19
37
  ```javascript
38
+
20
39
  import { registerFilter } from 'miki-template';
21
40
 
41
+
42
+
22
43
  registerFilter('reverse', (val) => {
44
+
23
45
  return String(val).split('').reverse().join('');
46
+
24
47
  });
48
+
25
49
  ```
26
50
 
51
+
52
+
27
53
  ### Filter Signature
28
54
 
55
+
56
+
29
57
  Filters receive `(value, argument, context)` and must return a string (or `SafeString`):
30
58
 
59
+
60
+
31
61
  ```javascript
62
+
32
63
  registerFilter('greet', (val, greeting, context) => {
64
+
33
65
  return `${greeting}, ${val}!`;
66
+
34
67
  });
68
+
35
69
  ```
36
70
 
71
+
72
+
37
73
  ### Async Filters
38
74
 
75
+
76
+
39
77
  Filters that return a Promise are awaited automatically when using `asyncRender()`:
40
78
 
79
+
80
+
41
81
  === "CommonJS"
42
82
 
83
+
84
+
43
85
  ```javascript
86
+
44
87
  const { registerFilter, asyncRender } = require('miki-template');
45
88
 
89
+
90
+
46
91
  registerFilter('fetch_data', async (url) => {
92
+
47
93
  const res = await fetch(url);
94
+
48
95
  return res.text();
96
+
49
97
  });
50
98
 
99
+
100
+
51
101
  const html = await asyncRender('{{ endpoint|fetch_data }}', {
102
+
52
103
  endpoint: 'https://api.example.com/data'
104
+
53
105
  });
106
+
54
107
  ```
55
108
 
109
+
110
+
56
111
  === "ES Modules"
57
112
 
113
+
114
+
58
115
  ```javascript
116
+
59
117
  import { registerFilter, asyncRender } from 'miki-template';
60
118
 
119
+
120
+
61
121
  registerFilter('fetch_data', async (url) => {
122
+
62
123
  const res = await fetch(url);
124
+
63
125
  return res.text();
126
+
64
127
  });
65
128
 
129
+
130
+
66
131
  const html = await asyncRender('{{ endpoint|fetch_data }}', {
132
+
67
133
  endpoint: 'https://api.example.com/data'
134
+
68
135
  });
136
+
69
137
  ```
70
138
 
139
+
140
+
71
141
  ## getFilter
72
142
 
143
+
144
+
73
145
  Retrieve a registered filter by name.
74
146
 
147
+
148
+
75
149
  === "CommonJS"
76
150
 
151
+
152
+
77
153
  ```javascript
154
+
78
155
  const { getFilter } = require('miki-template);
79
156
 
157
+
158
+
80
159
  const reverseFilter = getFilter('reverse');
160
+
81
161
  console.log(reverseFilter('hello')); // 'olleh'
162
+
82
163
  ```
83
164
 
165
+
166
+
84
167
  === "ES Modules"
85
168
 
169
+
170
+
86
171
  ```javascript
172
+
87
173
  import { getFilter } from 'miki-template';
88
174
 
175
+
176
+
89
177
  const reverseFilter = getFilter('reverse');
178
+
90
179
  ```
91
180
 
181
+
182
+
92
183
  ## Built-in Filters
93
184
 
185
+
186
+
94
187
  ### Text Filters
95
188
 
189
+
190
+
96
191
  | Filter | Description |
192
+
97
193
  |--------|-------------|
194
+
98
195
  | `upper` | Uppercase |
196
+
99
197
  | `lower` | Lowercase |
198
+
100
199
  | `title` | Title case |
200
+
101
201
  | `capfirst` | Capitalize first character |
202
+
102
203
  | `truncatewords:N` | Truncate to N words |
204
+
103
205
  | `truncatechars:N` | Truncate to N characters (ellipsis) |
206
+
104
207
  | `truncatechars_html:N` | Truncate to N chars, preserving HTML |
208
+
105
209
  | `wordcount` | Count words |
210
+
211
+ | `repeat` | Repeat a string N times |
212
+ | `range` | Generate a range of integers |
106
213
  | `striptags` | Remove HTML tags |
214
+
215
+
107
216
  | `slugify` | Convert to URL-friendly slug |
217
+
108
218
  | `linebreaks` | Convert newlines to `<br>` and `<p>` |
219
+
109
220
  | `linebreaksbr` | Convert newlines to `<br>` |
221
+
110
222
  | `length_is:N` | Return length if equals N, else empty |
111
223
 
224
+
225
+
112
226
  ### HTML Filters
113
227
 
228
+
229
+
114
230
  | Filter | Description |
231
+
115
232
  |--------|-------------|
233
+
116
234
  | `safe` | Mark as safe (no escaping) |
235
+
117
236
  | `escape` | Force HTML escaping |
118
237
 
238
+
239
+
119
240
  ### List Filters
120
241
 
242
+
243
+
121
244
  | Filter | Description |
245
+
122
246
  |--------|-------------|
247
+
123
248
  | `length` | Number of items |
249
+
124
250
  | `join:sep` | Join items with separator |
251
+
125
252
  | `slice:"start:end"` | Slice a list/string |
253
+
126
254
  | `dictsort:"key"` | Sort by key (ascending) |
255
+
127
256
  | `dictsortreversed:"key"` | Sort by key (descending) |
257
+
128
258
  | `sort` | Sort items |
259
+
129
260
  | `unique` | Remove duplicates |
261
+
130
262
  | `random` | Random item |
263
+
131
264
  | `reverse` | Reverse order |
265
+
132
266
  | `split:sep` | Split string into list |
267
+
133
268
  | `replace:"old,new"` | Replace occurrences |
134
269
 
270
+
271
+
135
272
  ### Default Filters
136
273
 
274
+
275
+
137
276
  | Filter | Description |
277
+
138
278
  |--------|-------------|
279
+
139
280
  | `default:"fallback"` | Show fallback if value is falsy |
281
+
140
282
  | `default_if_none:"fallback"` | Show fallback if value is `null`/`undefined` |
283
+
141
284
  | `firstof:v1 v2 v3` | First non-empty value |
142
285
 
286
+
287
+
143
288
  ### Date/Time Filters
144
289
 
290
+
291
+
145
292
  | Filter | Description |
293
+
146
294
  |--------|-------------|
295
+
147
296
  | `date:"Y-m-d"` | Django-style date format |
297
+
148
298
  | `time:"H:i"` | Django-style time format |
299
+
149
300
  | `date_format:"yyyy-MM-dd"` | Intl-style date format |
301
+
150
302
  | `strftime:"PPPP"` | Intl-style time format |
303
+
151
304
  | `timesince` | Time since date ("2 hours ago") |
305
+
152
306
  | `timeuntil` | Time until date |
307
+
153
308
  | `ago` | Short time-ago ("2m", "3h") |
309
+
154
310
  | `until` | Short time-until |
311
+
155
312
  | `time_diff:other_date` | Difference between two dates |
156
313
 
314
+
315
+
157
316
  ### Numeric Filters
158
317
 
318
+
319
+
159
320
  | Filter | Description |
321
+
160
322
  |--------|-------------|
323
+
161
324
  | `add:N` | Add N |
325
+
162
326
  | `sub:N` | Subtract N |
327
+
163
328
  | `mult:N` | Multiply by N |
329
+
164
330
  | `divisibleby:N` | Check divisibility |
331
+
165
332
  | `mod:N` | Modulo |
333
+
166
334
  | `floatformat:N` | Format float with N decimals |
335
+
167
336
  | `square` | Square a number |
337
+
168
338
  | `sqrt` | Square root |
339
+
169
340
  | `abs` | Absolute value |
341
+
170
342
  | `round:N` | Round to N decimals |
343
+
171
344
  | `floor` | Floor |
345
+
172
346
  | `ceil` | Ceiling |
347
+
173
348
  | `min:N` | Minimum of value and N |
349
+
174
350
  | `max:N` | Maximum of value and N |
351
+
175
352
  | `sum` | Sum of list |
353
+
176
354
  | `average` | Average of list |
177
355
 
356
+
357
+
178
358
  ### Currency and Data Formatting
179
359
 
360
+
361
+
180
362
  | Filter | Description |
363
+
181
364
  |--------|-------------|
365
+
182
366
  | `currency:"$"` | Format as currency |
367
+
183
368
  | `phone_number` | Format phone number |
369
+
184
370
  | `email` | Format as email link |
371
+
185
372
  | `url` | Format as URL link |
373
+
186
374
  | `mask:"*"` | Mask sensitive data |
375
+
187
376
  | `whatsapp_link:"msg"` | Generate WhatsApp link |
377
+
188
378
  | `credit_card` | Format credit card number |
379
+
189
380
  | `ssn` | Format SSN |
381
+
190
382
  | `ip_address` | Format IP address |
383
+
191
384
  | `uuid` | Format UUID |
385
+
192
386
  | `filesizeformat` | Human-readable file size |
387
+
193
388
  | `yesno:"yes,no,maybe"` | Yes/no based on boolean |
389
+
194
390
  | `pluralize:"s"` | Add plural suffix if needed |
391
+
195
392
  | `urlencode` | URL-encode |
393
+
196
394
  | `escapeuri` | Escape URI component |
395
+
197
396
  | `stringformat:"%s"` | String format |
397
+
198
398
  | `cut:"text"` | Remove occurrences |
399
+
199
400
  | `addslashes` | Add slashes |
401
+
200
402
  | `removetags:"p,div"` | Remove specified tags |
403
+
201
404
  | `trans:"key"` | Translate key |
405
+
202
406
  | `regroup:"attr"` | Regroup list by attribute |
407
+
203
408
  | `json` | Serialize to JSON |
409
+
204
410
  | `urlize` | Auto-link URLs in text |
205
411
 
412
+
413
+
206
414
  ### Encoding Filters
207
415
 
416
+
417
+
208
418
  | Filter | Description |
419
+
209
420
  |--------|-------------|
421
+
210
422
  | `base64_encode` | Base64 encode |
423
+
211
424
  | `base64_decode` | Base64 decode |
212
425
 
426
+
427
+
213
428
  ## Next Steps
214
429
 
215
- - [Filters Guide](../guide/filters)
216
- - [Custom Filters](../guide/custom-filters)
217
- - [API Reference](../)
430
+
431
+
432
+ - [Filters Guide](../guide/filters.md)
433
+
434
+ - [Custom Filters](../guide/custom-filters.md)
435
+
436
+ - [API Reference](../index.md)
437
+
@@ -1,94 +1,188 @@
1
- # Finder API
1
+ # Finder API
2
+
3
+
2
4
 
3
5
  ## findTemplateInViews
4
6
 
7
+
8
+
5
9
  Find a template file by name in the provided views directories.
6
10
 
11
+
12
+
7
13
  === "CommonJS"
8
14
 
15
+
16
+
9
17
  ```javascript
18
+
10
19
  const { findTemplateInViews } = require('miki-template');
11
20
 
21
+
22
+
12
23
  const found = findTemplateInViews('home', ['./views', './app/templates']);
24
+
13
25
  console.log(found);
26
+
14
27
  // Output: /absolute/path/to/home.html
28
+
15
29
  ```
16
30
 
31
+
32
+
17
33
  === "ES Modules"
18
34
 
35
+
36
+
19
37
  ```javascript
38
+
20
39
  import { findTemplateInViews } from 'miki-template';
21
40
 
41
+
42
+
22
43
  const found = findTemplateInViews('home', ['./views', './app/templates']);
44
+
23
45
  console.log(found);
46
+
24
47
  ```
25
48
 
49
+
50
+
26
51
  ### Parameters
27
52
 
53
+
54
+
28
55
  | Parameter | Type | Description |
56
+
29
57
  |-----------|------|-------------|
58
+
30
59
  | `templateName` | `string` | Template name to search for (with or without extension) |
60
+
31
61
  | `viewsDirs` | `string[]` | Array of views directories to search |
32
62
 
63
+
64
+
33
65
  ### Returns
34
66
 
67
+
68
+
35
69
  `string | null` — Absolute path to the template file, or `null` if not found.
36
70
 
71
+
72
+
37
73
  ### Behavior
38
74
 
75
+
76
+
39
77
  - Searches recursively through subdirectories for bare template names (no `/` in name).
78
+
40
79
  - Tries `.html` and `.miki` extensions when no extension is provided.
80
+
41
81
  - Also searches app-style `templates/` directories nested inside the views root.
82
+
42
83
  - Returns the **first match found**.
43
84
 
85
+
86
+
44
87
  ## setAppTemplateDirNames
45
88
 
89
+
90
+
46
91
  Configure which directory names are treated as app-style template directories.
47
92
 
93
+
94
+
48
95
  === "CommonJS"
49
96
 
97
+
98
+
50
99
  ```javascript
100
+
51
101
  const { setAppTemplateDirNames } = require('miki-template');
52
102
 
103
+
104
+
53
105
  setAppTemplateDirNames(['templates', 'views', 'pages']);
106
+
54
107
  ```
55
108
 
109
+
110
+
56
111
  === "ES Modules"
57
112
 
113
+
114
+
58
115
  ```javascript
116
+
59
117
  import { setAppTemplateDirNames } from 'miki-template';
60
118
 
119
+
120
+
61
121
  setAppTemplateDirNames(['templates', 'views', 'pages']);
122
+
62
123
  ```
63
124
 
125
+
126
+
64
127
  ### Parameters
65
128
 
129
+
130
+
66
131
  | Parameter | Type | Description |
132
+
67
133
  |-----------|------|-------------|
134
+
68
135
  | `names` | `string \| string[]` | Directory name(s) to recognize |
69
136
 
137
+
138
+
70
139
  ## getAppTemplateDirNames
71
140
 
141
+
142
+
72
143
  Get the current app template directory names.
73
144
 
145
+
146
+
74
147
  === "CommonJS"
75
148
 
149
+
150
+
76
151
  ```javascript
152
+
77
153
  const { getAppTemplateDirNames } = require('miki-template');
78
154
 
155
+
156
+
79
157
  console.log(getAppTemplateDirNames());
158
+
80
159
  // ['templates']
160
+
81
161
  ```
82
162
 
163
+
164
+
83
165
  === "ES Modules"
84
166
 
167
+
168
+
85
169
  ```javascript
170
+
86
171
  import { getAppTemplateDirNames } from 'miki-template';
87
172
 
173
+
174
+
88
175
  console.log(getAppTemplateDirNames());
176
+
89
177
  ```
90
178
 
179
+
180
+
91
181
  ## Next Steps
92
182
 
93
- - [Guide: Smart Template Discovery](../guide/template-discovery)
94
- - [API Reference](../)
183
+
184
+
185
+ - [Guide: Smart Template Discovery](../guide/template-discovery.md)
186
+
187
+ - [API Reference](../index.md)
188
+
@@ -1,53 +1,106 @@
1
- # Helpers API
1
+ # Helpers API
2
+
3
+
2
4
 
3
5
  ## registerHelper
4
6
 
7
+
8
+
5
9
  Register a helper function that can be called from templates.
6
10
 
11
+
12
+
7
13
  === "CommonJS"
8
14
 
15
+
16
+
9
17
  ```javascript
18
+
10
19
  const { registerHelper } = require('miki-template');
11
20
 
21
+
22
+
12
23
  registerHelper('bold', (inner, context) => `<b>${inner}</b>`);
24
+
13
25
  ```
14
26
 
27
+
28
+
15
29
  === "ES Modules"
16
30
 
31
+
32
+
17
33
  ```javascript
34
+
18
35
  import { registerHelper } from 'miki-template';
19
36
 
37
+
38
+
20
39
  registerHelper('bold', (inner, context) => `<b>${inner}</b>`);
40
+
21
41
  ```
22
42
 
43
+
44
+
23
45
  ### Helper Signature
24
46
 
47
+
48
+
25
49
  Helpers receive `(content, context)` where `content` is the rendered inner content of the tag:
26
50
 
51
+
52
+
27
53
  ```javascript
54
+
28
55
  registerHelper('panel', (content, context) => {
56
+
29
57
  return `<div class="panel">${content}</div>`;
58
+
30
59
  });
60
+
31
61
  ```
32
62
 
63
+
64
+
33
65
  Usage in templates:
34
66
 
67
+
68
+
35
69
  ```html
70
+
36
71
  {% panel %}
72
+
37
73
  <h2>{{ title }}</h2>
74
+
38
75
  <p>{{ description }}</p>
76
+
39
77
  {% endpanel %}
78
+
40
79
  ```
41
80
 
81
+
82
+
42
83
  ## Built-in Helpers
43
84
 
85
+
86
+
44
87
  miki-template includes built-in helpers for common formatting tasks:
45
88
 
89
+
90
+
46
91
  - `bold` — Wrap content in `<b>` tags
92
+
47
93
  - `italic` — Wrap content in `<i>` tags
94
+
48
95
  - `underline` — Wrap content in `<u>` tags
49
96
 
97
+
98
+
50
99
  ## Next Steps
51
100
 
52
- - [Custom Tags](../guide/custom-tags)
53
- - [API Reference](../)
101
+
102
+
103
+ - [Custom Tags](../guide/custom-tags.md)
104
+
105
+ - [API Reference](../index.md)
106
+