kempo-server 3.0.12 → 3.1.1
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/CHANGELOG.md +5 -0
- package/LICENSE.md +60 -0
- package/dist/router.js +1 -1
- package/docs/caching.html +60 -12
- package/docs/cli-utils.html +60 -12
- package/docs/configuration.html +60 -12
- package/docs/examples.html +60 -12
- package/docs/fs-utils.html +60 -12
- package/docs/getting-started.html +60 -12
- package/docs/index.html +60 -12
- package/docs/middleware.html +61 -13
- package/docs/request-response.html +60 -12
- package/docs/routing.html +60 -12
- package/docs/templating.html +61 -13
- package/docs/theme.css +1 -1
- package/docs-src/middleware.page.html +1 -1
- package/docs-src/nav.fragment.html +60 -12
- package/package.json +4 -1
- package/src/router.js +38 -14
- package/tests/router-custom-route-ssr.node-test.js +5 -5
package/docs/templating.html
CHANGED
|
@@ -37,13 +37,35 @@
|
|
|
37
37
|
class="d-if ph"
|
|
38
38
|
style="align-items: center"
|
|
39
39
|
>
|
|
40
|
-
<img
|
|
40
|
+
<img
|
|
41
|
+
src="./media/icon32.png"
|
|
42
|
+
alt="Kempo Server Icon"
|
|
43
|
+
class="pr"
|
|
44
|
+
/>
|
|
41
45
|
Kempo Server
|
|
42
46
|
</a>
|
|
43
47
|
<div class="flex"></div>
|
|
44
|
-
<a
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
<a
|
|
49
|
+
href="https://github.com/dustinpoissant/kempo-server?tab=License-1-ov-file#creative-commons-attribution-noncommercial-sharealike-20"
|
|
50
|
+
target="_blank"
|
|
51
|
+
><k-icon name="license"></k-icon></a>
|
|
52
|
+
<a
|
|
53
|
+
href="https://www.npmjs.com/package/kempo-server"
|
|
54
|
+
target="_blank"
|
|
55
|
+
><k-icon name="npm"></k-icon></a>
|
|
56
|
+
<a
|
|
57
|
+
href="https://github.com/dustinpoissant/kempo-server"
|
|
58
|
+
target="_blank"
|
|
59
|
+
><k-icon name="github-mark"></k-icon></a>
|
|
60
|
+
<k-theme-switcher
|
|
61
|
+
class="mr"
|
|
62
|
+
style="
|
|
63
|
+
--padding: 0.5rem;
|
|
64
|
+
--c_active: var(--tc_on_primary);
|
|
65
|
+
--tc_active: var(--c_primary);
|
|
66
|
+
--c_inactive__hover: rgba(255, 255, 255, 0.1);
|
|
67
|
+
"
|
|
68
|
+
></k-theme-switcher>
|
|
47
69
|
</k-nav>
|
|
48
70
|
<div style="width: 100%; height: 4rem;"></div>
|
|
49
71
|
<k-aside
|
|
@@ -51,9 +73,15 @@
|
|
|
51
73
|
state="offscreen"
|
|
52
74
|
>
|
|
53
75
|
<menu>
|
|
54
|
-
<a
|
|
76
|
+
<a
|
|
77
|
+
href="./"
|
|
78
|
+
class="ta-center bb mb r0"
|
|
79
|
+
>
|
|
55
80
|
<h1 class="tc-primary">Kempo Server</h1>
|
|
56
|
-
<img
|
|
81
|
+
<img
|
|
82
|
+
src="./media/icon128.png"
|
|
83
|
+
alt="Kempo UI Icon"
|
|
84
|
+
/>
|
|
57
85
|
</a>
|
|
58
86
|
|
|
59
87
|
<h3 class="mt mb0">Advanced Features</h3>
|
|
@@ -72,18 +100,38 @@
|
|
|
72
100
|
<br /><br />
|
|
73
101
|
|
|
74
102
|
</menu>
|
|
103
|
+
<k-aside-spacer></k-aside-spacer>
|
|
104
|
+
<k-theme-switcher
|
|
105
|
+
labels
|
|
106
|
+
style="--padding:var(--spacer_h);margin:var(--spacer_h) auto"
|
|
107
|
+
></k-theme-switcher>
|
|
75
108
|
</k-aside>
|
|
76
|
-
<script
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<script
|
|
109
|
+
<script
|
|
110
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/src/components/Aside.js"
|
|
111
|
+
type="module"
|
|
112
|
+
></script>
|
|
113
|
+
<script
|
|
114
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/src/components/Main.js"
|
|
115
|
+
type="module"
|
|
116
|
+
></script>
|
|
117
|
+
<script
|
|
118
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/src/components/Nav.js"
|
|
119
|
+
type="module"
|
|
120
|
+
></script>
|
|
121
|
+
<script
|
|
122
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/src/components/Icon.js"
|
|
123
|
+
type="module"
|
|
124
|
+
></script>
|
|
125
|
+
<script
|
|
126
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/src/components/ThemeSwitcher.js"
|
|
127
|
+
type="module"
|
|
128
|
+
></script>
|
|
81
129
|
<script>
|
|
82
130
|
document.getElementById('toggleNavSideMenu').addEventListener('click', async () => {
|
|
83
131
|
await window.customElements.whenDefined('k-aside');
|
|
84
132
|
document.getElementById('navSideMenu').toggle();
|
|
85
133
|
});
|
|
86
|
-
document.addEventListener('click', function(e) {
|
|
134
|
+
document.addEventListener('click', function (e) {
|
|
87
135
|
if (e.target.matches('a[href^="#"]')) {
|
|
88
136
|
e.preventDefault();
|
|
89
137
|
const targetId = e.target.getAttribute('href').replace('#', '');
|
|
@@ -336,7 +384,7 @@
|
|
|
336
384
|
</div>
|
|
337
385
|
<p>A common use case is sending templated emails. Create a dedicated email directory with a shared template, per-email pages, reusable fragments, and optional global content:</p>
|
|
338
386
|
<pre><code class="hljs javascript"><span class="hljs-keyword">import</span> { renderPageToString } <span class="hljs-keyword">from</span> <span class="hljs-string">'kempo-server/templating'</span>;<br /><span class="hljs-keyword">import</span> path <span class="hljs-keyword">from</span> <span class="hljs-string">'path'</span>;<br /><br /><span class="hljs-keyword">const</span> emailsDir = path.resolve(<span class="hljs-string">'./emails'</span>);<br /><span class="hljs-keyword">const</span> html = <span class="hljs-keyword">await</span> renderPageToString(<br /> path.join(emailsDir, <span class="hljs-string">'welcome.page.html'</span>),<br /> { userName: <span class="hljs-string">'Alice'</span>, orderId: <span class="hljs-string">'1234'</span> }<br />);</code></pre>
|
|
339
|
-
<p>Built-in vars (<code>2026</code>, <code>2026-04-
|
|
387
|
+
<p>Built-in vars (<code>2026</code>, <code>2026-04-20</code>, <code>2026-04-20T13:49:28.936Z</code>, <code>1776692968936</code>) are always available. Note that <code><page></code> tag attributes take highest priority and override <code>vars</code> with the same key.</p>
|
|
340
388
|
|
|
341
389
|
<h3 id="render-external-page">renderExternalPage</h3>
|
|
342
390
|
<p>Identical pipeline to <code>renderPage</code>, but decouples the page file's physical location from where templates and fragments are resolved. Use this when a page file lives outside <code>rootDir</code> — for example, in a plugin or extension package — but should be rendered using the host project's templates, fragments, and globals.</p>
|
package/docs/theme.css
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
:root {
|
|
2
2
|
--c_primary: hsl(262, 52%, 47%);
|
|
3
3
|
--c_secondary: rgb(51, 102, 255);
|
|
4
|
-
--tc_primary: light-dark(
|
|
4
|
+
--tc_primary: light-dark(hsl(262, 52%, 47%), rgb(187, 102, 255));
|
|
5
5
|
--tc_secondary: light-dark(#36f, rgb(138, 180, 248));
|
|
6
6
|
--c_highlight: light-dark(rgba(153, 51, 255, 0.25), rgba(153, 51, 255, 0.25));
|
|
7
7
|
}
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
|
|
77
77
|
<h3>Middleware Function Parameters</h3>
|
|
78
78
|
<ul>
|
|
79
|
-
<li><code>config</code> -
|
|
79
|
+
<li><code>config</code> - The middleware config object, extended with <code>rootPath</code> (absolute path to the server root directory)</li>
|
|
80
80
|
<li><code>req</code> - Request object (can be modified)</li>
|
|
81
81
|
<li><code>res</code> - Response object (can be modified)</li>
|
|
82
82
|
<li><code>next</code> - Function to call the next middleware or route handler</li>
|
|
@@ -14,13 +14,35 @@
|
|
|
14
14
|
class="d-if ph"
|
|
15
15
|
style="align-items: center"
|
|
16
16
|
>
|
|
17
|
-
<img
|
|
17
|
+
<img
|
|
18
|
+
src="./media/icon32.png"
|
|
19
|
+
alt="Kempo Server Icon"
|
|
20
|
+
class="pr"
|
|
21
|
+
/>
|
|
18
22
|
Kempo Server
|
|
19
23
|
</a>
|
|
20
24
|
<div class="flex"></div>
|
|
21
|
-
<a
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
<a
|
|
26
|
+
href="https://github.com/dustinpoissant/kempo-server?tab=License-1-ov-file#creative-commons-attribution-noncommercial-sharealike-20"
|
|
27
|
+
target="_blank"
|
|
28
|
+
><k-icon name="license"></k-icon></a>
|
|
29
|
+
<a
|
|
30
|
+
href="https://www.npmjs.com/package/kempo-server"
|
|
31
|
+
target="_blank"
|
|
32
|
+
><k-icon name="npm"></k-icon></a>
|
|
33
|
+
<a
|
|
34
|
+
href="https://github.com/dustinpoissant/kempo-server"
|
|
35
|
+
target="_blank"
|
|
36
|
+
><k-icon name="github-mark"></k-icon></a>
|
|
37
|
+
<k-theme-switcher
|
|
38
|
+
class="mr"
|
|
39
|
+
style="
|
|
40
|
+
--padding: 0.5rem;
|
|
41
|
+
--c_active: var(--tc_on_primary);
|
|
42
|
+
--tc_active: var(--c_primary);
|
|
43
|
+
--c_inactive__hover: rgba(255, 255, 255, 0.1);
|
|
44
|
+
"
|
|
45
|
+
></k-theme-switcher>
|
|
24
46
|
</k-nav>
|
|
25
47
|
<div style="width: 100%; height: 4rem;"></div>
|
|
26
48
|
<k-aside
|
|
@@ -28,24 +50,50 @@
|
|
|
28
50
|
state="offscreen"
|
|
29
51
|
>
|
|
30
52
|
<menu>
|
|
31
|
-
<a
|
|
53
|
+
<a
|
|
54
|
+
href="./"
|
|
55
|
+
class="ta-center bb mb r0"
|
|
56
|
+
>
|
|
32
57
|
<h1 class="tc-primary">Kempo Server</h1>
|
|
33
|
-
<img
|
|
58
|
+
<img
|
|
59
|
+
src="./media/icon128.png"
|
|
60
|
+
alt="Kempo UI Icon"
|
|
61
|
+
/>
|
|
34
62
|
</a>
|
|
35
63
|
<location name="links" />
|
|
36
64
|
</menu>
|
|
65
|
+
<k-aside-spacer></k-aside-spacer>
|
|
66
|
+
<k-theme-switcher
|
|
67
|
+
labels
|
|
68
|
+
style="--padding:var(--spacer_h);margin:var(--spacer_h) auto"
|
|
69
|
+
></k-theme-switcher>
|
|
37
70
|
</k-aside>
|
|
38
|
-
<script
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<script
|
|
71
|
+
<script
|
|
72
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/src/components/Aside.js"
|
|
73
|
+
type="module"
|
|
74
|
+
></script>
|
|
75
|
+
<script
|
|
76
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/src/components/Main.js"
|
|
77
|
+
type="module"
|
|
78
|
+
></script>
|
|
79
|
+
<script
|
|
80
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/src/components/Nav.js"
|
|
81
|
+
type="module"
|
|
82
|
+
></script>
|
|
83
|
+
<script
|
|
84
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/src/components/Icon.js"
|
|
85
|
+
type="module"
|
|
86
|
+
></script>
|
|
87
|
+
<script
|
|
88
|
+
src="https://cdn.jsdelivr.net/npm/kempo-ui@0.3/src/components/ThemeSwitcher.js"
|
|
89
|
+
type="module"
|
|
90
|
+
></script>
|
|
43
91
|
<script>
|
|
44
92
|
document.getElementById('toggleNavSideMenu').addEventListener('click', async () => {
|
|
45
93
|
await window.customElements.whenDefined('k-aside');
|
|
46
94
|
document.getElementById('navSideMenu').toggle();
|
|
47
95
|
});
|
|
48
|
-
document.addEventListener('click', function(e) {
|
|
96
|
+
document.addEventListener('click', function (e) {
|
|
49
97
|
if (e.target.matches('a[href^="#"]')) {
|
|
50
98
|
e.preventDefault();
|
|
51
99
|
const targetId = e.target.getAttribute('href').replace('#', '');
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kempo-server",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.1.1",
|
|
5
5
|
"description": "A lightweight, zero-dependency, file based routing server.",
|
|
6
6
|
"exports": {
|
|
7
7
|
"./rescan": "./dist/rescan.js",
|
|
@@ -32,5 +32,8 @@
|
|
|
32
32
|
"repository": {
|
|
33
33
|
"type": "git",
|
|
34
34
|
"url": "https://github.com/dustinpoissant/kempo-server"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"kempo-ui": "^0.3.17"
|
|
35
38
|
}
|
|
36
39
|
}
|
package/src/router.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
loggingMiddleware
|
|
18
18
|
} from './builtinMiddleware.js';
|
|
19
19
|
import { onRescan } from './rescan.js';
|
|
20
|
-
import { renderDir,
|
|
20
|
+
import { renderDir, renderExternalPage } from './templating/index.js';
|
|
21
21
|
|
|
22
22
|
export default async (flags, log) => {
|
|
23
23
|
log('Initializing router', 3);
|
|
@@ -218,7 +218,7 @@ export default async (flags, log) => {
|
|
|
218
218
|
const customMiddleware = middlewareModule.default;
|
|
219
219
|
|
|
220
220
|
if (typeof customMiddleware === 'function') {
|
|
221
|
-
middlewareRunner.use(customMiddleware(config.middleware));
|
|
221
|
+
middlewareRunner.use(customMiddleware({ ...config.middleware, rootPath }));
|
|
222
222
|
log(`Custom middleware loaded: ${middlewarePath}`, 3);
|
|
223
223
|
} else {
|
|
224
224
|
log(`Custom middleware error: ${middlewarePath} does not export a default function`, 1);
|
|
@@ -269,8 +269,9 @@ export default async (flags, log) => {
|
|
|
269
269
|
// Convert wildcard pattern to regex
|
|
270
270
|
// IMPORTANT: Replace ** BEFORE * to avoid replacing both * in **
|
|
271
271
|
const regexPattern = normalizedPattern
|
|
272
|
-
.replace(/\*\*/g, '
|
|
273
|
-
.replace(/\*/g, '([^/]+)')
|
|
272
|
+
.replace(/\*\*/g, '\x00GLOBSTAR\x00') // placeholder to avoid double-replace
|
|
273
|
+
.replace(/\*/g, '([^/]+)') // single * — one path segment
|
|
274
|
+
.replace(/\x00GLOBSTAR\x00/g, '(.*)'); // ** — zero or more segments including slashes
|
|
274
275
|
|
|
275
276
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
276
277
|
return regex.exec(requestPath);
|
|
@@ -395,6 +396,18 @@ export default async (flags, log) => {
|
|
|
395
396
|
return null;
|
|
396
397
|
};
|
|
397
398
|
|
|
399
|
+
// Build a virtual resolveDir that reflects the URL depth, so pathToRoot is computed correctly.
|
|
400
|
+
// For pages inside rootPath use their actual dir; for external pages fake a subdir at the right depth.
|
|
401
|
+
const getResolveDir = (pageFilePath, reqUrl) => {
|
|
402
|
+
if(pageFilePath.startsWith(rootPath)) return path.dirname(pageFilePath);
|
|
403
|
+
const urlParts = reqUrl.split('?')[0].replace(/\/+$/, '').split('/').filter(Boolean);
|
|
404
|
+
const depth = urlParts.length;
|
|
405
|
+
// Build a virtual path depth levels deep inside rootPath
|
|
406
|
+
let fakeDir = rootPath;
|
|
407
|
+
for(let i = 0; i < depth; i++) fakeDir = path.join(fakeDir, urlParts[i] || '__scope__');
|
|
408
|
+
return fakeDir;
|
|
409
|
+
};
|
|
410
|
+
|
|
398
411
|
// Serve a resolved file or directory (fileStat already known).
|
|
399
412
|
// Returns true if handled, null if directory has no matching route/index file.
|
|
400
413
|
const serveResolvedPath = async (filePath, fileStat, params, req, res) => {
|
|
@@ -418,7 +431,8 @@ export default async (flags, log) => {
|
|
|
418
431
|
if(candidate.endsWith('.page.html')) {
|
|
419
432
|
log(`Rendering page template: ${candidatePath}`, 2);
|
|
420
433
|
const {globals, state, maxFragmentDepth} = config.templating;
|
|
421
|
-
const
|
|
434
|
+
const resolveDir = getResolveDir(candidatePath, req.url);
|
|
435
|
+
const html = await renderExternalPage(candidatePath, rootPath, resolveDir, globals, state, maxFragmentDepth);
|
|
422
436
|
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
|
|
423
437
|
res.end(html);
|
|
424
438
|
return true;
|
|
@@ -439,7 +453,8 @@ export default async (flags, log) => {
|
|
|
439
453
|
if(fileName.endsWith('.page.html')) {
|
|
440
454
|
log(`Rendering page template: ${filePath}`, 2);
|
|
441
455
|
const {globals, state, maxFragmentDepth} = config.templating;
|
|
442
|
-
const
|
|
456
|
+
const resolveDir = getResolveDir(filePath, req.url);
|
|
457
|
+
const html = await renderExternalPage(filePath, rootPath, resolveDir, globals, state, maxFragmentDepth);
|
|
443
458
|
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
|
|
444
459
|
res.end(html);
|
|
445
460
|
return true;
|
|
@@ -496,7 +511,8 @@ export default async (flags, log) => {
|
|
|
496
511
|
for(const pageFile of [base + '.page.html', path.join(base, 'index.page.html')]) {
|
|
497
512
|
try {
|
|
498
513
|
await stat(pageFile);
|
|
499
|
-
const
|
|
514
|
+
const resolveDir = getResolveDir(pageFile, req.url);
|
|
515
|
+
const html = await renderExternalPage(pageFile, rootPath, resolveDir, globals, state, maxFragmentDepth);
|
|
500
516
|
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
|
|
501
517
|
res.end(html);
|
|
502
518
|
log(`SSR rendered custom route: ${pageFile}`, 2);
|
|
@@ -513,22 +529,23 @@ export default async (flags, log) => {
|
|
|
513
529
|
// Track 404 attempts to avoid unnecessary rescans
|
|
514
530
|
const rescanAttempts = new Map(); // path -> attempt count
|
|
515
531
|
|
|
516
|
-
// Walk up the directory tree
|
|
532
|
+
// Walk up the directory tree looking for CATCH.js, CATCH.html, CATCH.page.html.
|
|
533
|
+
// startDir: absolute path to begin the walk. boundDir: walk stops here (inclusive).
|
|
517
534
|
// Returns true if a catch fallback handler was found and served, false otherwise.
|
|
518
|
-
const
|
|
535
|
+
const serveCatchFallbackFrom = async (startDir, boundDir, req, res) => {
|
|
519
536
|
const candidates = ['CATCH.js', 'CATCH.html', 'CATCH.page.html'];
|
|
520
|
-
let dir = path.
|
|
521
|
-
|
|
522
|
-
if(path.extname(dir)) dir = path.dirname(dir);
|
|
537
|
+
let dir = path.extname(startDir) ? path.dirname(startDir) : startDir;
|
|
538
|
+
const bound = path.resolve(boundDir);
|
|
523
539
|
|
|
524
|
-
while(dir.startsWith(
|
|
540
|
+
while(path.resolve(dir).startsWith(bound)) {
|
|
525
541
|
for(const candidate of candidates) {
|
|
526
542
|
const candidatePath = path.join(dir, candidate);
|
|
527
543
|
try { await stat(candidatePath); } catch { continue; }
|
|
528
544
|
log(`Serving catch fallback: ${candidatePath}`, 2);
|
|
529
545
|
if(candidate === 'CATCH.page.html') {
|
|
530
546
|
const {globals, state, maxFragmentDepth} = config.templating;
|
|
531
|
-
const
|
|
547
|
+
const resolveDir = getResolveDir(candidatePath, req.url);
|
|
548
|
+
const html = await renderExternalPage(candidatePath, rootPath, resolveDir, globals, state, maxFragmentDepth);
|
|
532
549
|
res.writeHead(404, {'Content-Type': 'text/html; charset=utf-8'});
|
|
533
550
|
res.end(html);
|
|
534
551
|
return true;
|
|
@@ -549,6 +566,12 @@ export default async (flags, log) => {
|
|
|
549
566
|
}
|
|
550
567
|
return false;
|
|
551
568
|
};
|
|
569
|
+
|
|
570
|
+
const serveCatchFallback = (requestPath, req, res) => {
|
|
571
|
+
const startDir = path.join(rootPath, requestPath.startsWith('/') ? requestPath.slice(1) : requestPath);
|
|
572
|
+
return serveCatchFallbackFrom(startDir, rootPath, req, res);
|
|
573
|
+
};
|
|
574
|
+
|
|
552
575
|
const dynamicNoRescanPaths = new Set(); // paths that have exceeded max attempts
|
|
553
576
|
|
|
554
577
|
// Helper function to check if a path should skip rescanning
|
|
@@ -692,6 +715,7 @@ export default async (flags, log) => {
|
|
|
692
715
|
const result = await serveCustomRoutePath(resolvedFilePath, req, res, customRootDir);
|
|
693
716
|
if(result) return;
|
|
694
717
|
log(`Wildcard route path not found: ${requestPath}`, 2);
|
|
718
|
+
if(await serveCatchFallbackFrom(resolvedFilePath, customRootDir, req, res)) return;
|
|
695
719
|
} catch(error) {
|
|
696
720
|
log(`Error serving wildcard route ${requestPath}: ${error.message}`, 1);
|
|
697
721
|
enhancedResponse.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
@@ -23,7 +23,7 @@ export default {
|
|
|
23
23
|
const template = '<html><body><location name="main" /></body></html>';
|
|
24
24
|
const page = '<page template="default"><content location="main"><h1>Admin Page</h1></content></page>';
|
|
25
25
|
|
|
26
|
-
await write(dir, '
|
|
26
|
+
await write(dir, 'public/default.template.html', template);
|
|
27
27
|
await write(dir, 'admin/dashboard.page.html', page);
|
|
28
28
|
await write(dir, 'public/index.html', '<h1>root</h1>');
|
|
29
29
|
|
|
@@ -57,7 +57,7 @@ export default {
|
|
|
57
57
|
const template = '<html><body><location name="main" /></body></html>';
|
|
58
58
|
const page = '<page template="default"><content location="main"><h1>Section Index</h1></content></page>';
|
|
59
59
|
|
|
60
|
-
await write(dir, '
|
|
60
|
+
await write(dir, 'public/default.template.html', template);
|
|
61
61
|
await write(dir, 'admin/users/index.page.html', page);
|
|
62
62
|
await write(dir, 'public/index.html', '<h1>root</h1>');
|
|
63
63
|
|
|
@@ -115,15 +115,15 @@ export default {
|
|
|
115
115
|
}
|
|
116
116
|
},
|
|
117
117
|
|
|
118
|
-
'wildcard custom route SSR uses
|
|
118
|
+
'wildcard custom route SSR uses public root for template/fragment lookup': async ({pass, fail}) => {
|
|
119
119
|
try {
|
|
120
120
|
await withTempDir(async (dir) => {
|
|
121
121
|
const template = '<html><fragment name="nav" /><location name="main" /></html>';
|
|
122
122
|
const fragment = '<nav>Custom Nav</nav>';
|
|
123
123
|
const page = '<page template="default"><content location="main"><p>Content</p></content></page>';
|
|
124
124
|
|
|
125
|
-
await write(dir, '
|
|
126
|
-
await write(dir, '
|
|
125
|
+
await write(dir, 'public/default.template.html', template);
|
|
126
|
+
await write(dir, 'public/nav.fragment.html', fragment);
|
|
127
127
|
await write(dir, 'admin/about.page.html', page);
|
|
128
128
|
await write(dir, 'public/index.html', '<h1>root</h1>');
|
|
129
129
|
|