@emkodev/emroute 1.7.2 → 1.8.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +19 -45
- package/{src/renderer/ssr → core/renderer}/md.renderer.ts +21 -40
- package/{src/renderer/ssr → core/renderer}/ssr.renderer.ts +42 -53
- 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 +346 -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 +0 -2
- package/{src/route → core/util}/route-tree.util.ts +0 -2
- package/{src → core}/util/widget-resolve.util.ts +15 -45
- package/{src → core}/widget/widget.parser.ts +0 -21
- package/core/widget/widget.registry.ts +24 -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 +12 -33
- 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 +14 -31
- package/dist/core/renderer/md.renderer.js.map +1 -0
- package/dist/{src/renderer/ssr → core/renderer}/ssr.renderer.d.ts +11 -17
- package/dist/{src/renderer/ssr → core/renderer}/ssr.renderer.js +34 -36
- 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 +261 -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 +32 -0
- package/dist/{src → core}/util/widget-resolve.util.js +11 -41
- 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 +0 -21
- package/dist/core/widget/widget.parser.js.map +1 -0
- package/dist/core/widget/widget.registry.d.ts +13 -0
- package/dist/core/widget/widget.registry.js +19 -0
- package/dist/core/widget/widget.registry.js.map +1 -0
- package/dist/emroute.js +1137 -1151
- package/dist/emroute.js.map +36 -5
- package/dist/runtime/abstract.runtime.d.ts +50 -5
- package/dist/runtime/abstract.runtime.js +446 -6
- 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 +18 -2
- 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 +11 -1
- 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 +18 -2
- 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 +45 -36
- 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 -341
- 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 -68
- 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 +4 -7
- package/dist/src/element/component.element.js +23 -22
- 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 +4 -3
- package/dist/src/element/markdown.element.js.map +1 -1
- package/dist/src/index.d.ts +15 -13
- 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 +34 -12
- package/dist/src/renderer/spa/thin-client.js.map +1 -1
- package/dist/src/route/route.core.d.ts +3 -3
- package/dist/src/route/route.core.js +21 -7
- package/dist/src/route/route.core.js.map +1 -1
- 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 +483 -8
- package/runtime/bun/fs/bun-fs.runtime.ts +17 -1
- package/runtime/bun/sqlite/bun-sqlite.runtime.ts +11 -0
- package/runtime/fetch.runtime.ts +3 -3
- package/runtime/sitemap.generator.ts +2 -2
- package/runtime/universal/fs/universal-fs.runtime.ts +17 -1
- package/server/build.util.ts +53 -47
- package/server/codegen.util.ts +1 -1
- package/server/emroute.server.ts +12 -412
- package/src/element/component.element.ts +24 -31
- package/src/element/markdown.element.ts +5 -4
- package/src/index.ts +22 -18
- package/src/renderer/spa/{thin-client.ts → emroute.app.ts} +46 -22
- 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/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 -97
- package/src/component/abstract.component.ts +0 -231
- package/src/component/widget.component.ts +0 -85
- package/src/route/route.core.ts +0 -362
- package/src/route/route.trie.ts +0 -265
- 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
|
@@ -5,34 +5,37 @@
|
|
|
5
5
|
* Handles:
|
|
6
6
|
* - SSR hydration (ssr attribute)
|
|
7
7
|
* - Client-side data fetching with AbortSignal
|
|
8
|
-
* - Companion file loading (html, md, css)
|
|
8
|
+
* - Companion file loading (html, md, css)
|
|
9
9
|
* - Loading/error states
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} from '../component/abstract.component.ts';
|
|
17
|
-
import { HTMLElementBase, LAZY_ATTR, SSR_ATTR } from '../util/html.util.ts';
|
|
12
|
+
import type { Component } from '../../core/component/abstract.component.ts';
|
|
13
|
+
import type { ComponentContext, ContextProvider } from '../../core/type/component.type.ts';
|
|
14
|
+
import { HTMLElementBase } from '../util/html.util.ts';
|
|
15
|
+
import { LAZY_ATTR, SSR_ATTR } from '../../core/util/html.util.ts';
|
|
18
16
|
|
|
19
17
|
type ComponentState = 'idle' | 'loading' | 'ready' | 'error';
|
|
20
18
|
|
|
19
|
+
/** Strip keys with undefined values — returns the filtered object, or undefined if all values are undefined. */
|
|
20
|
+
function filterUndefined<T extends Record<string, unknown>>(obj: T): { [K in keyof T as T[K] extends undefined ? never : K]: NonNullable<T[K]> } | undefined {
|
|
21
|
+
const result: Record<string, unknown> = {};
|
|
22
|
+
let hasValue = false;
|
|
23
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
24
|
+
if (v !== undefined) { result[k] = v; hasValue = true; }
|
|
25
|
+
}
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
27
|
+
return hasValue ? result as any : undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
type WidgetFiles = { html?: string; md?: string; css?: string };
|
|
22
31
|
|
|
23
32
|
/**
|
|
24
33
|
* Custom element that renders a Component in the browser.
|
|
25
34
|
*/
|
|
26
35
|
export class ComponentElement<TParams, TData> extends HTMLElementBase {
|
|
27
|
-
/** Shared file content cache — deduplicates fetches across all widget instances. */
|
|
28
|
-
private static fileCache = new Map<string, Promise<string | undefined>>();
|
|
29
|
-
|
|
30
36
|
/** Lazy module loaders keyed by tag name — set by registerLazy(). */
|
|
31
37
|
private static lazyLoaders = new Map<string, () => Promise<unknown>>();
|
|
32
38
|
|
|
33
|
-
/** Cached module promises for lazy-loaded widgets — avoids re-fetching. */
|
|
34
|
-
private static lazyModules = new Map<string, Promise<unknown>>();
|
|
35
|
-
|
|
36
39
|
/** App-level context provider set once during router initialization. */
|
|
37
40
|
private static extendContext: ContextProvider | undefined;
|
|
38
41
|
|
|
@@ -42,7 +45,7 @@ export class ComponentElement<TParams, TData> extends HTMLElementBase {
|
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
private component: Component<TParams, TData>;
|
|
45
|
-
private effectiveFiles?: WidgetFiles;
|
|
48
|
+
private effectiveFiles?: WidgetFiles | undefined;
|
|
46
49
|
private params: TParams | null = null;
|
|
47
50
|
private data: TData | null = null;
|
|
48
51
|
private context!: ComponentContext;
|
|
@@ -171,12 +174,7 @@ export class ComponentElement<TParams, TData> extends HTMLElementBase {
|
|
|
171
174
|
const lazyLoader = ComponentElement.lazyLoaders.get(tagName);
|
|
172
175
|
if (lazyLoader) {
|
|
173
176
|
try {
|
|
174
|
-
|
|
175
|
-
if (!modulePromise) {
|
|
176
|
-
modulePromise = lazyLoader();
|
|
177
|
-
ComponentElement.lazyModules.set(tagName, modulePromise);
|
|
178
|
-
}
|
|
179
|
-
const mod = await modulePromise as Record<string, unknown>;
|
|
177
|
+
const mod = await lazyLoader() as Record<string, unknown>;
|
|
180
178
|
for (const exp of Object.values(mod)) {
|
|
181
179
|
if (exp && typeof exp === 'object' && 'getData' in exp) {
|
|
182
180
|
const WidgetClass = exp.constructor as new () => Component<TParams, TData>;
|
|
@@ -231,12 +229,13 @@ export class ComponentElement<TParams, TData> extends HTMLElementBase {
|
|
|
231
229
|
if (signal.aborted) return;
|
|
232
230
|
|
|
233
231
|
const currentUrl = globalThis.location ? new URL(location.href) : new URL('http://localhost/');
|
|
232
|
+
const filteredFiles = filterUndefined(files);
|
|
234
233
|
const base: ComponentContext = {
|
|
235
234
|
url: currentUrl,
|
|
236
235
|
pathname: currentUrl.pathname,
|
|
237
236
|
searchParams: currentUrl.searchParams,
|
|
238
237
|
params: this.params ?? {},
|
|
239
|
-
|
|
238
|
+
...(filteredFiles ? { files: filteredFiles } : {}),
|
|
240
239
|
};
|
|
241
240
|
this.context = ComponentElement.extendContext ? ComponentElement.extendContext(base) : base;
|
|
242
241
|
|
|
@@ -316,24 +315,18 @@ export class ComponentElement<TParams, TData> extends HTMLElementBase {
|
|
|
316
315
|
}
|
|
317
316
|
|
|
318
317
|
/**
|
|
319
|
-
* Fetch a single file by path
|
|
318
|
+
* Fetch a single file by path.
|
|
320
319
|
* Absolute URLs (http/https) pass through; relative paths get '/' prefix.
|
|
321
320
|
*/
|
|
322
321
|
private static loadFile(path: string): Promise<string | undefined> {
|
|
323
|
-
const cached = ComponentElement.fileCache.get(path);
|
|
324
|
-
if (cached) return cached;
|
|
325
|
-
|
|
326
322
|
const url = path.startsWith('http://') || path.startsWith('https://')
|
|
327
323
|
? path
|
|
328
324
|
: (path.startsWith('/') ? path : '/' + path);
|
|
329
325
|
|
|
330
|
-
|
|
326
|
+
return fetch(url).then(
|
|
331
327
|
(res) => res.ok ? res.text() : undefined,
|
|
332
328
|
() => undefined,
|
|
333
329
|
);
|
|
334
|
-
|
|
335
|
-
ComponentElement.fileCache.set(path, promise);
|
|
336
|
-
return promise;
|
|
337
330
|
}
|
|
338
331
|
|
|
339
332
|
/**
|
|
@@ -350,7 +343,7 @@ export class ComponentElement<TParams, TData> extends HTMLElementBase {
|
|
|
350
343
|
filePaths.css ? ComponentElement.loadFile(filePaths.css) : undefined,
|
|
351
344
|
]);
|
|
352
345
|
|
|
353
|
-
return { html, md, css };
|
|
346
|
+
return filterUndefined({ html, md, css }) ?? {};
|
|
354
347
|
}
|
|
355
348
|
|
|
356
349
|
private async loadData(): Promise<void> {
|
|
@@ -364,7 +357,7 @@ export class ComponentElement<TParams, TData> extends HTMLElementBase {
|
|
|
364
357
|
try {
|
|
365
358
|
const promise = this.component.getData({
|
|
366
359
|
params: this.params,
|
|
367
|
-
signal,
|
|
360
|
+
...(signal ? { signal } : {}),
|
|
368
361
|
context: this.context,
|
|
369
362
|
});
|
|
370
363
|
this.dataPromise = promise;
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* - Source attribute: <mark-down src="/path/to.md"></mark-down>
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
10
|
+
import { HTMLElementBase } from '../util/html.util.ts';
|
|
11
|
+
import { escapeHtml } from '../../core/util/html.util.ts';
|
|
12
|
+
import type { MarkdownRenderer } from '../../core/type/markdown.type.ts';
|
|
12
13
|
|
|
13
14
|
export class MarkdownElement extends HTMLElementBase {
|
|
14
15
|
private static renderer: MarkdownRenderer | null = null;
|
|
@@ -30,7 +31,7 @@ export class MarkdownElement extends HTMLElementBase {
|
|
|
30
31
|
MarkdownElement.rendererInitPromise = renderer.init ? renderer.init() : null;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
/** Get the current renderer (if set). Used by bootEmrouteApp to pass through to
|
|
34
|
+
/** Get the current renderer (if set). Used by bootEmrouteApp to pass through to Emroute.create(). */
|
|
34
35
|
static getConfiguredRenderer(): MarkdownRenderer | null {
|
|
35
36
|
return MarkdownElement.renderer;
|
|
36
37
|
}
|
|
@@ -80,7 +81,7 @@ export class MarkdownElement extends HTMLElementBase {
|
|
|
80
81
|
const signal = this.abortController?.signal;
|
|
81
82
|
|
|
82
83
|
try {
|
|
83
|
-
const response = await fetch(src, { signal });
|
|
84
|
+
const response = await fetch(src, signal ? { signal } : {});
|
|
84
85
|
|
|
85
86
|
if (!response.ok) {
|
|
86
87
|
throw new Error(`Failed to fetch ${src}: ${response.status}`);
|
package/src/index.ts
CHANGED
|
@@ -27,38 +27,42 @@ export type {
|
|
|
27
27
|
RouterEventListener,
|
|
28
28
|
RouterEventType,
|
|
29
29
|
RouterState,
|
|
30
|
-
} from '
|
|
30
|
+
} from '../core/type/route.type.ts';
|
|
31
31
|
|
|
32
|
-
export type { RouteNode } from '
|
|
33
|
-
export type { RouteResolver, ResolvedRoute } from '
|
|
34
|
-
export { RouteTrie } from '
|
|
32
|
+
export type { RouteNode } from '../core/type/route-tree.type.ts';
|
|
33
|
+
export type { RouteResolver, ResolvedRoute } from '../core/router/route.resolver.ts';
|
|
34
|
+
export { RouteTrie } from '../core/router/route.trie.ts';
|
|
35
35
|
|
|
36
36
|
export type {
|
|
37
37
|
ParsedWidgetBlock,
|
|
38
38
|
SpaMode,
|
|
39
39
|
WidgetManifestEntry,
|
|
40
40
|
WidgetsManifest,
|
|
41
|
-
} from '
|
|
41
|
+
} from '../core/type/widget.type.ts';
|
|
42
42
|
|
|
43
|
-
export type {
|
|
44
|
-
export
|
|
43
|
+
export type { ElementManifestEntry } from '../core/type/element.type.ts';
|
|
44
|
+
export type { MarkdownRenderer } from '../core/type/markdown.type.ts';
|
|
45
|
+
export { setLogger, type Logger } from '../core/type/logger.type.ts';
|
|
45
46
|
|
|
46
47
|
// Components
|
|
47
48
|
export {
|
|
48
49
|
Component,
|
|
49
|
-
|
|
50
|
-
type ComponentManifestEntry,
|
|
51
|
-
type ContextProvider,
|
|
52
|
-
type FileContents,
|
|
53
|
-
type RenderContext,
|
|
54
|
-
} from './component/abstract.component.ts';
|
|
50
|
+
} from '../core/component/abstract.component.ts';
|
|
55
51
|
|
|
56
|
-
export {
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
export type {
|
|
53
|
+
ComponentContext,
|
|
54
|
+
ComponentManifestEntry,
|
|
55
|
+
ContextProvider,
|
|
56
|
+
FileContents,
|
|
57
|
+
RenderContext,
|
|
58
|
+
} from '../core/type/component.type.ts';
|
|
59
|
+
|
|
60
|
+
export { PageComponent } from '../core/component/page.component.ts';
|
|
61
|
+
export { WidgetComponent } from '../core/component/widget.component.ts';
|
|
62
|
+
export { WidgetRegistry } from '../core/widget/widget.registry.ts';
|
|
59
63
|
|
|
60
64
|
// Route config
|
|
61
|
-
export { type BasePath, DEFAULT_BASE_PATH } from '
|
|
65
|
+
export { type BasePath, DEFAULT_BASE_PATH } from '../core/server/emroute.server.ts';
|
|
62
66
|
|
|
63
67
|
// Utils
|
|
64
|
-
export { escapeHtml, scopeWidgetCss } from '
|
|
68
|
+
export { escapeHtml, scopeWidgetCss } from '../core/util/html.util.ts';
|
|
@@ -3,37 +3,37 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Emroute App
|
|
5
5
|
*
|
|
6
|
-
* Browser entry point for `/app/` routes. Wraps an
|
|
6
|
+
* Browser entry point for `/app/` routes. Wraps an Emroute instance
|
|
7
7
|
* (same server, same pipeline) with Navigation API glue that intercepts
|
|
8
8
|
* link clicks, calls `htmlRouter.render()`, and injects the result.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import
|
|
12
|
-
import { createEmrouteServer } from '../../../server/emroute.server.ts';
|
|
11
|
+
import { Emroute } from '../../../core/server/emroute.server.ts';
|
|
13
12
|
import { FetchRuntime } from '../../../runtime/fetch.runtime.ts';
|
|
14
|
-
import { ROUTES_MANIFEST_PATH, WIDGETS_MANIFEST_PATH } from '../../../runtime/abstract.runtime.ts';
|
|
15
|
-
import type { RouteNode } from '
|
|
16
|
-
import type { NavigateOptions } from '
|
|
17
|
-
import type { WidgetManifestEntry } from '
|
|
18
|
-
import
|
|
19
|
-
import {
|
|
13
|
+
import { ROUTES_MANIFEST_PATH, WIDGETS_MANIFEST_PATH, ELEMENTS_MANIFEST_PATH } from '../../../core/runtime/abstract.runtime.ts';
|
|
14
|
+
import type { RouteNode } from '../../../core/type/route-tree.type.ts';
|
|
15
|
+
import type { NavigateOptions } from '../../../core/type/route.type.ts';
|
|
16
|
+
import type { WidgetManifestEntry } from '../../../core/type/widget.type.ts';
|
|
17
|
+
import type { ElementManifestEntry } from '../../../core/type/element.type.ts';
|
|
18
|
+
import { type BasePath, DEFAULT_BASE_PATH } from '../../../core/server/emroute.server.ts';
|
|
19
|
+
import { assertSafeRedirect, escapeHtml } from '../../../core/util/html.util.ts';
|
|
20
20
|
import { ComponentElement } from '../../element/component.element.ts';
|
|
21
21
|
import { MarkdownElement } from '../../element/markdown.element.ts';
|
|
22
|
-
import { WidgetRegistry } from '
|
|
22
|
+
import { WidgetRegistry } from '../../../core/widget/widget.registry.ts';
|
|
23
23
|
|
|
24
24
|
/** Options for `createEmrouteApp`. */
|
|
25
25
|
export interface EmrouteAppOptions {
|
|
26
26
|
basePath?: BasePath;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
/** Browser app — Navigation API wired to an
|
|
29
|
+
/** Browser app — Navigation API wired to an Emroute. */
|
|
30
30
|
export class EmrouteApp {
|
|
31
|
-
private readonly server:
|
|
31
|
+
private readonly server: Emroute;
|
|
32
32
|
private readonly appBase: string;
|
|
33
33
|
private slot: Element | null = null;
|
|
34
34
|
private abortController: AbortController | null = null;
|
|
35
35
|
|
|
36
|
-
constructor(server:
|
|
36
|
+
constructor(server: Emroute, options?: EmrouteAppOptions) {
|
|
37
37
|
const bp = options?.basePath ?? DEFAULT_BASE_PATH;
|
|
38
38
|
this.server = server;
|
|
39
39
|
this.appBase = bp.app;
|
|
@@ -113,13 +113,13 @@ export class EmrouteApp {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
private async handleNavigation(url: URL, signal: AbortSignal): Promise<void> {
|
|
116
|
-
if (!this.slot || !this.server.
|
|
116
|
+
if (!this.slot || !this.server.htmlRenderer) return;
|
|
117
117
|
|
|
118
118
|
const routePath = this.stripAppBase(url.pathname);
|
|
119
119
|
const routeUrl = new URL(routePath + url.search, url.origin);
|
|
120
120
|
|
|
121
121
|
try {
|
|
122
|
-
const { content, title, redirect } = await this.server.
|
|
122
|
+
const { content, title, redirect } = await this.server.htmlRenderer.render(routeUrl, signal);
|
|
123
123
|
|
|
124
124
|
if (signal.aborted) return;
|
|
125
125
|
|
|
@@ -158,7 +158,7 @@ export class EmrouteApp {
|
|
|
158
158
|
* Stored on `globalThis.__emroute_app` for programmatic access.
|
|
159
159
|
*/
|
|
160
160
|
export async function createEmrouteApp(
|
|
161
|
-
server:
|
|
161
|
+
server: Emroute,
|
|
162
162
|
options?: EmrouteAppOptions,
|
|
163
163
|
): Promise<EmrouteApp> {
|
|
164
164
|
const g = globalThis as Record<string, unknown>;
|
|
@@ -207,8 +207,14 @@ export async function bootEmrouteApp(options?: BootOptions): Promise<EmrouteApp>
|
|
|
207
207
|
? await widgetsResponse.json()
|
|
208
208
|
: [];
|
|
209
209
|
|
|
210
|
-
//
|
|
211
|
-
const
|
|
210
|
+
// Fetch element manifest (optional — app may have no custom elements)
|
|
211
|
+
const elementsResponse = await runtime.handle(ELEMENTS_MANIFEST_PATH);
|
|
212
|
+
const elementEntries: ElementManifestEntry[] = elementsResponse.ok
|
|
213
|
+
? await elementsResponse.json()
|
|
214
|
+
: [];
|
|
215
|
+
|
|
216
|
+
// Build lazy module loaders for all route + widget + element modules
|
|
217
|
+
const moduleLoaders = buildLazyLoaders(routeTree, widgetEntries, elementEntries, runtime);
|
|
212
218
|
|
|
213
219
|
// Register widgets eagerly (tag defined immediately, module loads on connectedCallback)
|
|
214
220
|
const widgets = new WidgetRegistry();
|
|
@@ -216,24 +222,41 @@ export async function bootEmrouteApp(options?: BootOptions): Promise<EmrouteApp>
|
|
|
216
222
|
ComponentElement.registerLazy(entry.name, entry.files, moduleLoaders[entry.modulePath]);
|
|
217
223
|
}
|
|
218
224
|
|
|
219
|
-
//
|
|
220
|
-
const
|
|
225
|
+
// Register custom elements — import all modules, define when loaded
|
|
226
|
+
for (const entry of elementEntries) {
|
|
227
|
+
const loader = moduleLoaders[entry.modulePath];
|
|
228
|
+
if (loader) {
|
|
229
|
+
loader().then((mod) => {
|
|
230
|
+
const cls = (mod as Record<string, unknown>).default;
|
|
231
|
+
if (typeof cls === 'function' && !customElements.get(entry.tagName)) {
|
|
232
|
+
customElements.define(entry.tagName, cls as CustomElementConstructor);
|
|
233
|
+
}
|
|
234
|
+
}).catch((e) => {
|
|
235
|
+
console.error(`[emroute] Failed to load element ${entry.tagName}:`, e);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Create Emroute instance (same class as SSR)
|
|
241
|
+
const mdRenderer = MarkdownElement.getConfiguredRenderer();
|
|
242
|
+
const server = await Emroute.create({
|
|
221
243
|
routeTree,
|
|
222
244
|
widgets,
|
|
223
245
|
moduleLoaders,
|
|
224
|
-
markdownRenderer:
|
|
246
|
+
...(mdRenderer ? { markdownRenderer: mdRenderer } : {}),
|
|
225
247
|
}, runtime);
|
|
226
248
|
|
|
227
249
|
return createEmrouteApp(server, options);
|
|
228
250
|
}
|
|
229
251
|
|
|
230
252
|
/**
|
|
231
|
-
* Walk the route tree and
|
|
253
|
+
* Walk the route tree, widget entries, and element entries to build a map of
|
|
232
254
|
* `path → () => runtime.loadModule(path)` lazy loaders.
|
|
233
255
|
*/
|
|
234
256
|
function buildLazyLoaders(
|
|
235
257
|
tree: RouteNode,
|
|
236
258
|
widgetEntries: WidgetManifestEntry[],
|
|
259
|
+
elementEntries: ElementManifestEntry[],
|
|
237
260
|
runtime: FetchRuntime,
|
|
238
261
|
): Record<string, () => Promise<unknown>> {
|
|
239
262
|
const paths = new Set<string>();
|
|
@@ -252,6 +275,7 @@ function buildLazyLoaders(
|
|
|
252
275
|
|
|
253
276
|
walk(tree);
|
|
254
277
|
for (const entry of widgetEntries) paths.add(entry.modulePath);
|
|
278
|
+
for (const entry of elementEntries) paths.add(entry.modulePath);
|
|
255
279
|
|
|
256
280
|
const loaders: Record<string, () => Promise<unknown>> = {};
|
|
257
281
|
for (const path of paths) {
|
package/src/renderer/spa/mod.ts
CHANGED
|
@@ -10,22 +10,22 @@
|
|
|
10
10
|
import { RouterSlot } from '../../element/slot.element.ts';
|
|
11
11
|
import { MarkdownElement } from '../../element/markdown.element.ts';
|
|
12
12
|
import { ComponentElement } from '../../element/component.element.ts';
|
|
13
|
-
import { WidgetRegistry } from '
|
|
13
|
+
import { WidgetRegistry } from '../../../core/widget/widget.registry.ts';
|
|
14
14
|
|
|
15
|
-
export { bootEmrouteApp, createEmrouteApp, EmrouteApp, type BootOptions, type EmrouteAppOptions } from './
|
|
15
|
+
export { bootEmrouteApp, createEmrouteApp, EmrouteApp, type BootOptions, type EmrouteAppOptions } from './emroute.app.ts';
|
|
16
16
|
export { ComponentElement, MarkdownElement, RouterSlot, WidgetRegistry };
|
|
17
|
-
export type { SpaMode, WidgetsManifest } from '
|
|
17
|
+
export type { SpaMode, WidgetsManifest } from '../../../core/type/widget.type.ts';
|
|
18
18
|
|
|
19
19
|
// Re-export base classes and types for consumer code (pages, widgets)
|
|
20
|
-
export { PageComponent } from '
|
|
21
|
-
export { WidgetComponent } from '
|
|
22
|
-
export {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
} from '
|
|
20
|
+
export { PageComponent } from '../../../core/component/page.component.ts';
|
|
21
|
+
export { WidgetComponent } from '../../../core/component/widget.component.ts';
|
|
22
|
+
export { Component } from '../../../core/component/abstract.component.ts';
|
|
23
|
+
export type {
|
|
24
|
+
ComponentContext,
|
|
25
|
+
ComponentManifestEntry,
|
|
26
|
+
ContextProvider,
|
|
27
|
+
RenderContext,
|
|
28
|
+
} from '../../../core/type/component.type.ts';
|
|
29
29
|
export type {
|
|
30
30
|
MatchedRoute,
|
|
31
31
|
NavigateOptions,
|
|
@@ -33,13 +33,13 @@ export type {
|
|
|
33
33
|
RouterEvent,
|
|
34
34
|
RouterEventListener,
|
|
35
35
|
RouterEventType,
|
|
36
|
-
} from '
|
|
37
|
-
export type { RouteNode } from '
|
|
38
|
-
export type { RouteResolver, ResolvedRoute } from '
|
|
39
|
-
export { RouteTrie } from '
|
|
40
|
-
export type { MarkdownRenderer } from '
|
|
41
|
-
export { type BasePath, DEFAULT_BASE_PATH } from '
|
|
42
|
-
export { escapeHtml, scopeWidgetCss } from '
|
|
36
|
+
} from '../../../core/type/route.type.ts';
|
|
37
|
+
export type { RouteNode } from '../../../core/type/route-tree.type.ts';
|
|
38
|
+
export type { RouteResolver, ResolvedRoute } from '../../../core/router/route.resolver.ts';
|
|
39
|
+
export { RouteTrie } from '../../../core/router/route.trie.ts';
|
|
40
|
+
export type { MarkdownRenderer } from '../../../core/type/markdown.type.ts';
|
|
41
|
+
export { type BasePath, DEFAULT_BASE_PATH } from '../../../core/server/emroute.server.ts';
|
|
42
|
+
export { escapeHtml, scopeWidgetCss } from '../../../core/util/html.util.ts';
|
|
43
43
|
export type {
|
|
44
44
|
ErrorBoundary,
|
|
45
45
|
RedirectConfig,
|
|
@@ -48,9 +48,9 @@ export type {
|
|
|
48
48
|
RouteFileType,
|
|
49
49
|
RouteInfo,
|
|
50
50
|
RouterState,
|
|
51
|
-
} from '
|
|
52
|
-
export type { ParsedWidgetBlock, WidgetManifestEntry } from '
|
|
53
|
-
export { type Logger
|
|
51
|
+
} from '../../../core/type/route.type.ts';
|
|
52
|
+
export type { ParsedWidgetBlock, WidgetManifestEntry } from '../../../core/type/widget.type.ts';
|
|
53
|
+
export { setLogger, type Logger } from '../../../core/type/logger.type.ts';
|
|
54
54
|
|
|
55
55
|
// Register core custom elements in the browser
|
|
56
56
|
if (globalThis.customElements) {
|
package/src/util/html.util.ts
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* HTML Utilities (Browser Layer)
|
|
3
|
+
*
|
|
4
|
+
* Re-exports pure functions from core/ and provides browser-specific
|
|
5
|
+
* SSR-compatible HTMLElement mock.
|
|
3
6
|
*/
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// Re-export everything from core
|
|
9
|
+
export {
|
|
10
|
+
SSR_ATTR,
|
|
11
|
+
LAZY_ATTR,
|
|
12
|
+
assertSafeRedirect,
|
|
13
|
+
escapeHtml,
|
|
14
|
+
unescapeHtml,
|
|
15
|
+
scopeWidgetCss,
|
|
16
|
+
STATUS_MESSAGES,
|
|
17
|
+
} from '../../core/util/html.util.ts';
|
|
10
18
|
|
|
11
19
|
/**
|
|
12
20
|
* SSR-compatible ShadowRoot mock.
|
|
13
|
-
* Provides a 1-to-1 subset of the browser ShadowRoot API for server-side rendering.
|
|
14
21
|
*/
|
|
15
22
|
class SsrShadowRoot {
|
|
16
23
|
private _innerHTML = '';
|
|
@@ -29,9 +36,7 @@ class SsrShadowRoot {
|
|
|
29
36
|
this._innerHTML = html;
|
|
30
37
|
}
|
|
31
38
|
|
|
32
|
-
append(..._nodes: (Node | string)[]): void {
|
|
33
|
-
// On the server, append is a no-op — SSR content is already serialized via innerHTML.
|
|
34
|
-
}
|
|
39
|
+
append(..._nodes: (Node | string)[]): void {}
|
|
35
40
|
|
|
36
41
|
querySelector(_selector: string): Element | null {
|
|
37
42
|
return null;
|
|
@@ -52,15 +57,11 @@ class SsrShadowRoot {
|
|
|
52
57
|
|
|
53
58
|
/**
|
|
54
59
|
* SSR-compatible HTMLElement mock.
|
|
55
|
-
* Provides a 1-to-1 subset of the browser HTMLElement API for server-side rendering.
|
|
56
|
-
* Methods that require DOM parsing (querySelector, childNodes) return empty results —
|
|
57
|
-
* SSR code should use innerHTML for content, not DOM traversal.
|
|
58
60
|
*/
|
|
59
61
|
class SsrHTMLElement {
|
|
60
62
|
private _innerHTML = '';
|
|
61
63
|
private _shadowRoot: SsrShadowRoot | null = null;
|
|
62
64
|
private _attributes = new Map<string, string>();
|
|
63
|
-
// Accept any CSS property assignment without error
|
|
64
65
|
readonly style = new Proxy({} as CSSStyleDeclaration, {
|
|
65
66
|
set(_target, _prop, _value) {
|
|
66
67
|
return true;
|
|
@@ -128,9 +129,7 @@ class SsrHTMLElement {
|
|
|
128
129
|
return [];
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
append(..._nodes: (Node | string)[]): void {
|
|
132
|
-
// No-op on server — use innerHTML for content
|
|
133
|
-
}
|
|
132
|
+
append(..._nodes: (Node | string)[]): void {}
|
|
134
133
|
|
|
135
134
|
appendChild(node: Node): Node {
|
|
136
135
|
return node;
|
|
@@ -140,47 +139,3 @@ class SsrHTMLElement {
|
|
|
140
139
|
/** Server-safe base class: HTMLElement in browser, SSR mock on server. */
|
|
141
140
|
export const HTMLElementBase = globalThis.HTMLElement ??
|
|
142
141
|
(SsrHTMLElement as unknown as typeof HTMLElement);
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Escape HTML entities for safe display.
|
|
146
|
-
*/
|
|
147
|
-
export function escapeHtml(text: string): string {
|
|
148
|
-
return text
|
|
149
|
-
.replaceAll('&', '&')
|
|
150
|
-
.replaceAll('<', '<')
|
|
151
|
-
.replaceAll('>', '>')
|
|
152
|
-
.replaceAll('"', '"')
|
|
153
|
-
.replaceAll("'", ''')
|
|
154
|
-
.replaceAll('`', '`');
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Unescape HTML entities back to plain text (server-side, no DOM).
|
|
159
|
-
*/
|
|
160
|
-
export function unescapeHtml(text: string): string {
|
|
161
|
-
return text
|
|
162
|
-
.replaceAll('`', '`')
|
|
163
|
-
.replaceAll(''', "'")
|
|
164
|
-
.replaceAll('"', '"')
|
|
165
|
-
.replaceAll('>', '>')
|
|
166
|
-
.replaceAll('<', '<')
|
|
167
|
-
.replaceAll('&', '&');
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Wrap CSS in a `@scope` rule scoped to the widget's custom element tag.
|
|
172
|
-
* Used by `WidgetComponent.renderHTML()` for companion CSS files.
|
|
173
|
-
*/
|
|
174
|
-
export function scopeWidgetCss(css: string, widgetName: string): string {
|
|
175
|
-
return `@scope (widget-${widgetName}) {\n${css}\n}`;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Status code to message mapping.
|
|
180
|
-
*/
|
|
181
|
-
export const STATUS_MESSAGES: Record<number, string> = {
|
|
182
|
-
401: 'Unauthorized',
|
|
183
|
-
403: 'Forbidden',
|
|
184
|
-
404: 'Not Found',
|
|
185
|
-
500: 'Internal Server Error',
|
|
186
|
-
};
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
* ```
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { WidgetComponent } from '
|
|
18
|
-
import { escapeHtml } from '
|
|
19
|
-
import type { ComponentContext } from '
|
|
17
|
+
import { WidgetComponent } from '../../core/component/widget.component.ts';
|
|
18
|
+
import { escapeHtml } from '../../core/util/html.util.ts';
|
|
19
|
+
import type { ComponentContext } from '../../core/type/component.type.ts';
|
|
20
20
|
|
|
21
21
|
const DEFAULT_HTML_SEPARATOR = ' \u203A ';
|
|
22
22
|
const DEFAULT_MD_SEPARATOR = ' > ';
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* <widget-page-title title="About Us"></widget-page-title>
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { WidgetComponent } from '
|
|
11
|
+
import { WidgetComponent } from '../../core/component/widget.component.ts';
|
|
12
12
|
|
|
13
13
|
interface PageTitleParams {
|
|
14
14
|
title: string;
|