@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFile, writeFile, stat, readdir, mkdir } from 'node:fs/promises';
|
|
1
|
+
import { readFile, writeFile, stat, readdir, mkdir, unlink } from 'node:fs/promises';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import {
|
|
4
4
|
CONTENT_TYPES,
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
Runtime,
|
|
9
9
|
type RuntimeConfig,
|
|
10
10
|
WIDGETS_MANIFEST_PATH,
|
|
11
|
+
ELEMENTS_MANIFEST_PATH,
|
|
11
12
|
} from '../../abstract.runtime.ts';
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -37,6 +38,8 @@ export class UniversalFsRuntime extends Runtime {
|
|
|
37
38
|
switch (method) {
|
|
38
39
|
case 'PUT':
|
|
39
40
|
return this.write(path, body);
|
|
41
|
+
case 'DELETE':
|
|
42
|
+
return this.delete(path);
|
|
40
43
|
default:
|
|
41
44
|
return this.read(path);
|
|
42
45
|
}
|
|
@@ -107,6 +110,7 @@ export class UniversalFsRuntime extends Runtime {
|
|
|
107
110
|
const pathname = path.slice(this.root.length);
|
|
108
111
|
if (pathname === ROUTES_MANIFEST_PATH) return this.resolveRoutesManifest();
|
|
109
112
|
if (pathname === WIDGETS_MANIFEST_PATH) return this.resolveWidgetsManifest();
|
|
113
|
+
if (pathname === ELEMENTS_MANIFEST_PATH) return this.resolveElementsManifest();
|
|
110
114
|
return new Response('Not Found', { status: 404 });
|
|
111
115
|
}
|
|
112
116
|
return new Response(`Internal Error: ${error}`, { status: 500 });
|
|
@@ -136,6 +140,18 @@ export class UniversalFsRuntime extends Runtime {
|
|
|
136
140
|
}
|
|
137
141
|
}
|
|
138
142
|
|
|
143
|
+
private async delete(path: string): Promise<Response> {
|
|
144
|
+
try {
|
|
145
|
+
await unlink(path);
|
|
146
|
+
return new Response(null, { status: 204 });
|
|
147
|
+
} catch (error) {
|
|
148
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
149
|
+
return new Response('Not Found', { status: 404 });
|
|
150
|
+
}
|
|
151
|
+
return new Response(`Delete failed: ${error}`, { status: 500 });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
139
155
|
override loadModule(path: string): Promise<unknown> {
|
|
140
156
|
return import(this.root + path);
|
|
141
157
|
}
|
package/server/build.util.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build Utilities
|
|
3
3
|
*
|
|
4
|
-
* Standalone client
|
|
4
|
+
* Standalone client build step — extracted from Runtime so that build is a
|
|
5
5
|
* separate concern from storage. Call `buildClientBundles()` before
|
|
6
|
-
* `
|
|
6
|
+
* `Emroute.create()` to produce emroute.js + app.js.
|
|
7
7
|
*
|
|
8
8
|
* Route tree and widget manifest are fetched as JSON at boot time by
|
|
9
9
|
* `bootEmrouteApp()` — no longer compiled into app.js.
|
|
@@ -20,12 +20,15 @@ import type { Runtime } from '../runtime/abstract.runtime.ts';
|
|
|
20
20
|
import {
|
|
21
21
|
ROUTES_MANIFEST_PATH,
|
|
22
22
|
WIDGETS_MANIFEST_PATH,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
import type {
|
|
23
|
+
ELEMENTS_MANIFEST_PATH,
|
|
24
|
+
} from '../core/runtime/abstract.runtime.ts';
|
|
25
|
+
import type { RouteNode } from '../core/type/route-tree.type.ts';
|
|
26
|
+
import type { WidgetManifestEntry } from '../core/type/widget.type.ts';
|
|
27
|
+
import type { ElementManifestEntry } from '../core/type/element.type.ts';
|
|
26
28
|
import { generateMainTs } from './codegen.util.ts';
|
|
27
|
-
import type { SpaMode } from '../
|
|
29
|
+
import type { SpaMode } from '../core/type/widget.type.ts';
|
|
28
30
|
|
|
31
|
+
/** Package specifiers that map to emroute.js via import map. */
|
|
29
32
|
export const EMROUTE_EXTERNALS = [
|
|
30
33
|
'@emkodev/emroute/spa',
|
|
31
34
|
'@emkodev/emroute/overlay',
|
|
@@ -34,15 +37,12 @@ export const EMROUTE_EXTERNALS = [
|
|
|
34
37
|
'@emkodev/emroute/runtime/fetch',
|
|
35
38
|
] as const;
|
|
36
39
|
|
|
37
|
-
/** esbuild namespace for virtual `emroute:routes` / `emroute:widgets` modules. */
|
|
38
|
-
export const EMROUTE_VIRTUAL_NS = 'emroute';
|
|
39
|
-
|
|
40
40
|
export interface BuildOptions {
|
|
41
41
|
/** Runtime instance to read manifests and source files from. */
|
|
42
42
|
runtime: Runtime;
|
|
43
|
-
/** Filesystem root for
|
|
43
|
+
/** Filesystem root for resolving the pre-built emroute.js bundle. */
|
|
44
44
|
root: string;
|
|
45
|
-
/** SPA mode — skips
|
|
45
|
+
/** SPA mode — skips build when 'none'. */
|
|
46
46
|
spa: SpaMode;
|
|
47
47
|
/** Consumer's SPA entry point (e.g. '/main.ts'). When absent, auto-generates one. */
|
|
48
48
|
entryPoint?: string;
|
|
@@ -53,13 +53,13 @@ export interface BuildOptions {
|
|
|
53
53
|
const DEFAULT_BUNDLE_PATHS = { emroute: '/emroute.js', app: '/app.js' };
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
* Build client
|
|
56
|
+
* Build client assets and write them into the runtime.
|
|
57
57
|
*
|
|
58
58
|
* Produces:
|
|
59
59
|
* - Merged .js modules — each .ts page/widget transpiled with companions inlined
|
|
60
60
|
* - Updated manifests — route tree and widget manifest reference .js paths
|
|
61
|
-
* - emroute.js — pre-built from dist/ (
|
|
62
|
-
* - app.js — consumer entry point (
|
|
61
|
+
* - emroute.js — pre-built from dist/ (copied into runtime)
|
|
62
|
+
* - app.js — consumer entry point (transpiled from .ts, no bundler)
|
|
63
63
|
* - index.html — shell with import map + script tags (if not already present)
|
|
64
64
|
*/
|
|
65
65
|
export async function buildClientBundles(options: BuildOptions): Promise<void> {
|
|
@@ -71,17 +71,14 @@ export async function buildClientBundles(options: BuildOptions): Promise<void> {
|
|
|
71
71
|
// Merge .ts modules → .js with inlined companions, update manifests
|
|
72
72
|
await mergeModules(runtime);
|
|
73
73
|
|
|
74
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
|
-
const esbuild = await loadEsbuild() as any;
|
|
76
|
-
|
|
77
74
|
// Copy pre-built emroute.js from the package dist/
|
|
78
75
|
const consumerRequire = createRequire(root + '/');
|
|
79
76
|
const emrouteJsPath = resolvePrebuiltBundle(consumerRequire);
|
|
80
77
|
const emrouteJs = await readFile(emrouteJsPath);
|
|
81
78
|
await runtime.command(paths.emroute, { body: emrouteJs });
|
|
82
79
|
|
|
83
|
-
// App
|
|
84
|
-
//
|
|
80
|
+
// App entry point — transpile consumer's main.ts (or generate a default one).
|
|
81
|
+
// Imports resolve via the import map in index.html — no bundler needed.
|
|
85
82
|
const ep = entryPoint ?? '/main.ts';
|
|
86
83
|
let source: string | undefined;
|
|
87
84
|
try {
|
|
@@ -92,19 +89,8 @@ export async function buildClientBundles(options: BuildOptions): Promise<void> {
|
|
|
92
89
|
}
|
|
93
90
|
source ??= generateMainTs(spa, '@emkodev/emroute');
|
|
94
91
|
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
write: false,
|
|
98
|
-
format: 'esm' as const,
|
|
99
|
-
platform: 'browser' as const,
|
|
100
|
-
stdin: { contents: source, loader: 'ts', resolveDir: root },
|
|
101
|
-
outfile: paths.app,
|
|
102
|
-
external: [...EMROUTE_EXTERNALS],
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
for (const file of result.outputFiles) {
|
|
106
|
-
await runtime.command(paths.app, { body: file.contents as unknown as BodyInit });
|
|
107
|
-
}
|
|
92
|
+
const appJs = await runtime.transpile(source);
|
|
93
|
+
await runtime.command(paths.app, { body: appJs });
|
|
108
94
|
|
|
109
95
|
// Copy main.css from disk into runtime if it exists (and runtime doesn't have it)
|
|
110
96
|
if ((await runtime.query('/main.css')).status === 404) {
|
|
@@ -116,8 +102,6 @@ export async function buildClientBundles(options: BuildOptions): Promise<void> {
|
|
|
116
102
|
|
|
117
103
|
// Write shell (index.html) if not already present
|
|
118
104
|
await writeShell(runtime, paths);
|
|
119
|
-
|
|
120
|
-
await esbuild.stop();
|
|
121
105
|
}
|
|
122
106
|
|
|
123
107
|
/**
|
|
@@ -146,10 +130,23 @@ async function writeShell(
|
|
|
146
130
|
): Promise<void> {
|
|
147
131
|
if ((await runtime.query('/index.html')).status !== 404) return;
|
|
148
132
|
|
|
133
|
+
// Base emroute imports
|
|
149
134
|
const imports: Record<string, string> = {};
|
|
150
135
|
for (const pkg of EMROUTE_EXTERNALS) {
|
|
151
136
|
imports[pkg] = paths.emroute;
|
|
152
137
|
}
|
|
138
|
+
|
|
139
|
+
// Merge user-provided importmap.json (user entries win on conflict)
|
|
140
|
+
const mapResponse = await runtime.query('/importmap.json');
|
|
141
|
+
if (mapResponse.status !== 404) {
|
|
142
|
+
const userMap = await mapResponse.json() as { imports?: Record<string, string> };
|
|
143
|
+
if (userMap.imports) {
|
|
144
|
+
for (const [key, value] of Object.entries(userMap.imports)) {
|
|
145
|
+
imports[key] = value;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
153
150
|
const importMap = JSON.stringify({ imports }, null, 2);
|
|
154
151
|
|
|
155
152
|
const html = `<!DOCTYPE html>
|
|
@@ -251,11 +248,10 @@ async function mergeModules(runtime: Runtime): Promise<void> {
|
|
|
251
248
|
// Merge route modules
|
|
252
249
|
async function walkRoutes(node: RouteNode): Promise<void> {
|
|
253
250
|
if (node.files?.ts) {
|
|
254
|
-
const companions = {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
};
|
|
251
|
+
const companions: { html?: string; md?: string; css?: string } = {};
|
|
252
|
+
if (node.files.html) companions.html = node.files.html;
|
|
253
|
+
if (node.files.md) companions.md = node.files.md;
|
|
254
|
+
if (node.files.css) companions.css = node.files.css;
|
|
259
255
|
node.files.js = await transpileAndMerge(runtime, node.files.ts, companions);
|
|
260
256
|
delete node.files.ts;
|
|
261
257
|
delete node.files.html;
|
|
@@ -290,6 +286,19 @@ async function mergeModules(runtime: Runtime): Promise<void> {
|
|
|
290
286
|
}
|
|
291
287
|
}
|
|
292
288
|
|
|
289
|
+
// Read element manifest
|
|
290
|
+
const elementsResponse = await runtime.query(ELEMENTS_MANIFEST_PATH);
|
|
291
|
+
const elementEntries: ElementManifestEntry[] = elementsResponse.status !== 404
|
|
292
|
+
? await elementsResponse.json()
|
|
293
|
+
: [];
|
|
294
|
+
|
|
295
|
+
// Merge element modules
|
|
296
|
+
for (const entry of elementEntries) {
|
|
297
|
+
if (entry.modulePath.endsWith('.ts')) {
|
|
298
|
+
entry.modulePath = await transpileAndMerge(runtime, entry.modulePath);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
293
302
|
// Write updated manifests back
|
|
294
303
|
await runtime.command(ROUTES_MANIFEST_PATH, {
|
|
295
304
|
body: JSON.stringify(routeTree),
|
|
@@ -297,12 +306,9 @@ async function mergeModules(runtime: Runtime): Promise<void> {
|
|
|
297
306
|
await runtime.command(WIDGETS_MANIFEST_PATH, {
|
|
298
307
|
body: JSON.stringify(widgetEntries),
|
|
299
308
|
});
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
async function loadEsbuild(): Promise<any> {
|
|
306
|
-
const consumerRequire = createRequire(process.cwd() + '/');
|
|
307
|
-
return consumerRequire('esbuild');
|
|
309
|
+
if (elementEntries.length > 0) {
|
|
310
|
+
await runtime.command(ELEMENTS_MANIFEST_PATH, {
|
|
311
|
+
body: JSON.stringify(elementEntries),
|
|
312
|
+
});
|
|
313
|
+
}
|
|
308
314
|
}
|
package/server/codegen.util.ts
CHANGED
package/server/emroute.server.ts
CHANGED
|
@@ -1,420 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Emroute
|
|
3
|
-
*
|
|
4
|
-
* Runtime-agnostic server that handles SSR rendering, manifest resolution,
|
|
5
|
-
* static file serving, and route matching. Works with any Runtime implementation.
|
|
6
|
-
*
|
|
7
|
-
* Usage (standalone):
|
|
8
|
-
* ```ts
|
|
9
|
-
* import { createEmrouteServer } from '@emkodev/emroute/server';
|
|
10
|
-
* import { BunFsRuntime } from '@emkodev/emroute/runtime/bun/fs';
|
|
11
|
-
*
|
|
12
|
-
* const runtime = new BunFsRuntime('.', { routesDir: '/routes' });
|
|
13
|
-
* const emroute = await createEmrouteServer({ spa: 'root' }, runtime);
|
|
14
|
-
*
|
|
15
|
-
* Bun.serve({ fetch: (req) => emroute.handleRequest(req) ?? new Response('Not Found', { status: 404 }) });
|
|
16
|
-
* ```
|
|
17
|
-
*
|
|
18
|
-
* Usage (composable):
|
|
19
|
-
* ```ts
|
|
20
|
-
* const emroute = await createEmrouteServer(config, runtime);
|
|
21
|
-
*
|
|
22
|
-
* Bun.serve({ async fetch(req) {
|
|
23
|
-
* if (isApiRoute(req)) return handleApi(req);
|
|
24
|
-
* const response = await emroute.handleRequest(req);
|
|
25
|
-
* if (response) return response;
|
|
26
|
-
* return new Response('Not Found', { status: 404 });
|
|
27
|
-
* }});
|
|
28
|
-
* ```
|
|
2
|
+
* Emroute — re-exports from core/server.
|
|
29
3
|
*/
|
|
4
|
+
export { Emroute } from '../core/server/emroute.server.ts';
|
|
30
5
|
|
|
31
|
-
|
|
32
|
-
import { RouteTrie } from '../src/route/route.trie.ts';
|
|
33
|
-
import { SsrHtmlRouter } from '../src/renderer/ssr/html.renderer.ts';
|
|
34
|
-
import { SsrMdRouter } from '../src/renderer/ssr/md.renderer.ts';
|
|
35
|
-
import type { RouteNode } from '../src/type/route-tree.type.ts';
|
|
36
|
-
import type { WidgetManifestEntry } from '../src/type/widget.type.ts';
|
|
37
|
-
import { WidgetRegistry } from '../src/widget/widget.registry.ts';
|
|
38
|
-
import type { WidgetComponent } from '../src/component/widget.component.ts';
|
|
39
|
-
import { escapeHtml } from '../src/util/html.util.ts';
|
|
40
|
-
import { rewriteMdLinks } from '../src/util/md.util.ts';
|
|
41
|
-
import {
|
|
42
|
-
ROUTES_MANIFEST_PATH,
|
|
43
|
-
Runtime,
|
|
44
|
-
WIDGETS_MANIFEST_PATH,
|
|
45
|
-
} from '../runtime/abstract.runtime.ts';
|
|
46
|
-
import type { EmrouteServer, EmrouteServerConfig } from './server-api.type.ts';
|
|
6
|
+
// ── Deprecated aliases ───────────────────────────────────────────────
|
|
47
7
|
|
|
48
|
-
|
|
8
|
+
import { Emroute as _Emroute } from '../core/server/emroute.server.ts';
|
|
9
|
+
import type { Runtime } from '../core/runtime/abstract.runtime.ts';
|
|
49
10
|
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
* Uses `runtime.loadModule()` — each runtime decides how to load modules
|
|
53
|
-
* (filesystem import, SQLite transpile + blob URL, etc.).
|
|
54
|
-
*/
|
|
55
|
-
function createModuleLoaders(
|
|
56
|
-
tree: RouteNode,
|
|
57
|
-
runtime: Runtime,
|
|
58
|
-
): Record<string, () => Promise<unknown>> {
|
|
59
|
-
const paths = new Set<string>();
|
|
60
|
-
|
|
61
|
-
function walk(node: RouteNode): void {
|
|
62
|
-
const modulePath = node.files?.ts ?? node.files?.js;
|
|
63
|
-
if (modulePath) paths.add(modulePath);
|
|
64
|
-
if (node.redirect) paths.add(node.redirect);
|
|
65
|
-
if (node.errorBoundary) paths.add(node.errorBoundary);
|
|
66
|
-
|
|
67
|
-
if (node.children) {
|
|
68
|
-
for (const child of Object.values(node.children)) walk(child);
|
|
69
|
-
}
|
|
70
|
-
if (node.dynamic) walk(node.dynamic.child);
|
|
71
|
-
if (node.wildcard) walk(node.wildcard.child);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
walk(tree);
|
|
75
|
-
|
|
76
|
-
const loaders: Record<string, () => Promise<unknown>> = {};
|
|
77
|
-
for (const path of paths) {
|
|
78
|
-
loaders[path] = () => runtime.loadModule(path);
|
|
79
|
-
}
|
|
80
|
-
return loaders;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ── Widget helpers ─────────────────────────────────────────────────────
|
|
84
|
-
|
|
85
|
-
/** Find a WidgetComponent export from a module. */
|
|
86
|
-
function extractWidgetExport(
|
|
87
|
-
mod: Record<string, unknown>,
|
|
88
|
-
): WidgetComponent | null {
|
|
89
|
-
for (const value of Object.values(mod)) {
|
|
90
|
-
if (!value) continue;
|
|
91
|
-
if (typeof value === 'object' && 'getData' in value) {
|
|
92
|
-
return value as WidgetComponent;
|
|
93
|
-
}
|
|
94
|
-
if (typeof value === 'function' && value.prototype?.getData) {
|
|
95
|
-
return new (value as new () => WidgetComponent)();
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** Import widget modules for SSR via runtime.loadModule(). */
|
|
102
|
-
async function importWidgets(
|
|
103
|
-
entries: WidgetManifestEntry[],
|
|
104
|
-
runtime: Runtime,
|
|
105
|
-
manual?: WidgetRegistry,
|
|
106
|
-
): Promise<{
|
|
107
|
-
registry: WidgetRegistry;
|
|
108
|
-
widgetFiles: Record<string, { html?: string; md?: string; css?: string }>;
|
|
109
|
-
}> {
|
|
110
|
-
const registry = new WidgetRegistry();
|
|
111
|
-
const widgetFiles: Record<string, { html?: string; md?: string; css?: string }> = {};
|
|
112
|
-
|
|
113
|
-
for (const entry of entries) {
|
|
114
|
-
try {
|
|
115
|
-
const runtimePath = entry.modulePath.startsWith('/')
|
|
116
|
-
? entry.modulePath
|
|
117
|
-
: `/${entry.modulePath}`;
|
|
118
|
-
|
|
119
|
-
const mod = await runtime.loadModule(runtimePath) as Record<string, unknown>;
|
|
120
|
-
const instance = extractWidgetExport(mod);
|
|
121
|
-
if (!instance) continue;
|
|
122
|
-
registry.add(instance);
|
|
123
|
-
|
|
124
|
-
// Prefer inlined __files from merged module over manifest paths
|
|
125
|
-
const inlined = mod.__files;
|
|
126
|
-
if (inlined && typeof inlined === 'object') {
|
|
127
|
-
widgetFiles[entry.name] = inlined as { html?: string; md?: string; css?: string };
|
|
128
|
-
} else if (entry.files) {
|
|
129
|
-
widgetFiles[entry.name] = entry.files;
|
|
130
|
-
}
|
|
131
|
-
} catch (e) {
|
|
132
|
-
console.error(`[emroute] Failed to load widget ${entry.modulePath}:`, e);
|
|
133
|
-
if (entry.files) widgetFiles[entry.name] = entry.files;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (manual) {
|
|
138
|
-
for (const widget of manual) {
|
|
139
|
-
registry.add(widget);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return { registry, widgetFiles };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ── HTML shell ─────────────────────────────────────────────────────────
|
|
147
|
-
|
|
148
|
-
/** Build a default HTML shell. */
|
|
149
|
-
function buildHtmlShell(title: string, htmlBase: string): string {
|
|
150
|
-
const baseTag = htmlBase ? `\n <base href="${escapeHtml(htmlBase)}/">` : '';
|
|
151
|
-
return `<!DOCTYPE html>
|
|
152
|
-
<html>
|
|
153
|
-
<head>${baseTag}
|
|
154
|
-
<meta charset="utf-8">
|
|
155
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
156
|
-
<title>${escapeHtml(title)}</title>
|
|
157
|
-
<style>@view-transition { navigation: auto; } router-slot { display: contents; }</style>
|
|
158
|
-
</head>
|
|
159
|
-
<body>
|
|
160
|
-
<router-slot></router-slot>
|
|
161
|
-
</body>
|
|
162
|
-
</html>`;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/** Inject SSR-rendered content into an HTML shell. */
|
|
166
|
-
function injectSsrContent(
|
|
167
|
-
html: string,
|
|
168
|
-
content: string,
|
|
169
|
-
title: string | undefined,
|
|
170
|
-
ssrRoute?: string,
|
|
171
|
-
): string {
|
|
172
|
-
const slotPattern = /<router-slot\b[^>]*>.*?<\/router-slot>/s;
|
|
173
|
-
if (!slotPattern.test(html)) return html;
|
|
174
|
-
|
|
175
|
-
const ssrAttr = ssrRoute ? ` data-ssr-route="${ssrRoute}"` : '';
|
|
176
|
-
html = html.replace(slotPattern, `<router-slot${ssrAttr}>${content}</router-slot>`);
|
|
177
|
-
|
|
178
|
-
if (title) {
|
|
179
|
-
html = html.replace(/<title>[^<]*<\/title>/, `<title>${escapeHtml(title)}</title>`);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return html;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/** Read the HTML shell from runtime, with fallback to a default shell. */
|
|
186
|
-
async function resolveShell(
|
|
187
|
-
runtime: Runtime,
|
|
188
|
-
title: string,
|
|
189
|
-
htmlBase: string,
|
|
190
|
-
): Promise<string> {
|
|
191
|
-
const response = await runtime.query('/index.html');
|
|
192
|
-
if (response.status !== 404) return await response.text();
|
|
193
|
-
return buildHtmlShell(title, htmlBase);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// ── More path helpers ─────────────────────────────────────────────────
|
|
11
|
+
/** @deprecated Use `Emroute` class directly. */
|
|
12
|
+
export type EmrouteServer = _Emroute;
|
|
197
13
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
* Create an emroute server.
|
|
202
|
-
*
|
|
203
|
-
* All paths are Runtime-relative (starting with `/`). Runtime root = appRoot.
|
|
204
|
-
*/
|
|
205
|
-
export async function createEmrouteServer(
|
|
206
|
-
config: EmrouteServerConfig,
|
|
14
|
+
/** @deprecated Use `Emroute.create(config, runtime)`. */
|
|
15
|
+
export function createEmrouteServer(
|
|
16
|
+
config: Parameters<typeof _Emroute.create>[0],
|
|
207
17
|
runtime: Runtime,
|
|
208
|
-
): Promise<
|
|
209
|
-
|
|
210
|
-
spa = 'root',
|
|
211
|
-
} = config;
|
|
212
|
-
|
|
213
|
-
const { html: htmlBase, md: mdBase, app: appBase } = config.basePath ?? DEFAULT_BASE_PATH;
|
|
214
|
-
|
|
215
|
-
// ── Route tree (read from runtime) ──────────────────────────────────
|
|
216
|
-
|
|
217
|
-
let routeTree: RouteNode;
|
|
218
|
-
|
|
219
|
-
if (config.routeTree) {
|
|
220
|
-
routeTree = config.routeTree;
|
|
221
|
-
} else {
|
|
222
|
-
const manifestResponse = await runtime.query(ROUTES_MANIFEST_PATH);
|
|
223
|
-
if (manifestResponse.status === 404) {
|
|
224
|
-
throw new Error(
|
|
225
|
-
`[emroute] ${ROUTES_MANIFEST_PATH} not found in runtime. ` +
|
|
226
|
-
'Provide routeTree in config or ensure the runtime produces it.',
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
routeTree = await manifestResponse.json();
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const moduleLoaders = config.moduleLoaders ?? createModuleLoaders(routeTree, runtime);
|
|
233
|
-
const resolver = new RouteTrie(routeTree);
|
|
234
|
-
|
|
235
|
-
// ── Widgets (read from runtime) ────────────────────────────────────
|
|
236
|
-
|
|
237
|
-
let widgets: WidgetRegistry | undefined = config.widgets;
|
|
238
|
-
let widgetFiles: Record<string, { html?: string; md?: string; css?: string }> = {};
|
|
239
|
-
let discoveredWidgetEntries: WidgetManifestEntry[] = [];
|
|
240
|
-
|
|
241
|
-
const widgetsResponse = await runtime.query(WIDGETS_MANIFEST_PATH);
|
|
242
|
-
if (widgetsResponse.status !== 404) {
|
|
243
|
-
discoveredWidgetEntries = await widgetsResponse.json();
|
|
244
|
-
if (config.widgets) {
|
|
245
|
-
// Widgets pre-provided (e.g. browser bundle) — just collect file paths
|
|
246
|
-
widgets = config.widgets;
|
|
247
|
-
for (const entry of discoveredWidgetEntries) {
|
|
248
|
-
if (entry.files) widgetFiles[entry.name] = entry.files;
|
|
249
|
-
}
|
|
250
|
-
} else {
|
|
251
|
-
const imported = await importWidgets(discoveredWidgetEntries, runtime);
|
|
252
|
-
widgets = imported.registry;
|
|
253
|
-
widgetFiles = imported.widgetFiles;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// ── SSR routers ──────────────────────────────────────────────────────
|
|
258
|
-
|
|
259
|
-
let ssrHtmlRouter: SsrHtmlRouter | null = null;
|
|
260
|
-
let ssrMdRouter: SsrMdRouter | null = null;
|
|
261
|
-
|
|
262
|
-
function buildSsrRouters(): void {
|
|
263
|
-
if (spa === 'only') {
|
|
264
|
-
ssrHtmlRouter = null;
|
|
265
|
-
ssrMdRouter = null;
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
ssrHtmlRouter = new SsrHtmlRouter(resolver, {
|
|
270
|
-
fileReader: (path) => runtime.query(path, { as: 'text' }),
|
|
271
|
-
moduleLoaders,
|
|
272
|
-
markdownRenderer: config.markdownRenderer,
|
|
273
|
-
extendContext: config.extendContext,
|
|
274
|
-
widgets,
|
|
275
|
-
widgetFiles,
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
ssrMdRouter = new SsrMdRouter(resolver, {
|
|
279
|
-
fileReader: (path) => runtime.query(path, { as: 'text' }),
|
|
280
|
-
moduleLoaders,
|
|
281
|
-
extendContext: config.extendContext,
|
|
282
|
-
widgets,
|
|
283
|
-
widgetFiles,
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
buildSsrRouters();
|
|
288
|
-
|
|
289
|
-
// ── HTML shell ───────────────────────────────────────────────────────
|
|
290
|
-
|
|
291
|
-
const title = config.title ?? 'emroute';
|
|
292
|
-
let shell = await resolveShell(runtime, title, htmlBase);
|
|
293
|
-
|
|
294
|
-
// Auto-discover main.css and inject <link> into <head>
|
|
295
|
-
if ((await runtime.query('/main.css')).status !== 404) {
|
|
296
|
-
shell = shell.replace('</head>', ' <link rel="stylesheet" href="/main.css">\n</head>');
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// ── handleRequest ────────────────────────────────────────────────────
|
|
300
|
-
|
|
301
|
-
async function handleRequest(req: Request): Promise<Response | null> {
|
|
302
|
-
const url = new URL(req.url);
|
|
303
|
-
const pathname = url.pathname;
|
|
304
|
-
|
|
305
|
-
const mdPrefix = mdBase + '/';
|
|
306
|
-
const htmlPrefix = htmlBase + '/';
|
|
307
|
-
const appPrefix = appBase + '/';
|
|
308
|
-
|
|
309
|
-
// SSR Markdown: /md/*
|
|
310
|
-
if (
|
|
311
|
-
ssrMdRouter &&
|
|
312
|
-
(pathname.startsWith(mdPrefix) || pathname === mdBase)
|
|
313
|
-
) {
|
|
314
|
-
// Normalize trailing slash: /md/about/ → 301 /md/about
|
|
315
|
-
const routePath = pathname === mdBase ? '/' : pathname.slice(mdBase.length);
|
|
316
|
-
if (routePath.length > 1 && routePath.endsWith('/')) {
|
|
317
|
-
const canonical = mdBase + routePath.slice(0, -1) + (url.search || '');
|
|
318
|
-
return Response.redirect(new URL(canonical, url.origin), 301);
|
|
319
|
-
}
|
|
320
|
-
try {
|
|
321
|
-
const routeUrl = new URL(routePath + url.search, url.origin);
|
|
322
|
-
const { content, status, redirect } = await ssrMdRouter.render(routeUrl, req.signal);
|
|
323
|
-
if (redirect) {
|
|
324
|
-
const target = redirect.startsWith('/') ? mdBase + redirect : redirect;
|
|
325
|
-
return Response.redirect(new URL(target, url.origin), status);
|
|
326
|
-
}
|
|
327
|
-
return new Response(rewriteMdLinks(content, mdBase, [mdBase, htmlBase]), {
|
|
328
|
-
status,
|
|
329
|
-
headers: { 'Content-Type': 'text/markdown; charset=utf-8; variant=CommonMark' },
|
|
330
|
-
});
|
|
331
|
-
} catch (e) {
|
|
332
|
-
console.error(`[emroute] Error rendering ${pathname}:`, e);
|
|
333
|
-
return new Response('Internal Server Error', { status: 500 });
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// SSR HTML: /html/*
|
|
338
|
-
if (
|
|
339
|
-
ssrHtmlRouter &&
|
|
340
|
-
(pathname.startsWith(htmlPrefix) || pathname === htmlBase)
|
|
341
|
-
) {
|
|
342
|
-
// Normalize trailing slash: /html/about/ → 301 /html/about
|
|
343
|
-
const routePath = pathname === htmlBase ? '/' : pathname.slice(htmlBase.length);
|
|
344
|
-
if (routePath.length > 1 && routePath.endsWith('/')) {
|
|
345
|
-
const canonical = htmlBase + routePath.slice(0, -1) + (url.search || '');
|
|
346
|
-
return Response.redirect(new URL(canonical, url.origin), 301);
|
|
347
|
-
}
|
|
348
|
-
try {
|
|
349
|
-
const routeUrl = new URL(routePath + url.search, url.origin);
|
|
350
|
-
const result = await ssrHtmlRouter.render(routeUrl, req.signal);
|
|
351
|
-
if (result.redirect) {
|
|
352
|
-
const target = result.redirect.startsWith('/') ? htmlBase + result.redirect : result.redirect;
|
|
353
|
-
return Response.redirect(new URL(target, url.origin), result.status);
|
|
354
|
-
}
|
|
355
|
-
const ssrTitle = result.title ?? title;
|
|
356
|
-
const html = injectSsrContent(shell, result.content, ssrTitle, pathname);
|
|
357
|
-
return new Response(html, {
|
|
358
|
-
status: result.status,
|
|
359
|
-
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
360
|
-
});
|
|
361
|
-
} catch (e) {
|
|
362
|
-
console.error(`[emroute] Error rendering ${pathname}:`, e);
|
|
363
|
-
return new Response('Internal Server Error', { status: 500 });
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// /app/* — serve shell (browser JS takes over via createEmrouteApp)
|
|
368
|
-
if (pathname.startsWith(appPrefix) || pathname === appBase) {
|
|
369
|
-
return new Response(shell, {
|
|
370
|
-
status: 200,
|
|
371
|
-
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// /html/* or /md/* that wasn't handled by SSR (e.g. 'only' mode) — serve shell
|
|
376
|
-
if (
|
|
377
|
-
pathname.startsWith(htmlPrefix) || pathname === htmlBase ||
|
|
378
|
-
pathname.startsWith(mdPrefix) || pathname === mdBase
|
|
379
|
-
) {
|
|
380
|
-
return new Response(shell, {
|
|
381
|
-
status: 200,
|
|
382
|
-
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Static files — only try runtime for paths with a file extension
|
|
387
|
-
const lastSegment = pathname.split('/').pop() ?? '';
|
|
388
|
-
if (lastSegment.includes('.')) {
|
|
389
|
-
const fileResponse = await runtime.handle(pathname);
|
|
390
|
-
if (fileResponse.status === 200) return fileResponse;
|
|
391
|
-
return null;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// Bare paths — redirect to /app/* in root/only modes, /html/* otherwise.
|
|
395
|
-
const base = (spa === 'root' || spa === 'only') ? appBase : htmlBase;
|
|
396
|
-
const bare = pathname === '/' ? '' : pathname.slice(1).replace(/\/$/, '');
|
|
397
|
-
return Response.redirect(new URL(`${base}/${bare}`, url.origin), 302);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// ── Return ───────────────────────────────────────────────────────────
|
|
401
|
-
|
|
402
|
-
return {
|
|
403
|
-
handleRequest,
|
|
404
|
-
get htmlRouter() {
|
|
405
|
-
return ssrHtmlRouter;
|
|
406
|
-
},
|
|
407
|
-
get mdRouter() {
|
|
408
|
-
return ssrMdRouter;
|
|
409
|
-
},
|
|
410
|
-
get routeTree() {
|
|
411
|
-
return routeTree;
|
|
412
|
-
},
|
|
413
|
-
get widgetEntries() {
|
|
414
|
-
return discoveredWidgetEntries;
|
|
415
|
-
},
|
|
416
|
-
get shell() {
|
|
417
|
-
return shell;
|
|
418
|
-
},
|
|
419
|
-
};
|
|
18
|
+
): Promise<_Emroute> {
|
|
19
|
+
return _Emroute.create(config, runtime);
|
|
420
20
|
}
|