kempo-server 2.1.1 → 3.0.0

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 (89) hide show
  1. package/CONFIG.md +295 -187
  2. package/README.md +31 -4
  3. package/SPA.md +14 -14
  4. package/UTILS.md +39 -0
  5. package/dist/defaultConfig.js +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/render.js +2 -0
  8. package/dist/rescan.js +1 -0
  9. package/dist/router.js +1 -1
  10. package/dist/serveFile.js +1 -1
  11. package/dist/templating/index.js +1 -0
  12. package/dist/templating/parse.js +1 -0
  13. package/docs/dist/caching.html +324 -0
  14. package/docs/dist/cli-utils.html +175 -0
  15. package/docs/dist/configuration.html +414 -0
  16. package/docs/dist/examples.html +296 -0
  17. package/docs/dist/fs-utils.html +206 -0
  18. package/docs/dist/getting-started.html +167 -0
  19. package/docs/dist/index.html +183 -0
  20. package/docs/dist/middleware.html +237 -0
  21. package/docs/dist/request-response.html +200 -0
  22. package/docs/dist/routing.html +177 -0
  23. package/docs/dist/templating.html +292 -0
  24. package/docs/{theme.css → dist/theme.css} +1 -3
  25. package/docs/src/.config.js +11 -0
  26. package/docs/{caching.html → src/caching.page.html} +4 -19
  27. package/docs/{cli-utils.html → src/cli-utils.page.html} +4 -20
  28. package/docs/{configuration.html → src/configuration.page.html} +4 -18
  29. package/docs/src/default.template.html +35 -0
  30. package/docs/{examples.html → src/examples.page.html} +9 -18
  31. package/docs/{fs-utils.html → src/fs-utils.page.html} +4 -20
  32. package/docs/{getting-started.html → src/getting-started.page.html} +4 -18
  33. package/docs/src/index.page.html +79 -0
  34. package/docs/{middleware.html → src/middleware.page.html} +4 -18
  35. package/docs/src/nav.fragment.html +73 -0
  36. package/docs/{request-response.html → src/request-response.page.html} +4 -18
  37. package/docs/{routing.html → src/routing.page.html} +4 -18
  38. package/docs/src/templating.page.html +188 -0
  39. package/{llm.txt → llms.txt} +100 -30
  40. package/package.json +7 -3
  41. package/scripts/build.js +19 -11
  42. package/scripts/render.js +58 -0
  43. package/src/defaultConfig.js +14 -2
  44. package/src/index.js +1 -1
  45. package/src/rescan.js +14 -0
  46. package/src/router.js +82 -11
  47. package/src/serveFile.js +27 -0
  48. package/src/templating/index.js +132 -0
  49. package/src/templating/parse.js +285 -0
  50. package/tests/cacheConfig.node-test.js +2 -2
  51. package/tests/config-flag.node-test.js +61 -25
  52. package/tests/customRoute-outside-root.node-test.js +1 -1
  53. package/tests/rescan.node-test.js +69 -0
  54. package/tests/router-wildcard.node-test.js +47 -2
  55. package/tests/templating-parse.node-test.js +243 -0
  56. package/tests/templating-render.node-test.js +188 -0
  57. package/tests/utils/test-scenario.js +4 -4
  58. package/docs/.config.json.example +0 -29
  59. package/docs/api/_admin/cache/DELETE.js +0 -28
  60. package/docs/api/_admin/cache/GET.js +0 -53
  61. package/docs/api/user/[id]/GET.js +0 -15
  62. package/docs/api/user/[id]/[info]/DELETE.js +0 -12
  63. package/docs/api/user/[id]/[info]/GET.js +0 -17
  64. package/docs/api/user/[id]/[info]/POST.js +0 -18
  65. package/docs/api/user/[id]/[info]/PUT.js +0 -19
  66. package/docs/index.html +0 -88
  67. package/docs/init.js +0 -0
  68. package/docs/kempo.min.css +0 -1
  69. package/docs/nav.inc.html +0 -41
  70. package/docs/nav.inc.js +0 -16
  71. /package/docs/{manifest.json → dist/manifest.json} +0 -0
  72. /package/docs/{media → dist/media}/hexagon.svg +0 -0
  73. /package/docs/{media → dist/media}/icon-maskable.png +0 -0
  74. /package/docs/{media → dist/media}/icon.svg +0 -0
  75. /package/docs/{media → dist/media}/icon128.png +0 -0
  76. /package/docs/{media → dist/media}/icon144.png +0 -0
  77. /package/docs/{media → dist/media}/icon152.png +0 -0
  78. /package/docs/{media → dist/media}/icon16-48.svg +0 -0
  79. /package/docs/{media → dist/media}/icon16.png +0 -0
  80. /package/docs/{media → dist/media}/icon192.png +0 -0
  81. /package/docs/{media → dist/media}/icon256.png +0 -0
  82. /package/docs/{media → dist/media}/icon32.png +0 -0
  83. /package/docs/{media → dist/media}/icon384.png +0 -0
  84. /package/docs/{media → dist/media}/icon48.png +0 -0
  85. /package/docs/{media → dist/media}/icon512.png +0 -0
  86. /package/docs/{media → dist/media}/icon64.png +0 -0
  87. /package/docs/{media → dist/media}/icon72.png +0 -0
  88. /package/docs/{media → dist/media}/icon96.png +0 -0
  89. /package/docs/{media → dist/media}/kempo-fist.svg +0 -0
