miki-template 2.0.0 → 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 +14 -10
- package/.github/workflows/docs.yml +105 -0
- package/.github/workflows/npm-publish-github-packages.yml +36 -0
- package/README.md +142 -26
- package/assets/logo.png +0 -0
- package/benchmarks/ejs-results.json +17 -0
- package/benchmarks/ejs.js +36 -0
- package/benchmarks/handlebars-results.json +17 -0
- package/benchmarks/handlebars.js +48 -0
- package/benchmarks/miki-results.json +17 -0
- package/benchmarks/miki.js +36 -0
- package/benchmarks/pug-results.json +17 -0
- package/benchmarks/pug.js +36 -0
- package/benchmarks/run.js +69 -37
- 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/live-test/integrations/elysia-example.js +16 -0
- package/live-test/integrations/express-example.js +24 -0
- package/live-test/integrations/fastify-example.js +20 -0
- package/live-test/integrations/hono-example.js +16 -0
- package/live-test/integrations/koa-example.js +30 -0
- package/live-test/integrations/nestjs-example.js +25 -0
- package/live-test/integrations/smoke-test.js +166 -0
- package/live-test/integrations/tsed-example.js +23 -0
- package/live-test/package-lock.json +235 -0
- package/live-test/package.json +4 -0
- package/live-test/views/home.html +17 -0
- package/mkdocs.yml +217 -0
- package/overrides/main.html +26 -0
- package/overrides/partials/footer.html +9 -0
- package/package.json +16 -6
- package/requirements-docs.txt +1 -0
- package/tests/integration/partial-render.test.cjs +13 -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/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,81 @@
|
|
|
1
|
+
# TSDX / TSed (Ts.ED)
|
|
2
|
+
|
|
3
|
+
Use miki-template with Ts.ED (TSed) by wiring it into the underlying Express or Koa adapter.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
Ts.ED can run on Express or Koa. When using the Express adapter, obtain the raw Express app and call `setupExpress()`.
|
|
8
|
+
|
|
9
|
+
=== "TypeScript"
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// server.ts
|
|
13
|
+
import { ServerLoader } from '@tsed/di';
|
|
14
|
+
import * as miki from 'miki-template';
|
|
15
|
+
|
|
16
|
+
async function bootstrap() {
|
|
17
|
+
const server = await ServerLoader.bootstrap();
|
|
18
|
+
// server.rawApp is the underlying Express/Koa instance depending on adapter
|
|
19
|
+
miki.setupExpress(server.rawApp, {
|
|
20
|
+
extension: 'html',
|
|
21
|
+
views: './views'
|
|
22
|
+
});
|
|
23
|
+
await server.listen();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
bootstrap();
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
=== "CommonJS"
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
const { ServerLoader } = require('@tsed/di');
|
|
33
|
+
const miki = require('miki-template');
|
|
34
|
+
|
|
35
|
+
async function bootstrap() {
|
|
36
|
+
const server = await ServerLoader.bootstrap();
|
|
37
|
+
miki.setupExpress(server.rawApp, {
|
|
38
|
+
extension: 'html',
|
|
39
|
+
views: './views'
|
|
40
|
+
});
|
|
41
|
+
await server.listen();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
bootstrap();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Using in Controllers
|
|
48
|
+
|
|
49
|
+
Once wired, use `res.render()` in your Ts.ED controllers:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { Controller, Get, Req, Res } from '@tsed/common';
|
|
53
|
+
import { Request, Response } from 'express';
|
|
54
|
+
|
|
55
|
+
@Controller('/')
|
|
56
|
+
export class AppController {
|
|
57
|
+
@Get('/')
|
|
58
|
+
renderHome(@Req() req: Request, @Res() res: Response) {
|
|
59
|
+
res.render('home', { user: req.user });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@Get('/partials/:name')
|
|
63
|
+
renderPartial(@Req() req: Request, @Res() res: Response) {
|
|
64
|
+
res.render(`home#${req.params.name}`, { user: req.user });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Partial Rendering
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
@Get('/cards/:id')
|
|
73
|
+
renderCard(@Req() req: Request, @Res() res: Response) {
|
|
74
|
+
res.render(`home#card`, { user: req.user });
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Next Steps
|
|
79
|
+
|
|
80
|
+
- [Integrations Overview](../)
|
|
81
|
+
- [API Reference: setupExpress](../api/setup-express)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
document$.subscribe(function () {
|
|
2
|
+
console.log("miki-template docs initialized");
|
|
3
|
+
|
|
4
|
+
const navbar = document.querySelector(".md-header");
|
|
5
|
+
if (navbar) {
|
|
6
|
+
let lastScroll = 0;
|
|
7
|
+
const scrollThreshold = 50;
|
|
8
|
+
|
|
9
|
+
window.addEventListener("scroll", function () {
|
|
10
|
+
const currentScroll = window.pageYOffset;
|
|
11
|
+
|
|
12
|
+
if (currentScroll <= 0) {
|
|
13
|
+
navbar.style.boxShadow = "0 2px 8px var(--md-shadow-color)";
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (currentScroll > lastScroll && currentScroll > scrollThreshold) {
|
|
18
|
+
navbar.style.transform = "translateY(-100%)";
|
|
19
|
+
navbar.style.transition = "transform 0.3s ease";
|
|
20
|
+
} else {
|
|
21
|
+
navbar.style.transform = "translateY(0)";
|
|
22
|
+
navbar.style.transition = "transform 0.3s ease";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
lastScroll = currentScroll;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
document.querySelectorAll(".md-clipboard").forEach(function (button) {
|
|
30
|
+
button.addEventListener("click", async function () {
|
|
31
|
+
const code = this.closest(".highlight").querySelector("code").innerText;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await navigator.clipboard.writeText(code);
|
|
35
|
+
const originalHTML = this.innerHTML;
|
|
36
|
+
this.innerHTML = '<span class="md-icon">✓</span> Copied!';
|
|
37
|
+
this.style.color = "var(--md-accent-fg-color)";
|
|
38
|
+
this.style.opacity = "1";
|
|
39
|
+
|
|
40
|
+
setTimeout(function () {
|
|
41
|
+
button.innerHTML = originalHTML;
|
|
42
|
+
button.style.color = "";
|
|
43
|
+
button.style.opacity = "";
|
|
44
|
+
}, 2000);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error("Failed to copy: ", err);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
document.querySelectorAll(".md-typeset a[href^='#']").forEach(function (anchor) {
|
|
52
|
+
anchor.addEventListener("click", function (e) {
|
|
53
|
+
const targetId = this.getAttribute("href");
|
|
54
|
+
if (targetId === "#") return;
|
|
55
|
+
|
|
56
|
+
const target = document.querySelector(targetId);
|
|
57
|
+
if (target) {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
const headerOffset = 80;
|
|
60
|
+
const elementPosition = target.getBoundingClientRect().top;
|
|
61
|
+
const offsetPosition = elementPosition + window.pageYOffset - headerOffset;
|
|
62
|
+
|
|
63
|
+
window.scrollTo({
|
|
64
|
+
top: offsetPosition,
|
|
65
|
+
behavior: "smooth",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
history.replaceState(null, null, targetId);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
document.querySelectorAll(".md-typeset .task-list-item input[type='checkbox']").forEach(function (checkbox) {
|
|
74
|
+
checkbox.addEventListener("change", function () {
|
|
75
|
+
const label = this.closest(".task-list-item");
|
|
76
|
+
if (this.checked) {
|
|
77
|
+
label.style.opacity = "0.6";
|
|
78
|
+
label.style.textDecoration = "line-through";
|
|
79
|
+
} else {
|
|
80
|
+
label.style.opacity = "1";
|
|
81
|
+
label.style.textDecoration = "none";
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
document.querySelectorAll(".md-tabs__link").forEach(function (tab) {
|
|
87
|
+
tab.addEventListener("click", function () {
|
|
88
|
+
document.querySelectorAll(".md-tabs__link").forEach(function (t) {
|
|
89
|
+
t.style.transform = "scale(1)";
|
|
90
|
+
});
|
|
91
|
+
this.style.transform = "scale(1.05)";
|
|
92
|
+
setTimeout(function () {
|
|
93
|
+
tab.style.transform = "scale(1)";
|
|
94
|
+
}, 200);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
document.querySelectorAll(".md-typeset .tabbed-set > label").forEach(function (tabLabel) {
|
|
99
|
+
tabLabel.addEventListener("click", function () {
|
|
100
|
+
this.style.transform = "scale(0.98)";
|
|
101
|
+
setTimeout(function () {
|
|
102
|
+
tabLabel.style.transform = "scale(1)";
|
|
103
|
+
}, 100);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const searchInput = document.querySelector(".md-search__input");
|
|
108
|
+
if (searchInput) {
|
|
109
|
+
searchInput.addEventListener("focus", function () {
|
|
110
|
+
this.parentElement.style.boxShadow = "0 0 0 3px var(--md-accent-fg-color), 0 4px 12px var(--md-shadow-color)";
|
|
111
|
+
this.parentElement.style.transition = "box-shadow 0.2s ease";
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
searchInput.addEventListener("blur", function () {
|
|
115
|
+
this.parentElement.style.boxShadow = "";
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
document.querySelectorAll(".md-typeset .admonition").forEach(function (admonition) {
|
|
120
|
+
admonition.addEventListener("mouseenter", function () {
|
|
121
|
+
this.style.transform = "translateY(-2px)";
|
|
122
|
+
this.style.transition = "transform 0.2s ease";
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
admonition.addEventListener("mouseleave", function () {
|
|
126
|
+
this.style.transform = "translateY(0)";
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
document.querySelectorAll(".md-typeset .md-button").forEach(function (button) {
|
|
131
|
+
button.addEventListener("mouseenter", function () {
|
|
132
|
+
this.style.transform = "translateY(-2px)";
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
button.addEventListener("mouseleave", function () {
|
|
136
|
+
this.style.transform = "translateY(0)";
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
document.querySelectorAll(".md-content__inner table:not([class]) tbody tr").forEach(function (row) {
|
|
141
|
+
row.addEventListener("mouseenter", function () {
|
|
142
|
+
this.style.transition = "background-color 0.2s ease";
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const observerOptions = {
|
|
147
|
+
root: null,
|
|
148
|
+
rootMargin: "0px",
|
|
149
|
+
threshold: 0.1,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const observer = new IntersectionObserver(function (entries) {
|
|
153
|
+
entries.forEach(function (entry) {
|
|
154
|
+
if (entry.isIntersecting) {
|
|
155
|
+
entry.target.style.opacity = "1";
|
|
156
|
+
entry.target.style.transform = "translateY(0)";
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}, observerOptions);
|
|
160
|
+
|
|
161
|
+
document.querySelectorAll(".md-typeset h2, .md-typeset h3, .md-typeset .admonition").forEach(function (el) {
|
|
162
|
+
el.style.opacity = "0";
|
|
163
|
+
el.style.transform = "translateY(10px)";
|
|
164
|
+
el.style.transition = "opacity 0.5s ease, transform 0.5s ease";
|
|
165
|
+
observer.observe(el);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
setTimeout(function () {
|
|
169
|
+
document.querySelectorAll(".md-typeset h2, .md-typeset h3, .md-typeset .admonition").forEach(function (el) {
|
|
170
|
+
el.style.opacity = "1";
|
|
171
|
+
el.style.transform = "translateY(0)";
|
|
172
|
+
});
|
|
173
|
+
}, 100);
|
|
174
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Performance
|
|
2
|
+
|
|
3
|
+
miki-template is built for real-world apps. Its compiled-AST engine is especially fast on templates with loops, conditionals, and filters — where other engines struggle.
|
|
4
|
+
|
|
5
|
+
## Benchmark Results
|
|
6
|
+
|
|
7
|
+
Renders per second (higher is better):
|
|
8
|
+
|
|
9
|
+
| Template | miki-template | pug | handlebars | ejs |
|
|
10
|
+
|----------|--------------|-----|------------|-----|
|
|
11
|
+
| Small | ~115k rps | 1.7M rps | 417k rps | 182k rps |
|
|
12
|
+
| Medium | ~454k rps | 625k rps | 48k rps | 29k rps |
|
|
13
|
+
| Large | **~476k rps** | 3.1k rps | 661 rps | 290 rps |
|
|
14
|
+
|
|
15
|
+
## Key Takeaways
|
|
16
|
+
|
|
17
|
+
- On **small** templates, miki-template has overhead from the compiled AST approach.
|
|
18
|
+
- On **medium** templates, miki-template is competitive with pug.
|
|
19
|
+
- On **large/realistic** templates (the ones that matter in production), miki-template **dominates by ~150×**.
|
|
20
|
+
|
|
21
|
+
## Why miki-template Wins on Real Templates
|
|
22
|
+
|
|
23
|
+
- **Compiled AST**: Templates are compiled once and reused, avoiding repeated parsing.
|
|
24
|
+
- **No runtime interpretation**: No string concatenation or function reconstruction per render.
|
|
25
|
+
- **Optimized loops and filters**: for-loops and filters are implemented as efficient node traversals.
|
|
26
|
+
- **Cache-friendly**: Compiled templates are cached by default.
|
|
27
|
+
|
|
28
|
+
## Running Benchmarks
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm run bench
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Next Steps
|
|
35
|
+
|
|
36
|
+
- [Getting Started](../guide/getting-started)
|
|
37
|
+
- [API Reference](../api/)
|