miki-template 1.2.0 → 1.3.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.
- package/.github/release-notes/v1.3.1.md +55 -0
- package/CHANGELOG.md +72 -0
- package/README.md +43 -26
- package/assets/banner.png +0 -0
- package/benchmarks/stress.mjs +647 -0
- package/dir/base.html +23 -0
- package/dir/cmpnt.html +11 -0
- package/dir/footer.html +3 -0
- package/dir/home.html +80 -0
- package/dir/navbar.html +9 -0
- package/docs/api.md +20 -3
- package/docs/filters.md +301 -133
- package/docs/partialdef.md +30 -1
- package/docs/tags.md +63 -0
- package/docs/usage.md +50 -3
- package/eslint.config.mjs +9 -1
- package/ex.mjs +33 -0
- package/miki-template-extension/.github/workflows/ci.yml +116 -0
- package/miki-template-extension/.vscodeignore +7 -0
- package/miki-template-extension/CHANGELOG.md +99 -0
- package/miki-template-extension/README.md +244 -53
- package/miki-template-extension/extension.js +1013 -0
- package/miki-template-extension/icon.png +0 -0
- package/miki-template-extension/miki-template-1.7.1.vsix +0 -0
- package/miki-template-extension/package.json +244 -10
- package/miki-template-extension/snippets/miki-template.json +612 -72
- package/miki-template-extension/syntaxes/language-configuration.json +101 -13
- package/miki-template-extension/syntaxes/miki-template.tmLanguage.json +270 -61
- package/miki-template-extension/tests/grammar-tests.json +162 -0
- package/miki-template-extension/tests/run-grammar-tests.js +82 -0
- package/package.json +7 -4
- package/scripts/build-vsix.js +129 -0
- package/scripts/build-vsix.ps1 +15 -0
- package/src/cache.js +41 -2
- package/src/context.js +9 -5
- package/src/context_processors.js +9 -2
- package/src/esm.mjs +12 -0
- package/src/filters.js +472 -24
- package/src/index.js +571 -85
- package/src/lexer.js +76 -54
- package/src/libraries.js +134 -3
- package/src/parser.js +22 -2
- package/src/security.js +4 -2
- package/src/tags/control.js +150 -21
- package/src/tags/extra.js +154 -0
- package/src/tags/i18n.js +49 -23
- package/src/tags/inheritance.js +142 -23
- package/src/tags/util.js +102 -24
- package/tests/esm.test.mjs +37 -2
- package/tests/filters.test.js +155 -0
- package/tests/integration/README.md +32 -0
- package/tests/integration/features.test.cjs +1681 -0
- package/tests/integration/features.test.mjs +1697 -0
- package/tests/integration/templates/base.miki +6 -0
- package/tests/integration/templates/child.miki +6 -0
- package/tests/integration/templates/index.html +17 -0
- package/tests/parser.test.js +5 -3
- package/tests/partialdef.test.js +40 -1
- package/tests/tags.test.js +30 -0
- package/miki-template-1.2.0.vsix +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# miki-template v1.3.1
|
|
2
|
+
|
|
3
|
+
## Highlights
|
|
4
|
+
|
|
5
|
+
- **One-line Express integration**: `miki.setupExpress(app, { extension: 'html', views: dir })` wires the view engine, the views directory, and a `res.render` shim that makes HTMX-style partial responses Just Work.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
const express = require('express');
|
|
9
|
+
const miki = require('miki-template');
|
|
10
|
+
|
|
11
|
+
const app = express();
|
|
12
|
+
miki.setupExpress(app, { extension: 'html', views: './views' });
|
|
13
|
+
|
|
14
|
+
// Full page
|
|
15
|
+
app.get('/', (req, res) => res.render('home', { user: req.user }));
|
|
16
|
+
|
|
17
|
+
// Partial — just append `#partialName` to the view name
|
|
18
|
+
app.get('/partials/:name', (req, res) =>
|
|
19
|
+
res.render(`home#${req.params.name}`, { user: req.user })
|
|
20
|
+
);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- **`miki.expressPartialRenderer()` middleware** for drop-in `res.renderPartial(view, locals)` without changing your existing `app.engine()` setup.
|
|
24
|
+
- **`miki.express()`** factory for `app.engine('html', miki.express())` — honors `view#partial` selectors.
|
|
25
|
+
- **`renderPartialFromSource(source, partialName, ctx, opts)`** and **`renderPartialFromFile(...)`** — render a single named `{% partialdef %}` from a string or file. Walks the AST (and the `extends` chain) to find partials nested inside blocks.
|
|
26
|
+
- **`{% include "file.html#partial" with ... %}`** — partial-selector syntax works with `include`, not just `res.render`.
|
|
27
|
+
- **Built-in libraries** (`humanize`, `cache`, `lorem`) are now auto-activated on module load. `{% lorem 5 p %}` works without `{% load lorem %}`.
|
|
28
|
+
|
|
29
|
+
## Tag fixes
|
|
30
|
+
|
|
31
|
+
- `{% with x=1, y=2 as pair %}` (multi-pair + alias) parses correctly.
|
|
32
|
+
- `{% with a=x b=y %}` (multi-pair, no alias) now binds each pair instead of being treated as one expression.
|
|
33
|
+
- `{% cycle 'a' 'b' as name %}` now emits nothing but stores the value (Django semantics).
|
|
34
|
+
- `{{ block.super }}` works inside partials that override blocks.
|
|
35
|
+
- `extends` resolves `views` from `options.settings.views` (Express path), `options.views`, or default.
|
|
36
|
+
- Path-traversal protection applies to `extends` *and* `include`, including `include "file#partial"`.
|
|
37
|
+
- `default` vs `default_if_none`: `default` falls back on `''`, `null`, and `undefined`; `default_if_none` only on `null`/`undefined` (Django parity).
|
|
38
|
+
- `removetags` accepts multiple tag names.
|
|
39
|
+
- `Context.get('a.b.c')` returns `undefined` for missing keys (was `''`), so `{% if x %}` and `default_if_none` work correctly.
|
|
40
|
+
|
|
41
|
+
## Test coverage
|
|
42
|
+
|
|
43
|
+
**382/382 tests pass.** Added a 190-test integration suite (`tests/integration/`) covering every tag, filter, and feature under both CommonJS and ESM, exercised against a real Express HTTP server on ephemeral ports. The test runner recursively picks up `.test.{cjs,mjs,js}` files.
|
|
44
|
+
|
|
45
|
+
## Docs
|
|
46
|
+
|
|
47
|
+
- `README.md` — promoted `miki.setupExpress` to the first listed feature; the Express section leads with the one-liner.
|
|
48
|
+
- `docs/usage.md` — full Express section rewritten with the new setup, options table, and middleware variant.
|
|
49
|
+
- `docs/api.md` — added rows for the new APIs; updated CJS/ESM import snippets.
|
|
50
|
+
- `docs/partialdef.md` — documented `include "file#partial"`, `renderPartialFromSource/File`, and the HTMX-over-HTTP flow.
|
|
51
|
+
- `CHANGELOG.md` — full entry for 1.3.1.
|
|
52
|
+
|
|
53
|
+
## Compatibility
|
|
54
|
+
|
|
55
|
+
No breaking changes. All existing public APIs continue to work; new APIs are opt-in.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,77 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.3.3] - 2026-09-04
|
|
4
|
+
### Fixed
|
|
5
|
+
- **`date` and `time` filters** — multi-character tokens (`yyyy`, `MM`, `dd`, `HH`, `mm`, `ii`, `ss`) now work correctly. Previously `"yyyy-MM-dd"` rendered as `"24242424-JunJun-1515"` because each character was treated as an independent token. The fix uses longest-first token matching and adds backward-compatible single-character aliases (`m`/`d`/`H`/`i`/`s` for Django-style unpadded values).
|
|
6
|
+
|
|
7
|
+
### Performance
|
|
8
|
+
- **Inheritance parent-source cache** — `{% extends %}` no longer re-reads the parent template from disk on every render. A 64-entry LRU cache in `src/cache.js` (`getParentSource` / `hasParentSource`) memoises the parent source. Inheritance rendering improved from ~481 rps to **~2,056 rps** in the stress benchmark (4.3× faster, no functional change).
|
|
9
|
+
|
|
10
|
+
### Tooling
|
|
11
|
+
- **`benchmarks/stress.mjs`** — comprehensive 37-check stress benchmark covering correctness, compile speed, render speed, cache behavior, scale, endurance, partial rendering, async, and concurrency. Run with `node benchmarks/stress.mjs`. Exits with code 1 on any failure.
|
|
12
|
+
- **`eslint.config.mjs`** — added `URL`, `URLSearchParams`, `TextEncoder`, `TextDecoder`, `fetch`, `crypto`, `performance` to the Node 18+ globals list so `no-undef` no longer flags standard Web APIs.
|
|
13
|
+
- Removed dead code flagged by the linter: unused `elifBranches` variable in `parseIfChanged`, unused `extractPluralMappings` function in `i18n.js`. Renamed unused tag-parser `parser` parameters to `_parser` in `lorem` library and `parseLoad`.
|
|
14
|
+
|
|
15
|
+
### Verification
|
|
16
|
+
- `npx eslint src/**/*.js` — 0 errors, 0 warnings
|
|
17
|
+
- `npm test` — 397/397 passing
|
|
18
|
+
- `node benchmarks/stress.mjs` — 37/37 passing
|
|
19
|
+
|
|
20
|
+
## [1.3.1] - 2026-09-03
|
|
21
|
+
### Highlights
|
|
22
|
+
- **One-line Express integration**: `miki.setupExpress(app, { extension: 'html', views: dir })` — wires the view engine, `views` directory, and a `res.render` shim that lets you do `res.render('home#card', ...)` for HTMX-style partial responses. No more boilerplate, no extra middleware.
|
|
23
|
+
- **Render-any-partial-from-string**: `renderPartialFromSource(source, partialName, ctx, opts)` loads a template string and returns only the named `{% partialdef %}` body. This is what powers `res.render('view#partial')` and HTMX responses.
|
|
24
|
+
- **True built-in libraries**: `humanize`, `cache`, and `lorem` are now auto-activated on module load — `{% lorem %}` works without `{% load lorem %}`. Existing libraries now also expose helpers, not just tags.
|
|
25
|
+
- **Comprehensive integration test suite**: 382 tests covering every tag, filter, and feature under both CommonJS and ESM, all running against a **real Express HTTP server** on ephemeral ports.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- `miki.setupExpress(app, { extension, views, async })` — one-line Express bootstrap. Sets `view engine`, registers the engine, and patches `res.render` to handle `view#partial` selectors. Replaces the need to call `app.engine()`, `app.set('views')`, and `app.set('view engine')` manually.
|
|
29
|
+
- `miki.express()` — factory that returns a view-engine function suitable for `app.engine(...)`. Honors `view#partial` suffixes.
|
|
30
|
+
- `miki.expressPartialRenderer()` — middleware that adds `res.renderPartial(view, locals)`. Useful as a drop-in HTMX helper.
|
|
31
|
+
- `renderPartialFromSource(source, partialName, context, options)` — render a single named partial from a template string. Resolves `extends` chains so partials defined inside `{% block %}` tags are discoverable. Walks the AST and registers every `PartialDefNode` it finds, even when nested inside `{% for %}` or `{% if %}` blocks that won't render during partial lookup.
|
|
32
|
+
- `renderPartialFromFile(filePath, partialName, context, options)` — file-based convenience wrapper.
|
|
33
|
+
- `Context.get(name)` now returns `undefined` for missing keys (was `''`). This lets `default_if_none` distinguish "not provided" from an explicit empty string, matching Django.
|
|
34
|
+
- `escapeHtml(value, force = false)` — added a `force` flag so the `|escape` filter re-escapes even a `SafeString` (Django parity).
|
|
35
|
+
- `Context` constructor now supports `partialDefs` and `options` for cloning the partial registry.
|
|
36
|
+
- New exports: `setupExpress`, `express`, `expressPartialRenderer`, `renderPartialFromFile`, `renderPartialFromSource`, `clearContextProcessors`.
|
|
37
|
+
- ESM surface (`src/esm.mjs`) now exposes all the new helpers, with the same `default` + named import shape.
|
|
38
|
+
- Lorem library now ships a real `{% lorem N method %}` tag, a `lorem` filter, plus helpers. Built-in and auto-activated.
|
|
39
|
+
- `library.activate(name)` now also activates helpers and `registerTag` functions, not just filters.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
- Context processors now follow Django semantics: **existing context values win** over processor defaults. If you render with `{ user: req.user }` and a processor returns `{ user: 'Guest' }`, the explicit value is preserved.
|
|
43
|
+
- `__express` and `__expressAsync` now detect a `#partial` suffix in the view name and delegate to `renderPartialFromSource` automatically. This makes `app.engine('html', miki.__express)` already HTMX-ready — `setupExpress` just adds convenience.
|
|
44
|
+
- `cache.getCompiled` ignores function-valued and `undefined` options when building cache keys, so passing the same `urlHelper` function to multiple `compile()` calls no longer causes cache misses.
|
|
45
|
+
- Lexer rewritten with brace-depth counting for more reliable `{{ }}` and `{% %}` tokenization in templates with nested braces, escaped braces, and unusual whitespace.
|
|
46
|
+
- Filters now receive the rendering `context` as a final argument, so custom filters can read other context variables (e.g. for locale-aware formatting).
|
|
47
|
+
- `for` and `with` tags fully reworked for the user-reported edge cases:
|
|
48
|
+
- `{% with x=1, y=2 as pair %}` — pair list followed by an alias.
|
|
49
|
+
- `{% with value=expr %}` — single pair (no comma).
|
|
50
|
+
- `{% with x=1, y=2 %}` — pure pair list (no alias).
|
|
51
|
+
- Quoted values containing commas (`{% with a="x,y" %}`) parse correctly.
|
|
52
|
+
- Pair values may be context variables.
|
|
53
|
+
- `partial` now accepts `with` kwargs and overrides context for the partial scope.
|
|
54
|
+
- `include` now supports `file#partial` syntax — the same partial-selector pattern works with `{% include %}` as with `res.render`.
|
|
55
|
+
- `url` tag respects `urlHelper` more cleanly: kwargs (a trailing object literal) are passed as the last argument instead of being conflated with positional args.
|
|
56
|
+
- `static` tag normalizes leading slashes; prefix can be customized.
|
|
57
|
+
- `csrf_token` and `csp_nonce_attr` outputs are properly escaped to prevent attribute injection.
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
- `with a=x b=y` (multi-pair) was previously treated as a single value/expression. Now correctly binds each pair.
|
|
61
|
+
- `cycle` with `as` form emits nothing but stores the value, matching Django.
|
|
62
|
+
- `block.super` works inside partials that override blocks.
|
|
63
|
+
- `extends` resolves `views` from `options.settings.views` (Express's normal path), `options.views`, or the default.
|
|
64
|
+
- Path traversal protection now applies to `extends` *and* `include`, including `include "file#partial"` form.
|
|
65
|
+
- `if` conditions handle dotted lookup, function auto-call, and missing variables without throwing.
|
|
66
|
+
- `default` vs `default_if_none`: `default` falls back on empty string, `null`, and `undefined`; `default_if_none` only on `null`/`undefined`. Both now match Django exactly.
|
|
67
|
+
- `removetags` filter now accepts multiple tag names.
|
|
68
|
+
- `safe` / `escape` filters correctly re-escape `SafeString` only when forced.
|
|
69
|
+
- `Context.get('a.b.c')` no longer stringifies `undefined` to `''`, fixing `{% if x %}` checks against missing variables.
|
|
70
|
+
- Cache key collisions when passing the same template string with different function-valued options (e.g. `urlHelper`).
|
|
71
|
+
|
|
72
|
+
### Compatibility
|
|
73
|
+
- No breaking changes. All existing public APIs continue to work. New APIs are opt-in.
|
|
74
|
+
|
|
3
75
|
## [1.2.0] - 2026-09-01
|
|
4
76
|
### Added
|
|
5
77
|
- Full **ESM** support via `src/esm.mjs` wrapper and conditional `package.json` exports.
|
package/README.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# miki-template
|
|
2
|
+

|
|
2
3
|
 
|
|
3
4
|
A robust, production-ready template engine that brings **Django's template language** features and syntax to Node.js and Express, fully compliant with modern JavaScript (ES6+), CommonJS, and **ESM** (`import`) support.
|
|
4
5
|
|
|
@@ -6,9 +7,11 @@ A robust, production-ready template engine that brings **Django's template langu
|
|
|
6
7
|
|
|
7
8
|
## 🚀 Features
|
|
8
9
|
|
|
10
|
+
- **One-line Express integration**: `miki.setupExpress(app, { extension: 'html', views: dir })` — wires the engine, views directory, and a `res.render` shim that makes `res.render('home#card', ...)` Just Work for HTMX-style partial responses. **No boilerplate, no extra middleware.**
|
|
11
|
+
- **Partial responses out of the box**: `{% partialdef %}` blocks can be rendered by name with `res.render('view#partial', ...)`, `miki.expressPartialRenderer()` middleware (`res.renderPartial(...)`), or `renderPartialFromSource(...)`.
|
|
9
12
|
- **Full Syntax Parity**: Supports variables, dotted lookups, filters (`|`), and block tags (`{% %}`).
|
|
10
13
|
- **Template Inheritance**: Multi-level inheritance with `extends`, block overrides, and `{{ block.super }}` support.
|
|
11
|
-
- **
|
|
14
|
+
- **Built-in libraries**: `humanize`, `cache`, and `lorem` ship pre-activated. `{% lorem 5 p %}` works without `{% load lorem %}`.
|
|
12
15
|
- **ESM & CommonJS**: Works seamlessly with both `import` and `require` syntax.
|
|
13
16
|
- **Security by Default**: Auto-escaping enabled by default with a `SafeString` wrapper.
|
|
14
17
|
- **CSRF & CSP Support**: Native tags for `{% csrf_token %}` and `{% csp_nonce_attr %}` to keep apps secure out-of-the-box.
|
|
@@ -107,47 +110,61 @@ console.log(result); // Output: "Hello World!"
|
|
|
107
110
|
|
|
108
111
|
### Express Integration
|
|
109
112
|
|
|
110
|
-
**
|
|
113
|
+
**The recommended, one-line setup** — wires the view engine, views directory, and partial responses in a single call:
|
|
114
|
+
|
|
111
115
|
```javascript
|
|
112
116
|
const express = require('express');
|
|
113
|
-
const
|
|
117
|
+
const miki = require('miki-template');
|
|
114
118
|
|
|
115
119
|
const app = express();
|
|
120
|
+
miki.setupExpress(app, { extension: 'html', views: './views' });
|
|
116
121
|
|
|
117
|
-
//
|
|
118
|
-
app.
|
|
119
|
-
app.engine('miki', renderDtpl);
|
|
120
|
-
app.set('view engine', 'miki');
|
|
121
|
-
app.set('views', './views');
|
|
122
|
+
// Full page
|
|
123
|
+
app.get('/', (req, res) => res.render('home', { user: req.user }));
|
|
122
124
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
});
|
|
128
|
-
});
|
|
125
|
+
// HTMX / partial response — just append `#partialName` to the view name
|
|
126
|
+
app.get('/partials/:name', (req, res) =>
|
|
127
|
+
res.render(`home#${req.params.name}`, { user: req.user })
|
|
128
|
+
);
|
|
129
129
|
|
|
130
|
-
app.listen(3000
|
|
130
|
+
app.listen(3000);
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
> `setupExpress` calls `app.engine()`, `app.set('views')`, and `app.set('view engine')` for you, and patches `res.render` so `view#partial` is dispatched to the partial renderer (not the file system). It works equally well for `.miki` files — just pass `extension: 'miki'`.
|
|
134
|
+
|
|
135
|
+
**The classic, fully manual setup still works** if you prefer it:
|
|
136
|
+
|
|
134
137
|
```javascript
|
|
135
|
-
|
|
136
|
-
|
|
138
|
+
const express = require('express');
|
|
139
|
+
const { __express } = require('miki-template');
|
|
137
140
|
|
|
138
141
|
const app = express();
|
|
139
|
-
app.engine('html',
|
|
142
|
+
app.engine('html', __express);
|
|
140
143
|
app.set('view engine', 'html');
|
|
141
144
|
app.set('views', './views');
|
|
145
|
+
```
|
|
142
146
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
**ESM:**
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
import express from 'express';
|
|
151
|
+
import miki from 'miki-template';
|
|
152
|
+
|
|
153
|
+
const app = express();
|
|
154
|
+
miki.setupExpress(app, { extension: 'html', views: './views' });
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Async Express 5+:**
|
|
158
|
+
```javascript
|
|
159
|
+
miki.setupExpress(app, { extension: 'html', views: './views', async: true });
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Or, if you only want partial responses** without changing your engine registration, add the middleware:
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
app.use(miki.expressPartialRenderer());
|
|
149
166
|
|
|
150
|
-
app.
|
|
167
|
+
app.get('/card', (req, res) => res.renderPartial('home#card', { user: req.user }));
|
|
151
168
|
```
|
|
152
169
|
|
|
153
170
|
---
|
|
Binary file
|