@@ -0,0 +1,177 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta
6
+ name="viewport"
7
+ content="width=device-width, initial-scale=1.0"
8
+ />
9
+ <title>Routing - Kempo Server</title>
10
+ <link rel="icon" type="image/svg+xml" href="./media/icon.svg" />
11
+ <link rel="icon" type="image/png" sizes="32x32" href="./media/icon32.png" />
12
+ <link rel="manifest" href="./manifest.json" />
13
+ <link
14
+ rel="stylesheet"
15
+ href="https://cdn.jsdelivr.net/npm/kempo-css@2/dist/kempo.min.css"
16
+ />
17
+ <link rel="stylesheet" href="./theme.css" />
18
+ <script>
19
+ window.litDisableBundleWarning = true;
20
+ window.kempo = { pathsToIcons: ['https://cdn.jsdelivr.net/npm/kempo-ui@0.0.42/icons/'] };
21
+ </script>
22
+ </head>
23
+ <body>
24
+
25
+ <k-nav
26
+ fixed
27
+ class="bg-primary"
28
+ >
29
+ <button
30
+ id="toggleNavSideMenu"
31
+ class="link"
32
+ >
33
+ <k-icon name="menu"></k-icon>
34
+ </button>
35
+ <a
36
+ href="./"
37
+ class="d-if ph"
38
+ style="align-items: center"
39
+ >
40
+ <img src="./media/icon32.png" alt="Kempo Server Icon" class="pr" />
41
+ Kempo Server
42
+ </a>
43
+ <div class="flex"></div>
44
+ <a href="https://github.com/dustinpoissant/kempo-ui?tab=License-1-ov-file#creative-commons-attribution-noncommercial-sharealike-20" target="_blank"><k-icon name="license"></k-icont></a>
45
+ <a href="https://github.com/dustinpoissant/kempo-ui" target="_blank"><k-icon name="github-mark"></k-icont></a>
46
+ <k-theme-switcher></k-theme-switcher>
47
+ </k-nav>
48
+ <div style="width: 100%; height: 4rem;"></div>
49
+ <k-aside
50
+ id="navSideMenu"
51
+ state="offscreen"
52
+ >
53
+ <menu>
54
+ <a href="./" class="ta-center bb mb r0">
55
+ <h1 class="tc-primary">Kempo Server</h1>
56
+ <img src="./media/icon128.png" alt="Kempo UI Icon" />
57
+ </a>
58
+ <h3>Getting Started</h3>
59
+ <a href="./" class="d-b pq pl">Quick Start</a>
60
+ <a href="./routing.html" class="d-b pq pl">Routing</a>
61
+ <a href="./request-response.html" class="d-b pq pl">Request & Response</a>
62
+ <br /><br />
63
+ <h3>Advanced Features</h3>
64
+ <a href="configuration.html" class="d-b pq pl">Configuration</a>
65
+ <a href="templating.html" class="d-b pq pl">Templating</a>
66
+ <a href="middleware.html" class="d-b pq pl">Middleware</a>
67
+ <a href="caching.html" class="d-b pq pl">Module Caching</a>
68
+ <a href="cli-utils.html" class="d-b pq pl">CLI Utilities</a>
69
+ <a href="fs-utils.html" class="d-b pq pl">File System Utilities</a>
70
+ <a href="examples.html" class="d-b pq pl">Examples & Demos</a>
71
+ </menu>
72
+ </k-aside>
73
+ <script src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3.5/src/components/Aside.js" type="module"></script>
74
+ <script src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3.5/src/components/Main.js" type="module"></script>
75
+ <script src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3.5/src/components/Nav.js" type="module"></script>
76
+ <script src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3.5/src/components/Icon.js" type="module"></script>
77
+ <script src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3.5/src/components/ThemeSwitcher.js" type="module"></script>
78
+ <script>
79
+ document.getElementById('toggleNavSideMenu').addEventListener('click', async () => {
80
+ await window.customElements.whenDefined('k-aside');
81
+ document.getElementById('navSideMenu').toggle();
82
+ });
83
+ document.addEventListener('click', function(e) {
84
+ if (e.target.matches('a[href^="#"]')) {
85
+ e.preventDefault();
86
+ const targetId = e.target.getAttribute('href').replace('#', '');
87
+ const target = document.getElementById(targetId);
88
+ if (target) {
89
+ target.scrollIntoView({ behavior: 'smooth' });
90
+ const url = window.location.pathname + window.location.search + '#' + targetId;
91
+ history.replaceState(null, '', url);
92
+ }
93
+ }
94
+ });
95
+ </script>
96
+
97
+ <main>
98
+ <h1 class="ta-center">Routing</h1>
99
+
100
+ <p>Learn how Kempo Server's file-based routing system works.</p>
101
+
102
+ <h2>How Routes Work</h2>
103
+ <p>A route is a request to a directory that will be handled by a file. To define routes, create the directory structure to the route and create a file with the name of the method that this file will handle. For example <code>GET.js</code> will handle the <code>GET</code> requests, <code>POST.js</code> will handle the <code>POST</code> requests and so on. Use <code>index.js</code> to handle all request types.</p>
104
+
105
+ <p>The Javascript file must have a <b>default</b> export that is the function that will handle the request. It will be passed a <code>request</code> object and a <code>response</code> object.</p>
106
+
107
+ <p>For example this directory structure:</p>
108
+ <pre><code class="hljs markdown">my/<br />├─ route/<br />│ ├─ GET.js<br />│ ├─ POST.js<br />├─ other/<br />│ ├─ route/<br />│ │ ├─ GET.js<br /></code></pre>
109
+ <p>Would be used to handle <code>GET my/route/</code>, <code>POST my/route/</code> and <code>GET my/other/route/</code></p>
110
+
111
+ <h2 id="htmlRoutes">HTML Routes</h2>
112
+ <p>Just like JS files, HTML files can be used to define a route. Use <code>GET.html</code>, <code>POST.html</code>, etc... to define files that will be served when that route is requested.</p>
113
+ <pre><code class="hljs markdown">my/<br />├─ route/<br />│ ├─ GET.js<br />│ ├─ POST.js<br />├─ other/<br />│ ├─ route/<br />│ │ ├─ GET.js<br />│ ├─ POST.html<br />│ ├─ GET.html<br /></code></pre>
114
+
115
+ <h3><code>index</code> fallbacks</h3>
116
+ <p><code>index.js</code> or <code>index.html</code> will be used as a fallback for all routes if a <i>method</i> file is not defined. In the above examples we do not have any routes defined for <code>DELETE</code>, <code>PUT</code> <code>PATCH</code>, etc... so lets use an <code>index.js</code> and <code>index.html</code> to be a "catch-all" for all the methods we have not created handlers for.</p>
117
+ <pre><code class="hljs markdown">my/<br />├─ route/<br />│ ├─ GET.js<br />│ ├─ POST.js<br />│ ├─ index.js<br />├─ other/<br />│ ├─ route/<br />│ │ ├─ GET.js<br />│ │ ├─ index.js<br />│ ├─ POST.html<br />│ ├─ GET.html<br />│ ├─ index.html<br />├─ index.html<br /></code></pre>
118
+
119
+ <h2>Dynamic Routes</h2>
120
+ <p>A dynamic route is a route with a "param" in its path. To define the dynamic parts of the route just wrap the directory name in square brackets. For example if you wanted to get a users profile, or perform CRUD operations on a user you might create the following directory structure.</p>
121
+ <pre><code class="hljs markdown">api/<br />├─ user/<br />│ ├─ [id]/<br />│ │ ├─ [info]/<br />│ │ │ ├─ GET.js<br />│ │ │ ├─ DELETE.js<br />│ │ │ ├─ PUT.js<br />│ │ │ ├─ POST.js<br />│ │ ├─ GET.js<br /></code></pre>
122
+ <p>When a request is made to <code>/api/user/123/info</code>, the route file <code>api/user/[id]/[info]/GET.js</code> would be executed and receive a request object with <code>request.params</code> containing <code>{ id: "123", info: "info" }</code>.</p>
123
+
124
+ <h2>Route Examples</h2>
125
+
126
+ <h3>Simple API Route</h3>
127
+ <pre><code class="hljs javascript"><span class="hljs-comment">// api/hello/GET.js</span><br /><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">request, response</span>) </span>{<br /> <span class="hljs-keyword">const</span> { name } = request.query;<br /> <span class="hljs-keyword">const</span> message = name ? <span class="hljs-string">`Hello ${name}!`</span> : <span class="hljs-string">'Hello World!'</span>;<br /> <br /> response.json({ message });<br />}</code></pre>
128
+
129
+ <h3>Dynamic User Profile Route</h3>
130
+ <pre><code class="hljs javascript"><span class="hljs-comment">// api/users/[id]/GET.js</span><br /><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">request, response</span>) </span>{<br /> <span class="hljs-keyword">const</span> { id } = request.params;<br /> <span class="hljs-keyword">const</span> { includeProfile } = request.query;<br /> <br /> <span class="hljs-comment">// Simulate database lookup</span><br /> <span class="hljs-keyword">const</span> user = {<br /> id: id,<br /> name: <span class="hljs-string">`User ${id}`</span>,<br /> email: <span class="hljs-string">`user${id}@example.com`</span><br /> };<br /> <br /> <span class="hljs-keyword">if</span> (includeProfile === <span class="hljs-string">'true'</span>) {<br /> user.profile = {<br /> bio: <span class="hljs-string">`Bio for user ${id}`</span>,<br /> joinDate: <span class="hljs-string">'2024-01-01'</span><br /> };<br /> }<br /> <br /> response.json(user);<br />}</code></pre>
131
+
132
+ <h3>Nested Dynamic Routes</h3>
133
+ <pre><code class="hljs javascript"><span class="hljs-comment">// api/users/[id]/posts/[postId]/GET.js</span><br /><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">request, response</span>) </span>{<br /> <span class="hljs-keyword">const</span> { id, postId } = request.params;<br /> <br /> <span class="hljs-keyword">const</span> post = {<br /> id: postId,<br /> userId: id,<br /> title: <span class="hljs-string">`Post ${postId} by User ${id}`</span>,<br /> content: <span class="hljs-string">'This is the post content...'</span>,<br /> createdAt: <span class="hljs-string">'2024-01-01T00:00:00.000Z'</span><br /> };<br /> <br /> response.json(post);<br />}</code></pre>
134
+
135
+ <h3>HTML Route Example</h3>
136
+ <pre><code class="hljs html"><span class="hljs-comment">&lt;!-- pages/about/GET.html --&gt;</span><br /><span class="hljs-meta">&lt;!DOCTYPE html&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>About Us<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span><br /><span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>About Our Company<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>We are a company that does amazing things.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span><br /><span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span><br /><span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></code></pre>
137
+
138
+ <h2>Route File Structure Best Practices</h2>
139
+
140
+ <h3>Organize by Feature</h3>
141
+ <pre><code class="hljs markdown">api/<br />├─ auth/<br />│ ├─ login/<br />│ │ ├─ POST.js<br />│ ├─ logout/<br />│ │ ├─ POST.js<br />│ ├─ register/<br />│ │ ├─ POST.js<br />├─ users/<br />│ ├─ [id]/<br />│ │ ├─ GET.js<br />│ │ ├─ PUT.js<br />│ │ ├─ DELETE.js<br />│ ├─ GET.js<br />│ ├─ POST.js<br />├─ posts/<br />│ ├─ [id]/<br />│ │ ├─ GET.js<br />│ │ ├─ PUT.js<br />│ │ ├─ DELETE.js<br />│ ├─ GET.js<br />│ ├─ POST.js<br /></code></pre>
142
+
143
+ <h3>Use Index Files for Fallbacks</h3>
144
+ <p>Use <code>index.js</code> to handle methods not explicitly defined:</p>
145
+ <pre><code class="hljs javascript"><span class="hljs-comment">// api/users/index.js</span><br /><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">request, response</span>) </span>{<br /> <span class="hljs-comment">// Handle any method not explicitly defined</span><br /> response.status(<span class="hljs-number">405</span>).json({<br /> error: <span class="hljs-string">'Method not allowed'</span>,<br /> allowed: [<span class="hljs-string">'GET'</span>, <span class="hljs-string">'POST'</span>]<br /> });<br />}</code></pre>
146
+
147
+ <h2>Static File Serving</h2>
148
+ <p>Any file that doesn't match a route pattern will be served as a static file. This includes:</p>
149
+ <ul>
150
+ <li>HTML files (except route files)</li>
151
+ <li>CSS files</li>
152
+ <li>JavaScript files (except route files)</li>
153
+ <li>Images</li>
154
+ <li>Any other static assets</li>
155
+ </ul>
156
+
157
+ <p>Example static file structure:</p>
158
+ <pre><code class="hljs markdown">public/<br />├─ index.html # Served at /<br />├─ styles.css # Served at /styles.css<br />├─ script.js # Served at /script.js<br />├─ images/<br />│ ├─ logo.png # Served at /images/logo.png<br />├─ api/ # Routes directory<br />│ ├─ hello/GET.js # Route handler<br /></code></pre>
159
+
160
+ <h2>Custom Route Directory Resolution</h2>
161
+ <p>When using <code>customRoutes</code> (see <a href="configuration.html#customRoutes">Configuration</a>), routes that resolve to a directory support full file-based routing. The server checks for route files and index files inside the directory using the same priority order as normal routing:</p>
162
+ <ol>
163
+ <li><code>METHOD.js</code> (e.g. <code>GET.js</code>, <code>POST.js</code>) &mdash; executed as a route module</li>
164
+ <li><code>METHOD.html</code> (e.g. <code>GET.html</code>) &mdash; served as static</li>
165
+ <li><code>index.js</code> &mdash; executed as a route module</li>
166
+ <li><code>index.html</code> / <code>index.htm</code> &mdash; served as static</li>
167
+ </ol>
168
+ <p>This applies to both exact and wildcard custom routes. For example, with a wildcard route <code>"/api/**": "../api/**"</code>, a request to <code>/api/auth/session</code> resolves to the <code>../api/auth/session/</code> directory and executes the appropriate route file (e.g. <code>GET.js</code> for GET requests).</p>
169
+
170
+ </main>
171
+ <div style="height:25vh"></div>
172
+ <script
173
+ type="module"
174
+ src="https://cdn.jsdelivr.net/npm/kempo-ui@0.0.42/dist/components/Import.js"
175
+ ></script>
176
+ </body>
177
+ </html>
@@ -0,0 +1,292 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta
6
+ name="viewport"
7
+ content="width=device-width, initial-scale=1.0"
8
+ />
9
+ <title>Templating - Kempo Server</title>
10
+ <link rel="icon" type="image/svg+xml" href="./media/icon.svg" />
11
+ <link rel="icon" type="image/png" sizes="32x32" href="./media/icon32.png" />
12
+ <link rel="manifest" href="./manifest.json" />
13
+ <link
14
+ rel="stylesheet"
15
+ href="https://cdn.jsdelivr.net/npm/kempo-css@2/dist/kempo.min.css"
16
+ />
17
+ <link rel="stylesheet" href="./theme.css" />
18
+ <script>
19
+ window.litDisableBundleWarning = true;
20
+ window.kempo = { pathsToIcons: ['https://cdn.jsdelivr.net/npm/kempo-ui@0.0.42/icons/'] };
21
+ </script>
22
+ </head>
23
+ <body>
24
+
25
+ <k-nav
26
+ fixed
27
+ class="bg-primary"
28
+ >
29
+ <button
30
+ id="toggleNavSideMenu"
31
+ class="link"
32
+ >
33
+ <k-icon name="menu"></k-icon>
34
+ </button>
35
+ <a
36
+ href="./"
37
+ class="d-if ph"
38
+ style="align-items: center"
39
+ >
40
+ <img src="./media/icon32.png" alt="Kempo Server Icon" class="pr" />
41
+ Kempo Server
42
+ </a>
43
+ <div class="flex"></div>
44
+ <a href="https://github.com/dustinpoissant/kempo-ui?tab=License-1-ov-file#creative-commons-attribution-noncommercial-sharealike-20" target="_blank"><k-icon name="license"></k-icont></a>
45
+ <a href="https://github.com/dustinpoissant/kempo-ui" target="_blank"><k-icon name="github-mark"></k-icont></a>
46
+ <k-theme-switcher></k-theme-switcher>
47
+ </k-nav>
48
+ <div style="width: 100%; height: 4rem;"></div>
49
+ <k-aside
50
+ id="navSideMenu"
51
+ state="offscreen"
52
+ >
53
+ <menu>
54
+ <a href="./" class="ta-center bb mb r0">
55
+ <h1 class="tc-primary">Kempo Server</h1>
56
+ <img src="./media/icon128.png" alt="Kempo UI Icon" />
57
+ </a>
58
+ <h3>Getting Started</h3>
59
+ <a href="./" class="d-b pq pl">Quick Start</a>
60
+ <a href="./routing.html" class="d-b pq pl">Routing</a>
61
+ <a href="./request-response.html" class="d-b pq pl">Request & Response</a>
62
+ <br /><br />
63
+ <h3>Advanced Features</h3>
64
+ <a href="configuration.html" class="d-b pq pl">Configuration</a>
65
+ <a href="templating.html" class="d-b pq pl">Templating</a>
66
+ <a href="middleware.html" class="d-b pq pl">Middleware</a>
67
+ <a href="caching.html" class="d-b pq pl">Module Caching</a>
68
+ <a href="cli-utils.html" class="d-b pq pl">CLI Utilities</a>
69
+ <a href="fs-utils.html" class="d-b pq pl">File System Utilities</a>
70
+ <a href="examples.html" class="d-b pq pl">Examples & Demos</a>
71
+ </menu>
72
+ </k-aside>
73
+ <script src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3.5/src/components/Aside.js" type="module"></script>
74
+ <script src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3.5/src/components/Main.js" type="module"></script>
75
+ <script src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3.5/src/components/Nav.js" type="module"></script>
76
+ <script src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3.5/src/components/Icon.js" type="module"></script>
77
+ <script src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3.5/src/components/ThemeSwitcher.js" type="module"></script>
78
+ <script>
79
+ document.getElementById('toggleNavSideMenu').addEventListener('click', async () => {
80
+ await window.customElements.whenDefined('k-aside');
81
+ document.getElementById('navSideMenu').toggle();
82
+ });
83
+ document.addEventListener('click', function(e) {
84
+ if (e.target.matches('a[href^="#"]')) {
85
+ e.preventDefault();
86
+ const targetId = e.target.getAttribute('href').replace('#', '');
87
+ const target = document.getElementById(targetId);
88
+ if (target) {
89
+ target.scrollIntoView({ behavior: 'smooth' });
90
+ const url = window.location.pathname + window.location.search + '#' + targetId;
91
+ history.replaceState(null, '', url);
92
+ }
93
+ }
94
+ });
95
+ </script>
96
+
97
+ <main>
98
+ <h1 class="ta-center">Templating</h1>
99
+
100
+ <p>Kempo Server includes a built-in XML-based templating system for building static sites with shared layouts, reusable fragments, and dynamic content. Pages can be pre-rendered at build time or server-side rendered on each request.</p>
101
+
102
+ <nav class="b r mb p">
103
+ <h4 class="mt0">On This Page</h4>
104
+ <ul>
105
+ <li><a href="#overview">Overview</a></li>
106
+ <li><a href="#file-types">File Types</a></li>
107
+ <li><a href="#templates">Templates</a></li>
108
+ <li><a href="#pages">Pages</a></li>
109
+ <li><a href="#fragments">Fragments</a></li>
110
+ <li><a href="#variables">Variables</a></li>
111
+ <li><a href="#conditionals">Conditionals</a></li>
112
+ <li><a href="#loops">Loops</a></li>
113
+ <li><a href="#rendering">Rendering</a></li>
114
+ <li><a href="#ssr">Server-Side Rendering</a></li>
115
+ <li><a href="#configuration">Configuration</a></li>
116
+ </ul>
117
+ </nav>
118
+
119
+ <h2 id="overview">Overview</h2>
120
+ <p>The templating system uses three file types that work together:</p>
121
+ <ul>
122
+ <li><strong>Templates</strong> (<code>*.template.html</code>) &mdash; Shared page layouts with named content slots</li>
123
+ <li><strong>Pages</strong> (<code>*.page.html</code>) &mdash; Individual pages that fill template slots with content</li>
124
+ <li><strong>Fragments</strong> (<code>*.fragment.html</code>) &mdash; Reusable HTML partials included in templates or other fragments</li>
125
+ </ul>
126
+ <p>All three file types are blocked from being served directly by the default <code>disallowedRegex</code> configuration.</p>
127
+
128
+ <h2 id="file-types">File Types</h2>
129
+ <p>A typical project structure:</p>
130
+ <pre><code class="hljs markdown">my-site/<br />├─ default.template.html # Shared layout<br />├─ nav.fragment.html # Reusable navigation<br />├─ footer.fragment.html # Reusable footer<br />├─ index.page.html # Homepage → renders to index.html<br />├─ about.page.html # About → renders to about.html<br />├─ blog/<br />│ ├─ index.page.html # Blog index → blog/index.html<br />│ ├─ post-1.page.html # Blog post → blog/post-1.html<br /></code></pre>
131
+ <p>Templates and fragments are resolved by walking up the directory tree from the page file to the root, so subdirectories can override them by providing their own versions.</p>
132
+
133
+ <h2 id="templates">Templates</h2>
134
+ <p>A template defines the shared HTML structure for your pages. Use <code>&lt;location&gt;</code> tags to define named content slots that pages will fill.</p>
135
+ <pre><code class="hljs xml"><span class="hljs-comment">&lt;!-- default.template.html --&gt;</span><br /><span class="hljs-meta">&lt;!DOCTYPE html&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Templating - Kempo Server<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span><br /><span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">fragment</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"nav"</span> /&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Templating<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">location</span> /&gt;</span><br /> <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">location</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"scripts"</span>&gt;</span><br /> <span class="hljs-comment">&lt;!-- default scripts if page doesn't provide any --&gt;</span><br /> <span class="hljs-tag">&lt;/<span class="hljs-name">location</span>&gt;</span><br /><span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span><br /><span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></code></pre>
136
+
137
+ <h3>Location Tags</h3>
138
+ <p>Locations are named slots in a template that pages fill with content. The <code>name</code> attribute is optional &mdash; a <code>&lt;location&gt;</code> without a name defaults to <code>"default"</code>.</p>
139
+ <ul>
140
+ <li><code>&lt;location /&gt;</code> &mdash; Self-closing default slot (equivalent to <code>&lt;location name="default" /&gt;</code>)</li>
141
+ <li><code>&lt;location name="scripts" /&gt;</code> &mdash; Named slot, renders empty if the page doesn't provide content for it</li>
142
+ <li><code>&lt;location name="scripts"&gt;...fallback...&lt;/location&gt;</code> &mdash; Includes fallback content used when the page doesn't override it</li>
143
+ </ul>
144
+
145
+ <h2 id="pages">Pages</h2>
146
+ <p>A page file wraps its content in a <code>&lt;page&gt;</code> root element and uses <code>&lt;content&gt;</code> blocks to fill the template's locations. The <code>location</code> attribute is optional &mdash; a <code>&lt;content&gt;</code> block without one targets the <code>"default"</code> location. Multiple <code>&lt;content&gt;</code> blocks targeting the same location are concatenated in order.</p>
147
+ <pre><code class="hljs xml"><span class="hljs-comment">&lt;!-- about.page.html → renders to about.html --&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">page</span> <span class="hljs-attr">pageName</span>=<span class="hljs-string">"About Us"</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"About - My Site"</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">content</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Welcome to our about page.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span><br /> <span class="hljs-tag">&lt;/<span class="hljs-name">content</span>&gt;</span><br /><span class="hljs-tag">&lt;/<span class="hljs-name">page</span>&gt;</span></code></pre>
148
+
149
+ <h3>Page Attributes</h3>
150
+ <p>Attributes on the <code>&lt;page&gt;</code> tag become template variables. In the example above, <code>Templating</code> resolves to <code>About Us</code> and <code>Templating - Kempo Server</code> resolves to <code>About - My Site</code>.</p>
151
+ <p>The special <code>template</code> attribute selects which template to use. It defaults to <code>default</code>, which looks for <code>default.template.html</code>.</p>
152
+ <pre><code class="hljs xml"><span class="hljs-comment">&lt;!-- Uses blog.template.html instead of default.template.html --&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">page</span> <span class="hljs-attr">template</span>=<span class="hljs-string">"blog"</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"My Post"</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">content</span> <span class="hljs-attr">location</span>=<span class="hljs-string">"main"</span>&gt;</span>...<span class="hljs-tag">&lt;/<span class="hljs-name">content</span>&gt;</span><br /><span class="hljs-tag">&lt;/<span class="hljs-name">page</span>&gt;</span></code></pre>
153
+
154
+ <h2 id="fragments">Fragments</h2>
155
+ <p>Fragments are reusable HTML partials. Include them in templates or other fragments using the <code>&lt;fragment&gt;</code> tag.</p>
156
+ <pre><code class="hljs xml"><span class="hljs-comment">&lt;!-- nav.fragment.html --&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">fragment</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"./"</span>&gt;</span>Home<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"./about.html"</span>&gt;</span>About<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><br /> <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span><br /><span class="hljs-tag">&lt;/<span class="hljs-name">fragment</span>&gt;</span></code></pre>
157
+ <p>Include it in a template:</p>
158
+ <pre><code class="hljs xml"><span class="hljs-comment">&lt;!-- Self-closing: renders empty if fragment file not found --&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">fragment</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"nav"</span> /&gt;</span><br /><br /><span class="hljs-comment">&lt;!-- With fallback content if fragment file not found --&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">fragment</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"nav"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span>Fallback Nav<span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">fragment</span>&gt;</span></code></pre>
159
+ <p>Fragments can include other fragments. The maximum nesting depth is controlled by <code>maxFragmentDepth</code> (default: 10).</p>
160
+
161
+ <h3>File Resolution</h3>
162
+ <p>When a page references a template or a template includes a fragment, the system searches for the file starting in the page's directory and walking up to the root. This means:</p>
163
+ <ul>
164
+ <li>Templates and fragments placed at the root apply to all pages</li>
165
+ <li>A subdirectory can provide its own version to override the parent</li>
166
+ </ul>
167
+
168
+ <h2 id="variables">Variables</h2>
169
+ <p>Use <code></code> syntax to insert dynamic values into templates and fragments.</p>
170
+
171
+ <h3>Built-in Variables</h3>
172
+ <table>
173
+ <thead>
174
+ <tr>
175
+ <th>Variable</th>
176
+ <th>Description</th>
177
+ </tr>
178
+ </thead>
179
+ <tbody>
180
+ <tr><td><code>./</code></td><td>Relative path from the page to the root directory (e.g. <code>./</code>, <code>../</code>, <code>../../</code>)</td></tr>
181
+ <tr><td><code>2026</code></td><td>Current four-digit year</td></tr>
182
+ <tr><td><code>2026-04-09</code></td><td>Current date in ISO format (<code>YYYY-MM-DD</code>)</td></tr>
183
+ <tr><td><code>2026-04-09T19:04:45.132Z</code></td><td>Full ISO 8601 datetime string</td></tr>
184
+ <tr><td><code>1775761485132</code></td><td>Unix timestamp in milliseconds</td></tr>
185
+ <tr><td><code></code></td><td>Version from the root <code>package.json</code></td></tr>
186
+ <tr><td><code></code></td><td>Value of <code>NODE_ENV</code></td></tr>
187
+ </tbody>
188
+ </table>
189
+
190
+ <h3>Page Attributes as Variables</h3>
191
+ <p>Any attribute on the <code>&lt;page&gt;</code> tag is available as a variable in the template:</p>
192
+ <pre><code class="hljs xml"><span class="hljs-comment">&lt;!-- page file --&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">page</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"My Page"</span> <span class="hljs-attr">author</span>=<span class="hljs-string">"Dustin"</span>&gt;</span>...<span class="hljs-tag">&lt;/<span class="hljs-name">page</span>&gt;</span><br /><br /><span class="hljs-comment">&lt;!-- template file --&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Templating - Kempo Server<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"author"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">""</span> /&gt;</span></code></pre>
193
+
194
+ <h3>Globals and State</h3>
195
+ <p>Additional variables can be provided through the <code>globals</code> and <code>state</code> configuration objects. Globals and state are merged with page attributes, with page attributes taking priority.</p>
196
+ <pre><code class="hljs javascript"><span class="hljs-comment">// .config.js</span><br /><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {<br /> <span class="hljs-attr">templating</span>: {<br /> <span class="hljs-attr">globals</span>: {<br /> <span class="hljs-attr">siteName</span>: <span class="hljs-string">'My Site'</span>,<br /> <span class="hljs-attr">copyright</span>: <span class="hljs-string">'© 2026 My Company'</span><br /> },<br /> <span class="hljs-attr">state</span>: {<br /> <span class="hljs-attr">buildId</span>: () =&gt; <span class="hljs-built_in">Date</span>.now().toString(<span class="hljs-number">36</span>)<br /> }<br /> }<br />};</code></pre>
197
+ <p>Function values in globals or state are called at render time, allowing dynamic values.</p>
198
+
199
+ <h3>Dot-Path Access</h3>
200
+ <p>Variables support dot notation for nested object access:</p>
201
+ <pre><code class="hljs xml"><br /></code></pre>
202
+
203
+ <h2 id="conditionals">Conditionals</h2>
204
+ <p>Use <code>&lt;if&gt;</code> blocks to conditionally include content based on variable values.</p>
205
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">if</span> <span class="hljs-attr">condition</span>=<span class="hljs-string">"showBanner"</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"banner"</span>&gt;</span>Welcome!<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span><br /><span class="hljs-tag">&lt;/<span class="hljs-name">if</span>&gt;</span></code></pre>
206
+
207
+ <h3>Supported Operators</h3>
208
+ <p>Conditions support a full expression syntax:</p>
209
+ <table>
210
+ <thead>
211
+ <tr>
212
+ <th>Operator</th>
213
+ <th>Description</th>
214
+ </tr>
215
+ </thead>
216
+ <tbody>
217
+ <tr><td><code>===</code></td><td>Strict equality</td></tr>
218
+ <tr><td><code>!==</code></td><td>Strict inequality</td></tr>
219
+ <tr><td><code>&gt;</code>, <code>&lt;</code>, <code>&gt;=</code>, <code>&lt;=</code></td><td>Comparison</td></tr>
220
+ <tr><td><code>&amp;&amp;</code></td><td>Logical AND</td></tr>
221
+ <tr><td><code>||</code></td><td>Logical OR</td></tr>
222
+ <tr><td><code>!</code></td><td>Logical NOT</td></tr>
223
+ <tr><td><code>( )</code></td><td>Grouping</td></tr>
224
+ </tbody>
225
+ </table>
226
+
227
+ <h3>Examples</h3>
228
+ <pre><code class="hljs xml"><span class="hljs-comment">&lt;!-- Truthy check --&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">if</span> <span class="hljs-attr">condition</span>=<span class="hljs-string">"isLoggedIn"</span>&gt;</span>Welcome back!<span class="hljs-tag">&lt;/<span class="hljs-name">if</span>&gt;</span><br /><br /><span class="hljs-comment">&lt;!-- Negation --&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">if</span> <span class="hljs-attr">condition</span>=<span class="hljs-string">"!isLoggedIn"</span>&gt;</span>Please log in.<span class="hljs-tag">&lt;/<span class="hljs-name">if</span>&gt;</span><br /><br /><span class="hljs-comment">&lt;!-- String comparison --&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">if</span> <span class="hljs-attr">condition</span>=<span class="hljs-string">"env === 'production'"</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"analytics.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span><br /><span class="hljs-tag">&lt;/<span class="hljs-name">if</span>&gt;</span><br /><br /><span class="hljs-comment">&lt;!-- Compound conditions --&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">if</span> <span class="hljs-attr">condition</span>=<span class="hljs-string">"isAdmin &amp;&amp; hasPermission"</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/admin"</span>&gt;</span>Admin Panel<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><br /><span class="hljs-tag">&lt;/<span class="hljs-name">if</span>&gt;</span></code></pre>
229
+
230
+ <h2 id="loops">Loops</h2>
231
+ <p>Use <code>&lt;foreach&gt;</code> blocks to iterate over arrays.</p>
232
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">foreach</span> <span class="hljs-attr">in</span>=<span class="hljs-string">"navLinks"</span> <span class="hljs-attr">as</span>=<span class="hljs-string">"link"</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">""</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><br /><span class="hljs-tag">&lt;/<span class="hljs-name">foreach</span>&gt;</span></code></pre>
233
+ <p>The <code>in</code> attribute references an array variable and <code>as</code> names the loop variable. The loop variable supports dot-path access for object items.</p>
234
+
235
+ <p>Provide the array through globals or state:</p>
236
+ <pre><code class="hljs javascript"><span class="hljs-comment">// .config.js</span><br /><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {<br /> <span class="hljs-attr">templating</span>: {<br /> <span class="hljs-attr">globals</span>: {<br /> <span class="hljs-attr">navLinks</span>: [<br /> { <span class="hljs-attr">url</span>: <span class="hljs-string">'/'</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">'Home'</span> },<br /> { <span class="hljs-attr">url</span>: <span class="hljs-string">'/about.html'</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">'About'</span> }<br /> ]<br /> }<br /> }<br />};</code></pre>
237
+
238
+ <h2 id="rendering">Rendering</h2>
239
+ <p>Pages can be pre-rendered to static HTML files using the CLI tool or the programmatic API.</p>
240
+
241
+ <h3>CLI</h3>
242
+ <pre><code class="hljs bash"><span class="hljs-comment"># Render to a separate output directory</span><br />npx kempo-server-render ./src ./dist<br /><br /><span class="hljs-comment"># Render in-place (output to same directory)</span><br />npx kempo-server-render ./src<br /><br /><span class="hljs-comment"># With a state file</span><br />npx kempo-server-render ./src ./dist state.json</code></pre>
243
+ <p>The CLI automatically loads <code>.config.js</code> or <code>.config.json</code> from the input directory for globals, state, and maxFragmentDepth settings.</p>
244
+
245
+ <h3>Programmatic API</h3>
246
+ <pre><code class="hljs javascript"><span class="hljs-keyword">import</span> { renderDir, renderPage } <span class="hljs-keyword">from</span> <span class="hljs-string">'kempo-server/templating'</span>;<br /><br /><span class="hljs-comment">// Render all pages in a directory</span><br /><span class="hljs-keyword">const</span> count = <span class="hljs-keyword">await</span> renderDir(<span class="hljs-string">'./src'</span>, <span class="hljs-string">'./dist'</span>, globals, state, maxDepth);<br /><br /><span class="hljs-comment">// Render a single page</span><br /><span class="hljs-keyword">const</span> html = <span class="hljs-keyword">await</span> renderPage(<span class="hljs-string">'./src/about.page.html'</span>, <span class="hljs-string">'./src'</span>, globals, state, maxDepth);</code></pre>
247
+
248
+ <h2 id="ssr">Server-Side Rendering</h2>
249
+ <p>Pages can also be rendered on each request instead of pre-built. This is useful during development so you can see changes without rebuilding.</p>
250
+
251
+ <h3>SSR Fallback</h3>
252
+ <p>When <code>ssr</code> is enabled, the server first looks for a static file. If no static file is found, it looks for a matching <code>.page.html</code> file and renders it on the fly.</p>
253
+ <pre><code class="hljs javascript"><span class="hljs-comment">// .config.js</span><br /><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {<br /> <span class="hljs-attr">templating</span>: {<br /> <span class="hljs-attr">ssr</span>: <span class="hljs-literal">true</span><br /> }<br />};</code></pre>
254
+
255
+ <h3>SSR Priority</h3>
256
+ <p>When <code>ssrPriority</code> is also enabled, the server renders the <code>.page.html</code> file <em>before</em> checking for static files. This ensures you always see the latest template changes, even if a pre-rendered <code>.html</code> file exists in the same directory.</p>
257
+ <pre><code class="hljs javascript"><span class="hljs-comment">// dev.config.js</span><br /><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {<br /> <span class="hljs-attr">templating</span>: {<br /> <span class="hljs-attr">ssr</span>: <span class="hljs-literal">true</span>,<br /> <span class="hljs-attr">ssrPriority</span>: <span class="hljs-literal">true</span><br /> }<br />};</code></pre>
258
+ <p>This is ideal for development. For production, pre-render your pages and serve them as static files with SSR disabled.</p>
259
+
260
+ <h2 id="configuration">Configuration</h2>
261
+ <p>All templating options live under the <code>templating</code> key in your configuration file.</p>
262
+ <table>
263
+ <thead>
264
+ <tr>
265
+ <th>Option</th>
266
+ <th>Default</th>
267
+ <th>Description</th>
268
+ </tr>
269
+ </thead>
270
+ <tbody>
271
+ <tr><td><code>preRender</code></td><td><code>false</code></td><td>Pre-render pages on server start</td></tr>
272
+ <tr><td><code>ssr</code></td><td><code>false</code></td><td>Enable server-side rendering as a fallback</td></tr>
273
+ <tr><td><code>ssrPriority</code></td><td><code>false</code></td><td>Render pages before checking for static files (requires <code>ssr: true</code>)</td></tr>
274
+ <tr><td><code>globals</code></td><td><code>{}</code></td><td>Variables available to all pages</td></tr>
275
+ <tr><td><code>state</code></td><td><code>{}</code></td><td>Additional variables (function values are called at render time)</td></tr>
276
+ <tr><td><code>maxFragmentDepth</code></td><td><code>10</code></td><td>Maximum fragment nesting depth</td></tr>
277
+ </tbody>
278
+ </table>
279
+
280
+ <h3>Development vs Production</h3>
281
+ <p>A common pattern is to use separate config files:</p>
282
+ <pre><code class="hljs javascript"><span class="hljs-comment">// dev.config.js — live rendering, no rebuild needed</span><br /><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {<br /> <span class="hljs-attr">templating</span>: { <span class="hljs-attr">ssr</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">ssrPriority</span>: <span class="hljs-literal">true</span> }<br />};<br /><br /><span class="hljs-comment">// prod.config.js — static files only</span><br /><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {<br /> <span class="hljs-attr">templating</span>: { <span class="hljs-attr">ssr</span>: <span class="hljs-literal">false</span> }<br />};</code></pre>
283
+ <pre><code class="hljs bash"><span class="hljs-comment"># Development</span><br />node dist/index.js -r ./docs/src -c dev.config.js<br /><br /><span class="hljs-comment"># Production (serve pre-rendered output)</span><br />node dist/index.js -r ./docs/dist</code></pre>
284
+
285
+ </main>
286
+ <div style="height:25vh"></div>
287
+ <script
288
+ type="module"
289
+ src="https://cdn.jsdelivr.net/npm/kempo-ui@0.0.42/dist/components/Import.js"
290
+ ></script>
291
+ </body>
292
+ </html>
@@ -1,8 +1,6 @@
1
1
  :root {
2
- --c_primary: rgb(153, 51, 255);
3
- --c_primary__hover: rgb(119, 17, 221);
2
+ --c_primary: hsl(262, 52%, 47%);
4
3
  --c_secondary: rgb(51, 102, 255);
5
- --c_secondary__hover: rgb(17, 68, 221);
6
4
  --tc_primary: light-dark(#93f, rgb(187, 102, 255));
7
5
  --tc_secondary: light-dark(#36f, rgb(138, 180, 248));
8
6
  --c_highlight: light-dark(rgba(153, 51, 255, 0.25), rgba(153, 51, 255, 0.25));
@@ -0,0 +1,11 @@
1
+ export default {
2
+ customRoutes: {
3
+ "media/*": "../dist/media/*",
4
+ "theme.css": "../dist/theme.css",
5
+ "manifest.json": "../dist/manifest.json"
6
+ },
7
+ templating: {
8
+ ssr: true,
9
+ ssrPriority: true
10
+ }
11
+ };
@@ -1,18 +1,5 @@
1
- <!DOCTYPE html>
2
- <html lang="en" theme="auto">
3
- <head>
4
- <meta charset='utf-8'>
5
- <meta http-equiv='X-UA-Compatible' content='IE=edge'>
6
- <title>Caching - Kempo Server</title>
7
- <meta name='viewport' content='width=device-width, initial-scale=1'>
8
- <link rel="icon" type="image/png" sizes="48x48" href="./media/icon48.png">
9
- <link rel="manifest" href="./manifest.json" />
10
- <link rel="stylesheet" href="./kempo.min.css" />
11
- </head>
12
- <body>
13
- <main>
14
- <a href="./" class="btn">Home</a>
15
- <h1>Module Caching</h1>
1
+ <page pageName="Module Caching" title="Module Caching - Kempo Server">
2
+ <content>
16
3
  <p>Kempo Server includes an intelligent module caching system that dramatically improves performance by caching JavaScript route modules in memory.</p>
17
4
 
18
5
  <h2>How It Works</h2>
@@ -229,7 +216,5 @@
229
216
  <li>Extend <code>ttlMs</code> for stable route files</li>
230
217
  <li>Monitor hit rates with admin endpoints</li>
231
218
  </ul>
232
-
233
- </main>
234
- </body>
235
- </html>
219
+ </content>
220
+ </page>
@@ -1,18 +1,5 @@
1
- <!DOCTYPE html>
2
- <html lang="en" theme="auto">
3
- <head>
4
- <meta charset='utf-8'>
5
- <meta http-equiv='X-UA-Compatible' content='IE=edge'>
6
- <title>CLI Utilities - Kempo Server</title>
7
- <meta name='viewport' content='width=device-width, initial-scale=1'>
8
- <link rel="icon" type="image/png" sizes="48x48" href="./media/icon48.png">
9
- <link rel="manifest" href="./manifest.json" />
10
- <link rel="stylesheet" href="./kempo.min.css" />
11
- </head>
12
- <body>
13
- <main>
14
- <a href="./" class="btn">Home</a>
15
- <h1>CLI Utilities</h1>
1
+ <page pageName="CLI Utilities" title="CLI Utilities - Kempo Server">
2
+ <content>
16
3
  <p>The CLI utilities provide simple command-line argument parsing functionality for Node.js applications.</p>
17
4
 
18
5
  <h2>Installation</h2>
@@ -80,8 +67,5 @@
80
67
  <li><code>promptUser(query)</code> - Prompt user for input</li>
81
68
  <li><code>promptYN(query, defaultValue)</code> - Prompt for yes/no with default</li>
82
69
  </ul>
83
- </main>
84
- <div style="height:25vh"></div>
85
- </body>
86
- </html></content>
87
- <parameter name="filePath">c:\Users\dusti\dev\kempo-server\docs\cli-utils.html
70
+ </content>
71
+ </page>