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.
Files changed (87) hide show
  1. package/.github/workflows/ci.yml +14 -10
  2. package/.github/workflows/docs.yml +105 -0
  3. package/.github/workflows/npm-publish-github-packages.yml +36 -0
  4. package/README.md +142 -26
  5. package/assets/logo.png +0 -0
  6. package/benchmarks/ejs-results.json +17 -0
  7. package/benchmarks/ejs.js +36 -0
  8. package/benchmarks/handlebars-results.json +17 -0
  9. package/benchmarks/handlebars.js +48 -0
  10. package/benchmarks/miki-results.json +17 -0
  11. package/benchmarks/miki.js +36 -0
  12. package/benchmarks/pug-results.json +17 -0
  13. package/benchmarks/pug.js +36 -0
  14. package/benchmarks/run.js +69 -37
  15. package/benchmarks/stress.mjs +1 -1
  16. package/docs/api/async-render.md +85 -0
  17. package/docs/api/cache.md +87 -0
  18. package/docs/api/compile.md +128 -0
  19. package/docs/api/context-processors.md +77 -0
  20. package/docs/api/filters.md +217 -0
  21. package/docs/api/finder.md +94 -0
  22. package/docs/api/helpers.md +53 -0
  23. package/docs/api/i18n.md +157 -0
  24. package/docs/api/index.md +54 -0
  25. package/docs/api/libraries.md +207 -0
  26. package/docs/api/render-partial.md +81 -0
  27. package/docs/api/render.md +92 -0
  28. package/docs/api/security.md +145 -0
  29. package/docs/api/setup-express.md +76 -0
  30. package/docs/api/tags.md +134 -0
  31. package/docs/assets/banner.png +0 -0
  32. package/docs/assets/logo.png +0 -0
  33. package/docs/guide/advanced-usage.md +397 -0
  34. package/docs/guide/async-rendering.md +308 -0
  35. package/docs/guide/context-processors.md +257 -0
  36. package/docs/guide/custom-filters.md +311 -0
  37. package/docs/guide/custom-tags.md +271 -0
  38. package/docs/guide/filters.md +642 -0
  39. package/docs/guide/getting-started.md +102 -0
  40. package/docs/guide/installation.md +95 -0
  41. package/docs/guide/partial-templates.md +367 -0
  42. package/docs/guide/quick-start.md +222 -0
  43. package/docs/guide/security.md +345 -0
  44. package/docs/guide/tags.md +783 -0
  45. package/docs/guide/template-discovery.md +170 -0
  46. package/docs/guide/template-inheritance.md +273 -0
  47. package/docs/guide/what-is-miki-template.md +28 -0
  48. package/docs/guide/why-miki-template.md +75 -0
  49. package/docs/index.md +104 -0
  50. package/docs/integrations/elysia.md +78 -0
  51. package/docs/integrations/express.md +219 -0
  52. package/docs/integrations/fastify.md +77 -0
  53. package/docs/integrations/hono.md +78 -0
  54. package/docs/integrations/index.md +68 -0
  55. package/docs/integrations/koa.md +88 -0
  56. package/docs/integrations/nestjs.md +78 -0
  57. package/docs/integrations/tsed.md +81 -0
  58. package/docs/javascripts/extra.js +174 -0
  59. package/docs/performance.md +37 -0
  60. package/docs/stylesheets/extra.css +819 -0
  61. package/live-test/integrations/elysia-example.js +16 -0
  62. package/live-test/integrations/express-example.js +24 -0
  63. package/live-test/integrations/fastify-example.js +20 -0
  64. package/live-test/integrations/hono-example.js +16 -0
  65. package/live-test/integrations/koa-example.js +30 -0
  66. package/live-test/integrations/nestjs-example.js +25 -0
  67. package/live-test/integrations/smoke-test.js +166 -0
  68. package/live-test/integrations/tsed-example.js +23 -0
  69. package/live-test/package-lock.json +235 -0
  70. package/live-test/package.json +4 -0
  71. package/live-test/views/home.html +17 -0
  72. package/mkdocs.yml +217 -0
  73. package/overrides/main.html +26 -0
  74. package/overrides/partials/footer.html +9 -0
  75. package/package.json +16 -6
  76. package/requirements-docs.txt +1 -0
  77. package/tests/integration/partial-render.test.cjs +13 -0
  78. package/docs/README.md +0 -18
  79. package/docs/advanced_usage.md +0 -71
  80. package/docs/api.md +0 -122
  81. package/docs/filters.md +0 -708
  82. package/docs/installation.md +0 -106
  83. package/docs/overview.md +0 -79
  84. package/docs/partialdef.md +0 -70
  85. package/docs/security.md +0 -27
  86. package/docs/tags.md +0 -673
  87. 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">&#10003;</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/)