miki-template 2.0.1 → 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 +13 -37
- package/.github/workflows/docs.yml +105 -0
- package/.github/workflows/npm-publish-github-packages.yml +36 -0
- package/README.md +69 -14
- package/assets/logo.png +0 -0
- package/benchmarks/ejs-results.json +4 -4
- package/benchmarks/handlebars-results.json +6 -6
- package/benchmarks/miki-results.json +4 -4
- package/benchmarks/pug-results.json +4 -4
- 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/mkdocs.yml +217 -0
- package/overrides/main.html +26 -0
- package/overrides/partials/footer.html +9 -0
- package/package.json +4 -2
- package/requirements-docs.txt +1 -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/integrations.md +0 -214
- 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
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Smart Template Discovery
|
|
2
|
+
|
|
3
|
+
miki-template includes a Django-inspired template finder that searches your project structure intelligently. You no longer need to manually configure every views directory or worry about `Failed to lookup view` errors when templates live in nested app folders.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [How It Works](#how-it-works)
|
|
8
|
+
- [Supported Layouts](#supported-layouts)
|
|
9
|
+
- [Express Integration](#express-integration)
|
|
10
|
+
- [Custom Template Directory Names](#custom-template-directory-names)
|
|
11
|
+
- [Manual Lookup](#manual-lookup)
|
|
12
|
+
- [ESM Import](#esm-import)
|
|
13
|
+
- [Next Steps](#next-steps)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## How It Works
|
|
18
|
+
|
|
19
|
+
When you call `res.render('name')` or `render('name', ctx, { views })`, miki-template:
|
|
20
|
+
|
|
21
|
+
1. Resolves the view name against the configured views directories.
|
|
22
|
+
2. Searches recursively through subdirectories for matching files.
|
|
23
|
+
3. Recognizes app-style `templates/` directories automatically.
|
|
24
|
+
4. Supports custom directory names via `setAppTemplateDirNames()`.
|
|
25
|
+
|
|
26
|
+
The search order is:
|
|
27
|
+
|
|
28
|
+
1. **Direct resolution** — if you pass `nested/path`, it resolves relative to each views root.
|
|
29
|
+
2. **Recursive search** — if you pass a bare name like `home`, the engine walks subdirectories searching for `home.html` or `home.miki`.
|
|
30
|
+
3. **App-style directories** — directories named `templates` (or whatever you configure) are treated as additional view roots at any depth.
|
|
31
|
+
|
|
32
|
+
## Supported Layouts
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
project/
|
|
36
|
+
├── views/
|
|
37
|
+
│ └── home.html
|
|
38
|
+
├── app/
|
|
39
|
+
│ └── templates/
|
|
40
|
+
│ └── dashboard.html
|
|
41
|
+
├── packages/
|
|
42
|
+
│ └── admin/
|
|
43
|
+
│ └── templates/
|
|
44
|
+
│ └── settings.html
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
All of these are discoverable without extra configuration.
|
|
48
|
+
|
|
49
|
+
## Express Integration
|
|
50
|
+
|
|
51
|
+
When you use `setupExpress()`, the engine automatically expands your views roots to include all directories that contain template files:
|
|
52
|
+
|
|
53
|
+
=== "CommonJS"
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
const express = require('express');
|
|
57
|
+
const miki = require('miki-template');
|
|
58
|
+
|
|
59
|
+
const app = express();
|
|
60
|
+
miki.setupExpress(app, { extension: 'html', views: './views' });
|
|
61
|
+
|
|
62
|
+
// Templates placed deeply in your project are found automatically:
|
|
63
|
+
app.get('/', (req, res) => res.render('home'));
|
|
64
|
+
app.get('/admin', (req, res) => res.render('settings'));
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
=== "ES Modules"
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
import express from 'express';
|
|
71
|
+
import miki from 'miki-template';
|
|
72
|
+
|
|
73
|
+
const app = express();
|
|
74
|
+
miki.setupExpress(app, { extension: 'html', views: './views' });
|
|
75
|
+
|
|
76
|
+
app.get('/', (req, res) => res.render('home'));
|
|
77
|
+
app.get('/admin', (req, res) => res.render('settings'));
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
You can also pass multiple roots:
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
miki.setupExpress(app, {
|
|
84
|
+
extension: 'html',
|
|
85
|
+
views: ['./views', './app/templates', './packages/*/templates']
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Custom Template Directory Names
|
|
90
|
+
|
|
91
|
+
If your project uses a different convention than `templates`, configure it globally:
|
|
92
|
+
|
|
93
|
+
=== "CommonJS"
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
const { setAppTemplateDirNames } = require('miki-template');
|
|
97
|
+
|
|
98
|
+
setAppTemplateDirNames(['templates', 'views', 'pages']);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
=== "ES Modules"
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
import { setAppTemplateDirNames } from 'miki-template';
|
|
105
|
+
|
|
106
|
+
setAppTemplateDirNames(['templates', 'views', 'pages']);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This affects both Express integration and manual `render()` / `findTemplateInViews()` calls.
|
|
110
|
+
|
|
111
|
+
## Manual Lookup
|
|
112
|
+
|
|
113
|
+
You can use the finder directly:
|
|
114
|
+
|
|
115
|
+
=== "CommonJS"
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
const { findTemplateInViews } = require('miki-template');
|
|
119
|
+
|
|
120
|
+
const found = findTemplateInViews('home', [
|
|
121
|
+
'./views',
|
|
122
|
+
'./app/templates'
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
console.log(found);
|
|
126
|
+
// Output: /absolute/path/to/home.html
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
=== "ES Modules"
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
import { findTemplateInViews } from 'miki-template';
|
|
133
|
+
|
|
134
|
+
const found = findTemplateInViews('home', [
|
|
135
|
+
'./views',
|
|
136
|
+
'./app/templates'
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
console.log(found);
|
|
140
|
+
// Output: /absolute/path/to/home.html
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Finder Behavior
|
|
144
|
+
|
|
145
|
+
- Searches recursively through subdirectories for bare template names.
|
|
146
|
+
- Tries `.html` and `.miki` extensions when no extension is provided.
|
|
147
|
+
- Also searches app-style `templates/` directories nested inside the views root.
|
|
148
|
+
- Returns the first match found, or `null` if not found.
|
|
149
|
+
|
|
150
|
+
### ESM Import
|
|
151
|
+
|
|
152
|
+
=== "ES Modules"
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
import { findTemplateInViews, setAppTemplateDirNames } from 'miki-template';
|
|
156
|
+
|
|
157
|
+
// Set custom directory names
|
|
158
|
+
setAppTemplateDirNames(['templates', 'app_templates']);
|
|
159
|
+
|
|
160
|
+
// Find a template
|
|
161
|
+
const path = findTemplateInViews('detail', ['./views', './packages']);
|
|
162
|
+
console.log(path);
|
|
163
|
+
// → /absolute/path/to/packages/product/templates/detail.html
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Next Steps
|
|
167
|
+
|
|
168
|
+
- [Partial Templates](./partial-templates)
|
|
169
|
+
- [Template Inheritance](./template-inheritance)
|
|
170
|
+
- [Integrations: Express](../integrations/express)
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# Template Inheritance
|
|
2
|
+
|
|
3
|
+
miki-template supports Django-style template inheritance via `{% extends %}` and `{% block %}`. This lets you build layout hierarchies where child templates override parent blocks.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Basic Inheritance](#basic-inheritance)
|
|
8
|
+
- [block.super](#blocksuper)
|
|
9
|
+
- [Multi-Level Inheritance](#multi-level-inheritance)
|
|
10
|
+
- [Rendering a Single Block](#rendering-a-single-block)
|
|
11
|
+
- [block Default Behavior](#block-default-behavior)
|
|
12
|
+
- [Path Traversal Protection](#path-traversal-protection)
|
|
13
|
+
- [Smart Template Discovery for Inheritance](#smart-template-discovery-for-inheritance)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Basic Inheritance
|
|
18
|
+
|
|
19
|
+
### base.html
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<!DOCTYPE html>
|
|
23
|
+
<html>
|
|
24
|
+
<head>
|
|
25
|
+
<title>{% block title %}Default Title{% endblock %}</title>
|
|
26
|
+
</head>
|
|
27
|
+
<body>
|
|
28
|
+
<header>{% block header %}Default Header{% endblock %}</header>
|
|
29
|
+
<main>{% block content %}Default Content{% endblock %}</main>
|
|
30
|
+
<footer>{% block footer %}Default Footer{% endblock %}</footer>
|
|
31
|
+
</body>
|
|
32
|
+
</html>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### child.html
|
|
36
|
+
|
|
37
|
+
```html
|
|
38
|
+
{% extends "base.html" %}
|
|
39
|
+
|
|
40
|
+
{% block title %}My Page{% endblock %}
|
|
41
|
+
|
|
42
|
+
{% block content %}
|
|
43
|
+
<h1>Hello, {{ user.name }}!</h1>
|
|
44
|
+
{% for item in items %}
|
|
45
|
+
<p>{{ item }}</p>
|
|
46
|
+
{% endfor %}
|
|
47
|
+
{% endblock %}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Key behaviors:**
|
|
51
|
+
|
|
52
|
+
- The child template's text outside `{% block %}` tags is **ignored** — only the blocks are used to override the parent.
|
|
53
|
+
- Any blocks not overridden in the child use the parent's default content.
|
|
54
|
+
- The parent is located using the `views` option (or Express's `views` directory).
|
|
55
|
+
|
|
56
|
+
## block.super
|
|
57
|
+
|
|
58
|
+
Inside a block, `{{ block.super }}` renders the parent template's version of that block. This is useful for augmentation rather than replacement.
|
|
59
|
+
|
|
60
|
+
=== "Example"
|
|
61
|
+
|
|
62
|
+
```html
|
|
63
|
+
{% extends "base.html" %}
|
|
64
|
+
|
|
65
|
+
{% block content %}
|
|
66
|
+
<h1>My Content</h1>
|
|
67
|
+
{{ block.super }}
|
|
68
|
+
{% endblock %}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
If `base.html`'s content block is `<p>Original</p>`, the output is:
|
|
72
|
+
|
|
73
|
+
```html
|
|
74
|
+
<h1>My Content</h1>
|
|
75
|
+
<p>Original</p>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Real-world sidebar that adds to the parent:**
|
|
79
|
+
|
|
80
|
+
```html
|
|
81
|
+
<!-- base.html -->
|
|
82
|
+
{% block sidebar %}
|
|
83
|
+
<ul class="nav">
|
|
84
|
+
<li><a href="/">Home</a></li>
|
|
85
|
+
</ul>
|
|
86
|
+
{% endblock %}
|
|
87
|
+
|
|
88
|
+
<!-- admin.html -->
|
|
89
|
+
{% extends "base.html" %}
|
|
90
|
+
{% block sidebar %}
|
|
91
|
+
{{ block.super }}
|
|
92
|
+
<li><a href="/admin">Admin Panel</a></li>
|
|
93
|
+
{% endblock %}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Multi-Level Inheritance
|
|
97
|
+
|
|
98
|
+
Inheritance chains can be arbitrarily deep:
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
base.html
|
|
102
|
+
└── child.html
|
|
103
|
+
└── grandchild.html
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Each level can override blocks from its parent, and `{{ block.super }}` traverses the chain correctly.
|
|
107
|
+
|
|
108
|
+
### Three-level example
|
|
109
|
+
|
|
110
|
+
**base.html:**
|
|
111
|
+
|
|
112
|
+
```html
|
|
113
|
+
<html>
|
|
114
|
+
<body>
|
|
115
|
+
{% block content %}Base content{% endblock %}
|
|
116
|
+
</body>
|
|
117
|
+
</html>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**child.html:**
|
|
121
|
+
|
|
122
|
+
```html
|
|
123
|
+
{% extends "base.html" %}
|
|
124
|
+
|
|
125
|
+
{% block content %}
|
|
126
|
+
<h2>Child content</h2>
|
|
127
|
+
{{ block.super }}
|
|
128
|
+
{% endblock %}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**grandchild.html:**
|
|
132
|
+
|
|
133
|
+
```html
|
|
134
|
+
{% extends "child.html" %}
|
|
135
|
+
|
|
136
|
+
{% block content %}
|
|
137
|
+
<h1>Grandchild content</h1>
|
|
138
|
+
{{ block.super }}
|
|
139
|
+
{% endblock %}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Rendering `grandchild.html` produces:
|
|
143
|
+
|
|
144
|
+
```html
|
|
145
|
+
<html>
|
|
146
|
+
<body>
|
|
147
|
+
<h1>Grandchild content</h1>
|
|
148
|
+
<h2>Child content</h2>
|
|
149
|
+
Base content
|
|
150
|
+
</body>
|
|
151
|
+
</html>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Rendering a Single Block
|
|
155
|
+
|
|
156
|
+
Compile a template and render only one block — useful for AJAX or HTMX responses where you only need a portion of the page:
|
|
157
|
+
|
|
158
|
+
=== "CommonJS"
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
const { compile } = require('miki-template');
|
|
162
|
+
|
|
163
|
+
const compiled = compile(childTemplateStr, { views: './templates' });
|
|
164
|
+
const partialHtml = compiled.renderBlock('content', context);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
=== "ES Modules"
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
import { compile } from 'miki-template';
|
|
171
|
+
|
|
172
|
+
const compiled = compile(childTemplateStr, { views: './templates' });
|
|
173
|
+
const partialHtml = compiled.renderBlock('content', context);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### renderBlock behavior
|
|
177
|
+
|
|
178
|
+
- If the block is not found, throws `Block 'blockName' not found in template`.
|
|
179
|
+
- If the block has no overrides, renders the default body.
|
|
180
|
+
- If the block has overrides, renders the child-most block first, then traverses up for `{{ block.super }}`.
|
|
181
|
+
|
|
182
|
+
**Real-world HTMX use case:**
|
|
183
|
+
|
|
184
|
+
```html
|
|
185
|
+
<!-- layout.html -->
|
|
186
|
+
{% block main %}
|
|
187
|
+
<div id="main-content">
|
|
188
|
+
<!-- default content -->
|
|
189
|
+
</div>
|
|
190
|
+
{% endblock %}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
// Return only the main block for an AJAX update
|
|
195
|
+
app.get('/ajax/content', (req, res) => {
|
|
196
|
+
const compiled = miki.compile(template, { views: './views' });
|
|
197
|
+
res.send(compiled.renderBlock('main', { user: req.user }));
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## block Default Behavior
|
|
202
|
+
|
|
203
|
+
If a child template does not override a block, the parent's default content is rendered:
|
|
204
|
+
|
|
205
|
+
```html
|
|
206
|
+
<!-- base.html -->
|
|
207
|
+
<html>
|
|
208
|
+
<body>
|
|
209
|
+
{% block sidebar %}Default sidebar{% endblock %}
|
|
210
|
+
</body>
|
|
211
|
+
</html>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
```html
|
|
215
|
+
<!-- child.html -->
|
|
216
|
+
{% extends "base.html" %}
|
|
217
|
+
|
|
218
|
+
{% block content %}Main content{% endblock %}
|
|
219
|
+
<!-- sidebar block is not overridden, so "Default sidebar" is used -->
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Dynamic extends
|
|
223
|
+
|
|
224
|
+
You can use expressions in `extends` for device-specific or conditional layouts:
|
|
225
|
+
|
|
226
|
+
```html
|
|
227
|
+
{% extends device_type|default:"base.html" %}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
```html
|
|
231
|
+
{% extends user.theme|default:"default.html" %}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Path Traversal Protection
|
|
235
|
+
|
|
236
|
+
`{% extends %}` and `{% include %}` paths are validated to prevent directory traversal attacks:
|
|
237
|
+
|
|
238
|
+
```html
|
|
239
|
+
{% extends "../../etc/passwd" %} {# REJECTED #}
|
|
240
|
+
{% include "../../secrets" %} {# REJECTED #}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The engine checks that resolved paths stay within the allowed views directories. An `Error` with message starting `path traversal` is thrown if a path escapes the views root.
|
|
244
|
+
|
|
245
|
+
## Smart Template Discovery for Inheritance
|
|
246
|
+
|
|
247
|
+
When using `setupExpress()`, the engine automatically searches for parent templates in:
|
|
248
|
+
|
|
249
|
+
- The configured `views` directory
|
|
250
|
+
- Nested `templates/` directories inside the views root
|
|
251
|
+
- Subdirectories of the views root
|
|
252
|
+
- App-style `app/templates/...`, `packages/*/templates/...`, etc.
|
|
253
|
+
|
|
254
|
+
This means you can organize templates like:
|
|
255
|
+
|
|
256
|
+
```text
|
|
257
|
+
project/
|
|
258
|
+
├── views/
|
|
259
|
+
│ ├── base.html
|
|
260
|
+
│ └── home.html
|
|
261
|
+
├── app/
|
|
262
|
+
│ └── templates/
|
|
263
|
+
│ └── admin/
|
|
264
|
+
│ └── dashboard.html
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
And `{% extends "base.html" %}` will be found regardless of where the child template lives.
|
|
268
|
+
|
|
269
|
+
## Next Steps
|
|
270
|
+
|
|
271
|
+
- [Partial Templates](./partial-templates)
|
|
272
|
+
- [Tags: extends and block](./tags#inheritance-tags)
|
|
273
|
+
- [Template Discovery](./template-discovery)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# What is miki-template?
|
|
2
|
+
|
|
3
|
+
miki-template is a **Django-style template engine for Node.js and Express**. It brings Django's beloved template language — variables, filters, tags, inheritance, and partials — to the Node.js ecosystem with first-class Express integration and modern JavaScript support.
|
|
4
|
+
|
|
5
|
+
## Philosophy
|
|
6
|
+
|
|
7
|
+
miki-template is designed for developers who love Django's template syntax but want the speed and flexibility of Node.js. It prioritizes:
|
|
8
|
+
|
|
9
|
+
- **Developer experience**: Familiar Django syntax, excellent error messages, and sensible defaults.
|
|
10
|
+
- **Performance**: Compiled AST rendering that scales to large, real-world templates.
|
|
11
|
+
- **Modern Node.js**: Full ESM and CommonJS support, async rendering, and compatibility with current Express/Koa/Fastify/Hono/Elysia versions.
|
|
12
|
+
- **Security**: Auto-escaping, safe strings, and Django-style security primitives built in.
|
|
13
|
+
|
|
14
|
+
## Key Concepts
|
|
15
|
+
|
|
16
|
+
- **Templates** are text files using `{{ variables }}`, `{% tags %}`, and `| filters`.
|
|
17
|
+
- **Partials** let you define reusable components with `{% partialdef %}` and render them by name.
|
|
18
|
+
- **Template inheritance** uses `{% extends %}` and `{% block %}` to build layout hierarchies.
|
|
19
|
+
- **Smart discovery** finds templates across `views/`, `app/templates/`, and nested folders automatically.
|
|
20
|
+
|
|
21
|
+
## Who is it for?
|
|
22
|
+
|
|
23
|
+
miki-template is a great fit if you:
|
|
24
|
+
|
|
25
|
+
- Prefer Django-style templates over JSX or pure string concatenation.
|
|
26
|
+
- Need **partial rendering** for HTMX or AJAX-heavy apps.
|
|
27
|
+
- Want a template engine that **scales** without rewriting templates as your app grows.
|
|
28
|
+
- Value **security** and want XSS protection by default.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Why miki-template?
|
|
2
|
+
|
|
3
|
+
There are plenty of template engines for Node.js. Here is why miki-template stands out.
|
|
4
|
+
|
|
5
|
+
## Django Syntax You Already Know
|
|
6
|
+
|
|
7
|
+
If you have used Django, you already know miki-template. The syntax is intentionally aligned:
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
{% if user.is_admin %}
|
|
11
|
+
<p>Welcome, {{ user.name|title }}!</p>
|
|
12
|
+
{% elif user.is_staff %}
|
|
13
|
+
<p>Staff dashboard</p>
|
|
14
|
+
{% else %}
|
|
15
|
+
<p>Please log in.</p>
|
|
16
|
+
{% endif %}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
No new DSL to learn. No context switching between backend and frontend templating styles.
|
|
20
|
+
|
|
21
|
+
## Built for HTMX and Partial Responses
|
|
22
|
+
|
|
23
|
+
Modern web apps increasingly use HTMX, Turbo, or custom AJAX. miki-template makes partial rendering trivial:
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
app.get('/card/:id', (req, res) =>
|
|
27
|
+
res.render(`home#card`, { title: 'Hello', body: '...' })
|
|
28
|
+
);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
No extra middleware. No manual view resolution. Just `view#partial`.
|
|
32
|
+
|
|
33
|
+
## Smart Template Discovery
|
|
34
|
+
|
|
35
|
+
Forget `Failed to lookup view` errors. miki-template searches your project structure intelligently:
|
|
36
|
+
|
|
37
|
+
- `views/`
|
|
38
|
+
- `app/templates/`
|
|
39
|
+
- `packages/*/templates/`
|
|
40
|
+
- Any custom folder name you configure
|
|
41
|
+
|
|
42
|
+
This mirrors Django's `APP_DIRS` behavior and means templates can live where they make sense in your codebase.
|
|
43
|
+
|
|
44
|
+
## Performance That Scales
|
|
45
|
+
|
|
46
|
+
miki-template's compiled AST approach is especially fast on realistic templates — the ones with loops, conditionals, filters, and partials that make up real pages.
|
|
47
|
+
|
|
48
|
+
| Template | miki-template | pug | handlebars | ejs |
|
|
49
|
+
|----------|--------------|-----|------------|-----|
|
|
50
|
+
| Small | ~115k rps | 1.7M rps | 417k rps | 182k rps |
|
|
51
|
+
| Medium | ~454k rps | 625k rps | 48k rps | 29k rps |
|
|
52
|
+
| Large | **~476k rps** | 3.1k rps | 661 rps | 290 rps |
|
|
53
|
+
|
|
54
|
+
On large templates, miki-template is **~150× faster** than pug, handlebars, and ejs.
|
|
55
|
+
|
|
56
|
+
## Security by Default
|
|
57
|
+
|
|
58
|
+
- Auto-escaping enabled by default.
|
|
59
|
+
- SafeString wrapper for explicit bypass.
|
|
60
|
+
- CSRF and CSP tags included.
|
|
61
|
+
- No `eval()` or unsafe code execution.
|
|
62
|
+
|
|
63
|
+
## First-Class Express Integration
|
|
64
|
+
|
|
65
|
+
One function wires everything:
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
miki.setupExpress(app, { extension: 'html', views: './views' });
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
That is it. No `app.engine()` boilerplate. No manual `res.render` patching. Partial responses work out of the box.
|
|
72
|
+
|
|
73
|
+
## Extensible and Future-Proof
|
|
74
|
+
|
|
75
|
+
Need a custom tag? A custom filter? The API is clean and well-documented. miki-template is built to grow with your app, not lock you into a fragile abstraction.
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<div class="md-hero">
|
|
2
|
+
<h1 class="md-hero__title">miki-template</h1>
|
|
3
|
+
<p class="md-hero__subtitle">Django-style template magic for Node.js — blazing fast partials, smart template discovery, and zero friction for HTMX.</p>
|
|
4
|
+
<div class="md-hero__buttons">
|
|
5
|
+
<a href="guide/quick-start" class="md-button md-button--primary">Get Started</a>
|
|
6
|
+
<a href="api/" class="md-button">API Reference</a>
|
|
7
|
+
<a href="https://github.com/alainmiki/miki-template" class="md-button" target="_blank" rel="noopener">
|
|
8
|
+
<span class="md-icon">💾</span> GitHub
|
|
9
|
+
</a>
|
|
10
|
+
</div>
|
|
11
|
+
<div style="margin-top: 1.5rem; display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; position: relative; align-items: center;">
|
|
12
|
+
<img src="assets/banner.png" alt="miki-template banner" style="max-width: 100%; height: auto; border-radius: 0.5rem; box-shadow: 0 4px 12px var(--md-shadow-color); max-height: 200px;">
|
|
13
|
+
</div>
|
|
14
|
+
<div style="margin-top: 1rem; display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; position: relative; align-items: center;">
|
|
15
|
+
<a href="https://www.npmjs.com/package/miki-template" target="_blank" rel="noopener">
|
|
16
|
+
<img src="https://img.shields.io/npm/v/miki-template.svg" alt="npm version" style="height: 20px;">
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://www.npmjs.com/package/miki-template" target="_blank" rel="noopener">
|
|
19
|
+
<img src="https://img.shields.io/npm/dm/miki-template.svg" alt="npm downloads" style="height: 20px;">
|
|
20
|
+
</a>
|
|
21
|
+
<a href="https://github.com/alainmiki/miki-template" target="_blank" rel="noopener">
|
|
22
|
+
<img src="https://img.shields.io/github/actions/workflow/status/alainmiki/miki-template/ci.yml?branch=main" alt="CI status" style="height: 20px;">
|
|
23
|
+
</a>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="md-typeset">
|
|
28
|
+
|
|
29
|
+
## Why miki-template?
|
|
30
|
+
|
|
31
|
+
miki-template brings Django's beloved template language to Node.js and Express. Define reusable partials with `{% partialdef %}`, render any slice of a page with `render('home#card')`, and let the engine find templates across your whole project — `templates/`, `app/templates/`, or whatever structure you prefer.
|
|
32
|
+
|
|
33
|
+
- **Partial-powered templating**: `{% partialdef %}` blocks render by name anywhere — `res.render('home#card')`, `renderPartialFromSource(...)`, or `compiled.renderBlock('block')`. Built for HTMX-style partial responses.
|
|
34
|
+
- **Smart template discovery**: Stop hardcoding view paths. The engine searches `templates/`, nested app directories, and custom folder names automatically — just like Django.
|
|
35
|
+
- **One-line Express integration**: `miki.setupExpress(app, { extension: 'html', views: dir })` wires everything up. No boilerplate, no extra middleware.
|
|
36
|
+
- **Full Django syntax parity**: Variables, dotted lookups, filters (`|`), block tags (`{% %}`), template inheritance with `extends` and `block.super`.
|
|
37
|
+
- **Blazing fast**: Compiled AST rendering dominates on realistic pages — ~150× faster than pug, handlebars, and ejs on large templates.
|
|
38
|
+
- **ESM & CommonJS**: Works seamlessly with both `import` and `require` syntax.
|
|
39
|
+
- **Security by default**: Auto-escaping, `SafeString`, CSRF and CSP tags.
|
|
40
|
+
|
|
41
|
+
## Quick example
|
|
42
|
+
|
|
43
|
+
=== "CommonJS"
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
const express = require('express');
|
|
47
|
+
const miki = require('miki-template');
|
|
48
|
+
|
|
49
|
+
const app = express();
|
|
50
|
+
miki.setupExpress(app, { extension: 'html', views: './views' });
|
|
51
|
+
|
|
52
|
+
app.get('/', (req, res) => res.render('home', { user: req.user }));
|
|
53
|
+
app.get('/partials/:name', (req, res) =>
|
|
54
|
+
res.render(`home#${req.params.name}`, { user: req.user })
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
app.listen(3000);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
=== "ES Modules"
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
import express from 'express';
|
|
64
|
+
import miki from 'miki-template';
|
|
65
|
+
|
|
66
|
+
const app = express();
|
|
67
|
+
miki.setupExpress(app, { extension: 'html', views: './views' });
|
|
68
|
+
|
|
69
|
+
app.get('/', (req, res) => res.render('home', { user: req.user }));
|
|
70
|
+
app.get('/partials/:name', (req, res) =>
|
|
71
|
+
res.render(`home#${req.params.name}`, { user: req.user })
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
app.listen(3000);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm install miki-template
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
See the [Installation guide](guide/installation) for pnpm, yarn, and Bun instructions.
|
|
84
|
+
|
|
85
|
+
## Documentation
|
|
86
|
+
|
|
87
|
+
- [What is miki-template?](guide/what-is-miki-template)
|
|
88
|
+
- [Getting Started](guide/getting-started)
|
|
89
|
+
- [Quick Start](guide/quick-start)
|
|
90
|
+
- [Filters](guide/filters)
|
|
91
|
+
- [Tags](guide/tags)
|
|
92
|
+
- [Partial Templates](guide/partial-templates)
|
|
93
|
+
- [Template Inheritance](guide/template-inheritance)
|
|
94
|
+
- [Template Discovery](guide/template-discovery)
|
|
95
|
+
- [Security](guide/security)
|
|
96
|
+
- [Integrations](integrations/)
|
|
97
|
+
- [API Reference](api/)
|
|
98
|
+
- [Performance](performance)
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT
|
|
103
|
+
|
|
104
|
+
</div>
|