@emkodev/emroute 1.7.3 → 1.8.0-beta.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/README.md +1 -1
- package/core/component/abstract.component.ts +74 -0
- package/{src → core}/component/page.component.ts +3 -61
- package/core/component/widget.component.ts +54 -0
- package/core/pipeline/pipeline.ts +224 -0
- package/{src/renderer/ssr → core/renderer}/html.renderer.ts +26 -47
- package/{src/renderer/ssr → core/renderer}/md.renderer.ts +22 -41
- package/{src/renderer/ssr → core/renderer}/ssr.renderer.ts +44 -58
- package/{src/route → core/router}/route.resolver.ts +1 -10
- package/core/router/route.trie.ts +175 -0
- package/core/runtime/abstract.runtime.ts +47 -0
- package/core/server/emroute.server.ts +324 -0
- package/core/type/component.type.ts +39 -0
- package/core/type/element.type.ts +10 -0
- package/core/type/logger.type.ts +20 -0
- package/core/type/markdown.type.ts +8 -0
- package/core/type/route-tree.type.ts +28 -0
- package/core/type/route.type.ts +75 -0
- package/core/type/widget.type.ts +27 -0
- package/core/util/html.util.ts +50 -0
- package/{src → core}/util/md.util.ts +3 -5
- package/{src/route → core/util}/route-tree.util.ts +0 -2
- package/{src → core}/util/widget-resolve.util.ts +15 -46
- package/{src → core}/widget/widget.parser.ts +2 -23
- package/core/widget/widget.registry.ts +36 -0
- package/dist/core/component/abstract.component.d.ts +48 -0
- package/dist/core/component/abstract.component.js +42 -0
- package/dist/core/component/abstract.component.js.map +1 -0
- package/dist/core/component/page.component.d.ts +23 -0
- package/dist/core/component/page.component.js +49 -0
- package/dist/core/component/page.component.js.map +1 -0
- package/dist/core/component/widget.component.d.ts +17 -0
- package/dist/core/component/widget.component.js +37 -0
- package/dist/core/component/widget.component.js.map +1 -0
- package/dist/core/pipeline/pipeline.d.ts +61 -0
- package/dist/core/pipeline/pipeline.js +189 -0
- package/dist/core/pipeline/pipeline.js.map +1 -0
- package/dist/{src/renderer/ssr → core/renderer}/html.renderer.d.ts +8 -24
- package/dist/{src/renderer/ssr → core/renderer}/html.renderer.js +20 -35
- package/dist/core/renderer/html.renderer.js.map +1 -0
- package/dist/{src/renderer/ssr → core/renderer}/md.renderer.d.ts +6 -21
- package/dist/{src/renderer/ssr → core/renderer}/md.renderer.js +16 -32
- package/dist/core/renderer/md.renderer.js.map +1 -0
- package/dist/{src/renderer/ssr → core/renderer}/ssr.renderer.d.ts +11 -27
- package/dist/{src/renderer/ssr → core/renderer}/ssr.renderer.js +33 -37
- package/dist/core/renderer/ssr.renderer.js.map +1 -0
- package/dist/{src/route → core/router}/route.resolver.d.ts +1 -8
- package/dist/{src/route → core/router}/route.resolver.js +0 -1
- package/dist/core/router/route.resolver.js.map +1 -0
- package/dist/core/router/route.trie.d.ts +32 -0
- package/dist/core/router/route.trie.js +152 -0
- package/dist/core/router/route.trie.js.map +1 -0
- package/dist/core/runtime/abstract.runtime.d.ts +32 -0
- package/dist/core/runtime/abstract.runtime.js +26 -0
- package/dist/core/runtime/abstract.runtime.js.map +1 -0
- package/dist/core/server/emroute.server.d.ts +48 -0
- package/dist/core/server/emroute.server.js +239 -0
- package/dist/core/server/emroute.server.js.map +1 -0
- package/dist/core/server/server.type.d.ts +45 -0
- package/dist/core/server/server.type.js +11 -0
- package/dist/core/server/server.type.js.map +1 -0
- package/dist/core/type/component.type.d.ts +37 -0
- package/dist/core/type/component.type.js +7 -0
- package/dist/core/type/component.type.js.map +1 -0
- package/dist/core/type/element.type.d.ts +9 -0
- package/dist/core/type/element.type.js +5 -0
- package/dist/core/type/element.type.js.map +1 -0
- package/dist/core/type/logger.type.d.ts +14 -0
- package/dist/core/type/logger.type.js +8 -0
- package/dist/core/type/logger.type.js.map +1 -0
- package/dist/core/type/markdown.type.d.ts +7 -0
- package/dist/core/type/markdown.type.js +5 -0
- package/dist/core/type/markdown.type.js.map +1 -0
- package/dist/{src → core}/type/route-tree.type.d.ts +0 -12
- package/dist/{src → core}/type/route-tree.type.js +0 -1
- package/dist/core/type/route-tree.type.js.map +1 -0
- package/dist/core/type/route.type.d.ts +62 -0
- package/dist/core/type/route.type.js +7 -0
- package/dist/core/type/route.type.js.map +1 -0
- package/dist/core/type/widget.type.d.ts +27 -0
- package/dist/core/type/widget.type.js +5 -0
- package/dist/core/type/widget.type.js.map +1 -0
- package/dist/core/util/html.util.d.ts +14 -0
- package/dist/core/util/html.util.js +43 -0
- package/dist/core/util/html.util.js.map +1 -0
- package/dist/{src → core}/util/md.util.d.ts +0 -1
- package/dist/{src → core}/util/md.util.js +0 -2
- package/dist/core/util/md.util.js.map +1 -0
- package/dist/{src/route → core/util}/route-tree.util.js +0 -2
- package/dist/core/util/route-tree.util.js.map +1 -0
- package/dist/core/util/widget-resolve.util.d.ts +28 -0
- package/dist/{src → core}/util/widget-resolve.util.js +12 -42
- package/dist/core/util/widget-resolve.util.js.map +1 -0
- package/dist/{src → core}/widget/widget.parser.d.ts +0 -13
- package/dist/{src → core}/widget/widget.parser.js +1 -22
- package/dist/core/widget/widget.parser.js.map +1 -0
- package/dist/core/widget/widget.registry.d.ts +14 -0
- package/dist/core/widget/widget.registry.js +26 -0
- package/dist/core/widget/widget.registry.js.map +1 -0
- package/dist/emroute.js +1092 -1220
- package/dist/emroute.js.map +36 -5
- package/dist/runtime/abstract.runtime.d.ts +41 -7
- package/dist/runtime/abstract.runtime.js +404 -9
- package/dist/runtime/abstract.runtime.js.map +1 -1
- package/dist/runtime/bun/fs/bun-fs.runtime.d.ts +1 -0
- package/dist/runtime/bun/fs/bun-fs.runtime.js +15 -1
- package/dist/runtime/bun/fs/bun-fs.runtime.js.map +1 -1
- package/dist/runtime/bun/sqlite/bun-sqlite.runtime.d.ts +2 -0
- package/dist/runtime/bun/sqlite/bun-sqlite.runtime.js +8 -0
- package/dist/runtime/bun/sqlite/bun-sqlite.runtime.js.map +1 -1
- package/dist/runtime/fetch.runtime.d.ts +3 -3
- package/dist/runtime/fetch.runtime.js +3 -3
- package/dist/runtime/sitemap.generator.d.ts +1 -1
- package/dist/runtime/sitemap.generator.js +1 -1
- package/dist/runtime/sitemap.generator.js.map +1 -1
- package/dist/runtime/universal/fs/universal-fs.runtime.d.ts +1 -0
- package/dist/runtime/universal/fs/universal-fs.runtime.js +15 -1
- package/dist/runtime/universal/fs/universal-fs.runtime.js.map +1 -1
- package/dist/server/build.util.d.ts +9 -10
- package/dist/server/build.util.js +11 -31
- package/dist/server/build.util.js.map +1 -1
- package/dist/server/codegen.util.d.ts +1 -1
- package/dist/server/emroute.server.d.ts +8 -35
- package/dist/server/emroute.server.js +7 -351
- package/dist/server/emroute.server.js.map +1 -1
- package/dist/server/esbuild-manifest.plugin.js +1 -1
- package/dist/server/esbuild-manifest.plugin.js.map +1 -1
- package/dist/server/server-api.type.d.ts +3 -71
- package/dist/server/server-api.type.js +1 -8
- package/dist/server/server-api.type.js.map +1 -1
- package/dist/src/element/component.element.d.ts +6 -14
- package/dist/src/element/component.element.js +13 -40
- package/dist/src/element/component.element.js.map +1 -1
- package/dist/src/element/markdown.element.d.ts +2 -2
- package/dist/src/element/markdown.element.js +3 -2
- package/dist/src/element/markdown.element.js.map +1 -1
- package/dist/src/index.d.ts +15 -14
- package/dist/src/index.js +8 -8
- package/dist/src/index.js.map +1 -1
- package/dist/src/renderer/spa/emroute.app.d.ts +50 -0
- package/dist/src/renderer/spa/emroute.app.js +246 -0
- package/dist/src/renderer/spa/emroute.app.js.map +1 -0
- package/dist/src/renderer/spa/mod.d.ts +17 -16
- package/dist/src/renderer/spa/mod.js +9 -9
- package/dist/src/renderer/spa/mod.js.map +1 -1
- package/dist/src/renderer/spa/thin-client.d.ts +3 -3
- package/dist/src/renderer/spa/thin-client.js +7 -7
- package/dist/src/renderer/spa/thin-client.js.map +1 -1
- package/dist/src/route/route.core.d.ts +3 -3
- package/dist/src/util/html.util.d.ts +5 -22
- package/dist/src/util/html.util.js +8 -56
- package/dist/src/util/html.util.js.map +1 -1
- package/dist/src/widget/breadcrumb.widget.d.ts +2 -2
- package/dist/src/widget/breadcrumb.widget.js +2 -2
- package/dist/src/widget/breadcrumb.widget.js.map +1 -1
- package/dist/src/widget/page-title.widget.d.ts +1 -1
- package/dist/src/widget/page-title.widget.js +1 -1
- package/dist/src/widget/page-title.widget.js.map +1 -1
- package/package.json +8 -8
- package/runtime/abstract.runtime.ts +433 -17
- package/runtime/bun/fs/bun-fs.runtime.ts +15 -1
- package/runtime/bun/sqlite/bun-sqlite.runtime.ts +9 -0
- package/runtime/fetch.runtime.ts +3 -3
- package/runtime/sitemap.generator.ts +2 -2
- package/runtime/universal/fs/universal-fs.runtime.ts +15 -1
- package/server/build.util.ts +17 -43
- package/server/codegen.util.ts +1 -1
- package/server/emroute.server.ts +12 -426
- package/src/element/component.element.ts +14 -54
- package/src/element/markdown.element.ts +4 -3
- package/src/index.ts +22 -19
- package/src/renderer/spa/{thin-client.ts → emroute.app.ts} +19 -20
- package/src/renderer/spa/mod.ts +22 -22
- package/src/util/html.util.ts +16 -61
- package/src/widget/breadcrumb.widget.ts +3 -3
- package/src/widget/page-title.widget.ts +1 -1
- package/dist/src/component/abstract.component.d.ts +0 -199
- package/dist/src/component/abstract.component.js +0 -84
- package/dist/src/component/abstract.component.js.map +0 -1
- package/dist/src/component/page.component.d.ts +0 -74
- package/dist/src/component/page.component.js +0 -107
- package/dist/src/component/page.component.js.map +0 -1
- package/dist/src/component/widget.component.d.ts +0 -47
- package/dist/src/component/widget.component.js +0 -69
- package/dist/src/component/widget.component.js.map +0 -1
- package/dist/src/renderer/ssr/html.renderer.js.map +0 -1
- package/dist/src/renderer/ssr/md.renderer.js.map +0 -1
- package/dist/src/renderer/ssr/ssr.renderer.js.map +0 -1
- package/dist/src/route/route-tree.util.js.map +0 -1
- package/dist/src/route/route.matcher.d.ts +0 -86
- package/dist/src/route/route.matcher.js +0 -214
- package/dist/src/route/route.matcher.js.map +0 -1
- package/dist/src/route/route.resolver.js.map +0 -1
- package/dist/src/route/route.trie.d.ts +0 -38
- package/dist/src/route/route.trie.js +0 -206
- package/dist/src/route/route.trie.js.map +0 -1
- package/dist/src/type/element.type.d.ts +0 -19
- package/dist/src/type/element.type.js +0 -9
- package/dist/src/type/element.type.js.map +0 -1
- package/dist/src/type/logger.type.d.ts +0 -17
- package/dist/src/type/logger.type.js +0 -9
- package/dist/src/type/logger.type.js.map +0 -1
- package/dist/src/type/markdown.type.d.ts +0 -20
- package/dist/src/type/markdown.type.js +0 -2
- package/dist/src/type/markdown.type.js.map +0 -1
- package/dist/src/type/route-tree.type.js.map +0 -1
- package/dist/src/type/route.type.d.ts +0 -94
- package/dist/src/type/route.type.js +0 -8
- package/dist/src/type/route.type.js.map +0 -1
- package/dist/src/type/widget.type.d.ts +0 -55
- package/dist/src/type/widget.type.js +0 -10
- package/dist/src/type/widget.type.js.map +0 -1
- package/dist/src/util/logger.util.d.ts +0 -26
- package/dist/src/util/logger.util.js +0 -80
- package/dist/src/util/logger.util.js.map +0 -1
- package/dist/src/util/md.util.js.map +0 -1
- package/dist/src/util/widget-resolve.util.d.ts +0 -52
- package/dist/src/util/widget-resolve.util.js.map +0 -1
- package/dist/src/widget/widget.parser.js.map +0 -1
- package/dist/src/widget/widget.registry.d.ts +0 -23
- package/dist/src/widget/widget.registry.js +0 -42
- package/dist/src/widget/widget.registry.js.map +0 -1
- package/runtime/bun/esbuild-runtime-loader.plugin.ts +0 -112
- package/server/esbuild-manifest.plugin.ts +0 -209
- package/server/server-api.type.ts +0 -101
- package/src/component/abstract.component.ts +0 -231
- package/src/component/widget.component.ts +0 -85
- package/src/route/route.core.ts +0 -371
- package/src/route/route.trie.ts +0 -265
- package/src/type/element.type.ts +0 -22
- package/src/type/logger.type.ts +0 -24
- package/src/type/markdown.type.ts +0 -21
- package/src/type/route-tree.type.ts +0 -51
- package/src/type/route.type.ts +0 -124
- package/src/type/widget.type.ts +0 -65
- package/src/util/logger.util.ts +0 -83
- package/src/widget/widget.registry.ts +0 -51
- /package/dist/{src/route → core/util}/route-tree.util.d.ts +0 -0
package/dist/emroute.js
CHANGED
|
@@ -1,7 +1,32 @@
|
|
|
1
|
-
// dist/
|
|
1
|
+
// dist/core/util/html.util.js
|
|
2
2
|
var SSR_ATTR = "ssr";
|
|
3
3
|
var LAZY_ATTR = "lazy";
|
|
4
|
-
var
|
|
4
|
+
var BLOCKED_PROTOCOLS = /^(javascript|data|vbscript):/i;
|
|
5
|
+
function assertSafeRedirect(url) {
|
|
6
|
+
if (BLOCKED_PROTOCOLS.test(url.trim())) {
|
|
7
|
+
throw new Error(`Unsafe redirect URL blocked: ${url}`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function escapeHtml(text) {
|
|
11
|
+
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'").replaceAll("`", "`");
|
|
12
|
+
}
|
|
13
|
+
function unescapeHtml(text) {
|
|
14
|
+
return text.replaceAll("`", "`").replaceAll("'", "'").replaceAll(""", '"').replaceAll(">", ">").replaceAll("<", "<").replaceAll("&", "&");
|
|
15
|
+
}
|
|
16
|
+
function scopeWidgetCss(css, widgetName) {
|
|
17
|
+
return `@scope (widget-${widgetName}) {
|
|
18
|
+
${css}
|
|
19
|
+
}`;
|
|
20
|
+
}
|
|
21
|
+
var STATUS_MESSAGES = {
|
|
22
|
+
401: "Unauthorized",
|
|
23
|
+
403: "Forbidden",
|
|
24
|
+
404: "Not Found",
|
|
25
|
+
500: "Internal Server Error"
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// dist/src/util/html.util.js
|
|
29
|
+
class SsrShadowRoot {
|
|
5
30
|
host;
|
|
6
31
|
_innerHTML = "";
|
|
7
32
|
constructor(host) {
|
|
@@ -16,8 +41,7 @@ var SsrShadowRoot = class {
|
|
|
16
41
|
setHTMLUnsafe(html, _options) {
|
|
17
42
|
this._innerHTML = html;
|
|
18
43
|
}
|
|
19
|
-
append(..._nodes) {
|
|
20
|
-
}
|
|
44
|
+
append(..._nodes) {}
|
|
21
45
|
querySelector(_selector) {
|
|
22
46
|
return null;
|
|
23
47
|
}
|
|
@@ -30,12 +54,12 @@ var SsrShadowRoot = class {
|
|
|
30
54
|
get firstChild() {
|
|
31
55
|
return null;
|
|
32
56
|
}
|
|
33
|
-
}
|
|
34
|
-
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
class SsrHTMLElement {
|
|
35
60
|
_innerHTML = "";
|
|
36
61
|
_shadowRoot = null;
|
|
37
|
-
_attributes =
|
|
38
|
-
// Accept any CSS property assignment without error
|
|
62
|
+
_attributes = new Map;
|
|
39
63
|
style = new Proxy({}, {
|
|
40
64
|
set(_target, _prop, _value) {
|
|
41
65
|
return true;
|
|
@@ -43,7 +67,7 @@ var SsrHTMLElement = class {
|
|
|
43
67
|
get(_target, prop) {
|
|
44
68
|
if (typeof prop === "string")
|
|
45
69
|
return "";
|
|
46
|
-
return
|
|
70
|
+
return;
|
|
47
71
|
}
|
|
48
72
|
});
|
|
49
73
|
get innerHTML() {
|
|
@@ -90,73 +114,41 @@ var SsrHTMLElement = class {
|
|
|
90
114
|
querySelectorAll(_selector) {
|
|
91
115
|
return [];
|
|
92
116
|
}
|
|
93
|
-
append(..._nodes) {
|
|
94
|
-
}
|
|
117
|
+
append(..._nodes) {}
|
|
95
118
|
appendChild(node) {
|
|
96
119
|
return node;
|
|
97
120
|
}
|
|
98
|
-
};
|
|
99
|
-
var HTMLElementBase = globalThis.HTMLElement ?? SsrHTMLElement;
|
|
100
|
-
function escapeHtml(text) {
|
|
101
|
-
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'").replaceAll("`", "`");
|
|
102
|
-
}
|
|
103
|
-
function unescapeHtml(text) {
|
|
104
|
-
return text.replaceAll("`", "`").replaceAll("'", "'").replaceAll(""", '"').replaceAll(">", ">").replaceAll("<", "<").replaceAll("&", "&");
|
|
105
|
-
}
|
|
106
|
-
function scopeWidgetCss(css, widgetName) {
|
|
107
|
-
return `@scope (widget-${widgetName}) {
|
|
108
|
-
${css}
|
|
109
|
-
}`;
|
|
110
121
|
}
|
|
111
|
-
var
|
|
112
|
-
401: "Unauthorized",
|
|
113
|
-
403: "Forbidden",
|
|
114
|
-
404: "Not Found",
|
|
115
|
-
500: "Internal Server Error"
|
|
116
|
-
};
|
|
122
|
+
var HTMLElementBase = globalThis.HTMLElement ?? SsrHTMLElement;
|
|
117
123
|
|
|
118
124
|
// dist/src/element/slot.element.js
|
|
119
|
-
|
|
120
|
-
}
|
|
125
|
+
class RouterSlot extends HTMLElementBase {
|
|
126
|
+
}
|
|
121
127
|
|
|
122
128
|
// dist/src/element/markdown.element.js
|
|
123
|
-
|
|
129
|
+
class MarkdownElement extends HTMLElementBase {
|
|
124
130
|
static renderer = null;
|
|
125
131
|
static rendererInitPromise = null;
|
|
126
132
|
abortController = null;
|
|
127
|
-
/**
|
|
128
|
-
* Set the markdown renderer.
|
|
129
|
-
* Must be called before any <mark-down> elements are connected.
|
|
130
|
-
*
|
|
131
|
-
* @example
|
|
132
|
-
* ```ts
|
|
133
|
-
* import { createEmkoRenderer } from './emko.renderer.ts';
|
|
134
|
-
* MarkdownElement.setRenderer(await createEmkoRenderer());
|
|
135
|
-
* ```
|
|
136
|
-
*/
|
|
137
133
|
static setRenderer(renderer) {
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
MarkdownElement.renderer = renderer;
|
|
135
|
+
MarkdownElement.rendererInitPromise = renderer.init ? renderer.init() : null;
|
|
140
136
|
}
|
|
141
|
-
/** Get the current renderer (if set). Used by bootEmrouteApp to pass through to createEmrouteServer. */
|
|
142
137
|
static getConfiguredRenderer() {
|
|
143
|
-
return
|
|
138
|
+
return MarkdownElement.renderer;
|
|
144
139
|
}
|
|
145
|
-
/**
|
|
146
|
-
* Get the current renderer, waiting for init if needed.
|
|
147
|
-
*/
|
|
148
140
|
static async getRenderer() {
|
|
149
|
-
const renderer =
|
|
141
|
+
const renderer = MarkdownElement.renderer;
|
|
150
142
|
if (!renderer) {
|
|
151
143
|
throw new Error("No markdown renderer configured. Call MarkdownElement.setRenderer() before using <mark-down> elements.");
|
|
152
144
|
}
|
|
153
|
-
if (
|
|
154
|
-
await
|
|
145
|
+
if (MarkdownElement.rendererInitPromise) {
|
|
146
|
+
await MarkdownElement.rendererInitPromise;
|
|
155
147
|
}
|
|
156
148
|
return renderer;
|
|
157
149
|
}
|
|
158
150
|
async connectedCallback() {
|
|
159
|
-
this.abortController = new AbortController
|
|
151
|
+
this.abortController = new AbortController;
|
|
160
152
|
await this.loadContent();
|
|
161
153
|
}
|
|
162
154
|
disconnectedCallback() {
|
|
@@ -192,7 +184,7 @@ var MarkdownElement = class _MarkdownElement extends HTMLElementBase {
|
|
|
192
184
|
}
|
|
193
185
|
async renderContent(markdown) {
|
|
194
186
|
try {
|
|
195
|
-
const renderer = await
|
|
187
|
+
const renderer = await MarkdownElement.getRenderer();
|
|
196
188
|
this.innerHTML = renderer.render(markdown);
|
|
197
189
|
} catch (error) {
|
|
198
190
|
this.showError(error);
|
|
@@ -202,32 +194,26 @@ var MarkdownElement = class _MarkdownElement extends HTMLElementBase {
|
|
|
202
194
|
const message = error instanceof Error ? error.message : String(error);
|
|
203
195
|
this.innerHTML = `<div>Markdown Error: ${escapeHtml(message)}</div>`;
|
|
204
196
|
}
|
|
205
|
-
}
|
|
197
|
+
}
|
|
206
198
|
|
|
207
199
|
// dist/src/element/component.element.js
|
|
208
200
|
function filterUndefined(obj) {
|
|
209
201
|
const result = {};
|
|
210
202
|
let hasValue = false;
|
|
211
203
|
for (const [k, v] of Object.entries(obj)) {
|
|
212
|
-
if (v !==
|
|
204
|
+
if (v !== undefined) {
|
|
213
205
|
result[k] = v;
|
|
214
206
|
hasValue = true;
|
|
215
207
|
}
|
|
216
208
|
}
|
|
217
|
-
return hasValue ? result :
|
|
209
|
+
return hasValue ? result : undefined;
|
|
218
210
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
static
|
|
222
|
-
/** Lazy module loaders keyed by tag name — set by registerLazy(). */
|
|
223
|
-
static lazyLoaders = /* @__PURE__ */ new Map();
|
|
224
|
-
/** Cached module promises for lazy-loaded widgets — avoids re-fetching. */
|
|
225
|
-
static lazyModules = /* @__PURE__ */ new Map();
|
|
226
|
-
/** App-level context provider set once during router initialization. */
|
|
211
|
+
|
|
212
|
+
class ComponentElement extends HTMLElementBase {
|
|
213
|
+
static lazyLoaders = new Map;
|
|
227
214
|
static extendContext;
|
|
228
|
-
/** Register (or clear) the context provider that enriches every widget's ComponentContext. */
|
|
229
215
|
static setContextProvider(provider) {
|
|
230
|
-
|
|
216
|
+
ComponentElement.extendContext = provider;
|
|
231
217
|
}
|
|
232
218
|
component;
|
|
233
219
|
effectiveFiles;
|
|
@@ -239,7 +225,6 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
|
|
|
239
225
|
deferred = null;
|
|
240
226
|
abortController = null;
|
|
241
227
|
intersectionObserver = null;
|
|
242
|
-
/** Promise that resolves with fetched data (available after loadData starts) */
|
|
243
228
|
dataPromise = null;
|
|
244
229
|
constructor(component, files) {
|
|
245
230
|
super();
|
|
@@ -249,52 +234,36 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
|
|
|
249
234
|
this.attachShadow({ mode: "open" });
|
|
250
235
|
}
|
|
251
236
|
}
|
|
252
|
-
/**
|
|
253
|
-
* Register a widget as a custom element: `widget-{name}`.
|
|
254
|
-
* Creates a fresh widget instance per DOM element (per-element state).
|
|
255
|
-
* Optional `files` parameter provides discovered file paths without mutating
|
|
256
|
-
* the component instance.
|
|
257
|
-
*/
|
|
258
237
|
static register(component, files) {
|
|
259
238
|
const tagName = `widget-${component.name}`;
|
|
260
239
|
if (!globalThis.customElements || customElements.get(tagName)) {
|
|
261
240
|
return;
|
|
262
241
|
}
|
|
263
242
|
const WidgetClass = component.constructor;
|
|
264
|
-
const BoundElement = class extends
|
|
243
|
+
const BoundElement = class extends ComponentElement {
|
|
265
244
|
constructor() {
|
|
266
|
-
super(new WidgetClass
|
|
245
|
+
super(new WidgetClass, files);
|
|
267
246
|
}
|
|
268
247
|
};
|
|
269
248
|
customElements.define(tagName, BoundElement);
|
|
270
249
|
}
|
|
271
|
-
/**
|
|
272
|
-
* Register a widget class (not instance) as a custom element: `widget-{name}`.
|
|
273
|
-
* Used for manifest-based registration where classes are loaded dynamically.
|
|
274
|
-
*/
|
|
275
250
|
static registerClass(WidgetClass, name, files) {
|
|
276
251
|
const tagName = `widget-${name}`;
|
|
277
252
|
if (!globalThis.customElements || customElements.get(tagName)) {
|
|
278
253
|
return;
|
|
279
254
|
}
|
|
280
|
-
const BoundElement = class extends
|
|
255
|
+
const BoundElement = class extends ComponentElement {
|
|
281
256
|
constructor() {
|
|
282
|
-
super(new WidgetClass
|
|
257
|
+
super(new WidgetClass, files);
|
|
283
258
|
}
|
|
284
259
|
};
|
|
285
260
|
customElements.define(tagName, BoundElement);
|
|
286
261
|
}
|
|
287
|
-
|
|
288
|
-
* Register a widget lazily: define the custom element immediately (so SSR
|
|
289
|
-
* content via Declarative Shadow DOM is adopted), but defer loading the
|
|
290
|
-
* module until connectedCallback fires. Once loaded, the real component
|
|
291
|
-
* replaces the placeholder and hydration proceeds normally.
|
|
292
|
-
*/
|
|
293
|
-
static registerLazy(name, files, loader) {
|
|
262
|
+
static registerLazy(name, loader) {
|
|
294
263
|
const tagName = `widget-${name}`;
|
|
295
264
|
if (!globalThis.customElements || customElements.get(tagName))
|
|
296
265
|
return;
|
|
297
|
-
|
|
266
|
+
ComponentElement.lazyLoaders.set(tagName, loader);
|
|
298
267
|
const placeholder = {
|
|
299
268
|
name,
|
|
300
269
|
getData: () => Promise.resolve(null),
|
|
@@ -303,17 +272,13 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
|
|
|
303
272
|
renderError: () => "",
|
|
304
273
|
renderMarkdownError: () => ""
|
|
305
274
|
};
|
|
306
|
-
const BoundElement = class extends
|
|
275
|
+
const BoundElement = class extends ComponentElement {
|
|
307
276
|
constructor() {
|
|
308
|
-
super(placeholder
|
|
277
|
+
super(placeholder);
|
|
309
278
|
}
|
|
310
279
|
};
|
|
311
280
|
customElements.define(tagName, BoundElement);
|
|
312
281
|
}
|
|
313
|
-
/**
|
|
314
|
-
* Promise that resolves when component is ready (data loaded and rendered).
|
|
315
|
-
* Used by router to wait for async components.
|
|
316
|
-
*/
|
|
317
282
|
get ready() {
|
|
318
283
|
if (this.state === "ready") {
|
|
319
284
|
return Promise.resolve();
|
|
@@ -323,23 +288,21 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
|
|
|
323
288
|
}
|
|
324
289
|
async connectedCallback() {
|
|
325
290
|
const tagName = this.tagName.toLowerCase();
|
|
326
|
-
const lazyLoader =
|
|
291
|
+
const lazyLoader = ComponentElement.lazyLoaders.get(tagName);
|
|
327
292
|
if (lazyLoader) {
|
|
328
293
|
try {
|
|
329
|
-
|
|
330
|
-
if (
|
|
331
|
-
|
|
332
|
-
_ComponentElement.lazyModules.set(tagName, modulePromise);
|
|
294
|
+
const mod = await lazyLoader();
|
|
295
|
+
if (mod.__files && typeof mod.__files === "object") {
|
|
296
|
+
this.effectiveFiles = mod.__files;
|
|
333
297
|
}
|
|
334
|
-
const mod = await modulePromise;
|
|
335
298
|
for (const exp of Object.values(mod)) {
|
|
336
299
|
if (exp && typeof exp === "object" && "getData" in exp) {
|
|
337
300
|
const WidgetClass = exp.constructor;
|
|
338
|
-
this.component = new WidgetClass
|
|
301
|
+
this.component = new WidgetClass;
|
|
339
302
|
break;
|
|
340
303
|
}
|
|
341
304
|
if (typeof exp === "function" && exp.prototype?.getData) {
|
|
342
|
-
this.component = new exp
|
|
305
|
+
this.component = new exp;
|
|
343
306
|
break;
|
|
344
307
|
}
|
|
345
308
|
}
|
|
@@ -353,7 +316,7 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
|
|
|
353
316
|
}
|
|
354
317
|
this.component.element = this;
|
|
355
318
|
this.style.contentVisibility = "auto";
|
|
356
|
-
this.abortController = new AbortController
|
|
319
|
+
this.abortController = new AbortController;
|
|
357
320
|
const signal = this.abortController.signal;
|
|
358
321
|
const params = {};
|
|
359
322
|
for (const attr of this.attributes) {
|
|
@@ -386,15 +349,14 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
|
|
|
386
349
|
params: this.params ?? {},
|
|
387
350
|
...filteredFiles ? { files: filteredFiles } : {}
|
|
388
351
|
};
|
|
389
|
-
this.context =
|
|
352
|
+
this.context = ComponentElement.extendContext ? ComponentElement.extendContext(base) : base;
|
|
390
353
|
if (this.hasAttribute(SSR_ATTR)) {
|
|
391
354
|
this.removeAttribute(SSR_ATTR);
|
|
392
355
|
const lightText = this.textContent?.trim();
|
|
393
356
|
if (lightText) {
|
|
394
357
|
try {
|
|
395
358
|
this.data = JSON.parse(lightText);
|
|
396
|
-
} catch {
|
|
397
|
-
}
|
|
359
|
+
} catch {}
|
|
398
360
|
}
|
|
399
361
|
this.textContent = "";
|
|
400
362
|
this.state = "ready";
|
|
@@ -408,7 +370,8 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
|
|
|
408
370
|
return;
|
|
409
371
|
}
|
|
410
372
|
if (this.hasAttribute(LAZY_ATTR)) {
|
|
411
|
-
this.intersectionObserver = new IntersectionObserver((
|
|
373
|
+
this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
374
|
+
const entry = entries[0];
|
|
412
375
|
if (entry.isIntersecting) {
|
|
413
376
|
this.intersectionObserver?.disconnect();
|
|
414
377
|
this.intersectionObserver = null;
|
|
@@ -422,56 +385,28 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
|
|
|
422
385
|
}
|
|
423
386
|
disconnectedCallback() {
|
|
424
387
|
this.component.destroy?.();
|
|
425
|
-
this.component.element =
|
|
388
|
+
this.component.element = undefined;
|
|
426
389
|
this.intersectionObserver?.disconnect();
|
|
427
390
|
this.intersectionObserver = null;
|
|
428
391
|
this.abortController?.abort();
|
|
429
392
|
this.abortController = null;
|
|
430
393
|
this.state = "idle";
|
|
431
394
|
this.data = null;
|
|
432
|
-
this.context =
|
|
395
|
+
this.context = undefined;
|
|
433
396
|
this.dataPromise = null;
|
|
434
397
|
this.errorMessage = "";
|
|
435
398
|
this.signalReady();
|
|
436
399
|
this.deferred = null;
|
|
437
400
|
}
|
|
438
|
-
/**
|
|
439
|
-
* Reload component data. Aborts any in-flight request first.
|
|
440
|
-
*/
|
|
441
401
|
async reload() {
|
|
442
402
|
if (this.params === null)
|
|
443
403
|
return;
|
|
444
404
|
this.abortController?.abort();
|
|
445
|
-
this.abortController = new AbortController
|
|
405
|
+
this.abortController = new AbortController;
|
|
446
406
|
await this.loadData();
|
|
447
407
|
}
|
|
448
|
-
/**
|
|
449
|
-
* Fetch a single file by path, with caching.
|
|
450
|
-
* Absolute URLs (http/https) pass through; relative paths get '/' prefix.
|
|
451
|
-
*/
|
|
452
|
-
static loadFile(path) {
|
|
453
|
-
const cached = _ComponentElement.fileCache.get(path);
|
|
454
|
-
if (cached)
|
|
455
|
-
return cached;
|
|
456
|
-
const url = path.startsWith("http://") || path.startsWith("https://") ? path : path.startsWith("/") ? path : "/" + path;
|
|
457
|
-
const promise = fetch(url).then((res) => res.ok ? res.text() : void 0, () => void 0);
|
|
458
|
-
_ComponentElement.fileCache.set(path, promise);
|
|
459
|
-
return promise;
|
|
460
|
-
}
|
|
461
|
-
/**
|
|
462
|
-
* Load all companion files for this widget instance.
|
|
463
|
-
* Uses effectiveFiles (from registration) falling back to component.files.
|
|
464
|
-
*/
|
|
465
408
|
async loadFiles() {
|
|
466
|
-
|
|
467
|
-
if (!filePaths)
|
|
468
|
-
return {};
|
|
469
|
-
const [html, md, css] = await Promise.all([
|
|
470
|
-
filePaths.html ? _ComponentElement.loadFile(filePaths.html) : void 0,
|
|
471
|
-
filePaths.md ? _ComponentElement.loadFile(filePaths.md) : void 0,
|
|
472
|
-
filePaths.css ? _ComponentElement.loadFile(filePaths.css) : void 0
|
|
473
|
-
]);
|
|
474
|
-
return filterUndefined({ html, md, css }) ?? {};
|
|
409
|
+
return this.effectiveFiles ?? {};
|
|
475
410
|
}
|
|
476
411
|
async loadData() {
|
|
477
412
|
if (this.params === null)
|
|
@@ -535,189 +470,242 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
|
|
|
535
470
|
});
|
|
536
471
|
}
|
|
537
472
|
}
|
|
538
|
-
}
|
|
473
|
+
}
|
|
539
474
|
|
|
540
|
-
// dist/
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
/** Look up a widget by name. */
|
|
475
|
+
// dist/core/widget/widget.registry.js
|
|
476
|
+
class WidgetRegistry {
|
|
477
|
+
entries = new Map;
|
|
478
|
+
add(widget, modulePath) {
|
|
479
|
+
this.entries.set(widget.name, { widget, modulePath });
|
|
480
|
+
}
|
|
548
481
|
get(name) {
|
|
549
|
-
return this.
|
|
482
|
+
return this.entries.get(name)?.widget;
|
|
483
|
+
}
|
|
484
|
+
getModulePath(name) {
|
|
485
|
+
return this.entries.get(name)?.modulePath;
|
|
550
486
|
}
|
|
551
|
-
/** Iterate all registered widgets. */
|
|
552
487
|
[Symbol.iterator]() {
|
|
553
|
-
|
|
488
|
+
const entries = this.entries.values();
|
|
489
|
+
return function* () {
|
|
490
|
+
for (const entry of entries)
|
|
491
|
+
yield entry.widget;
|
|
492
|
+
}();
|
|
554
493
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// dist/core/router/route.trie.js
|
|
497
|
+
class RouteTrie {
|
|
498
|
+
tree;
|
|
499
|
+
constructor(tree) {
|
|
500
|
+
this.tree = tree;
|
|
501
|
+
}
|
|
502
|
+
match(pathname) {
|
|
503
|
+
pathname = this.normalizePath(pathname);
|
|
504
|
+
if (pathname === "/") {
|
|
505
|
+
if (this.tree.files || this.tree.redirect) {
|
|
506
|
+
return { node: this.tree, pattern: "/", params: {} };
|
|
507
|
+
}
|
|
508
|
+
return;
|
|
568
509
|
}
|
|
569
|
-
return
|
|
510
|
+
return this.walk(this.tree, this.splitSegments(pathname), 0, {}, "/");
|
|
570
511
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m, tsx, d, ext, cm) {
|
|
577
|
-
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : d + ext + "." + cm.toLowerCase() + "js";
|
|
578
|
-
});
|
|
512
|
+
findErrorBoundary(pathname) {
|
|
513
|
+
pathname = this.normalizePath(pathname);
|
|
514
|
+
if (pathname === "/")
|
|
515
|
+
return this.tree.errorBoundary;
|
|
516
|
+
return this.walkForBoundary(this.tree, this.splitSegments(pathname), 0, this.tree.errorBoundary);
|
|
579
517
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
518
|
+
findRoute(pattern) {
|
|
519
|
+
if (pattern === "/") {
|
|
520
|
+
return this.tree.files || this.tree.redirect ? this.tree : undefined;
|
|
521
|
+
}
|
|
522
|
+
const segments = this.splitSegments(pattern);
|
|
523
|
+
let node = this.tree;
|
|
524
|
+
for (const segment of segments) {
|
|
525
|
+
let child;
|
|
526
|
+
if (segment.startsWith(":") && segment.endsWith("*")) {
|
|
527
|
+
child = node.wildcard?.child;
|
|
528
|
+
} else if (segment.startsWith(":")) {
|
|
529
|
+
child = node.dynamic?.child;
|
|
530
|
+
} else {
|
|
531
|
+
child = node.children?.[segment];
|
|
532
|
+
}
|
|
533
|
+
if (!child)
|
|
534
|
+
return;
|
|
535
|
+
node = child;
|
|
536
|
+
}
|
|
537
|
+
return node.files || node.redirect ? node : undefined;
|
|
538
|
+
}
|
|
539
|
+
safeDecode(segment) {
|
|
540
|
+
try {
|
|
541
|
+
return decodeURIComponent(segment);
|
|
542
|
+
} catch {
|
|
543
|
+
return segment;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
splitSegments(pathname) {
|
|
547
|
+
return pathname.substring(1).split("/");
|
|
548
|
+
}
|
|
549
|
+
normalizePath(pathname) {
|
|
550
|
+
if (pathname.length > 1 && pathname.endsWith("/")) {
|
|
551
|
+
pathname = pathname.slice(0, -1);
|
|
552
|
+
}
|
|
553
|
+
if (!pathname.startsWith("/")) {
|
|
554
|
+
pathname = "/" + pathname;
|
|
555
|
+
}
|
|
556
|
+
return pathname;
|
|
557
|
+
}
|
|
558
|
+
walk(node, segments, index, params, pattern) {
|
|
559
|
+
if (index === segments.length) {
|
|
560
|
+
if (node.files || node.redirect) {
|
|
561
|
+
return { node, pattern, params: { ...params } };
|
|
562
|
+
}
|
|
563
|
+
if (node.wildcard && (node.wildcard.child.files || node.wildcard.child.redirect)) {
|
|
564
|
+
const wp = pattern === "/" ? `/:${node.wildcard.param}*` : `${pattern}/:${node.wildcard.param}*`;
|
|
565
|
+
return {
|
|
566
|
+
node: node.wildcard.child,
|
|
567
|
+
pattern: wp,
|
|
568
|
+
params: { ...params, [node.wildcard.param]: "" }
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const segment = segments[index];
|
|
574
|
+
const staticChild = node.children?.[segment];
|
|
575
|
+
if (staticChild) {
|
|
576
|
+
const childPattern = pattern === "/" ? `/${segment}` : `${pattern}/${segment}`;
|
|
577
|
+
const result = this.walk(staticChild, segments, index + 1, params, childPattern);
|
|
578
|
+
if (result)
|
|
579
|
+
return result;
|
|
580
|
+
}
|
|
581
|
+
if (node.dynamic) {
|
|
582
|
+
const { param, child } = node.dynamic;
|
|
583
|
+
params[param] = this.safeDecode(segment);
|
|
584
|
+
const childPattern = pattern === "/" ? `/:${param}` : `${pattern}/:${param}`;
|
|
585
|
+
const result = this.walk(child, segments, index + 1, params, childPattern);
|
|
586
|
+
if (result)
|
|
587
|
+
return result;
|
|
588
|
+
delete params[param];
|
|
589
|
+
}
|
|
590
|
+
if (node.wildcard && (node.wildcard.child.files || node.wildcard.child.redirect)) {
|
|
591
|
+
const { param, child } = node.wildcard;
|
|
592
|
+
let rest = this.safeDecode(segment);
|
|
593
|
+
for (let i = index + 1;i < segments.length; i++) {
|
|
594
|
+
rest += "/" + this.safeDecode(segments[i]);
|
|
595
|
+
}
|
|
596
|
+
const wp = pattern === "/" ? `/:${param}*` : `${pattern}/:${param}*`;
|
|
597
|
+
return {
|
|
598
|
+
node: child,
|
|
599
|
+
pattern: wp,
|
|
600
|
+
params: { ...params, [param]: rest }
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
walkForBoundary(node, segments, index, deepest) {
|
|
606
|
+
if (index === segments.length) {
|
|
607
|
+
return node.errorBoundary ?? deepest;
|
|
608
|
+
}
|
|
609
|
+
const segment = segments[index];
|
|
610
|
+
const staticChild = node.children?.[segment];
|
|
611
|
+
if (staticChild) {
|
|
612
|
+
return this.walkForBoundary(staticChild, segments, index + 1, staticChild.errorBoundary ?? deepest);
|
|
613
|
+
}
|
|
614
|
+
if (node.dynamic) {
|
|
615
|
+
return this.walkForBoundary(node.dynamic.child, segments, index + 1, node.dynamic.child.errorBoundary ?? deepest);
|
|
616
|
+
}
|
|
617
|
+
if (node.wildcard) {
|
|
618
|
+
return node.wildcard.child.errorBoundary ?? deepest;
|
|
619
|
+
}
|
|
620
|
+
return deepest;
|
|
587
621
|
}
|
|
588
622
|
}
|
|
623
|
+
|
|
624
|
+
// dist/core/runtime/abstract.runtime.js
|
|
625
|
+
var ROUTES_MANIFEST_PATH = "/routes.manifest.json";
|
|
626
|
+
var WIDGETS_MANIFEST_PATH = "/widgets.manifest.json";
|
|
627
|
+
var ELEMENTS_MANIFEST_PATH = "/elements.manifest.json";
|
|
628
|
+
|
|
629
|
+
// dist/core/type/logger.type.js
|
|
630
|
+
var noop = () => {};
|
|
631
|
+
var defaultLogger = { error: noop, warn: noop };
|
|
632
|
+
function setLogger(_logger) {
|
|
633
|
+
console.warn("[emroute] setLogger() is deprecated. Pass `logger` in Emroute.create() config instead.");
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// dist/core/pipeline/pipeline.js
|
|
589
637
|
var DEFAULT_ROOT_ROUTE = {
|
|
590
638
|
pattern: "/",
|
|
591
639
|
type: "page",
|
|
592
640
|
modulePath: "__default_root__"
|
|
593
641
|
};
|
|
594
|
-
function toRouteConfig(
|
|
595
|
-
const node = resolved.node;
|
|
642
|
+
function toRouteConfig(node, pattern) {
|
|
596
643
|
return {
|
|
597
|
-
pattern
|
|
644
|
+
pattern,
|
|
598
645
|
type: node.redirect ? "redirect" : "page",
|
|
599
646
|
modulePath: node.redirect ?? node.files?.ts ?? node.files?.js ?? node.files?.html ?? node.files?.md ?? "",
|
|
600
647
|
...node.files ? { files: node.files } : {}
|
|
601
648
|
};
|
|
602
649
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
650
|
+
|
|
651
|
+
class Pipeline {
|
|
652
|
+
runtime;
|
|
606
653
|
contextProvider;
|
|
607
|
-
|
|
608
|
-
moduleCache = /* @__PURE__ */ new Map();
|
|
609
|
-
widgetFileCache = /* @__PURE__ */ new Map();
|
|
654
|
+
logger;
|
|
610
655
|
moduleLoaders;
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
this.
|
|
615
|
-
this.readFile = options.fileReader ?? ((path) => fetch(path, { headers: { Accept: "text/plain" } }).then((r) => r.text()));
|
|
616
|
-
this.contextProvider = options.extendContext;
|
|
656
|
+
constructor(options) {
|
|
657
|
+
this.runtime = options.runtime;
|
|
658
|
+
this.contextProvider = options.contextProvider;
|
|
659
|
+
this.logger = options.logger ?? defaultLogger;
|
|
617
660
|
this.moduleLoaders = options.moduleLoaders ?? {};
|
|
618
661
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
return this.currentRoute?.params ?? {};
|
|
624
|
-
}
|
|
625
|
-
/**
|
|
626
|
-
* Add event listener for router events.
|
|
627
|
-
*/
|
|
628
|
-
addEventListener(listener) {
|
|
629
|
-
this.listeners.add(listener);
|
|
630
|
-
return () => this.listeners.delete(listener);
|
|
631
|
-
}
|
|
632
|
-
/**
|
|
633
|
-
* Emit router event to listeners.
|
|
634
|
-
*/
|
|
635
|
-
emit(event) {
|
|
636
|
-
for (const listener of this.listeners) {
|
|
637
|
-
try {
|
|
638
|
-
listener(event);
|
|
639
|
-
} catch (e) {
|
|
640
|
-
console.error("[Router] Event listener error:", e);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
662
|
+
async getResolver() {
|
|
663
|
+
const response = await this.runtime.query(ROUTES_MANIFEST_PATH);
|
|
664
|
+
const tree = response.status === 404 ? {} : await response.json();
|
|
665
|
+
return new RouteTrie(tree);
|
|
643
666
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
*/
|
|
648
|
-
match(url) {
|
|
649
|
-
const pathname = url.pathname;
|
|
650
|
-
const resolved = this.resolver.match(pathname);
|
|
667
|
+
async match(url) {
|
|
668
|
+
const resolver = await this.getResolver();
|
|
669
|
+
const resolved = resolver.match(url.pathname);
|
|
651
670
|
if (resolved) {
|
|
652
|
-
return {
|
|
653
|
-
route: toRouteConfig(resolved),
|
|
654
|
-
params: resolved.params
|
|
655
|
-
};
|
|
671
|
+
return { route: toRouteConfig(resolved.node, resolved.pattern), params: resolved.params };
|
|
656
672
|
}
|
|
657
|
-
if (pathname === "/" || pathname === "") {
|
|
658
|
-
return {
|
|
659
|
-
route: DEFAULT_ROOT_ROUTE,
|
|
660
|
-
params: {}
|
|
661
|
-
};
|
|
673
|
+
if (url.pathname === "/" || url.pathname === "") {
|
|
674
|
+
return { route: DEFAULT_ROOT_ROUTE, params: {} };
|
|
662
675
|
}
|
|
663
|
-
return
|
|
676
|
+
return;
|
|
664
677
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
const node =
|
|
678
|
+
async findRoute(pattern) {
|
|
679
|
+
const resolver = await this.getResolver();
|
|
680
|
+
const node = resolver.findRoute(pattern);
|
|
668
681
|
if (!node)
|
|
669
|
-
return
|
|
670
|
-
return
|
|
671
|
-
pattern: `/${status}`,
|
|
672
|
-
type: "page",
|
|
673
|
-
modulePath: node.files?.ts ?? node.files?.js ?? node.files?.html ?? node.files?.md ?? "",
|
|
674
|
-
...node.files ? { files: node.files } : {}
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
/** Get global error handler (root errorBoundary). */
|
|
678
|
-
getErrorHandler() {
|
|
679
|
-
const modulePath = this.resolver.findErrorBoundary("/");
|
|
680
|
-
if (!modulePath)
|
|
681
|
-
return void 0;
|
|
682
|
-
return { pattern: "/", type: "error", modulePath };
|
|
682
|
+
return;
|
|
683
|
+
return toRouteConfig(node, pattern);
|
|
683
684
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
* Callers should only rely on modulePath.
|
|
688
|
-
*/
|
|
689
|
-
findErrorBoundary(pathname) {
|
|
690
|
-
const modulePath = this.resolver.findErrorBoundary(pathname);
|
|
685
|
+
async findErrorBoundary(pathname) {
|
|
686
|
+
const resolver = await this.getResolver();
|
|
687
|
+
const modulePath = resolver.findErrorBoundary(pathname);
|
|
691
688
|
if (!modulePath)
|
|
692
|
-
return
|
|
689
|
+
return;
|
|
693
690
|
return { pattern: pathname, modulePath };
|
|
694
691
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
*/
|
|
699
|
-
findRoute(pattern) {
|
|
700
|
-
const node = this.resolver.findRoute(pattern);
|
|
692
|
+
async getStatusPage(status) {
|
|
693
|
+
const resolver = await this.getResolver();
|
|
694
|
+
const node = resolver.findRoute(`/${status}`);
|
|
701
695
|
if (!node)
|
|
702
|
-
return
|
|
703
|
-
return {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
696
|
+
return;
|
|
697
|
+
return toRouteConfig(node, `/${status}`);
|
|
698
|
+
}
|
|
699
|
+
async getErrorHandler() {
|
|
700
|
+
const resolver = await this.getResolver();
|
|
701
|
+
const modulePath = resolver.findErrorBoundary("/");
|
|
702
|
+
if (!modulePath)
|
|
703
|
+
return;
|
|
704
|
+
return { pattern: "/", type: "error", modulePath };
|
|
709
705
|
}
|
|
710
|
-
/**
|
|
711
|
-
* Build route hierarchy from a pattern.
|
|
712
|
-
* Patterns are always unprefixed (no basePath).
|
|
713
|
-
*
|
|
714
|
-
* e.g., '/projects/:id/tasks'
|
|
715
|
-
* → ['/', '/projects', '/projects/:id', '/projects/:id/tasks']
|
|
716
|
-
*/
|
|
717
706
|
buildRouteHierarchy(pattern) {
|
|
718
|
-
if (pattern === "/")
|
|
707
|
+
if (pattern === "/")
|
|
719
708
|
return ["/"];
|
|
720
|
-
}
|
|
721
709
|
const segments = pattern.split("/").filter(Boolean);
|
|
722
710
|
const hierarchy = ["/"];
|
|
723
711
|
let current = "";
|
|
@@ -727,351 +715,119 @@ var RouteCore = class {
|
|
|
727
715
|
}
|
|
728
716
|
return hierarchy;
|
|
729
717
|
}
|
|
730
|
-
/**
|
|
731
|
-
* Normalize URL by removing trailing slashes (except bare '/').
|
|
732
|
-
*/
|
|
733
|
-
normalizeUrl(url) {
|
|
734
|
-
if (url.length > 1 && url.endsWith("/")) {
|
|
735
|
-
return url.slice(0, -1);
|
|
736
|
-
}
|
|
737
|
-
return url;
|
|
738
|
-
}
|
|
739
|
-
/**
|
|
740
|
-
* Convert relative path to absolute path.
|
|
741
|
-
*/
|
|
742
|
-
toAbsolutePath(path) {
|
|
743
|
-
return path.startsWith("/") ? path : "/" + path;
|
|
744
|
-
}
|
|
745
|
-
/**
|
|
746
|
-
* Load a module with caching.
|
|
747
|
-
* Uses pre-bundled loaders when available, falls back to dynamic import.
|
|
748
|
-
*/
|
|
749
718
|
async loadModule(modulePath) {
|
|
750
|
-
if (this.moduleCache.has(modulePath)) {
|
|
751
|
-
return this.moduleCache.get(modulePath);
|
|
752
|
-
}
|
|
753
|
-
let module;
|
|
754
719
|
const loader = this.moduleLoaders[modulePath];
|
|
755
720
|
if (loader) {
|
|
756
|
-
|
|
757
|
-
} else {
|
|
758
|
-
const absolutePath = this.toAbsolutePath(modulePath);
|
|
759
|
-
module = await import(__rewriteRelativeImportExtension(absolutePath));
|
|
721
|
+
return await loader();
|
|
760
722
|
}
|
|
761
|
-
|
|
762
|
-
return
|
|
723
|
+
const abs = modulePath.startsWith("/") ? modulePath : "/" + modulePath;
|
|
724
|
+
return await this.runtime.loadModule(abs);
|
|
725
|
+
}
|
|
726
|
+
getModuleFiles(mod) {
|
|
727
|
+
if (!mod || typeof mod !== "object")
|
|
728
|
+
return;
|
|
729
|
+
const files = mod.__files;
|
|
730
|
+
if (!files || typeof files !== "object")
|
|
731
|
+
return;
|
|
732
|
+
return files;
|
|
763
733
|
}
|
|
764
|
-
|
|
765
|
-
* Load widget file contents with caching.
|
|
766
|
-
*/
|
|
767
|
-
async loadWidgetFiles(widgetFiles) {
|
|
734
|
+
async loadFiles(files) {
|
|
768
735
|
const load = async (path) => {
|
|
769
|
-
const
|
|
770
|
-
const cached = this.widgetFileCache.get(absPath);
|
|
771
|
-
if (cached !== void 0)
|
|
772
|
-
return cached;
|
|
736
|
+
const abs = path.startsWith("/") ? path : "/" + path;
|
|
773
737
|
try {
|
|
774
|
-
|
|
775
|
-
this.widgetFileCache.set(absPath, content);
|
|
776
|
-
return content;
|
|
738
|
+
return await this.runtime.query(abs, { as: "text" });
|
|
777
739
|
} catch (e) {
|
|
778
|
-
console.warn(`[
|
|
779
|
-
return
|
|
740
|
+
console.warn(`[Pipeline] Failed to load file ${path}:`, e instanceof Error ? e.message : e);
|
|
741
|
+
return;
|
|
780
742
|
}
|
|
781
743
|
};
|
|
782
744
|
const [html, md, css] = await Promise.all([
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
745
|
+
files.html ? load(files.html) : undefined,
|
|
746
|
+
files.md ? load(files.md) : undefined,
|
|
747
|
+
files.css ? load(files.css) : undefined
|
|
786
748
|
]);
|
|
787
749
|
const result = {};
|
|
788
|
-
if (html
|
|
750
|
+
if (html !== undefined)
|
|
789
751
|
result.html = html;
|
|
790
|
-
if (md
|
|
752
|
+
if (md !== undefined)
|
|
791
753
|
result.md = md;
|
|
792
|
-
if (css
|
|
754
|
+
if (css !== undefined)
|
|
793
755
|
result.css = css;
|
|
794
756
|
return result;
|
|
795
757
|
}
|
|
796
|
-
/**
|
|
797
|
-
* Build a RouteInfo from a matched route and the resolved URL pathname.
|
|
798
|
-
* Called once per navigation; the result is reused across the route hierarchy.
|
|
799
|
-
*/
|
|
800
758
|
toRouteInfo(matched, url) {
|
|
801
|
-
return {
|
|
802
|
-
url,
|
|
803
|
-
params: matched.params
|
|
804
|
-
};
|
|
759
|
+
return { url, params: matched.params };
|
|
805
760
|
}
|
|
806
|
-
|
|
807
|
-
* Get inlined `__files` from a cached module (merged module pattern).
|
|
808
|
-
* Returns undefined if the module isn't cached or has no __files.
|
|
809
|
-
*/
|
|
810
|
-
getModuleFiles(modulePath) {
|
|
811
|
-
const cached = this.moduleCache.get(modulePath);
|
|
812
|
-
if (!cached || typeof cached !== "object")
|
|
813
|
-
return void 0;
|
|
814
|
-
const files = cached.__files;
|
|
815
|
-
if (!files || typeof files !== "object")
|
|
816
|
-
return void 0;
|
|
817
|
-
return files;
|
|
818
|
-
}
|
|
819
|
-
/**
|
|
820
|
-
* Build a ComponentContext by extending RouteInfo with loaded file contents.
|
|
821
|
-
*
|
|
822
|
-
* When the route module is a merged module (contains `__files`), uses
|
|
823
|
-
* inlined content directly. Otherwise falls back to reading companion files.
|
|
824
|
-
*/
|
|
825
|
-
async buildComponentContext(routeInfo, route, signal, isLeaf) {
|
|
761
|
+
async buildContext(routeInfo, route, signal, isLeaf, loadedModule) {
|
|
826
762
|
const rf = route.files;
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
let html;
|
|
830
|
-
let md;
|
|
831
|
-
let css;
|
|
763
|
+
const inlined = loadedModule ? this.getModuleFiles(loadedModule) : undefined;
|
|
764
|
+
let files;
|
|
832
765
|
if (inlined) {
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
766
|
+
files = inlined;
|
|
767
|
+
} else if (rf) {
|
|
768
|
+
const filePaths = {};
|
|
769
|
+
if (rf.html)
|
|
770
|
+
filePaths.html = rf.html;
|
|
771
|
+
if (rf.md)
|
|
772
|
+
filePaths.md = rf.md;
|
|
773
|
+
if (rf.css)
|
|
774
|
+
filePaths.css = rf.css;
|
|
775
|
+
files = await this.loadFiles(filePaths);
|
|
836
776
|
} else {
|
|
837
|
-
|
|
838
|
-
[html, md, css] = await Promise.all([
|
|
839
|
-
rf?.html ? fetchFile(rf.html) : void 0,
|
|
840
|
-
rf?.md ? fetchFile(rf.md) : void 0,
|
|
841
|
-
rf?.css ? fetchFile(rf.css) : void 0
|
|
842
|
-
]);
|
|
777
|
+
files = {};
|
|
843
778
|
}
|
|
844
|
-
const files = {};
|
|
845
|
-
if (html != null)
|
|
846
|
-
files.html = html;
|
|
847
|
-
if (md != null)
|
|
848
|
-
files.md = md;
|
|
849
|
-
if (css != null)
|
|
850
|
-
files.css = css;
|
|
851
779
|
const base = {
|
|
852
780
|
...routeInfo,
|
|
853
781
|
pathname: routeInfo.url.pathname,
|
|
854
782
|
searchParams: routeInfo.url.searchParams,
|
|
855
783
|
files,
|
|
856
784
|
...signal ? { signal } : {},
|
|
857
|
-
...isLeaf
|
|
785
|
+
...isLeaf !== undefined ? { isLeaf } : {}
|
|
858
786
|
};
|
|
859
787
|
return this.contextProvider ? this.contextProvider(base) : base;
|
|
860
788
|
}
|
|
861
|
-
};
|
|
862
|
-
|
|
863
|
-
// dist/src/route/route.trie.js
|
|
864
|
-
function createNode() {
|
|
865
|
-
return { static: /* @__PURE__ */ new Map() };
|
|
866
|
-
}
|
|
867
|
-
function safeDecode(segment) {
|
|
868
|
-
try {
|
|
869
|
-
return decodeURIComponent(segment);
|
|
870
|
-
} catch {
|
|
871
|
-
return segment;
|
|
872
|
-
}
|
|
873
789
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
function
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
node.pattern = pattern;
|
|
882
|
-
}
|
|
883
|
-
if (source.errorBoundary) {
|
|
884
|
-
node.errorBoundary = source.errorBoundary;
|
|
885
|
-
}
|
|
886
|
-
if (source.children) {
|
|
887
|
-
for (const [segment, child] of Object.entries(source.children)) {
|
|
888
|
-
const childPattern = pattern === "/" ? `/${segment}` : `${pattern}/${segment}`;
|
|
889
|
-
node.static.set(segment, convertNode(child, childPattern));
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
if (source.dynamic) {
|
|
893
|
-
const { param, child } = source.dynamic;
|
|
894
|
-
const childPattern = pattern === "/" ? `/:${param}` : `${pattern}/:${param}`;
|
|
895
|
-
node.dynamic = { param, node: convertNode(child, childPattern) };
|
|
896
|
-
}
|
|
897
|
-
if (source.wildcard) {
|
|
898
|
-
const { param, child } = source.wildcard;
|
|
899
|
-
const childPattern = pattern === "/" ? `/:${param}*` : `${pattern}/:${param}*`;
|
|
900
|
-
node.wildcard = { param, node: convertNode(child, childPattern) };
|
|
790
|
+
|
|
791
|
+
// dist/core/util/widget-resolve.util.js
|
|
792
|
+
var MAX_WIDGET_DEPTH = 10;
|
|
793
|
+
async function resolveRecursively(content, parse, resolve, replace, depth = 0, logger = defaultLogger) {
|
|
794
|
+
if (depth >= MAX_WIDGET_DEPTH) {
|
|
795
|
+
logger.warn(`Widget nesting depth limit reached (${MAX_WIDGET_DEPTH}). ` + "Possible circular dependency or excessive nesting.");
|
|
796
|
+
return content;
|
|
901
797
|
}
|
|
902
|
-
|
|
798
|
+
const widgets = parse(content);
|
|
799
|
+
if (widgets.length === 0)
|
|
800
|
+
return content;
|
|
801
|
+
const replacements = new Map;
|
|
802
|
+
await Promise.all(widgets.map(async (widget) => {
|
|
803
|
+
let rendered = await resolve(widget);
|
|
804
|
+
rendered = await resolveRecursively(rendered, parse, resolve, replace, depth + 1, logger);
|
|
805
|
+
replacements.set(widget, rendered);
|
|
806
|
+
}));
|
|
807
|
+
return replace(content, replacements);
|
|
903
808
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
return
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
if (pathname.length > 1 && pathname.endsWith("/")) {
|
|
927
|
-
pathname = pathname.slice(0, -1);
|
|
928
|
-
}
|
|
929
|
-
if (!pathname.startsWith("/")) {
|
|
930
|
-
pathname = "/" + pathname;
|
|
931
|
-
}
|
|
932
|
-
if (pathname === "/")
|
|
933
|
-
return this.root.errorBoundary;
|
|
934
|
-
const segments = splitSegments(pathname);
|
|
935
|
-
return this.walkForBoundary(this.root, segments, 0, this.root.errorBoundary);
|
|
936
|
-
}
|
|
937
|
-
findRoute(pattern) {
|
|
938
|
-
if (pattern === "/") {
|
|
939
|
-
return this.root.route;
|
|
940
|
-
}
|
|
941
|
-
const segments = splitSegments(pattern);
|
|
942
|
-
let node = this.root;
|
|
943
|
-
for (const segment of segments) {
|
|
944
|
-
let child;
|
|
945
|
-
if (segment.startsWith(":") && segment.endsWith("*")) {
|
|
946
|
-
child = node.wildcard?.node;
|
|
947
|
-
} else if (segment.startsWith(":")) {
|
|
948
|
-
child = node.dynamic?.node;
|
|
949
|
-
} else {
|
|
950
|
-
child = node.static.get(segment);
|
|
951
|
-
}
|
|
952
|
-
if (!child)
|
|
953
|
-
return void 0;
|
|
954
|
-
node = child;
|
|
955
|
-
}
|
|
956
|
-
return node.route;
|
|
957
|
-
}
|
|
958
|
-
// ── Private matching ──────────────────────────────────────────────────
|
|
959
|
-
walk(node, segments, index, params) {
|
|
960
|
-
if (index === segments.length) {
|
|
961
|
-
if (node.route) {
|
|
962
|
-
return { node: node.route, pattern: node.pattern, params: { ...params } };
|
|
963
|
-
}
|
|
964
|
-
if (node.wildcard?.node.route) {
|
|
965
|
-
return {
|
|
966
|
-
node: node.wildcard.node.route,
|
|
967
|
-
pattern: node.wildcard.node.pattern,
|
|
968
|
-
params: { ...params, [node.wildcard.param]: "" }
|
|
969
|
-
};
|
|
970
|
-
}
|
|
971
|
-
return void 0;
|
|
972
|
-
}
|
|
973
|
-
const segment = segments[index];
|
|
974
|
-
const staticChild = node.static.get(segment);
|
|
975
|
-
if (staticChild) {
|
|
976
|
-
const result = this.walk(staticChild, segments, index + 1, params);
|
|
977
|
-
if (result)
|
|
978
|
-
return result;
|
|
979
|
-
}
|
|
980
|
-
if (node.dynamic) {
|
|
981
|
-
const { param, node: dynamicNode } = node.dynamic;
|
|
982
|
-
params[param] = safeDecode(segment);
|
|
983
|
-
const result = this.walk(dynamicNode, segments, index + 1, params);
|
|
984
|
-
if (result)
|
|
985
|
-
return result;
|
|
986
|
-
delete params[param];
|
|
987
|
-
}
|
|
988
|
-
if (node.wildcard?.node.route) {
|
|
989
|
-
const { param, node: wildcardNode } = node.wildcard;
|
|
990
|
-
let rest = safeDecode(segments[index]);
|
|
991
|
-
for (let i = index + 1; i < segments.length; i++) {
|
|
992
|
-
rest += "/" + safeDecode(segments[i]);
|
|
993
|
-
}
|
|
994
|
-
return {
|
|
995
|
-
node: wildcardNode.route,
|
|
996
|
-
pattern: wildcardNode.pattern,
|
|
997
|
-
params: { ...params, [param]: rest }
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
return void 0;
|
|
1001
|
-
}
|
|
1002
|
-
/**
|
|
1003
|
-
* Walk for error boundary. Follows the same priority as match
|
|
1004
|
-
* (static → dynamic → wildcard) without backtracking across branches.
|
|
1005
|
-
* Returns the deepest error boundary module path found along the path.
|
|
1006
|
-
*/
|
|
1007
|
-
walkForBoundary(node, segments, index, deepest) {
|
|
1008
|
-
if (index === segments.length) {
|
|
1009
|
-
return node.errorBoundary ?? deepest;
|
|
1010
|
-
}
|
|
1011
|
-
const segment = segments[index];
|
|
1012
|
-
const staticChild = node.static.get(segment);
|
|
1013
|
-
if (staticChild) {
|
|
1014
|
-
return this.walkForBoundary(staticChild, segments, index + 1, staticChild.errorBoundary ?? deepest);
|
|
1015
|
-
}
|
|
1016
|
-
if (node.dynamic) {
|
|
1017
|
-
return this.walkForBoundary(node.dynamic.node, segments, index + 1, node.dynamic.node.errorBoundary ?? deepest);
|
|
1018
|
-
}
|
|
1019
|
-
if (node.wildcard) {
|
|
1020
|
-
return node.wildcard.node.errorBoundary ?? deepest;
|
|
1021
|
-
}
|
|
1022
|
-
return deepest;
|
|
1023
|
-
}
|
|
1024
|
-
};
|
|
1025
|
-
|
|
1026
|
-
// dist/src/type/logger.type.js
|
|
1027
|
-
var noop = () => {
|
|
1028
|
-
};
|
|
1029
|
-
var logger = { error: noop, warn: noop };
|
|
1030
|
-
function setLogger(impl) {
|
|
1031
|
-
logger.error = impl.error.bind(impl);
|
|
1032
|
-
logger.warn = impl.warn.bind(impl);
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
// dist/src/util/widget-resolve.util.js
|
|
1036
|
-
var MAX_WIDGET_DEPTH = 10;
|
|
1037
|
-
async function resolveRecursively(content, parse, resolve, replace, depth = 0) {
|
|
1038
|
-
if (depth >= MAX_WIDGET_DEPTH) {
|
|
1039
|
-
logger.warn(`Widget nesting depth limit reached (${MAX_WIDGET_DEPTH}). Possible circular dependency or excessive nesting.`);
|
|
1040
|
-
return content;
|
|
1041
|
-
}
|
|
1042
|
-
const widgets = parse(content);
|
|
1043
|
-
if (widgets.length === 0)
|
|
1044
|
-
return content;
|
|
1045
|
-
const replacements = /* @__PURE__ */ new Map();
|
|
1046
|
-
await Promise.all(widgets.map(async (widget) => {
|
|
1047
|
-
let rendered = await resolve(widget);
|
|
1048
|
-
rendered = await resolveRecursively(rendered, parse, resolve, replace, depth + 1);
|
|
1049
|
-
replacements.set(widget, rendered);
|
|
1050
|
-
}));
|
|
1051
|
-
return replace(content, replacements);
|
|
1052
|
-
}
|
|
1053
|
-
function resolveWidgetTags(html, registry, routeInfo, loadFiles, contextProvider) {
|
|
1054
|
-
const tagPattern = /<widget-(?<name>[a-z][a-z0-9-]*)(?<attrs>\s[^>]*)?>(?<content>.*?)<\/widget-\k<name>>/gis;
|
|
1055
|
-
const wrappers = /* @__PURE__ */ new Map();
|
|
1056
|
-
const ssrAttrPattern = new RegExp(`\\s${SSR_ATTR}(?:\\s|=|$)`);
|
|
1057
|
-
const parse = (content) => {
|
|
1058
|
-
const matches = content.matchAll(tagPattern).toArray();
|
|
1059
|
-
return matches.filter((match) => {
|
|
1060
|
-
const attrsString = match.groups.attrs || "";
|
|
1061
|
-
return !ssrAttrPattern.test(attrsString);
|
|
1062
|
-
});
|
|
1063
|
-
};
|
|
1064
|
-
const resolve = async (match) => {
|
|
1065
|
-
const widgetName = match.groups.name;
|
|
1066
|
-
const attrsString = match.groups.attrs?.trim() ?? "";
|
|
1067
|
-
const widget = registry.get(widgetName);
|
|
1068
|
-
if (!widget)
|
|
1069
|
-
return match[0];
|
|
1070
|
-
const params = parseAttrsToParams(attrsString);
|
|
1071
|
-
try {
|
|
1072
|
-
let files;
|
|
1073
|
-
if (loadFiles) {
|
|
1074
|
-
files = await loadFiles(widgetName, widget.files);
|
|
809
|
+
function resolveWidgetTags(html, registry, routeInfo, loadFiles, contextProvider, logger = defaultLogger) {
|
|
810
|
+
const tagPattern = /<widget-(?<name>[a-z][a-z0-9-]*)(?<attrs>\s[^>]*)?>(?<content>.*?)<\/widget-\k<name>>/gis;
|
|
811
|
+
const wrappers = new Map;
|
|
812
|
+
const ssrAttrPattern = new RegExp(`\\s${SSR_ATTR}(?:\\s|=|$)`);
|
|
813
|
+
const parse = (content) => {
|
|
814
|
+
const matches = content.matchAll(tagPattern).toArray();
|
|
815
|
+
return matches.filter((match) => {
|
|
816
|
+
const attrsString = match.groups.attrs || "";
|
|
817
|
+
return !ssrAttrPattern.test(attrsString);
|
|
818
|
+
});
|
|
819
|
+
};
|
|
820
|
+
const resolve = async (match) => {
|
|
821
|
+
const widgetName = match.groups.name;
|
|
822
|
+
const attrsString = match.groups.attrs?.trim() ?? "";
|
|
823
|
+
const widget = registry.get(widgetName);
|
|
824
|
+
if (!widget)
|
|
825
|
+
return match[0];
|
|
826
|
+
const params = parseAttrsToParams(attrsString);
|
|
827
|
+
try {
|
|
828
|
+
let files;
|
|
829
|
+
if (loadFiles) {
|
|
830
|
+
files = await loadFiles(widgetName);
|
|
1075
831
|
}
|
|
1076
832
|
const baseContext = {
|
|
1077
833
|
...routeInfo,
|
|
@@ -1089,7 +845,7 @@ function resolveWidgetTags(html, registry, routeInfo, loadFiles, contextProvider
|
|
|
1089
845
|
});
|
|
1090
846
|
return rendered;
|
|
1091
847
|
} catch (e) {
|
|
1092
|
-
logger.error(`[SSR HTML] Widget "${widgetName}" render failed`, e instanceof Error ? e :
|
|
848
|
+
logger.error(`[SSR HTML] Widget "${widgetName}" render failed`, e instanceof Error ? e : undefined);
|
|
1093
849
|
return match[0];
|
|
1094
850
|
}
|
|
1095
851
|
};
|
|
@@ -1106,7 +862,7 @@ function resolveWidgetTags(html, registry, routeInfo, loadFiles, contextProvider
|
|
|
1106
862
|
}
|
|
1107
863
|
return result;
|
|
1108
864
|
};
|
|
1109
|
-
return resolveRecursively(html, parse, resolve, replace);
|
|
865
|
+
return resolveRecursively(html, parse, resolve, replace, 0, logger);
|
|
1110
866
|
}
|
|
1111
867
|
function parseAttrsToParams(attrsString) {
|
|
1112
868
|
const params = {};
|
|
@@ -1115,11 +871,11 @@ function parseAttrsToParams(attrsString) {
|
|
|
1115
871
|
const attrPattern = /(?<attr>[a-z][a-z0-9-]*)(?:="(?<dq>[^"]*)"|='(?<sq>[^']*)'|=(?<uq>[^\s>]+))?/gi;
|
|
1116
872
|
for (const match of attrsString.matchAll(attrPattern)) {
|
|
1117
873
|
const { attr: attrName, dq, sq, uq } = match.groups;
|
|
1118
|
-
if (attrName === SSR_ATTR || attrName === LAZY_ATTR)
|
|
874
|
+
if (!attrName || attrName === SSR_ATTR || attrName === LAZY_ATTR)
|
|
1119
875
|
continue;
|
|
1120
876
|
const key = attrName.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
1121
877
|
const rawValue = dq ?? sq ?? uq;
|
|
1122
|
-
if (rawValue ===
|
|
878
|
+
if (rawValue === undefined) {
|
|
1123
879
|
params[key] = "";
|
|
1124
880
|
continue;
|
|
1125
881
|
}
|
|
@@ -1136,32 +892,11 @@ function escapeAttr(value) {
|
|
|
1136
892
|
return value.replaceAll("&", "&").replaceAll("'", "'");
|
|
1137
893
|
}
|
|
1138
894
|
|
|
1139
|
-
// dist/
|
|
1140
|
-
|
|
1141
|
-
/** Host element reference, set by ComponentElement in the browser. */
|
|
895
|
+
// dist/core/component/abstract.component.js
|
|
896
|
+
class Component {
|
|
1142
897
|
element;
|
|
1143
|
-
/** Associated file paths for pre-loaded content (html, md, css). */
|
|
1144
898
|
files;
|
|
1145
|
-
/**
|
|
1146
|
-
* When true, SSR serializes the getData() result into the element's
|
|
1147
|
-
* light DOM so the client can access it immediately in hydrate()
|
|
1148
|
-
* without re-fetching.
|
|
1149
|
-
*
|
|
1150
|
-
* Default is false — hydrate() receives `data: null`. Most widgets
|
|
1151
|
-
* don't need this because the rendered Shadow DOM already contains
|
|
1152
|
-
* the visual representation of the data.
|
|
1153
|
-
*
|
|
1154
|
-
* If you find yourself parsing the shadow DOM in hydrate() trying to
|
|
1155
|
-
* reconstruct the original data object, set this to true instead.
|
|
1156
|
-
* The server-fetched data will be available as `args.data` in hydrate().
|
|
1157
|
-
*/
|
|
1158
899
|
exposeSsrData;
|
|
1159
|
-
/**
|
|
1160
|
-
* Render as HTML for browser context.
|
|
1161
|
-
*
|
|
1162
|
-
* Default implementation converts renderMarkdown() output to HTML.
|
|
1163
|
-
* Override for custom HTML rendering with rich styling/interactivity.
|
|
1164
|
-
*/
|
|
1165
900
|
renderHTML(args) {
|
|
1166
901
|
if (args.data === null) {
|
|
1167
902
|
return `<div data-component="${this.name}">Loading...</div>`;
|
|
@@ -1173,56 +908,23 @@ var Component = class {
|
|
|
1173
908
|
});
|
|
1174
909
|
return `<div data-component="${this.name}" data-markdown>${escapeHtml(markdown)}</div>`;
|
|
1175
910
|
}
|
|
1176
|
-
/**
|
|
1177
|
-
* Render error state.
|
|
1178
|
-
*/
|
|
1179
911
|
renderError(args) {
|
|
1180
912
|
const msg = args.error instanceof Error ? args.error.message : String(args.error);
|
|
1181
913
|
return `<div data-component="${this.name}">Error: ${escapeHtml(msg)}</div>`;
|
|
1182
914
|
}
|
|
1183
|
-
/**
|
|
1184
|
-
* Render error as markdown.
|
|
1185
|
-
*/
|
|
1186
915
|
renderMarkdownError(error) {
|
|
1187
916
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1188
917
|
return `> **Error** (\`${this.name}\`): ${msg}`;
|
|
1189
918
|
}
|
|
1190
|
-
}
|
|
919
|
+
}
|
|
1191
920
|
|
|
1192
|
-
// dist/
|
|
1193
|
-
|
|
921
|
+
// dist/core/component/page.component.js
|
|
922
|
+
class PageComponent extends Component {
|
|
1194
923
|
name = "page";
|
|
1195
|
-
/** Route pattern this page handles (optional — set by subclasses) */
|
|
1196
924
|
pattern;
|
|
1197
|
-
/**
|
|
1198
|
-
* Fetch or compute page data. Override in subclasses.
|
|
1199
|
-
* Default: returns null (no data needed).
|
|
1200
|
-
*
|
|
1201
|
-
* @example
|
|
1202
|
-
* ```ts
|
|
1203
|
-
* override getData({ params, context }: this['DataArgs']) {
|
|
1204
|
-
* return fetch(`/api/${params.id}`, { signal: context?.signal });
|
|
1205
|
-
* }
|
|
1206
|
-
* ```
|
|
1207
|
-
*/
|
|
1208
925
|
getData(_args) {
|
|
1209
926
|
return Promise.resolve(null);
|
|
1210
927
|
}
|
|
1211
|
-
/**
|
|
1212
|
-
* Render page as HTML.
|
|
1213
|
-
*
|
|
1214
|
-
* Fallback chain:
|
|
1215
|
-
* 1. html file content from context
|
|
1216
|
-
* 2. md file content wrapped in `<mark-down>`
|
|
1217
|
-
* 3. `<router-slot />` (bare slot for child routes)
|
|
1218
|
-
*
|
|
1219
|
-
* @example
|
|
1220
|
-
* ```ts
|
|
1221
|
-
* override renderHTML({ data, params, context }: this['RenderArgs']) {
|
|
1222
|
-
* return `<h1>${params.id}</h1><p>${context?.files?.html ?? ''}</p>`;
|
|
1223
|
-
* }
|
|
1224
|
-
* ```
|
|
1225
|
-
*/
|
|
1226
928
|
renderHTML(args) {
|
|
1227
929
|
const files = args.context.files;
|
|
1228
930
|
const style = files?.css ? `<style>${files.css}</style>
|
|
@@ -1236,25 +938,12 @@ var PageComponent = class extends Component {
|
|
|
1236
938
|
}
|
|
1237
939
|
if (files?.md) {
|
|
1238
940
|
const hasSlot = files.md.includes("```router-slot");
|
|
1239
|
-
const slot = args.context.isLeaf || hasSlot ? "" :
|
|
941
|
+
const slot = args.context.isLeaf || hasSlot ? "" : `
|
|
942
|
+
<router-slot></router-slot>`;
|
|
1240
943
|
return `${style}<mark-down>${escapeHtml(files.md)}</mark-down>${slot}`;
|
|
1241
944
|
}
|
|
1242
945
|
return args.context.isLeaf ? "" : "<router-slot></router-slot>";
|
|
1243
946
|
}
|
|
1244
|
-
/**
|
|
1245
|
-
* Render page as Markdown.
|
|
1246
|
-
*
|
|
1247
|
-
* Fallback chain:
|
|
1248
|
-
* 1. md file content from context
|
|
1249
|
-
* 2. `` ```router-slot\n``` `` (slot placeholder in markdown — newline required)
|
|
1250
|
-
*
|
|
1251
|
-
* @example
|
|
1252
|
-
* ```ts
|
|
1253
|
-
* override renderMarkdown({ data, params, context }: this['RenderArgs']) {
|
|
1254
|
-
* return `# ${params.id}\n\n${context?.files?.md ?? ''}`;
|
|
1255
|
-
* }
|
|
1256
|
-
* ```
|
|
1257
|
-
*/
|
|
1258
947
|
renderMarkdown(args) {
|
|
1259
948
|
const files = args.context.files;
|
|
1260
949
|
if (files?.md) {
|
|
@@ -1262,53 +951,39 @@ var PageComponent = class extends Component {
|
|
|
1262
951
|
}
|
|
1263
952
|
return args.context.isLeaf ? "" : "```router-slot\n```";
|
|
1264
953
|
}
|
|
1265
|
-
/**
|
|
1266
|
-
* Page title. Override in subclasses.
|
|
1267
|
-
* Default: undefined (no title).
|
|
1268
|
-
*
|
|
1269
|
-
* @example
|
|
1270
|
-
* ```ts
|
|
1271
|
-
* override getTitle({ data, params }: this['RenderArgs']) {
|
|
1272
|
-
* return `Project ${params.id}`;
|
|
1273
|
-
* }
|
|
1274
|
-
* ```
|
|
1275
|
-
*/
|
|
1276
954
|
getTitle(_args) {
|
|
1277
|
-
return
|
|
955
|
+
return;
|
|
1278
956
|
}
|
|
1279
|
-
}
|
|
1280
|
-
var page_component_default = new PageComponent
|
|
957
|
+
}
|
|
958
|
+
var page_component_default = new PageComponent;
|
|
1281
959
|
|
|
1282
|
-
// dist/
|
|
1283
|
-
|
|
1284
|
-
|
|
960
|
+
// dist/core/renderer/ssr.renderer.js
|
|
961
|
+
class SsrRenderer {
|
|
962
|
+
pipeline;
|
|
1285
963
|
widgets;
|
|
1286
|
-
|
|
1287
|
-
constructor(
|
|
1288
|
-
this.
|
|
964
|
+
logger;
|
|
965
|
+
constructor(pipeline, options = {}) {
|
|
966
|
+
this.pipeline = pipeline;
|
|
967
|
+
this.logger = pipeline.logger;
|
|
1289
968
|
this.widgets = options.widgets ?? null;
|
|
1290
|
-
this.widgetFiles = options.widgetFiles ?? {};
|
|
1291
969
|
}
|
|
1292
|
-
/**
|
|
1293
|
-
* Render a URL to a content string.
|
|
1294
|
-
*/
|
|
1295
970
|
async render(url, signal) {
|
|
1296
|
-
const matched = this.
|
|
971
|
+
const matched = await this.pipeline.match(url);
|
|
1297
972
|
if (!matched) {
|
|
1298
|
-
const statusPage = this.
|
|
973
|
+
const statusPage = await this.pipeline.getStatusPage(404);
|
|
1299
974
|
if (statusPage) {
|
|
1300
975
|
try {
|
|
1301
976
|
const ri = { url, params: {} };
|
|
1302
|
-
const result = await this.renderRouteContent(ri, statusPage,
|
|
1303
|
-
return { content: this.stripSlots(result.content), status: 404, ...result.title
|
|
977
|
+
const result = await this.renderRouteContent(ri, statusPage, undefined, signal);
|
|
978
|
+
return { content: this.stripSlots(result.content), status: 404, ...result.title !== undefined ? { title: result.title } : {} };
|
|
1304
979
|
} catch (e) {
|
|
1305
|
-
logger.error(`[${this.label}] Failed to render 404 status page for ${url.pathname}`, e instanceof Error ? e :
|
|
980
|
+
this.logger.error(`[${this.label}] Failed to render 404 status page for ${url.pathname}`, e instanceof Error ? e : undefined);
|
|
1306
981
|
}
|
|
1307
982
|
}
|
|
1308
983
|
return { content: this.renderStatusPage(404, url), status: 404 };
|
|
1309
984
|
}
|
|
1310
985
|
if (matched.route.type === "redirect") {
|
|
1311
|
-
const module = await this.
|
|
986
|
+
const module = await this.pipeline.loadModule(matched.route.modulePath);
|
|
1312
987
|
const redirectConfig = module.default;
|
|
1313
988
|
assertSafeRedirect(redirectConfig.to);
|
|
1314
989
|
return {
|
|
@@ -1317,36 +992,36 @@ var SsrRenderer = class _SsrRenderer {
|
|
|
1317
992
|
redirect: redirectConfig.to
|
|
1318
993
|
};
|
|
1319
994
|
}
|
|
1320
|
-
const routeInfo = this.
|
|
995
|
+
const routeInfo = this.pipeline.toRouteInfo(matched, url);
|
|
1321
996
|
try {
|
|
1322
997
|
const { content, title } = await this.renderPage(routeInfo, matched, signal);
|
|
1323
|
-
return { content, status: 200, ...title
|
|
998
|
+
return { content, status: 200, ...title !== undefined ? { title } : {} };
|
|
1324
999
|
} catch (error) {
|
|
1325
1000
|
if (error instanceof Response) {
|
|
1326
|
-
const statusPage = this.
|
|
1001
|
+
const statusPage = await this.pipeline.getStatusPage(error.status);
|
|
1327
1002
|
if (statusPage) {
|
|
1328
1003
|
try {
|
|
1329
1004
|
const ri = { url, params: {} };
|
|
1330
|
-
const result = await this.renderRouteContent(ri, statusPage,
|
|
1005
|
+
const result = await this.renderRouteContent(ri, statusPage, undefined, signal);
|
|
1331
1006
|
return {
|
|
1332
1007
|
content: this.stripSlots(result.content),
|
|
1333
1008
|
status: error.status,
|
|
1334
|
-
...result.title
|
|
1009
|
+
...result.title !== undefined ? { title: result.title } : {}
|
|
1335
1010
|
};
|
|
1336
1011
|
} catch (e) {
|
|
1337
|
-
logger.error(`[${this.label}] Failed to render ${error.status} status page for ${url.pathname}`, e instanceof Error ? e :
|
|
1012
|
+
this.logger.error(`[${this.label}] Failed to render ${error.status} status page for ${url.pathname}`, e instanceof Error ? e : undefined);
|
|
1338
1013
|
}
|
|
1339
1014
|
}
|
|
1340
1015
|
return { content: this.renderStatusPage(error.status, url), status: error.status };
|
|
1341
1016
|
}
|
|
1342
|
-
logger.error(`[${this.label}] Error rendering ${url.pathname}:`, error instanceof Error ? error :
|
|
1343
|
-
const boundary = this.
|
|
1017
|
+
this.logger.error(`[${this.label}] Error rendering ${url.pathname}:`, error instanceof Error ? error : undefined);
|
|
1018
|
+
const boundary = await this.pipeline.findErrorBoundary(url.pathname);
|
|
1344
1019
|
if (boundary) {
|
|
1345
1020
|
const result = await this.tryRenderErrorModule(boundary.modulePath, url, "boundary");
|
|
1346
1021
|
if (result)
|
|
1347
1022
|
return result;
|
|
1348
1023
|
}
|
|
1349
|
-
const errorHandler = this.
|
|
1024
|
+
const errorHandler = await this.pipeline.getErrorHandler();
|
|
1350
1025
|
if (errorHandler) {
|
|
1351
1026
|
const result = await this.tryRenderErrorModule(errorHandler.modulePath, url, "handler");
|
|
1352
1027
|
if (result)
|
|
@@ -1355,15 +1030,12 @@ var SsrRenderer = class _SsrRenderer {
|
|
|
1355
1030
|
return { content: this.renderErrorPage(error, url), status: 500 };
|
|
1356
1031
|
}
|
|
1357
1032
|
}
|
|
1358
|
-
/**
|
|
1359
|
-
* Render a matched page by composing the route hierarchy.
|
|
1360
|
-
*/
|
|
1361
1033
|
async renderPage(routeInfo, matched, signal) {
|
|
1362
|
-
const hierarchy = this.
|
|
1034
|
+
const hierarchy = this.pipeline.buildRouteHierarchy(matched.route.pattern);
|
|
1363
1035
|
const segments = [];
|
|
1364
|
-
for (let i = 0;
|
|
1036
|
+
for (let i = 0;i < hierarchy.length; i++) {
|
|
1365
1037
|
const routePattern = hierarchy[i];
|
|
1366
|
-
let route = this.
|
|
1038
|
+
let route = await this.pipeline.findRoute(routePattern);
|
|
1367
1039
|
if (!route && routePattern === "/") {
|
|
1368
1040
|
route = DEFAULT_ROOT_ROUTE;
|
|
1369
1041
|
}
|
|
@@ -1377,7 +1049,7 @@ var SsrRenderer = class _SsrRenderer {
|
|
|
1377
1049
|
let result = "";
|
|
1378
1050
|
let pageTitle;
|
|
1379
1051
|
let lastRenderedPattern = "";
|
|
1380
|
-
for (let i = 0;
|
|
1052
|
+
for (let i = 0;i < segments.length; i++) {
|
|
1381
1053
|
const { content, title } = results[i];
|
|
1382
1054
|
if (title) {
|
|
1383
1055
|
pageTitle = title;
|
|
@@ -1387,59 +1059,57 @@ var SsrRenderer = class _SsrRenderer {
|
|
|
1387
1059
|
} else {
|
|
1388
1060
|
const injected = this.injectSlot(result, content, lastRenderedPattern);
|
|
1389
1061
|
if (injected === result) {
|
|
1390
|
-
logger.warn(`[${this.label}] Route "${lastRenderedPattern}" has no <router-slot> for child route "${hierarchy[i]}" to render into. Add <router-slot></router-slot> to the parent template.`);
|
|
1062
|
+
this.logger.warn(`[${this.label}] Route "${lastRenderedPattern}" has no <router-slot> ` + `for child route "${hierarchy[i]}" to render into. ` + `Add <router-slot></router-slot> to the parent template.`);
|
|
1391
1063
|
}
|
|
1392
1064
|
result = injected;
|
|
1393
1065
|
}
|
|
1394
1066
|
lastRenderedPattern = segments[i].route.pattern;
|
|
1395
1067
|
}
|
|
1396
1068
|
result = this.stripSlots(result);
|
|
1397
|
-
return { content: result, ...pageTitle
|
|
1069
|
+
return { content: result, ...pageTitle !== undefined ? { title: pageTitle } : {} };
|
|
1398
1070
|
}
|
|
1399
|
-
/** Load component, build context, get data, render content, get title. */
|
|
1400
1071
|
async loadRouteContent(routeInfo, route, isLeaf, signal) {
|
|
1401
1072
|
const files = route.files ?? {};
|
|
1402
1073
|
const tsModule = files.ts ?? files.js;
|
|
1403
|
-
const
|
|
1404
|
-
const
|
|
1074
|
+
const loadedModule = tsModule ? await this.pipeline.loadModule(tsModule) : undefined;
|
|
1075
|
+
const component = loadedModule?.default ?? page_component_default;
|
|
1076
|
+
const context = await this.pipeline.buildContext(routeInfo, route, signal, isLeaf, loadedModule);
|
|
1405
1077
|
const data = await component.getData({ params: routeInfo.params, ...signal ? { signal } : {}, context });
|
|
1406
1078
|
const content = this.renderContent(component, { data, params: routeInfo.params, context });
|
|
1407
1079
|
const title = component.getTitle({ data, params: routeInfo.params, context });
|
|
1408
|
-
return { content, ...title
|
|
1080
|
+
return { content, ...title !== undefined ? { title } : {} };
|
|
1409
1081
|
}
|
|
1410
|
-
/** Render a component for error boundary/handler with minimal context. */
|
|
1411
1082
|
renderComponent(component, data, context) {
|
|
1412
1083
|
return this.renderContent(component, { data, params: {}, context });
|
|
1413
1084
|
}
|
|
1414
1085
|
static EMPTY_URL = new URL("http://error");
|
|
1415
|
-
/** Try to load and render an error boundary or handler module. Returns null on failure. */
|
|
1416
1086
|
async tryRenderErrorModule(modulePath, url, kind) {
|
|
1417
1087
|
try {
|
|
1418
|
-
const module = await this.
|
|
1088
|
+
const module = await this.pipeline.loadModule(modulePath);
|
|
1419
1089
|
const component = module.default;
|
|
1420
1090
|
const minCtx = {
|
|
1421
|
-
url:
|
|
1091
|
+
url: SsrRenderer.EMPTY_URL,
|
|
1422
1092
|
params: {},
|
|
1423
1093
|
pathname: "",
|
|
1424
|
-
searchParams: new URLSearchParams
|
|
1094
|
+
searchParams: new URLSearchParams
|
|
1425
1095
|
};
|
|
1426
1096
|
const data = await component.getData({ params: {}, context: minCtx });
|
|
1427
1097
|
const content = this.renderComponent(component, data, minCtx);
|
|
1428
1098
|
return { content, status: 500 };
|
|
1429
1099
|
} catch (e) {
|
|
1430
|
-
logger.error(`[${this.label}] Error ${kind} failed for ${url.pathname}`, e instanceof Error ? e :
|
|
1100
|
+
this.logger.error(`[${this.label}] Error ${kind} failed for ${url.pathname}`, e instanceof Error ? e : undefined);
|
|
1431
1101
|
return null;
|
|
1432
1102
|
}
|
|
1433
1103
|
}
|
|
1434
|
-
}
|
|
1104
|
+
}
|
|
1435
1105
|
|
|
1436
|
-
// dist/
|
|
1437
|
-
|
|
1106
|
+
// dist/core/renderer/html.renderer.js
|
|
1107
|
+
class SsrHtmlRenderer extends SsrRenderer {
|
|
1438
1108
|
label = "SSR HTML";
|
|
1439
1109
|
markdownRenderer;
|
|
1440
1110
|
markdownReady = null;
|
|
1441
|
-
constructor(
|
|
1442
|
-
super(
|
|
1111
|
+
constructor(pipeline, options = {}) {
|
|
1112
|
+
super(pipeline, options);
|
|
1443
1113
|
this.markdownRenderer = options.markdownRenderer ?? null;
|
|
1444
1114
|
if (this.markdownRenderer?.init) {
|
|
1445
1115
|
this.markdownReady = this.markdownRenderer.init();
|
|
@@ -1452,9 +1122,6 @@ var SsrHtmlRouter = class extends SsrRenderer {
|
|
|
1452
1122
|
stripSlots(result) {
|
|
1453
1123
|
return result.replace(/<router-slot[^>]*><\/router-slot>/g, "");
|
|
1454
1124
|
}
|
|
1455
|
-
/**
|
|
1456
|
-
* Render a single route's content.
|
|
1457
|
-
*/
|
|
1458
1125
|
async renderRouteContent(routeInfo, route, isLeaf, signal) {
|
|
1459
1126
|
if (route.modulePath === DEFAULT_ROOT_ROUTE.modulePath) {
|
|
1460
1127
|
return { content: `<router-slot pattern="${route.pattern}"></router-slot>` };
|
|
@@ -1464,12 +1131,18 @@ var SsrHtmlRouter = class extends SsrRenderer {
|
|
|
1464
1131
|
content = await this.expandMarkdown(content);
|
|
1465
1132
|
content = this.attributeSlots(content, route.pattern);
|
|
1466
1133
|
if (this.widgets) {
|
|
1467
|
-
content = await resolveWidgetTags(content, this.widgets, routeInfo, (name
|
|
1468
|
-
const
|
|
1469
|
-
|
|
1470
|
-
|
|
1134
|
+
content = await resolveWidgetTags(content, this.widgets, routeInfo, async (name) => {
|
|
1135
|
+
const modulePath = this.widgets.getModulePath(name);
|
|
1136
|
+
if (modulePath) {
|
|
1137
|
+
const mod = await this.pipeline.loadModule(modulePath);
|
|
1138
|
+
const inlined = this.pipeline.getModuleFiles(mod);
|
|
1139
|
+
if (inlined)
|
|
1140
|
+
return inlined;
|
|
1141
|
+
}
|
|
1142
|
+
return {};
|
|
1143
|
+
}, this.pipeline.contextProvider, this.logger);
|
|
1471
1144
|
}
|
|
1472
|
-
return { content, ...title
|
|
1145
|
+
return { content, ...title !== undefined ? { title } : {} };
|
|
1473
1146
|
}
|
|
1474
1147
|
renderContent(component, args) {
|
|
1475
1148
|
return component.renderHTML(args);
|
|
@@ -1491,14 +1164,9 @@ var SsrHtmlRouter = class extends SsrRenderer {
|
|
|
1491
1164
|
<p>${escapeHtml(message)}</p>
|
|
1492
1165
|
`;
|
|
1493
1166
|
}
|
|
1494
|
-
/** Add pattern attribute to bare <router-slot> tags. */
|
|
1495
1167
|
attributeSlots(content, routePattern) {
|
|
1496
1168
|
return content.replace(/<router-slot(?![^>]*\bpattern=)([^>]*)><\/router-slot>/g, `<router-slot pattern="${routePattern}"$1></router-slot>`);
|
|
1497
1169
|
}
|
|
1498
|
-
/**
|
|
1499
|
-
* Expand <mark-down> tags by rendering markdown to HTML server-side.
|
|
1500
|
-
* Leaves content unchanged if no markdown renderer is configured.
|
|
1501
|
-
*/
|
|
1502
1170
|
async expandMarkdown(content) {
|
|
1503
1171
|
if (!this.markdownRenderer)
|
|
1504
1172
|
return content;
|
|
@@ -1511,13 +1179,12 @@ var SsrHtmlRouter = class extends SsrRenderer {
|
|
|
1511
1179
|
const pattern = /<mark-down>([\s\S]*?)<\/mark-down>/g;
|
|
1512
1180
|
return content.replace(pattern, (_match, escaped) => {
|
|
1513
1181
|
const markdown = unescapeHtml(escaped);
|
|
1514
|
-
|
|
1515
|
-
return rendered;
|
|
1182
|
+
return renderer.render(markdown);
|
|
1516
1183
|
});
|
|
1517
1184
|
}
|
|
1518
|
-
}
|
|
1185
|
+
}
|
|
1519
1186
|
|
|
1520
|
-
// dist/
|
|
1187
|
+
// dist/core/widget/widget.parser.js
|
|
1521
1188
|
var WIDGET_PATTERN = /```widget:(?<name>[a-z][a-z0-9-]*)\n(?<params>.*?)```/gs;
|
|
1522
1189
|
function parseWidgetBlocks(markdown) {
|
|
1523
1190
|
const blocks = [];
|
|
@@ -1560,17 +1227,18 @@ function replaceWidgetBlocks(markdown, replacements) {
|
|
|
1560
1227
|
return result;
|
|
1561
1228
|
}
|
|
1562
1229
|
|
|
1563
|
-
// dist/
|
|
1230
|
+
// dist/core/renderer/md.renderer.js
|
|
1564
1231
|
var BARE_SLOT_BLOCK = "```router-slot\n```";
|
|
1565
1232
|
function routerSlotBlock(pattern) {
|
|
1566
1233
|
return `\`\`\`router-slot
|
|
1567
1234
|
{"pattern":"${pattern}"}
|
|
1568
1235
|
\`\`\``;
|
|
1569
1236
|
}
|
|
1570
|
-
|
|
1237
|
+
|
|
1238
|
+
class SsrMdRenderer extends SsrRenderer {
|
|
1571
1239
|
label = "SSR MD";
|
|
1572
|
-
constructor(
|
|
1573
|
-
super(
|
|
1240
|
+
constructor(pipeline, options = {}) {
|
|
1241
|
+
super(pipeline, options);
|
|
1574
1242
|
}
|
|
1575
1243
|
injectSlot(parent, child, parentPattern) {
|
|
1576
1244
|
return parent.replace(routerSlotBlock(parentPattern), child);
|
|
@@ -1578,9 +1246,6 @@ var SsrMdRouter = class extends SsrRenderer {
|
|
|
1578
1246
|
stripSlots(result) {
|
|
1579
1247
|
return result.replace(/```router-slot\n(?:\{[^}]*\}\n)?```/g, "").trim();
|
|
1580
1248
|
}
|
|
1581
|
-
/**
|
|
1582
|
-
* Render a single route's content to Markdown.
|
|
1583
|
-
*/
|
|
1584
1249
|
async renderRouteContent(routeInfo, route, isLeaf, signal) {
|
|
1585
1250
|
if (route.modulePath === DEFAULT_ROOT_ROUTE.modulePath) {
|
|
1586
1251
|
return { content: routerSlotBlock(route.pattern) };
|
|
@@ -1591,7 +1256,7 @@ var SsrMdRouter = class extends SsrRenderer {
|
|
|
1591
1256
|
if (this.widgets) {
|
|
1592
1257
|
content = await this.resolveWidgets(content, routeInfo);
|
|
1593
1258
|
}
|
|
1594
|
-
return { content, ...title
|
|
1259
|
+
return { content, ...title !== undefined ? { title } : {} };
|
|
1595
1260
|
}
|
|
1596
1261
|
renderContent(component, args) {
|
|
1597
1262
|
return component.renderMarkdown(args);
|
|
@@ -1609,10 +1274,6 @@ Path: \`${url.pathname}\``;
|
|
|
1609
1274
|
|
|
1610
1275
|
Path: \`${url.pathname}\``;
|
|
1611
1276
|
}
|
|
1612
|
-
/**
|
|
1613
|
-
* Resolve fenced widget blocks in markdown content.
|
|
1614
|
-
* Replaces ```widget:name blocks with rendered markdown output.
|
|
1615
|
-
*/
|
|
1616
1277
|
resolveWidgets(content, routeInfo) {
|
|
1617
1278
|
return resolveRecursively(content, parseWidgetBlocks, async (block) => {
|
|
1618
1279
|
if (block.parseError || !block.params) {
|
|
@@ -1624,9 +1285,10 @@ Path: \`${url.pathname}\``;
|
|
|
1624
1285
|
}
|
|
1625
1286
|
try {
|
|
1626
1287
|
let files;
|
|
1627
|
-
const
|
|
1628
|
-
if (
|
|
1629
|
-
|
|
1288
|
+
const modulePath = this.widgets.getModulePath(block.widgetName);
|
|
1289
|
+
if (modulePath) {
|
|
1290
|
+
const mod = await this.pipeline.loadModule(modulePath);
|
|
1291
|
+
files = this.pipeline.getModuleFiles(mod);
|
|
1630
1292
|
}
|
|
1631
1293
|
const baseContext = {
|
|
1632
1294
|
...routeInfo,
|
|
@@ -1634,25 +1296,26 @@ Path: \`${url.pathname}\``;
|
|
|
1634
1296
|
searchParams: routeInfo.url.searchParams,
|
|
1635
1297
|
...files ? { files } : {}
|
|
1636
1298
|
};
|
|
1637
|
-
const context = this.
|
|
1299
|
+
const context = this.pipeline.contextProvider ? this.pipeline.contextProvider(baseContext) : baseContext;
|
|
1638
1300
|
const data = await widget.getData({ params: block.params, context });
|
|
1639
1301
|
return widget.renderMarkdown({ data, params: block.params, context });
|
|
1640
1302
|
} catch (e) {
|
|
1641
1303
|
return widget.renderMarkdownError(e);
|
|
1642
1304
|
}
|
|
1643
|
-
}, replaceWidgetBlocks);
|
|
1305
|
+
}, replaceWidgetBlocks, 0, this.logger);
|
|
1644
1306
|
}
|
|
1645
|
-
}
|
|
1307
|
+
}
|
|
1646
1308
|
|
|
1647
|
-
// dist/
|
|
1309
|
+
// dist/core/util/md.util.js
|
|
1648
1310
|
function rewriteMdLinks(markdown, base, skipPrefixes) {
|
|
1649
1311
|
const prefix = base + "/";
|
|
1650
1312
|
const skip = skipPrefixes.map((p) => p.slice(1) + "/").join("|");
|
|
1651
1313
|
const inlineRe = new RegExp(`\\]\\(\\/(?!${skip})`, "g");
|
|
1652
1314
|
const refRe = new RegExp(`^(\\[[^\\]]+\\]:\\s+)\\/(?!${skip})`, "g");
|
|
1653
|
-
const lines = markdown.split(
|
|
1315
|
+
const lines = markdown.split(`
|
|
1316
|
+
`);
|
|
1654
1317
|
let inCodeBlock = false;
|
|
1655
|
-
for (let i = 0;
|
|
1318
|
+
for (let i = 0;i < lines.length; i++) {
|
|
1656
1319
|
if (lines[i].startsWith("```")) {
|
|
1657
1320
|
inCodeBlock = !inCodeBlock;
|
|
1658
1321
|
continue;
|
|
@@ -1662,10 +1325,219 @@ function rewriteMdLinks(markdown, base, skipPrefixes) {
|
|
|
1662
1325
|
lines[i] = lines[i].replaceAll(inlineRe, `](${prefix}`);
|
|
1663
1326
|
lines[i] = lines[i].replaceAll(refRe, `$1${prefix}`);
|
|
1664
1327
|
}
|
|
1665
|
-
return lines.join(
|
|
1328
|
+
return lines.join(`
|
|
1329
|
+
`);
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
// dist/core/server/emroute.server.js
|
|
1333
|
+
var DEFAULT_BASE_PATH = { html: "/html", md: "/md", app: "/app" };
|
|
1334
|
+
|
|
1335
|
+
class Emroute {
|
|
1336
|
+
runtime;
|
|
1337
|
+
htmlBase;
|
|
1338
|
+
mdBase;
|
|
1339
|
+
appBase;
|
|
1340
|
+
spa;
|
|
1341
|
+
title;
|
|
1342
|
+
htmlRenderer;
|
|
1343
|
+
mdRenderer;
|
|
1344
|
+
shell;
|
|
1345
|
+
constructor(htmlRenderer, mdRenderer, shell, runtime, htmlBase, mdBase, appBase, spa, title) {
|
|
1346
|
+
this.runtime = runtime;
|
|
1347
|
+
this.htmlBase = htmlBase;
|
|
1348
|
+
this.mdBase = mdBase;
|
|
1349
|
+
this.appBase = appBase;
|
|
1350
|
+
this.spa = spa;
|
|
1351
|
+
this.title = title;
|
|
1352
|
+
this.htmlRenderer = htmlRenderer;
|
|
1353
|
+
this.mdRenderer = mdRenderer;
|
|
1354
|
+
this.shell = shell;
|
|
1355
|
+
}
|
|
1356
|
+
static async create(config, runtime) {
|
|
1357
|
+
const { spa = "root" } = config;
|
|
1358
|
+
const { html: htmlBase, md: mdBase, app: appBase } = config.basePath ?? DEFAULT_BASE_PATH;
|
|
1359
|
+
const manifestResponse = await runtime.query(ROUTES_MANIFEST_PATH);
|
|
1360
|
+
if (manifestResponse.status === 404 && !config.routeTree) {
|
|
1361
|
+
throw new Error(`[emroute] ${ROUTES_MANIFEST_PATH} not found in runtime. ` + "Provide routeTree in config or ensure the runtime produces it.");
|
|
1362
|
+
}
|
|
1363
|
+
if (config.routeTree && manifestResponse.status === 404) {
|
|
1364
|
+
await runtime.command(ROUTES_MANIFEST_PATH, {
|
|
1365
|
+
body: JSON.stringify(config.routeTree)
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
const pipeline = new Pipeline({
|
|
1369
|
+
runtime,
|
|
1370
|
+
...config.extendContext ? { contextProvider: config.extendContext } : {},
|
|
1371
|
+
...config.moduleLoaders ? { moduleLoaders: config.moduleLoaders } : {}
|
|
1372
|
+
});
|
|
1373
|
+
let widgets = config.widgets;
|
|
1374
|
+
const widgetsResponse = await runtime.query(WIDGETS_MANIFEST_PATH);
|
|
1375
|
+
if (widgetsResponse.status !== 404) {
|
|
1376
|
+
const entries = await widgetsResponse.json();
|
|
1377
|
+
if (!config.widgets) {
|
|
1378
|
+
widgets = await Emroute.importWidgets(entries, runtime);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
let ssrHtmlRenderer = null;
|
|
1382
|
+
let ssrMdRenderer = null;
|
|
1383
|
+
if (spa !== "only") {
|
|
1384
|
+
ssrHtmlRenderer = new SsrHtmlRenderer(pipeline, {
|
|
1385
|
+
...config.markdownRenderer ? { markdownRenderer: config.markdownRenderer } : {},
|
|
1386
|
+
...widgets ? { widgets } : {}
|
|
1387
|
+
});
|
|
1388
|
+
ssrMdRenderer = new SsrMdRenderer(pipeline, {
|
|
1389
|
+
...widgets ? { widgets } : {}
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
const title = config.title ?? "emroute";
|
|
1393
|
+
const shellBase = spa === "root" || spa === "only" ? appBase : htmlBase;
|
|
1394
|
+
let shell = await Emroute.resolveShell(runtime, title, shellBase);
|
|
1395
|
+
if ((await runtime.query("/main.css")).status !== 404) {
|
|
1396
|
+
shell = shell.replace("</head>", ` <link rel="stylesheet" href="/main.css">
|
|
1397
|
+
</head>`);
|
|
1398
|
+
}
|
|
1399
|
+
return new Emroute(ssrHtmlRenderer, ssrMdRenderer, shell, runtime, htmlBase, mdBase, appBase, spa, title);
|
|
1400
|
+
}
|
|
1401
|
+
async handleRequest(req) {
|
|
1402
|
+
const url = new URL(req.url);
|
|
1403
|
+
const pathname = url.pathname;
|
|
1404
|
+
const mdPrefix = this.mdBase + "/";
|
|
1405
|
+
const htmlPrefix = this.htmlBase + "/";
|
|
1406
|
+
const appPrefix = this.appBase + "/";
|
|
1407
|
+
if (this.mdRenderer && (pathname.startsWith(mdPrefix) || pathname === this.mdBase)) {
|
|
1408
|
+
const routePath = pathname === this.mdBase ? "/" : pathname.slice(this.mdBase.length);
|
|
1409
|
+
if (routePath.length > 1 && routePath.endsWith("/")) {
|
|
1410
|
+
const canonical = this.mdBase + routePath.slice(0, -1) + (url.search || "");
|
|
1411
|
+
return Response.redirect(new URL(canonical, url.origin), 301);
|
|
1412
|
+
}
|
|
1413
|
+
try {
|
|
1414
|
+
const routeUrl = new URL(routePath + url.search, url.origin);
|
|
1415
|
+
const { content, status, redirect } = await this.mdRenderer.render(routeUrl, req.signal);
|
|
1416
|
+
if (redirect) {
|
|
1417
|
+
const target = redirect.startsWith("/") ? this.mdBase + redirect : redirect;
|
|
1418
|
+
return Response.redirect(new URL(target, url.origin), status);
|
|
1419
|
+
}
|
|
1420
|
+
return new Response(rewriteMdLinks(content, this.mdBase, [this.mdBase, this.htmlBase]), {
|
|
1421
|
+
status,
|
|
1422
|
+
headers: { "Content-Type": "text/markdown; charset=utf-8; variant=CommonMark" }
|
|
1423
|
+
});
|
|
1424
|
+
} catch (e) {
|
|
1425
|
+
console.error(`[emroute] Error rendering ${pathname}:`, e);
|
|
1426
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
if (this.htmlRenderer && (pathname.startsWith(htmlPrefix) || pathname === this.htmlBase)) {
|
|
1430
|
+
const routePath = pathname === this.htmlBase ? "/" : pathname.slice(this.htmlBase.length);
|
|
1431
|
+
if (routePath.length > 1 && routePath.endsWith("/")) {
|
|
1432
|
+
const canonical = this.htmlBase + routePath.slice(0, -1) + (url.search || "");
|
|
1433
|
+
return Response.redirect(new URL(canonical, url.origin), 301);
|
|
1434
|
+
}
|
|
1435
|
+
try {
|
|
1436
|
+
const routeUrl = new URL(routePath + url.search, url.origin);
|
|
1437
|
+
const result = await this.htmlRenderer.render(routeUrl, req.signal);
|
|
1438
|
+
if (result.redirect) {
|
|
1439
|
+
const target = result.redirect.startsWith("/") ? this.htmlBase + result.redirect : result.redirect;
|
|
1440
|
+
return Response.redirect(new URL(target, url.origin), result.status);
|
|
1441
|
+
}
|
|
1442
|
+
const ssrTitle = result.title ?? this.title;
|
|
1443
|
+
const html = Emroute.injectSsrContent(this.shell, result.content, ssrTitle, pathname);
|
|
1444
|
+
return new Response(html, {
|
|
1445
|
+
status: result.status,
|
|
1446
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
1447
|
+
});
|
|
1448
|
+
} catch (e) {
|
|
1449
|
+
console.error(`[emroute] Error rendering ${pathname}:`, e);
|
|
1450
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
if (pathname.startsWith(appPrefix) || pathname === this.appBase) {
|
|
1454
|
+
return new Response(this.shell, {
|
|
1455
|
+
status: 200,
|
|
1456
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
if (pathname.startsWith(htmlPrefix) || pathname === this.htmlBase || pathname.startsWith(mdPrefix) || pathname === this.mdBase) {
|
|
1460
|
+
return new Response(this.shell, {
|
|
1461
|
+
status: 200,
|
|
1462
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
const lastSegment = pathname.split("/").pop() ?? "";
|
|
1466
|
+
if (lastSegment.includes(".")) {
|
|
1467
|
+
const fileResponse = await this.runtime.handle(pathname);
|
|
1468
|
+
if (fileResponse.status === 200)
|
|
1469
|
+
return fileResponse;
|
|
1470
|
+
return null;
|
|
1471
|
+
}
|
|
1472
|
+
const base = this.spa === "root" || this.spa === "only" ? this.appBase : this.htmlBase;
|
|
1473
|
+
const bare = pathname === "/" ? "" : pathname.slice(1).replace(/\/$/, "");
|
|
1474
|
+
return Response.redirect(new URL(`${base}/${bare}`, url.origin), 302);
|
|
1475
|
+
}
|
|
1476
|
+
static extractWidgetExport(mod) {
|
|
1477
|
+
for (const value of Object.values(mod)) {
|
|
1478
|
+
if (!value)
|
|
1479
|
+
continue;
|
|
1480
|
+
if (typeof value === "object" && "getData" in value) {
|
|
1481
|
+
return value;
|
|
1482
|
+
}
|
|
1483
|
+
if (typeof value === "function" && value.prototype?.getData) {
|
|
1484
|
+
return new value;
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
return null;
|
|
1488
|
+
}
|
|
1489
|
+
static async importWidgets(entries, runtime) {
|
|
1490
|
+
const registry = new WidgetRegistry;
|
|
1491
|
+
for (const entry of entries) {
|
|
1492
|
+
try {
|
|
1493
|
+
const runtimePath = entry.modulePath.startsWith("/") ? entry.modulePath : `/${entry.modulePath}`;
|
|
1494
|
+
const mod = await runtime.loadModule(runtimePath);
|
|
1495
|
+
const instance = Emroute.extractWidgetExport(mod);
|
|
1496
|
+
if (!instance)
|
|
1497
|
+
continue;
|
|
1498
|
+
registry.add(instance, runtimePath);
|
|
1499
|
+
} catch (e) {
|
|
1500
|
+
console.error(`[emroute] Failed to load widget ${entry.modulePath}:`, e);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
return registry;
|
|
1504
|
+
}
|
|
1505
|
+
static buildHtmlShell(title, htmlBase) {
|
|
1506
|
+
const baseTag = htmlBase ? `
|
|
1507
|
+
<base href="${escapeHtml(htmlBase)}/">` : "";
|
|
1508
|
+
return `<!DOCTYPE html>
|
|
1509
|
+
<html>
|
|
1510
|
+
<head>${baseTag}
|
|
1511
|
+
<meta charset="utf-8">
|
|
1512
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1513
|
+
<title>${escapeHtml(title)}</title>
|
|
1514
|
+
<style>@view-transition { navigation: auto; } router-slot { display: contents; }</style>
|
|
1515
|
+
</head>
|
|
1516
|
+
<body>
|
|
1517
|
+
<router-slot></router-slot>
|
|
1518
|
+
</body>
|
|
1519
|
+
</html>`;
|
|
1520
|
+
}
|
|
1521
|
+
static injectSsrContent(html, content, title, ssrRoute) {
|
|
1522
|
+
const slotPattern = /<router-slot\b[^>]*>.*?<\/router-slot>/s;
|
|
1523
|
+
if (!slotPattern.test(html))
|
|
1524
|
+
return html;
|
|
1525
|
+
const ssrAttr = ssrRoute ? ` data-ssr-route="${ssrRoute}"` : "";
|
|
1526
|
+
html = html.replace(slotPattern, `<router-slot${ssrAttr}>${content}</router-slot>`);
|
|
1527
|
+
if (title) {
|
|
1528
|
+
html = html.replace(/<title>[^<]*<\/title>/, `<title>${escapeHtml(title)}</title>`);
|
|
1529
|
+
}
|
|
1530
|
+
return html;
|
|
1531
|
+
}
|
|
1532
|
+
static async resolveShell(runtime, title, htmlBase) {
|
|
1533
|
+
const response = await runtime.query("/index.html");
|
|
1534
|
+
if (response.status !== 404)
|
|
1535
|
+
return await response.text();
|
|
1536
|
+
return Emroute.buildHtmlShell(title, htmlBase);
|
|
1537
|
+
}
|
|
1666
1538
|
}
|
|
1667
1539
|
|
|
1668
|
-
// dist/
|
|
1540
|
+
// dist/core/util/route-tree.util.js
|
|
1669
1541
|
function resolveTargetNode(node, name, isRoot) {
|
|
1670
1542
|
if (name === "index") {
|
|
1671
1543
|
if (isRoot)
|
|
@@ -1684,104 +1556,425 @@ function resolveTargetNode(node, name, isRoot) {
|
|
|
1684
1556
|
}
|
|
1685
1557
|
|
|
1686
1558
|
// dist/runtime/abstract.runtime.js
|
|
1559
|
+
var CONTENT_TYPES = new Map([
|
|
1560
|
+
[".html", "text/html; charset=utf-8"],
|
|
1561
|
+
[".css", "text/css; charset=utf-8"],
|
|
1562
|
+
[".js", "application/javascript; charset=utf-8"],
|
|
1563
|
+
[".mjs", "application/javascript; charset=utf-8"],
|
|
1564
|
+
[".ts", "text/typescript; charset=utf-8"],
|
|
1565
|
+
[".json", "application/json; charset=utf-8"],
|
|
1566
|
+
[".md", "text/plain; charset=utf-8"],
|
|
1567
|
+
[".txt", "text/plain; charset=utf-8"],
|
|
1568
|
+
[".wasm", "application/wasm"],
|
|
1569
|
+
[".map", "application/json; charset=utf-8"],
|
|
1570
|
+
[".png", "image/png"],
|
|
1571
|
+
[".jpg", "image/jpeg"],
|
|
1572
|
+
[".jpeg", "image/jpeg"],
|
|
1573
|
+
[".gif", "image/gif"],
|
|
1574
|
+
[".svg", "image/svg+xml"],
|
|
1575
|
+
[".ico", "image/x-icon"],
|
|
1576
|
+
[".webp", "image/webp"],
|
|
1577
|
+
[".avif", "image/avif"],
|
|
1578
|
+
[".woff", "font/woff"],
|
|
1579
|
+
[".woff2", "font/woff2"],
|
|
1580
|
+
[".ttf", "font/ttf"]
|
|
1581
|
+
]);
|
|
1687
1582
|
var DEFAULT_ROUTES_DIR = "/routes";
|
|
1688
1583
|
var DEFAULT_WIDGETS_DIR = "/widgets";
|
|
1689
1584
|
var DEFAULT_ELEMENTS_DIR = "/elements";
|
|
1690
|
-
|
|
1691
|
-
var WIDGETS_MANIFEST_PATH = "/widgets.manifest.json";
|
|
1692
|
-
var ELEMENTS_MANIFEST_PATH = "/elements.manifest.json";
|
|
1693
|
-
var Runtime = class {
|
|
1585
|
+
class Runtime {
|
|
1694
1586
|
config;
|
|
1695
1587
|
constructor(config = {}) {
|
|
1696
1588
|
this.config = config;
|
|
1697
1589
|
this.config = config;
|
|
1698
1590
|
}
|
|
1699
|
-
/** Write. Defaults to PUT; pass `{ method: "DELETE" }` etc. to override. */
|
|
1700
1591
|
command(resource, options) {
|
|
1701
1592
|
const path = typeof resource === "string" ? resource : new URL(resource instanceof Request ? resource.url : resource.toString()).pathname;
|
|
1702
|
-
const
|
|
1593
|
+
const method = options?.method ?? "PUT";
|
|
1594
|
+
const isDelete = method === "DELETE";
|
|
1595
|
+
const result = this.handle(resource, { method, ...options });
|
|
1703
1596
|
const routesDir = this.config.routesDir ?? DEFAULT_ROUTES_DIR;
|
|
1597
|
+
const widgetsDir = this.config.widgetsDir ?? DEFAULT_WIDGETS_DIR;
|
|
1598
|
+
const elementsDir = this.config.elementsDir ?? DEFAULT_ELEMENTS_DIR;
|
|
1704
1599
|
if (path.startsWith(routesDir + "/")) {
|
|
1705
1600
|
return result.then(async (res) => {
|
|
1706
|
-
|
|
1601
|
+
if (isDelete) {
|
|
1602
|
+
await this.pruneRouteFromManifest(path, routesDir);
|
|
1603
|
+
} else {
|
|
1604
|
+
await this.mergeRouteIntoManifest(path, routesDir);
|
|
1605
|
+
await this.retranspileIfNeeded(path, routesDir, "route");
|
|
1606
|
+
}
|
|
1707
1607
|
return res;
|
|
1708
1608
|
});
|
|
1709
1609
|
}
|
|
1710
|
-
|
|
1610
|
+
if (path.startsWith(widgetsDir + "/")) {
|
|
1611
|
+
return result.then(async (res) => {
|
|
1612
|
+
if (isDelete) {
|
|
1613
|
+
await this.pruneWidgetFromManifest(path, widgetsDir);
|
|
1614
|
+
} else {
|
|
1615
|
+
await this.mergeWidgetIntoManifest(path, widgetsDir);
|
|
1616
|
+
await this.retranspileIfNeeded(path, widgetsDir, "widget");
|
|
1617
|
+
}
|
|
1618
|
+
return res;
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
if (path.startsWith(elementsDir + "/")) {
|
|
1622
|
+
return result.then(async (res) => {
|
|
1623
|
+
if (isDelete) {
|
|
1624
|
+
await this.pruneElementFromManifest(path, elementsDir);
|
|
1625
|
+
} else {
|
|
1626
|
+
await this.mergeElementIntoManifest(path, elementsDir);
|
|
1627
|
+
await this.retranspileIfNeeded(path, elementsDir, "element");
|
|
1628
|
+
}
|
|
1629
|
+
return res;
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
return result;
|
|
1633
|
+
}
|
|
1634
|
+
async mergeRouteIntoManifest(filePath, routesDir) {
|
|
1635
|
+
const relativePath = filePath.slice(routesDir.length + 1);
|
|
1636
|
+
const parts = relativePath.split("/");
|
|
1637
|
+
const filename = parts[parts.length - 1];
|
|
1638
|
+
const dirSegments = parts.slice(0, -1);
|
|
1639
|
+
const match = filename.match(/^(.+?)\.(page|error|redirect)\.(ts|js|html|md|css)$/);
|
|
1640
|
+
if (!match)
|
|
1641
|
+
return;
|
|
1642
|
+
const name = match[1];
|
|
1643
|
+
const kind = match[2];
|
|
1644
|
+
const ext = match[3];
|
|
1645
|
+
const response = await this.handle(ROUTES_MANIFEST_PATH);
|
|
1646
|
+
const tree = response.status === 404 ? {} : await response.json();
|
|
1647
|
+
let node = tree;
|
|
1648
|
+
for (const dir of dirSegments) {
|
|
1649
|
+
if (dir.startsWith("[") && dir.endsWith("]")) {
|
|
1650
|
+
const param = dir.slice(1, -1);
|
|
1651
|
+
node.dynamic ??= { param, child: {} };
|
|
1652
|
+
node = node.dynamic.child;
|
|
1653
|
+
} else {
|
|
1654
|
+
node.children ??= {};
|
|
1655
|
+
node.children[dir] ??= {};
|
|
1656
|
+
node = node.children[dir];
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
if (kind === "error") {
|
|
1660
|
+
node.errorBoundary = filePath;
|
|
1661
|
+
} else {
|
|
1662
|
+
const target = resolveTargetNode(node, name, dirSegments.length === 0);
|
|
1663
|
+
if (kind === "redirect") {
|
|
1664
|
+
target.redirect = filePath;
|
|
1665
|
+
} else {
|
|
1666
|
+
target.files ??= {};
|
|
1667
|
+
target.files[ext] = filePath;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
this.routesManifestCache = null;
|
|
1671
|
+
await this.handle(ROUTES_MANIFEST_PATH, {
|
|
1672
|
+
method: "PUT",
|
|
1673
|
+
body: JSON.stringify(tree)
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
async pruneRouteFromManifest(filePath, routesDir) {
|
|
1677
|
+
const relativePath = filePath.slice(routesDir.length + 1);
|
|
1678
|
+
const parts = relativePath.split("/");
|
|
1679
|
+
const filename = parts[parts.length - 1];
|
|
1680
|
+
const dirSegments = parts.slice(0, -1);
|
|
1681
|
+
const match = filename.match(/^(.+?)\.(page|error|redirect)\.(ts|js|html|md|css)$/);
|
|
1682
|
+
if (!match)
|
|
1683
|
+
return;
|
|
1684
|
+
const name = match[1];
|
|
1685
|
+
const kind = match[2];
|
|
1686
|
+
const ext = match[3];
|
|
1687
|
+
const response = await this.handle(ROUTES_MANIFEST_PATH);
|
|
1688
|
+
if (response.status === 404)
|
|
1689
|
+
return;
|
|
1690
|
+
const tree = await response.json();
|
|
1691
|
+
const ancestors = [];
|
|
1692
|
+
let node = tree;
|
|
1693
|
+
for (const dir of dirSegments) {
|
|
1694
|
+
if (dir.startsWith("[") && dir.endsWith("]")) {
|
|
1695
|
+
if (!node.dynamic)
|
|
1696
|
+
return;
|
|
1697
|
+
ancestors.push({ node, key: dir, via: "dynamic" });
|
|
1698
|
+
node = node.dynamic.child;
|
|
1699
|
+
} else {
|
|
1700
|
+
if (!node.children?.[dir])
|
|
1701
|
+
return;
|
|
1702
|
+
ancestors.push({ node, key: dir, via: "children" });
|
|
1703
|
+
node = node.children[dir];
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
if (kind === "error") {
|
|
1707
|
+
if (node.errorBoundary === filePath)
|
|
1708
|
+
delete node.errorBoundary;
|
|
1709
|
+
} else {
|
|
1710
|
+
const isRoot = dirSegments.length === 0;
|
|
1711
|
+
const target = this.findTargetNode(node, name, isRoot);
|
|
1712
|
+
if (!target)
|
|
1713
|
+
return;
|
|
1714
|
+
if (kind === "redirect") {
|
|
1715
|
+
if (target.redirect === filePath)
|
|
1716
|
+
delete target.redirect;
|
|
1717
|
+
} else {
|
|
1718
|
+
if (target.files?.[ext] === filePath) {
|
|
1719
|
+
delete target.files[ext];
|
|
1720
|
+
if (Object.keys(target.files).length === 0)
|
|
1721
|
+
delete target.files;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
if (target !== node && this.isEmptyNode(target)) {
|
|
1725
|
+
if (name === "index" && !isRoot) {
|
|
1726
|
+
delete node.wildcard;
|
|
1727
|
+
} else if (name.startsWith("[") && name.endsWith("]")) {
|
|
1728
|
+
delete node.dynamic;
|
|
1729
|
+
} else if (node.children) {
|
|
1730
|
+
delete node.children[name];
|
|
1731
|
+
if (Object.keys(node.children).length === 0)
|
|
1732
|
+
delete node.children;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
for (let i = ancestors.length - 1;i >= 0; i--) {
|
|
1737
|
+
const { node: parent, key, via } = ancestors[i];
|
|
1738
|
+
const child = via === "dynamic" ? parent.dynamic?.child : parent.children?.[key];
|
|
1739
|
+
if (child && this.isEmptyNode(child)) {
|
|
1740
|
+
if (via === "dynamic") {
|
|
1741
|
+
delete parent.dynamic;
|
|
1742
|
+
} else if (parent.children) {
|
|
1743
|
+
delete parent.children[key];
|
|
1744
|
+
if (Object.keys(parent.children).length === 0)
|
|
1745
|
+
delete parent.children;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
this.routesManifestCache = null;
|
|
1750
|
+
await this.handle(ROUTES_MANIFEST_PATH, {
|
|
1751
|
+
method: "PUT",
|
|
1752
|
+
body: JSON.stringify(tree)
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
findTargetNode(node, name, isRoot) {
|
|
1756
|
+
if (name === "index") {
|
|
1757
|
+
return isRoot ? node : node.wildcard?.child ?? null;
|
|
1758
|
+
}
|
|
1759
|
+
if (name.startsWith("[") && name.endsWith("]")) {
|
|
1760
|
+
return node.dynamic?.child ?? null;
|
|
1761
|
+
}
|
|
1762
|
+
return node.children?.[name] ?? null;
|
|
1763
|
+
}
|
|
1764
|
+
isEmptyNode(node) {
|
|
1765
|
+
return !node.files && !node.errorBoundary && !node.redirect && !node.children && !node.dynamic && !node.wildcard;
|
|
1766
|
+
}
|
|
1767
|
+
async pruneWidgetFromManifest(filePath, widgetsDir) {
|
|
1768
|
+
const relativePath = filePath.slice(widgetsDir.length + 1);
|
|
1769
|
+
const parts = relativePath.split("/");
|
|
1770
|
+
if (parts.length !== 2)
|
|
1771
|
+
return;
|
|
1772
|
+
const [dirName, filename] = parts;
|
|
1773
|
+
const match = filename.match(/^(.+?)\.widget\.(ts|js|html|md|css)$/);
|
|
1774
|
+
if (!match)
|
|
1775
|
+
return;
|
|
1776
|
+
const name = match[1];
|
|
1777
|
+
const ext = match[2];
|
|
1778
|
+
if (name !== dirName)
|
|
1779
|
+
return;
|
|
1780
|
+
const response = await this.handle(WIDGETS_MANIFEST_PATH);
|
|
1781
|
+
if (response.status === 404)
|
|
1782
|
+
return;
|
|
1783
|
+
const entries = await response.json();
|
|
1784
|
+
if (ext === "ts" || ext === "js") {
|
|
1785
|
+
const idx = entries.findIndex((e) => e.name === name);
|
|
1786
|
+
if (idx === -1)
|
|
1787
|
+
return;
|
|
1788
|
+
entries.splice(idx, 1);
|
|
1789
|
+
} else {
|
|
1790
|
+
const entry = entries.find((e) => e.name === name);
|
|
1791
|
+
if (!entry?.files)
|
|
1792
|
+
return;
|
|
1793
|
+
delete entry.files[ext];
|
|
1794
|
+
if (Object.keys(entry.files).length === 0)
|
|
1795
|
+
delete entry.files;
|
|
1796
|
+
}
|
|
1797
|
+
this.widgetsManifestCache = null;
|
|
1798
|
+
await this.handle(WIDGETS_MANIFEST_PATH, {
|
|
1799
|
+
method: "PUT",
|
|
1800
|
+
body: JSON.stringify(entries)
|
|
1801
|
+
});
|
|
1802
|
+
}
|
|
1803
|
+
async pruneElementFromManifest(filePath, elementsDir) {
|
|
1804
|
+
const relativePath = filePath.slice(elementsDir.length + 1);
|
|
1805
|
+
const parts = relativePath.split("/");
|
|
1806
|
+
if (parts.length !== 2)
|
|
1807
|
+
return;
|
|
1808
|
+
const [dirName, filename] = parts;
|
|
1809
|
+
const match = filename.match(/^(.+?)\.element\.(ts|js)$/);
|
|
1810
|
+
if (!match)
|
|
1811
|
+
return;
|
|
1812
|
+
const name = match[1];
|
|
1813
|
+
if (name !== dirName)
|
|
1814
|
+
return;
|
|
1815
|
+
const response = await this.handle(ELEMENTS_MANIFEST_PATH);
|
|
1816
|
+
if (response.status === 404)
|
|
1817
|
+
return;
|
|
1818
|
+
const entries = await response.json();
|
|
1819
|
+
const idx = entries.findIndex((e) => e.name === name);
|
|
1820
|
+
if (idx === -1)
|
|
1821
|
+
return;
|
|
1822
|
+
entries.splice(idx, 1);
|
|
1823
|
+
this.elementsManifestCache = null;
|
|
1824
|
+
await this.handle(ELEMENTS_MANIFEST_PATH, {
|
|
1825
|
+
method: "PUT",
|
|
1826
|
+
body: JSON.stringify(entries)
|
|
1827
|
+
});
|
|
1828
|
+
}
|
|
1829
|
+
async retranspileIfNeeded(filePath, dir, kind) {
|
|
1830
|
+
if (filePath.endsWith(".js"))
|
|
1831
|
+
return;
|
|
1832
|
+
const relativePath = filePath.slice(dir.length + 1);
|
|
1833
|
+
const parts = relativePath.split("/");
|
|
1834
|
+
const filename = parts[parts.length - 1];
|
|
1835
|
+
let jsPath;
|
|
1836
|
+
if (kind === "route") {
|
|
1837
|
+
const match = filename.match(/^(.+?)\.(page)\.(ts|html|md|css)$/);
|
|
1838
|
+
if (!match)
|
|
1839
|
+
return;
|
|
1840
|
+
const name = match[1];
|
|
1841
|
+
jsPath = `${dir}/${parts.slice(0, -1).join("/")}${parts.length > 1 ? "/" : ""}${name}.page.js`;
|
|
1842
|
+
} else if (kind === "widget") {
|
|
1843
|
+
const match = filename.match(/^(.+?)\.(widget)\.(ts|html|md|css)$/);
|
|
1844
|
+
if (!match)
|
|
1845
|
+
return;
|
|
1846
|
+
const name = match[1];
|
|
1847
|
+
jsPath = `${dir}/${name}/${name}.widget.js`;
|
|
1848
|
+
} else {
|
|
1849
|
+
const match = filename.match(/^(.+?)\.(element)\.ts$/);
|
|
1850
|
+
if (!match)
|
|
1851
|
+
return;
|
|
1852
|
+
const name = match[1];
|
|
1853
|
+
jsPath = `${dir}/${name}/${name}.element.js`;
|
|
1854
|
+
}
|
|
1855
|
+
const jsResponse = await this.handle(jsPath);
|
|
1856
|
+
if (jsResponse.status === 404)
|
|
1857
|
+
return;
|
|
1858
|
+
const tsPath = jsPath.replace(/\.js$/, ".ts");
|
|
1859
|
+
let tsSource;
|
|
1860
|
+
try {
|
|
1861
|
+
tsSource = await this.query(tsPath, { as: "text" });
|
|
1862
|
+
} catch {
|
|
1863
|
+
return;
|
|
1864
|
+
}
|
|
1865
|
+
const companionExts = kind === "element" ? [] : ["html", "md", "css"];
|
|
1866
|
+
const files = {};
|
|
1867
|
+
for (const ext of companionExts) {
|
|
1868
|
+
const companionPath = tsPath.replace(/\.ts$/, `.${ext}`);
|
|
1869
|
+
try {
|
|
1870
|
+
files[ext] = await this.query(companionPath, { as: "text" });
|
|
1871
|
+
} catch {}
|
|
1872
|
+
}
|
|
1873
|
+
let jsCode;
|
|
1874
|
+
try {
|
|
1875
|
+
jsCode = await this.transpile(tsSource);
|
|
1876
|
+
} catch {
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1879
|
+
if (Object.keys(files).length > 0) {
|
|
1880
|
+
const entries = Object.entries(files).map(([k, v]) => `${k}: \`${v.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$")}\``).join(", ");
|
|
1881
|
+
jsCode += `
|
|
1882
|
+
export const __files = { ${entries} };
|
|
1883
|
+
`;
|
|
1884
|
+
}
|
|
1885
|
+
await this.handle(jsPath, { method: "PUT", body: jsCode });
|
|
1711
1886
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1887
|
+
loadModule(_path) {
|
|
1888
|
+
throw new Error(`loadModule not implemented for ${this.constructor.name}`);
|
|
1889
|
+
}
|
|
1890
|
+
transpile(_source) {
|
|
1891
|
+
throw new Error(`transpile not implemented for ${this.constructor.name}`);
|
|
1892
|
+
}
|
|
1893
|
+
async mergeWidgetIntoManifest(filePath, widgetsDir) {
|
|
1894
|
+
const relativePath = filePath.slice(widgetsDir.length + 1);
|
|
1719
1895
|
const parts = relativePath.split("/");
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
const
|
|
1896
|
+
if (parts.length !== 2)
|
|
1897
|
+
return;
|
|
1898
|
+
const [dirName, filename] = parts;
|
|
1899
|
+
const match = filename.match(/^(.+?)\.widget\.(ts|js|html|md|css)$/);
|
|
1723
1900
|
if (!match)
|
|
1724
1901
|
return;
|
|
1725
|
-
const
|
|
1726
|
-
const
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1902
|
+
const name = match[1];
|
|
1903
|
+
const ext = match[2];
|
|
1904
|
+
if (name !== dirName)
|
|
1905
|
+
return;
|
|
1906
|
+
const response = await this.handle(WIDGETS_MANIFEST_PATH);
|
|
1907
|
+
const entries = response.status === 404 ? [] : await response.json();
|
|
1908
|
+
const prefix = widgetsDir.replace(/^\//, "");
|
|
1909
|
+
if (ext === "ts" || ext === "js") {
|
|
1910
|
+
let entry = entries.find((e) => e.name === name);
|
|
1911
|
+
if (!entry) {
|
|
1912
|
+
entry = {
|
|
1913
|
+
name,
|
|
1914
|
+
modulePath: `${prefix}/${name}/${filename}`,
|
|
1915
|
+
tagName: `widget-${name}`
|
|
1916
|
+
};
|
|
1917
|
+
entries.push(entry);
|
|
1918
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
1734
1919
|
} else {
|
|
1735
|
-
|
|
1736
|
-
node.children[dir] ??= {};
|
|
1737
|
-
node = node.children[dir];
|
|
1920
|
+
entry.modulePath = `${prefix}/${name}/${filename}`;
|
|
1738
1921
|
}
|
|
1739
|
-
}
|
|
1740
|
-
if (kind === "error") {
|
|
1741
|
-
node.errorBoundary = filePath;
|
|
1742
1922
|
} else {
|
|
1743
|
-
const
|
|
1744
|
-
if (
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
target.files[ext] = filePath;
|
|
1749
|
-
}
|
|
1923
|
+
const entry = entries.find((e) => e.name === name);
|
|
1924
|
+
if (!entry)
|
|
1925
|
+
return;
|
|
1926
|
+
entry.files ??= {};
|
|
1927
|
+
entry.files[ext] = `${prefix}/${name}/${filename}`;
|
|
1750
1928
|
}
|
|
1751
|
-
this.
|
|
1752
|
-
await this.handle(
|
|
1929
|
+
this.widgetsManifestCache = null;
|
|
1930
|
+
await this.handle(WIDGETS_MANIFEST_PATH, {
|
|
1753
1931
|
method: "PUT",
|
|
1754
|
-
body: JSON.stringify(
|
|
1932
|
+
body: JSON.stringify(entries)
|
|
1755
1933
|
});
|
|
1756
1934
|
}
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1935
|
+
async mergeElementIntoManifest(filePath, elementsDir) {
|
|
1936
|
+
const relativePath = filePath.slice(elementsDir.length + 1);
|
|
1937
|
+
const parts = relativePath.split("/");
|
|
1938
|
+
if (parts.length !== 2)
|
|
1939
|
+
return;
|
|
1940
|
+
const [dirName, filename] = parts;
|
|
1941
|
+
const match = filename.match(/^(.+?)\.element\.(ts|js)$/);
|
|
1942
|
+
if (!match)
|
|
1943
|
+
return;
|
|
1944
|
+
const name = match[1];
|
|
1945
|
+
if (name !== dirName)
|
|
1946
|
+
return;
|
|
1947
|
+
if (!name.includes("-"))
|
|
1948
|
+
return;
|
|
1949
|
+
const response = await this.handle(ELEMENTS_MANIFEST_PATH);
|
|
1950
|
+
const entries = response.status === 404 ? [] : await response.json();
|
|
1951
|
+
const prefix = elementsDir.replace(/^\//, "");
|
|
1952
|
+
let entry = entries.find((e) => e.name === name);
|
|
1953
|
+
if (!entry) {
|
|
1954
|
+
entry = {
|
|
1955
|
+
name,
|
|
1956
|
+
modulePath: `${prefix}/${name}/${filename}`,
|
|
1957
|
+
tagName: name
|
|
1958
|
+
};
|
|
1959
|
+
entries.push(entry);
|
|
1960
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
1961
|
+
} else {
|
|
1962
|
+
entry.modulePath = `${prefix}/${name}/${filename}`;
|
|
1963
|
+
}
|
|
1964
|
+
this.elementsManifestCache = null;
|
|
1965
|
+
await this.handle(ELEMENTS_MANIFEST_PATH, {
|
|
1966
|
+
method: "PUT",
|
|
1967
|
+
body: JSON.stringify(entries)
|
|
1968
|
+
});
|
|
1770
1969
|
}
|
|
1771
|
-
// ── Manifest resolution ─────────────────────────────────────────────
|
|
1772
1970
|
routesManifestCache = null;
|
|
1773
1971
|
widgetsManifestCache = null;
|
|
1774
1972
|
elementsManifestCache = null;
|
|
1775
|
-
/** Clear cached manifests so the next query triggers a fresh scan. */
|
|
1776
1973
|
invalidateManifests() {
|
|
1777
1974
|
this.routesManifestCache = null;
|
|
1778
1975
|
this.widgetsManifestCache = null;
|
|
1779
1976
|
this.elementsManifestCache = null;
|
|
1780
1977
|
}
|
|
1781
|
-
/**
|
|
1782
|
-
* Resolve the routes manifest. Called when the concrete runtime returns
|
|
1783
|
-
* 404 for ROUTES_MANIFEST_PATH. Scans `config.routesDir` (or default).
|
|
1784
|
-
*/
|
|
1785
1978
|
async resolveRoutesManifest() {
|
|
1786
1979
|
if (this.routesManifestCache)
|
|
1787
1980
|
return this.routesManifestCache.clone();
|
|
@@ -1794,10 +1987,6 @@ var Runtime = class {
|
|
|
1794
1987
|
this.routesManifestCache = Response.json(tree);
|
|
1795
1988
|
return this.routesManifestCache.clone();
|
|
1796
1989
|
}
|
|
1797
|
-
/**
|
|
1798
|
-
* Resolve the widgets manifest. Called when the concrete runtime returns
|
|
1799
|
-
* 404 for WIDGETS_MANIFEST_PATH. Scans `config.widgetsDir` (or default).
|
|
1800
|
-
*/
|
|
1801
1990
|
async resolveWidgetsManifest() {
|
|
1802
1991
|
if (this.widgetsManifestCache)
|
|
1803
1992
|
return this.widgetsManifestCache.clone();
|
|
@@ -1810,10 +1999,6 @@ var Runtime = class {
|
|
|
1810
1999
|
this.widgetsManifestCache = Response.json(entries);
|
|
1811
2000
|
return this.widgetsManifestCache.clone();
|
|
1812
2001
|
}
|
|
1813
|
-
/**
|
|
1814
|
-
* Resolve the elements manifest. Called when the concrete runtime returns
|
|
1815
|
-
* 404 for ELEMENTS_MANIFEST_PATH. Scans `config.elementsDir` (or default).
|
|
1816
|
-
*/
|
|
1817
2002
|
async resolveElementsManifest() {
|
|
1818
2003
|
if (this.elementsManifestCache)
|
|
1819
2004
|
return this.elementsManifestCache.clone();
|
|
@@ -1826,8 +2011,7 @@ var Runtime = class {
|
|
|
1826
2011
|
this.elementsManifestCache = Response.json(entries);
|
|
1827
2012
|
return this.elementsManifestCache.clone();
|
|
1828
2013
|
}
|
|
1829
|
-
|
|
1830
|
-
async *walkDirectory(dir) {
|
|
2014
|
+
async* walkDirectory(dir) {
|
|
1831
2015
|
const trailingDir = dir.endsWith("/") ? dir : dir + "/";
|
|
1832
2016
|
const response = await this.query(trailingDir);
|
|
1833
2017
|
const entries = await response.json();
|
|
@@ -1840,10 +2024,6 @@ var Runtime = class {
|
|
|
1840
2024
|
}
|
|
1841
2025
|
}
|
|
1842
2026
|
}
|
|
1843
|
-
/**
|
|
1844
|
-
* Scan a routes directory and build a RouteNode tree.
|
|
1845
|
-
* The filesystem structure maps directly to the tree — no intermediate array.
|
|
1846
|
-
*/
|
|
1847
2027
|
async scanRoutes(routesDir) {
|
|
1848
2028
|
const root = {};
|
|
1849
2029
|
const allFiles = [];
|
|
@@ -1858,7 +2038,9 @@ var Runtime = class {
|
|
|
1858
2038
|
const match = filename.match(/^(.+?)\.(page|error|redirect)\.(ts|js|html|md|css)$/);
|
|
1859
2039
|
if (!match)
|
|
1860
2040
|
continue;
|
|
1861
|
-
const
|
|
2041
|
+
const name = match[1];
|
|
2042
|
+
const kind = match[2];
|
|
2043
|
+
const ext = match[3];
|
|
1862
2044
|
let node = root;
|
|
1863
2045
|
for (const dir of dirSegments) {
|
|
1864
2046
|
if (dir.startsWith("[") && dir.endsWith("]")) {
|
|
@@ -1957,280 +2139,10 @@ var Runtime = class {
|
|
|
1957
2139
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
1958
2140
|
return entries;
|
|
1959
2141
|
}
|
|
1960
|
-
};
|
|
1961
|
-
|
|
1962
|
-
// dist/server/emroute.server.js
|
|
1963
|
-
function createModuleLoaders(tree, runtime) {
|
|
1964
|
-
const paths = /* @__PURE__ */ new Set();
|
|
1965
|
-
function walk(node) {
|
|
1966
|
-
const modulePath = node.files?.ts ?? node.files?.js;
|
|
1967
|
-
if (modulePath)
|
|
1968
|
-
paths.add(modulePath);
|
|
1969
|
-
if (node.redirect)
|
|
1970
|
-
paths.add(node.redirect);
|
|
1971
|
-
if (node.errorBoundary)
|
|
1972
|
-
paths.add(node.errorBoundary);
|
|
1973
|
-
if (node.children) {
|
|
1974
|
-
for (const child of Object.values(node.children))
|
|
1975
|
-
walk(child);
|
|
1976
|
-
}
|
|
1977
|
-
if (node.dynamic)
|
|
1978
|
-
walk(node.dynamic.child);
|
|
1979
|
-
if (node.wildcard)
|
|
1980
|
-
walk(node.wildcard.child);
|
|
1981
|
-
}
|
|
1982
|
-
walk(tree);
|
|
1983
|
-
const loaders = {};
|
|
1984
|
-
for (const path of paths) {
|
|
1985
|
-
loaders[path] = () => runtime.loadModule(path);
|
|
1986
|
-
}
|
|
1987
|
-
return loaders;
|
|
1988
|
-
}
|
|
1989
|
-
function extractWidgetExport(mod) {
|
|
1990
|
-
for (const value of Object.values(mod)) {
|
|
1991
|
-
if (!value)
|
|
1992
|
-
continue;
|
|
1993
|
-
if (typeof value === "object" && "getData" in value) {
|
|
1994
|
-
return value;
|
|
1995
|
-
}
|
|
1996
|
-
if (typeof value === "function" && value.prototype?.getData) {
|
|
1997
|
-
return new value();
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
return null;
|
|
2001
|
-
}
|
|
2002
|
-
async function importWidgets(entries, runtime, manual) {
|
|
2003
|
-
const registry = new WidgetRegistry();
|
|
2004
|
-
const widgetFiles = {};
|
|
2005
|
-
for (const entry of entries) {
|
|
2006
|
-
try {
|
|
2007
|
-
const runtimePath = entry.modulePath.startsWith("/") ? entry.modulePath : `/${entry.modulePath}`;
|
|
2008
|
-
const mod = await runtime.loadModule(runtimePath);
|
|
2009
|
-
const instance = extractWidgetExport(mod);
|
|
2010
|
-
if (!instance)
|
|
2011
|
-
continue;
|
|
2012
|
-
registry.add(instance);
|
|
2013
|
-
const inlined = mod.__files;
|
|
2014
|
-
if (inlined && typeof inlined === "object") {
|
|
2015
|
-
widgetFiles[entry.name] = inlined;
|
|
2016
|
-
} else if (entry.files) {
|
|
2017
|
-
widgetFiles[entry.name] = entry.files;
|
|
2018
|
-
}
|
|
2019
|
-
} catch (e) {
|
|
2020
|
-
console.error(`[emroute] Failed to load widget ${entry.modulePath}:`, e);
|
|
2021
|
-
if (entry.files)
|
|
2022
|
-
widgetFiles[entry.name] = entry.files;
|
|
2023
|
-
}
|
|
2024
|
-
}
|
|
2025
|
-
if (manual) {
|
|
2026
|
-
for (const widget of manual) {
|
|
2027
|
-
registry.add(widget);
|
|
2028
|
-
}
|
|
2029
|
-
}
|
|
2030
|
-
return { registry, widgetFiles };
|
|
2031
|
-
}
|
|
2032
|
-
function buildHtmlShell(title, htmlBase) {
|
|
2033
|
-
const baseTag = htmlBase ? `
|
|
2034
|
-
<base href="${escapeHtml(htmlBase)}/">` : "";
|
|
2035
|
-
return `<!DOCTYPE html>
|
|
2036
|
-
<html>
|
|
2037
|
-
<head>${baseTag}
|
|
2038
|
-
<meta charset="utf-8">
|
|
2039
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2040
|
-
<title>${escapeHtml(title)}</title>
|
|
2041
|
-
<style>@view-transition { navigation: auto; } router-slot { display: contents; }</style>
|
|
2042
|
-
</head>
|
|
2043
|
-
<body>
|
|
2044
|
-
<router-slot></router-slot>
|
|
2045
|
-
</body>
|
|
2046
|
-
</html>`;
|
|
2047
|
-
}
|
|
2048
|
-
function injectSsrContent(html, content, title, ssrRoute) {
|
|
2049
|
-
const slotPattern = /<router-slot\b[^>]*>.*?<\/router-slot>/s;
|
|
2050
|
-
if (!slotPattern.test(html))
|
|
2051
|
-
return html;
|
|
2052
|
-
const ssrAttr = ssrRoute ? ` data-ssr-route="${ssrRoute}"` : "";
|
|
2053
|
-
html = html.replace(slotPattern, `<router-slot${ssrAttr}>${content}</router-slot>`);
|
|
2054
|
-
if (title) {
|
|
2055
|
-
html = html.replace(/<title>[^<]*<\/title>/, `<title>${escapeHtml(title)}</title>`);
|
|
2056
|
-
}
|
|
2057
|
-
return html;
|
|
2058
|
-
}
|
|
2059
|
-
async function resolveShell(runtime, title, htmlBase) {
|
|
2060
|
-
const response = await runtime.query("/index.html");
|
|
2061
|
-
if (response.status !== 404)
|
|
2062
|
-
return await response.text();
|
|
2063
|
-
return buildHtmlShell(title, htmlBase);
|
|
2064
|
-
}
|
|
2065
|
-
async function createEmrouteServer(config, runtime) {
|
|
2066
|
-
const { spa = "root" } = config;
|
|
2067
|
-
const { html: htmlBase, md: mdBase, app: appBase } = config.basePath ?? DEFAULT_BASE_PATH;
|
|
2068
|
-
let routeTree;
|
|
2069
|
-
if (config.routeTree) {
|
|
2070
|
-
routeTree = config.routeTree;
|
|
2071
|
-
} else {
|
|
2072
|
-
const manifestResponse = await runtime.query(ROUTES_MANIFEST_PATH);
|
|
2073
|
-
if (manifestResponse.status === 404) {
|
|
2074
|
-
throw new Error(`[emroute] ${ROUTES_MANIFEST_PATH} not found in runtime. Provide routeTree in config or ensure the runtime produces it.`);
|
|
2075
|
-
}
|
|
2076
|
-
routeTree = await manifestResponse.json();
|
|
2077
|
-
}
|
|
2078
|
-
const moduleLoaders = config.moduleLoaders ?? createModuleLoaders(routeTree, runtime);
|
|
2079
|
-
const resolver = new RouteTrie(routeTree);
|
|
2080
|
-
let widgets = config.widgets;
|
|
2081
|
-
let widgetFiles = {};
|
|
2082
|
-
let discoveredWidgetEntries = [];
|
|
2083
|
-
const widgetsResponse = await runtime.query(WIDGETS_MANIFEST_PATH);
|
|
2084
|
-
if (widgetsResponse.status !== 404) {
|
|
2085
|
-
discoveredWidgetEntries = await widgetsResponse.json();
|
|
2086
|
-
if (config.widgets) {
|
|
2087
|
-
widgets = config.widgets;
|
|
2088
|
-
for (const entry of discoveredWidgetEntries) {
|
|
2089
|
-
if (entry.files)
|
|
2090
|
-
widgetFiles[entry.name] = entry.files;
|
|
2091
|
-
}
|
|
2092
|
-
} else {
|
|
2093
|
-
const imported = await importWidgets(discoveredWidgetEntries, runtime);
|
|
2094
|
-
widgets = imported.registry;
|
|
2095
|
-
widgetFiles = imported.widgetFiles;
|
|
2096
|
-
}
|
|
2097
|
-
}
|
|
2098
|
-
let discoveredElementEntries = [];
|
|
2099
|
-
const elementsResponse = await runtime.query(ELEMENTS_MANIFEST_PATH);
|
|
2100
|
-
if (elementsResponse.status !== 404) {
|
|
2101
|
-
discoveredElementEntries = await elementsResponse.json();
|
|
2102
|
-
}
|
|
2103
|
-
let ssrHtmlRouter = null;
|
|
2104
|
-
let ssrMdRouter = null;
|
|
2105
|
-
function buildSsrRouters() {
|
|
2106
|
-
if (spa === "only") {
|
|
2107
|
-
ssrHtmlRouter = null;
|
|
2108
|
-
ssrMdRouter = null;
|
|
2109
|
-
return;
|
|
2110
|
-
}
|
|
2111
|
-
ssrHtmlRouter = new SsrHtmlRouter(resolver, {
|
|
2112
|
-
fileReader: (path) => runtime.query(path, { as: "text" }),
|
|
2113
|
-
moduleLoaders,
|
|
2114
|
-
...config.markdownRenderer ? { markdownRenderer: config.markdownRenderer } : {},
|
|
2115
|
-
...config.extendContext ? { extendContext: config.extendContext } : {},
|
|
2116
|
-
...widgets ? { widgets } : {},
|
|
2117
|
-
widgetFiles
|
|
2118
|
-
});
|
|
2119
|
-
ssrMdRouter = new SsrMdRouter(resolver, {
|
|
2120
|
-
fileReader: (path) => runtime.query(path, { as: "text" }),
|
|
2121
|
-
moduleLoaders,
|
|
2122
|
-
...config.extendContext ? { extendContext: config.extendContext } : {},
|
|
2123
|
-
...widgets ? { widgets } : {},
|
|
2124
|
-
widgetFiles
|
|
2125
|
-
});
|
|
2126
|
-
}
|
|
2127
|
-
buildSsrRouters();
|
|
2128
|
-
const title = config.title ?? "emroute";
|
|
2129
|
-
const shellBase = spa === "root" || spa === "only" ? appBase : htmlBase;
|
|
2130
|
-
let shell = await resolveShell(runtime, title, shellBase);
|
|
2131
|
-
if ((await runtime.query("/main.css")).status !== 404) {
|
|
2132
|
-
shell = shell.replace("</head>", ' <link rel="stylesheet" href="/main.css">\n</head>');
|
|
2133
|
-
}
|
|
2134
|
-
async function handleRequest(req) {
|
|
2135
|
-
const url = new URL(req.url);
|
|
2136
|
-
const pathname = url.pathname;
|
|
2137
|
-
const mdPrefix = mdBase + "/";
|
|
2138
|
-
const htmlPrefix = htmlBase + "/";
|
|
2139
|
-
const appPrefix = appBase + "/";
|
|
2140
|
-
if (ssrMdRouter && (pathname.startsWith(mdPrefix) || pathname === mdBase)) {
|
|
2141
|
-
const routePath = pathname === mdBase ? "/" : pathname.slice(mdBase.length);
|
|
2142
|
-
if (routePath.length > 1 && routePath.endsWith("/")) {
|
|
2143
|
-
const canonical = mdBase + routePath.slice(0, -1) + (url.search || "");
|
|
2144
|
-
return Response.redirect(new URL(canonical, url.origin), 301);
|
|
2145
|
-
}
|
|
2146
|
-
try {
|
|
2147
|
-
const routeUrl = new URL(routePath + url.search, url.origin);
|
|
2148
|
-
const { content, status, redirect } = await ssrMdRouter.render(routeUrl, req.signal);
|
|
2149
|
-
if (redirect) {
|
|
2150
|
-
const target = redirect.startsWith("/") ? mdBase + redirect : redirect;
|
|
2151
|
-
return Response.redirect(new URL(target, url.origin), status);
|
|
2152
|
-
}
|
|
2153
|
-
return new Response(rewriteMdLinks(content, mdBase, [mdBase, htmlBase]), {
|
|
2154
|
-
status,
|
|
2155
|
-
headers: { "Content-Type": "text/markdown; charset=utf-8; variant=CommonMark" }
|
|
2156
|
-
});
|
|
2157
|
-
} catch (e) {
|
|
2158
|
-
console.error(`[emroute] Error rendering ${pathname}:`, e);
|
|
2159
|
-
return new Response("Internal Server Error", { status: 500 });
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
if (ssrHtmlRouter && (pathname.startsWith(htmlPrefix) || pathname === htmlBase)) {
|
|
2163
|
-
const routePath = pathname === htmlBase ? "/" : pathname.slice(htmlBase.length);
|
|
2164
|
-
if (routePath.length > 1 && routePath.endsWith("/")) {
|
|
2165
|
-
const canonical = htmlBase + routePath.slice(0, -1) + (url.search || "");
|
|
2166
|
-
return Response.redirect(new URL(canonical, url.origin), 301);
|
|
2167
|
-
}
|
|
2168
|
-
try {
|
|
2169
|
-
const routeUrl = new URL(routePath + url.search, url.origin);
|
|
2170
|
-
const result = await ssrHtmlRouter.render(routeUrl, req.signal);
|
|
2171
|
-
if (result.redirect) {
|
|
2172
|
-
const target = result.redirect.startsWith("/") ? htmlBase + result.redirect : result.redirect;
|
|
2173
|
-
return Response.redirect(new URL(target, url.origin), result.status);
|
|
2174
|
-
}
|
|
2175
|
-
const ssrTitle = result.title ?? title;
|
|
2176
|
-
const html = injectSsrContent(shell, result.content, ssrTitle, pathname);
|
|
2177
|
-
return new Response(html, {
|
|
2178
|
-
status: result.status,
|
|
2179
|
-
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
2180
|
-
});
|
|
2181
|
-
} catch (e) {
|
|
2182
|
-
console.error(`[emroute] Error rendering ${pathname}:`, e);
|
|
2183
|
-
return new Response("Internal Server Error", { status: 500 });
|
|
2184
|
-
}
|
|
2185
|
-
}
|
|
2186
|
-
if (pathname.startsWith(appPrefix) || pathname === appBase) {
|
|
2187
|
-
return new Response(shell, {
|
|
2188
|
-
status: 200,
|
|
2189
|
-
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
2190
|
-
});
|
|
2191
|
-
}
|
|
2192
|
-
if (pathname.startsWith(htmlPrefix) || pathname === htmlBase || pathname.startsWith(mdPrefix) || pathname === mdBase) {
|
|
2193
|
-
return new Response(shell, {
|
|
2194
|
-
status: 200,
|
|
2195
|
-
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
2196
|
-
});
|
|
2197
|
-
}
|
|
2198
|
-
const lastSegment = pathname.split("/").pop() ?? "";
|
|
2199
|
-
if (lastSegment.includes(".")) {
|
|
2200
|
-
const fileResponse = await runtime.handle(pathname);
|
|
2201
|
-
if (fileResponse.status === 200)
|
|
2202
|
-
return fileResponse;
|
|
2203
|
-
return null;
|
|
2204
|
-
}
|
|
2205
|
-
const base = spa === "root" || spa === "only" ? appBase : htmlBase;
|
|
2206
|
-
const bare = pathname === "/" ? "" : pathname.slice(1).replace(/\/$/, "");
|
|
2207
|
-
return Response.redirect(new URL(`${base}/${bare}`, url.origin), 302);
|
|
2208
|
-
}
|
|
2209
|
-
return {
|
|
2210
|
-
handleRequest,
|
|
2211
|
-
get htmlRouter() {
|
|
2212
|
-
return ssrHtmlRouter;
|
|
2213
|
-
},
|
|
2214
|
-
get mdRouter() {
|
|
2215
|
-
return ssrMdRouter;
|
|
2216
|
-
},
|
|
2217
|
-
get routeTree() {
|
|
2218
|
-
return routeTree;
|
|
2219
|
-
},
|
|
2220
|
-
get widgetEntries() {
|
|
2221
|
-
return discoveredWidgetEntries;
|
|
2222
|
-
},
|
|
2223
|
-
get elementEntries() {
|
|
2224
|
-
return discoveredElementEntries;
|
|
2225
|
-
},
|
|
2226
|
-
get shell() {
|
|
2227
|
-
return shell;
|
|
2228
|
-
}
|
|
2229
|
-
};
|
|
2230
2142
|
}
|
|
2231
2143
|
|
|
2232
2144
|
// dist/runtime/fetch.runtime.js
|
|
2233
|
-
var
|
|
2145
|
+
var __rewriteRelativeImportExtension = function(path, preserveJsx) {
|
|
2234
2146
|
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
2235
2147
|
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m, tsx, d, ext, cm) {
|
|
2236
2148
|
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : d + ext + "." + cm.toLowerCase() + "js";
|
|
@@ -2238,11 +2150,9 @@ var __rewriteRelativeImportExtension2 = function(path, preserveJsx) {
|
|
|
2238
2150
|
}
|
|
2239
2151
|
return path;
|
|
2240
2152
|
};
|
|
2241
|
-
|
|
2153
|
+
|
|
2154
|
+
class FetchRuntime extends Runtime {
|
|
2242
2155
|
origin;
|
|
2243
|
-
/**
|
|
2244
|
-
* @param origin — Server origin, e.g. `'http://localhost:4100'` or `location.origin`.
|
|
2245
|
-
*/
|
|
2246
2156
|
constructor(origin, config = {}) {
|
|
2247
2157
|
super(config);
|
|
2248
2158
|
this.origin = origin.endsWith("/") ? origin.slice(0, -1) : origin;
|
|
@@ -2262,7 +2172,7 @@ var FetchRuntime = class extends Runtime {
|
|
|
2262
2172
|
const response = await fetch(url);
|
|
2263
2173
|
const js = await response.text();
|
|
2264
2174
|
const blob = new Blob([js], { type: "application/javascript" });
|
|
2265
|
-
return import(
|
|
2175
|
+
return import(__rewriteRelativeImportExtension(URL.createObjectURL(blob)));
|
|
2266
2176
|
}
|
|
2267
2177
|
toUrl(resource) {
|
|
2268
2178
|
if (typeof resource === "string")
|
|
@@ -2271,10 +2181,10 @@ var FetchRuntime = class extends Runtime {
|
|
|
2271
2181
|
return `${this.origin}${resource.pathname}${resource.search}`;
|
|
2272
2182
|
return `${this.origin}${new URL(resource.url).pathname}`;
|
|
2273
2183
|
}
|
|
2274
|
-
}
|
|
2184
|
+
}
|
|
2275
2185
|
|
|
2276
|
-
// dist/src/renderer/spa/
|
|
2277
|
-
|
|
2186
|
+
// dist/src/renderer/spa/emroute.app.js
|
|
2187
|
+
class EmrouteApp {
|
|
2278
2188
|
server;
|
|
2279
2189
|
appBase;
|
|
2280
2190
|
slot = null;
|
|
@@ -2294,7 +2204,7 @@ var EmrouteApp = class {
|
|
|
2294
2204
|
console.warn("[EmrouteApp] Navigation API not available");
|
|
2295
2205
|
return;
|
|
2296
2206
|
}
|
|
2297
|
-
this.abortController = new AbortController
|
|
2207
|
+
this.abortController = new AbortController;
|
|
2298
2208
|
const { signal } = this.abortController;
|
|
2299
2209
|
navigation.addEventListener("navigate", (event) => {
|
|
2300
2210
|
if (!event.canIntercept)
|
|
@@ -2350,12 +2260,12 @@ var EmrouteApp = class {
|
|
|
2350
2260
|
return pathname;
|
|
2351
2261
|
}
|
|
2352
2262
|
async handleNavigation(url, signal) {
|
|
2353
|
-
if (!this.slot || !this.server.
|
|
2263
|
+
if (!this.slot || !this.server.htmlRenderer)
|
|
2354
2264
|
return;
|
|
2355
2265
|
const routePath = this.stripAppBase(url.pathname);
|
|
2356
2266
|
const routeUrl = new URL(routePath + url.search, url.origin);
|
|
2357
2267
|
try {
|
|
2358
|
-
const { content, title, redirect } = await this.server.
|
|
2268
|
+
const { content, title, redirect } = await this.server.htmlRenderer.render(routeUrl, signal);
|
|
2359
2269
|
if (signal.aborted)
|
|
2360
2270
|
return;
|
|
2361
2271
|
if (redirect) {
|
|
@@ -2385,7 +2295,7 @@ var EmrouteApp = class {
|
|
|
2385
2295
|
}
|
|
2386
2296
|
}
|
|
2387
2297
|
}
|
|
2388
|
-
}
|
|
2298
|
+
}
|
|
2389
2299
|
async function createEmrouteApp(server, options) {
|
|
2390
2300
|
const g = globalThis;
|
|
2391
2301
|
if (g.__emroute_app) {
|
|
@@ -2410,9 +2320,9 @@ async function bootEmrouteApp(options) {
|
|
|
2410
2320
|
const elementsResponse = await runtime.handle(ELEMENTS_MANIFEST_PATH);
|
|
2411
2321
|
const elementEntries = elementsResponse.ok ? await elementsResponse.json() : [];
|
|
2412
2322
|
const moduleLoaders = buildLazyLoaders(routeTree, widgetEntries, elementEntries, runtime);
|
|
2413
|
-
const widgets = new WidgetRegistry
|
|
2323
|
+
const widgets = new WidgetRegistry;
|
|
2414
2324
|
for (const entry of widgetEntries) {
|
|
2415
|
-
ComponentElement.registerLazy(entry.name,
|
|
2325
|
+
ComponentElement.registerLazy(entry.name, moduleLoaders[entry.modulePath]);
|
|
2416
2326
|
}
|
|
2417
2327
|
for (const entry of elementEntries) {
|
|
2418
2328
|
const loader = moduleLoaders[entry.modulePath];
|
|
@@ -2428,7 +2338,7 @@ async function bootEmrouteApp(options) {
|
|
|
2428
2338
|
}
|
|
2429
2339
|
}
|
|
2430
2340
|
const mdRenderer = MarkdownElement.getConfiguredRenderer();
|
|
2431
|
-
const server = await
|
|
2341
|
+
const server = await Emroute.create({
|
|
2432
2342
|
routeTree,
|
|
2433
2343
|
widgets,
|
|
2434
2344
|
moduleLoaders,
|
|
@@ -2437,7 +2347,7 @@ async function bootEmrouteApp(options) {
|
|
|
2437
2347
|
return createEmrouteApp(server, options);
|
|
2438
2348
|
}
|
|
2439
2349
|
function buildLazyLoaders(tree, widgetEntries, elementEntries, runtime) {
|
|
2440
|
-
const paths =
|
|
2350
|
+
const paths = new Set;
|
|
2441
2351
|
function walk(node) {
|
|
2442
2352
|
const modulePath = node.files?.ts ?? node.files?.js;
|
|
2443
2353
|
if (modulePath)
|
|
@@ -2467,24 +2377,8 @@ function buildLazyLoaders(tree, widgetEntries, elementEntries, runtime) {
|
|
|
2467
2377
|
}
|
|
2468
2378
|
return loaders;
|
|
2469
2379
|
}
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
var WidgetComponent = class extends Component {
|
|
2473
|
-
/**
|
|
2474
|
-
* Render widget as HTML.
|
|
2475
|
-
*
|
|
2476
|
-
* Fallback chain:
|
|
2477
|
-
* 1. html file content from context
|
|
2478
|
-
* 2. md file content wrapped in `<mark-down>`
|
|
2479
|
-
* 3. base Component default (markdown→HTML conversion)
|
|
2480
|
-
*
|
|
2481
|
-
* @example
|
|
2482
|
-
* ```ts
|
|
2483
|
-
* override renderHTML({ data, params }: this['RenderArgs']) {
|
|
2484
|
-
* return `<span>${params.coin}: $${data?.price}</span>`;
|
|
2485
|
-
* }
|
|
2486
|
-
* ```
|
|
2487
|
-
*/
|
|
2380
|
+
// dist/core/component/widget.component.js
|
|
2381
|
+
class WidgetComponent extends Component {
|
|
2488
2382
|
renderHTML(args) {
|
|
2489
2383
|
const files = args.context.files;
|
|
2490
2384
|
const style = files?.css ? `<style>${scopeWidgetCss(files.css, this.name)}</style>
|
|
@@ -2500,20 +2394,6 @@ var WidgetComponent = class extends Component {
|
|
|
2500
2394
|
}
|
|
2501
2395
|
return super.renderHTML(args);
|
|
2502
2396
|
}
|
|
2503
|
-
/**
|
|
2504
|
-
* Render widget as Markdown.
|
|
2505
|
-
*
|
|
2506
|
-
* Fallback chain:
|
|
2507
|
-
* 1. md file content from context
|
|
2508
|
-
* 2. empty string
|
|
2509
|
-
*
|
|
2510
|
-
* @example
|
|
2511
|
-
* ```ts
|
|
2512
|
-
* override renderMarkdown({ data, params }: this['RenderArgs']) {
|
|
2513
|
-
* return `**${params.coin}**: $${data?.price}`;
|
|
2514
|
-
* }
|
|
2515
|
-
* ```
|
|
2516
|
-
*/
|
|
2517
2397
|
renderMarkdown(args) {
|
|
2518
2398
|
const files = args.context.files;
|
|
2519
2399
|
if (files?.md) {
|
|
@@ -2521,12 +2401,9 @@ var WidgetComponent = class extends Component {
|
|
|
2521
2401
|
}
|
|
2522
2402
|
return "";
|
|
2523
2403
|
}
|
|
2524
|
-
}
|
|
2525
|
-
|
|
2404
|
+
}
|
|
2526
2405
|
// dist/src/overlay/overlay.css.js
|
|
2527
|
-
var overlayCSS =
|
|
2528
|
-
/* css */
|
|
2529
|
-
`
|
|
2406
|
+
var overlayCSS = `
|
|
2530
2407
|
:root {
|
|
2531
2408
|
--overlay-backdrop: oklch(0% 0 0 / 0.5);
|
|
2532
2409
|
--overlay-surface: oklch(100% 0 0);
|
|
@@ -2687,8 +2564,7 @@ dialog[data-overlay-modal][data-dismissing]::backdrop {
|
|
|
2687
2564
|
opacity: 0;
|
|
2688
2565
|
scale: 0.95;
|
|
2689
2566
|
}
|
|
2690
|
-
|
|
2691
|
-
);
|
|
2567
|
+
`;
|
|
2692
2568
|
|
|
2693
2569
|
// dist/src/overlay/overlay.service.js
|
|
2694
2570
|
var ANIMATION_SAFETY_TIMEOUT = 300;
|
|
@@ -2730,7 +2606,7 @@ function createOverlayService() {
|
|
|
2730
2606
|
document.body.appendChild(dialog);
|
|
2731
2607
|
dialog.addEventListener("click", (e) => {
|
|
2732
2608
|
if (e.target === dialog) {
|
|
2733
|
-
closeModal(
|
|
2609
|
+
closeModal(undefined);
|
|
2734
2610
|
}
|
|
2735
2611
|
});
|
|
2736
2612
|
return dialog;
|
|
@@ -2761,12 +2637,12 @@ function createOverlayService() {
|
|
|
2761
2637
|
if (d.open) {
|
|
2762
2638
|
d.close();
|
|
2763
2639
|
if (modalResolve) {
|
|
2764
|
-
modalResolve(
|
|
2640
|
+
modalResolve(undefined);
|
|
2765
2641
|
modalResolve = null;
|
|
2766
2642
|
}
|
|
2767
2643
|
if (modalOnClose) {
|
|
2768
2644
|
modalOnClose();
|
|
2769
|
-
modalOnClose =
|
|
2645
|
+
modalOnClose = undefined;
|
|
2770
2646
|
}
|
|
2771
2647
|
}
|
|
2772
2648
|
d.innerHTML = "";
|
|
@@ -2784,7 +2660,7 @@ function createOverlayService() {
|
|
|
2784
2660
|
const onClose = modalOnClose;
|
|
2785
2661
|
const dialogRef = dialog;
|
|
2786
2662
|
modalResolve = null;
|
|
2787
|
-
modalOnClose =
|
|
2663
|
+
modalOnClose = undefined;
|
|
2788
2664
|
animateDismiss(dialogRef, () => {
|
|
2789
2665
|
if (dialogRef && dialogRef.open) {
|
|
2790
2666
|
dialogRef.close();
|
|
@@ -2830,8 +2706,7 @@ function createOverlayService() {
|
|
|
2830
2706
|
cleanupPopoverAnchorObserver();
|
|
2831
2707
|
try {
|
|
2832
2708
|
el.hidePopover();
|
|
2833
|
-
} catch {
|
|
2834
|
-
}
|
|
2709
|
+
} catch {}
|
|
2835
2710
|
el.removeAttribute("data-dismissing");
|
|
2836
2711
|
el.innerHTML = "";
|
|
2837
2712
|
options.render(el);
|
|
@@ -2870,8 +2745,7 @@ function createOverlayService() {
|
|
|
2870
2745
|
return;
|
|
2871
2746
|
try {
|
|
2872
2747
|
popoverEl.hidePopover();
|
|
2873
|
-
} catch {
|
|
2874
|
-
}
|
|
2748
|
+
} catch {}
|
|
2875
2749
|
popoverEl.removeAttribute("data-dismissing");
|
|
2876
2750
|
}
|
|
2877
2751
|
function cleanupPopoverAnchorObserver() {
|
|
@@ -2895,8 +2769,7 @@ function createOverlayService() {
|
|
|
2895
2769
|
animateDismiss(popoverEl, () => {
|
|
2896
2770
|
try {
|
|
2897
2771
|
popoverEl.hidePopover();
|
|
2898
|
-
} catch {
|
|
2899
|
-
}
|
|
2772
|
+
} catch {}
|
|
2900
2773
|
});
|
|
2901
2774
|
}
|
|
2902
2775
|
function dismissAll() {
|
|
@@ -2904,11 +2777,11 @@ function createOverlayService() {
|
|
|
2904
2777
|
const resolve = modalResolve;
|
|
2905
2778
|
const onClose = modalOnClose;
|
|
2906
2779
|
modalResolve = null;
|
|
2907
|
-
modalOnClose =
|
|
2780
|
+
modalOnClose = undefined;
|
|
2908
2781
|
dialog.removeAttribute("data-dismissing");
|
|
2909
2782
|
dialog.close();
|
|
2910
2783
|
if (resolve)
|
|
2911
|
-
resolve(
|
|
2784
|
+
resolve(undefined);
|
|
2912
2785
|
if (onClose)
|
|
2913
2786
|
onClose();
|
|
2914
2787
|
}
|
|
@@ -2922,8 +2795,7 @@ function createOverlayService() {
|
|
|
2922
2795
|
for (const el of document.querySelectorAll(":popover-open")) {
|
|
2923
2796
|
el.hidePopover();
|
|
2924
2797
|
}
|
|
2925
|
-
} catch {
|
|
2926
|
-
}
|
|
2798
|
+
} catch {}
|
|
2927
2799
|
for (const el of document.querySelectorAll("dialog[open]")) {
|
|
2928
2800
|
if (el !== dialog)
|
|
2929
2801
|
el.close();
|
|
@@ -2938,9 +2810,8 @@ function createOverlayService() {
|
|
|
2938
2810
|
dismissAll
|
|
2939
2811
|
};
|
|
2940
2812
|
}
|
|
2941
|
-
|
|
2942
2813
|
// dist/src/widget/page-title.widget.js
|
|
2943
|
-
|
|
2814
|
+
class PageTitleWidget extends WidgetComponent {
|
|
2944
2815
|
name = "page-title";
|
|
2945
2816
|
getData(args) {
|
|
2946
2817
|
return Promise.resolve({ title: args.params.title });
|
|
@@ -2959,14 +2830,14 @@ var PageTitleWidget = class extends WidgetComponent {
|
|
|
2959
2830
|
if (!params.title || typeof params.title !== "string") {
|
|
2960
2831
|
return 'page-title widget requires a "title" string param';
|
|
2961
2832
|
}
|
|
2962
|
-
return
|
|
2833
|
+
return;
|
|
2963
2834
|
}
|
|
2964
|
-
}
|
|
2965
|
-
|
|
2835
|
+
}
|
|
2966
2836
|
// dist/src/widget/breadcrumb.widget.js
|
|
2967
|
-
var DEFAULT_HTML_SEPARATOR = "
|
|
2837
|
+
var DEFAULT_HTML_SEPARATOR = " › ";
|
|
2968
2838
|
var DEFAULT_MD_SEPARATOR = " > ";
|
|
2969
|
-
|
|
2839
|
+
|
|
2840
|
+
class BreadcrumbWidget extends WidgetComponent {
|
|
2970
2841
|
name = "breadcrumb";
|
|
2971
2842
|
getData(args) {
|
|
2972
2843
|
const pathname = args.context.pathname || "/";
|
|
@@ -3004,8 +2875,7 @@ var BreadcrumbWidget = class extends WidgetComponent {
|
|
|
3004
2875
|
const sep = args.params.separator ?? DEFAULT_MD_SEPARATOR;
|
|
3005
2876
|
return args.data.segments.map((seg, i, arr) => i === arr.length - 1 ? `**${seg.label}**` : `[${seg.label}](${seg.href})`).join(sep);
|
|
3006
2877
|
}
|
|
3007
|
-
}
|
|
3008
|
-
|
|
2878
|
+
}
|
|
3009
2879
|
// dist/src/renderer/spa/mod.js
|
|
3010
2880
|
if (globalThis.customElements) {
|
|
3011
2881
|
if (!customElements.get("router-slot"))
|
|
@@ -3014,25 +2884,27 @@ if (globalThis.customElements) {
|
|
|
3014
2884
|
customElements.define("mark-down", MarkdownElement);
|
|
3015
2885
|
}
|
|
3016
2886
|
export {
|
|
3017
|
-
|
|
3018
|
-
Component,
|
|
3019
|
-
ComponentElement,
|
|
3020
|
-
DEFAULT_BASE_PATH,
|
|
3021
|
-
EmrouteApp,
|
|
3022
|
-
FetchRuntime,
|
|
3023
|
-
MarkdownElement,
|
|
3024
|
-
PageComponent,
|
|
3025
|
-
PageTitleWidget,
|
|
3026
|
-
RouteTrie,
|
|
3027
|
-
RouterSlot,
|
|
3028
|
-
WidgetComponent,
|
|
3029
|
-
WidgetRegistry,
|
|
3030
|
-
bootEmrouteApp,
|
|
3031
|
-
createEmrouteApp,
|
|
3032
|
-
createEmrouteServer,
|
|
3033
|
-
createOverlayService,
|
|
3034
|
-
escapeHtml,
|
|
2887
|
+
setLogger,
|
|
3035
2888
|
scopeWidgetCss,
|
|
3036
|
-
|
|
2889
|
+
escapeHtml,
|
|
2890
|
+
createOverlayService,
|
|
2891
|
+
createEmrouteApp,
|
|
2892
|
+
bootEmrouteApp,
|
|
2893
|
+
WidgetRegistry,
|
|
2894
|
+
WidgetComponent,
|
|
2895
|
+
RouterSlot,
|
|
2896
|
+
RouteTrie,
|
|
2897
|
+
PageTitleWidget,
|
|
2898
|
+
PageComponent,
|
|
2899
|
+
MarkdownElement,
|
|
2900
|
+
FetchRuntime,
|
|
2901
|
+
EmrouteApp,
|
|
2902
|
+
Emroute,
|
|
2903
|
+
DEFAULT_BASE_PATH,
|
|
2904
|
+
ComponentElement,
|
|
2905
|
+
Component,
|
|
2906
|
+
BreadcrumbWidget
|
|
3037
2907
|
};
|
|
2908
|
+
|
|
2909
|
+
//# debugId=48A0CD62710AC7C064756E2164756E21
|
|
3038
2910
|
//# sourceMappingURL=emroute.js.map
|