@emkodev/emroute 1.6.6-beta.2 → 1.6.6-beta.4
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/dist/emroute.js +2757 -0
- package/dist/emroute.js.map +7 -0
- package/dist/runtime/abstract.runtime.d.ts +0 -28
- package/dist/runtime/abstract.runtime.js +10 -58
- package/dist/runtime/abstract.runtime.js.map +1 -1
- package/dist/runtime/bun/esbuild-runtime-loader.plugin.js +3 -0
- package/dist/runtime/bun/esbuild-runtime-loader.plugin.js.map +1 -1
- package/dist/runtime/bun/fs/bun-fs.runtime.d.ts +0 -5
- package/dist/runtime/bun/fs/bun-fs.runtime.js +1 -95
- package/dist/runtime/bun/fs/bun-fs.runtime.js.map +1 -1
- package/dist/runtime/bun/sqlite/bun-sqlite.runtime.d.ts +0 -5
- package/dist/runtime/bun/sqlite/bun-sqlite.runtime.js +2 -96
- package/dist/runtime/bun/sqlite/bun-sqlite.runtime.js.map +1 -1
- package/dist/runtime/fetch.runtime.d.ts +26 -0
- package/dist/runtime/fetch.runtime.js +55 -0
- package/dist/runtime/fetch.runtime.js.map +1 -0
- package/dist/runtime/sitemap.generator.d.ts +4 -4
- package/dist/runtime/sitemap.generator.js +32 -11
- package/dist/runtime/sitemap.generator.js.map +1 -1
- package/dist/runtime/universal/fs/universal-fs.runtime.d.ts +0 -5
- package/dist/runtime/universal/fs/universal-fs.runtime.js +1 -95
- package/dist/runtime/universal/fs/universal-fs.runtime.js.map +1 -1
- package/dist/server/build.util.d.ts +38 -0
- package/dist/server/build.util.js +133 -0
- package/dist/server/build.util.js.map +1 -0
- package/dist/server/codegen.util.d.ts +3 -0
- package/dist/server/codegen.util.js +28 -10
- package/dist/server/codegen.util.js.map +1 -1
- package/dist/server/emroute.server.js +53 -29
- package/dist/server/emroute.server.js.map +1 -1
- package/dist/server/esbuild-manifest.plugin.js +6 -4
- package/dist/server/esbuild-manifest.plugin.js.map +1 -1
- package/dist/server/server-api.type.d.ts +6 -0
- package/dist/src/component/abstract.component.d.ts +5 -3
- package/dist/src/component/abstract.component.js.map +1 -1
- package/dist/src/element/component.element.js +5 -4
- package/dist/src/element/component.element.js.map +1 -1
- package/dist/src/renderer/spa/mod.d.ts +2 -3
- package/dist/src/renderer/spa/mod.js +2 -3
- package/dist/src/renderer/spa/mod.js.map +1 -1
- package/dist/src/renderer/spa/thin-client.d.ts +34 -0
- package/dist/src/renderer/spa/thin-client.js +138 -0
- package/dist/src/renderer/spa/thin-client.js.map +1 -0
- package/dist/src/renderer/ssr/html.renderer.d.ts +3 -3
- package/dist/src/renderer/ssr/html.renderer.js +6 -6
- package/dist/src/renderer/ssr/html.renderer.js.map +1 -1
- package/dist/src/renderer/ssr/md.renderer.d.ts +3 -3
- package/dist/src/renderer/ssr/md.renderer.js +12 -7
- package/dist/src/renderer/ssr/md.renderer.js.map +1 -1
- package/dist/src/renderer/ssr/ssr.renderer.d.ts +7 -6
- package/dist/src/renderer/ssr/ssr.renderer.js +42 -44
- package/dist/src/renderer/ssr/ssr.renderer.js.map +1 -1
- package/dist/src/route/route.core.d.ts +16 -6
- package/dist/src/route/route.core.js +44 -23
- package/dist/src/route/route.core.js.map +1 -1
- package/dist/src/type/route-tree.type.d.ts +2 -0
- package/dist/src/type/route.type.d.ts +6 -24
- package/dist/src/util/md.util.d.ts +8 -0
- package/dist/src/util/md.util.js +28 -0
- package/dist/src/util/md.util.js.map +1 -0
- package/dist/src/util/widget-resolve.util.js +6 -1
- package/dist/src/util/widget-resolve.util.js.map +1 -1
- package/dist/src/widget/breadcrumb.widget.d.ts +0 -1
- package/dist/src/widget/breadcrumb.widget.js +4 -15
- package/dist/src/widget/breadcrumb.widget.js.map +1 -1
- package/package.json +13 -2
- package/runtime/abstract.runtime.ts +9 -82
- package/runtime/bun/esbuild-runtime-loader.plugin.ts +2 -0
- package/runtime/bun/fs/bun-fs.runtime.ts +0 -109
- package/runtime/bun/sqlite/bun-sqlite.runtime.ts +1 -112
- package/runtime/fetch.runtime.ts +70 -0
- package/runtime/sitemap.generator.ts +37 -12
- package/runtime/universal/fs/universal-fs.runtime.ts +0 -109
- package/server/build.util.ts +168 -0
- package/server/codegen.util.ts +29 -11
- package/server/emroute.server.ts +50 -30
- package/server/esbuild-manifest.plugin.ts +5 -3
- package/server/server-api.type.ts +7 -0
- package/src/component/abstract.component.ts +5 -3
- package/src/element/component.element.ts +5 -4
- package/src/renderer/spa/mod.ts +2 -8
- package/src/renderer/spa/thin-client.ts +165 -0
- package/src/renderer/ssr/html.renderer.ts +6 -5
- package/src/renderer/ssr/md.renderer.ts +12 -6
- package/src/renderer/ssr/ssr.renderer.ts +54 -48
- package/src/route/route.core.ts +49 -28
- package/src/type/route-tree.type.ts +2 -0
- package/src/type/route.type.ts +7 -32
- package/src/util/md.util.ts +31 -0
- package/src/util/widget-resolve.util.ts +6 -1
- package/src/widget/breadcrumb.widget.ts +4 -16
- package/server/scanner.util.ts +0 -243
- package/src/renderer/spa/base.renderer.ts +0 -186
- package/src/renderer/spa/hash.renderer.ts +0 -238
- package/src/renderer/spa/html.renderer.ts +0 -399
- package/src/route/route.matcher.ts +0 -260
- package/src/web-doc/index.md +0 -15
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { stat, readdir, mkdir } from 'node:fs/promises';
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
2
|
import { resolve } from 'node:path';
|
|
4
3
|
import {
|
|
5
4
|
CONTENT_TYPES,
|
|
6
|
-
DEFAULT_ROUTES_DIR,
|
|
7
|
-
DEFAULT_WIDGETS_DIR,
|
|
8
|
-
EMROUTE_EXTERNALS,
|
|
9
5
|
type FetchParams,
|
|
10
6
|
type FetchReturn,
|
|
11
7
|
ROUTES_MANIFEST_PATH,
|
|
@@ -13,17 +9,11 @@ import {
|
|
|
13
9
|
type RuntimeConfig,
|
|
14
10
|
WIDGETS_MANIFEST_PATH,
|
|
15
11
|
} from '../../abstract.runtime.ts';
|
|
16
|
-
import { createManifestPlugin } from '../../../server/esbuild-manifest.plugin.ts';
|
|
17
|
-
import { createRuntimeLoaderPlugin } from '../esbuild-runtime-loader.plugin.ts';
|
|
18
|
-
import { generateMainTs } from '../../../server/codegen.util.ts';
|
|
19
12
|
|
|
20
13
|
export class BunFsRuntime extends Runtime {
|
|
21
14
|
private readonly root: string;
|
|
22
15
|
|
|
23
16
|
constructor(root: string, config: RuntimeConfig = {}) {
|
|
24
|
-
if (config.entryPoint && !config.bundlePaths) {
|
|
25
|
-
config.bundlePaths = { emroute: '/emroute.js', app: '/app.js' };
|
|
26
|
-
}
|
|
27
17
|
super(config);
|
|
28
18
|
const abs = resolve(root);
|
|
29
19
|
this.root = abs.endsWith('/') ? abs.slice(0, -1) : abs;
|
|
@@ -142,103 +132,4 @@ export class BunFsRuntime extends Runtime {
|
|
|
142
132
|
return import(this.root + path);
|
|
143
133
|
}
|
|
144
134
|
|
|
145
|
-
// ── Bundling ─────────────────────────────────────────────────────────
|
|
146
|
-
|
|
147
|
-
override async bundle(): Promise<void> {
|
|
148
|
-
if (this.config.spa === 'none') return;
|
|
149
|
-
const paths = this.config.bundlePaths;
|
|
150
|
-
if (!paths) return;
|
|
151
|
-
|
|
152
|
-
const esbuild = await BunFsRuntime.esbuild();
|
|
153
|
-
const builds: Promise<{ outputFiles: { path: string; contents: Uint8Array }[] }>[] = [];
|
|
154
|
-
const shared = { bundle: true, write: false, format: 'esm' as const, platform: 'browser' as const };
|
|
155
|
-
const runtimeLoader = createRuntimeLoaderPlugin({ runtime: this, root: this.root });
|
|
156
|
-
|
|
157
|
-
// Emroute SPA bundle — resolve from consumer's node_modules (no runtime loader needed)
|
|
158
|
-
const consumerRequire = createRequire(this.root + '/');
|
|
159
|
-
const spaEntry = consumerRequire.resolve('@emkodev/emroute/spa');
|
|
160
|
-
builds.push(esbuild.build({
|
|
161
|
-
...shared,
|
|
162
|
-
entryPoints: [spaEntry],
|
|
163
|
-
outfile: `${this.root}${paths.emroute}`,
|
|
164
|
-
}));
|
|
165
|
-
|
|
166
|
-
// App bundle — generate main.ts if absent, virtual plugin resolves manifests
|
|
167
|
-
if (this.config.entryPoint) {
|
|
168
|
-
if ((await this.query(this.config.entryPoint)).status === 404) {
|
|
169
|
-
const hasRoutes = (await this.query((this.config.routesDir ?? DEFAULT_ROUTES_DIR) + '/')).status !== 404;
|
|
170
|
-
const hasWidgets = (await this.query((this.config.widgetsDir ?? DEFAULT_WIDGETS_DIR) + '/')).status !== 404;
|
|
171
|
-
const code = generateMainTs('root', hasRoutes, hasWidgets, '@emkodev/emroute');
|
|
172
|
-
await this.command(this.config.entryPoint, { body: code });
|
|
173
|
-
}
|
|
174
|
-
const manifestPlugin = createManifestPlugin({
|
|
175
|
-
runtime: this,
|
|
176
|
-
resolveDir: this.root,
|
|
177
|
-
});
|
|
178
|
-
builds.push(esbuild.build({
|
|
179
|
-
...shared,
|
|
180
|
-
entryPoints: [`${this.root}${this.config.entryPoint}`],
|
|
181
|
-
outfile: `${this.root}${paths.app}`,
|
|
182
|
-
external: [...EMROUTE_EXTERNALS],
|
|
183
|
-
plugins: [manifestPlugin, runtimeLoader],
|
|
184
|
-
}));
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Widgets bundle
|
|
188
|
-
if (paths.widgets) {
|
|
189
|
-
const widgetsTsPath = paths.widgets.replace('.js', '.ts');
|
|
190
|
-
if ((await this.query(widgetsTsPath)).status !== 404) {
|
|
191
|
-
builds.push(esbuild.build({
|
|
192
|
-
...shared,
|
|
193
|
-
entryPoints: [`${this.root}${widgetsTsPath}`],
|
|
194
|
-
outfile: `${this.root}${paths.widgets}`,
|
|
195
|
-
external: [...EMROUTE_EXTERNALS],
|
|
196
|
-
plugins: [runtimeLoader],
|
|
197
|
-
}));
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const results = await Promise.all(builds);
|
|
202
|
-
|
|
203
|
-
// Write all output files through the runtime
|
|
204
|
-
for (const result of results) {
|
|
205
|
-
for (const file of result.outputFiles) {
|
|
206
|
-
const runtimePath = file.path.startsWith(this.root)
|
|
207
|
-
? file.path.slice(this.root.length)
|
|
208
|
-
: '/' + file.path;
|
|
209
|
-
await this.command(runtimePath, { body: file.contents as unknown as BodyInit });
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
await this.writeShell(paths);
|
|
214
|
-
|
|
215
|
-
await esbuild.stop();
|
|
216
|
-
BunFsRuntime._esbuild = null;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// ── Transpile / esbuild ───────────────────────────────────────────────
|
|
220
|
-
|
|
221
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
222
|
-
private static _esbuild: any = null;
|
|
223
|
-
|
|
224
|
-
private static async esbuild() {
|
|
225
|
-
if (!BunFsRuntime._esbuild) {
|
|
226
|
-
// Resolve esbuild from the consumer's node_modules, not the package's
|
|
227
|
-
const consumerRequire = createRequire(process.cwd() + '/');
|
|
228
|
-
BunFsRuntime._esbuild = consumerRequire('esbuild');
|
|
229
|
-
}
|
|
230
|
-
return BunFsRuntime._esbuild;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
static override transpile(source: string): Promise<string> {
|
|
234
|
-
const transpiler = new Bun.Transpiler({ loader: 'ts' });
|
|
235
|
-
return Promise.resolve(transpiler.transformSync(source));
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
static override async stopBundler(): Promise<void> {
|
|
239
|
-
if (BunFsRuntime._esbuild) {
|
|
240
|
-
await BunFsRuntime._esbuild.stop();
|
|
241
|
-
BunFsRuntime._esbuild = null;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
135
|
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import { createRequire } from 'node:module';
|
|
2
1
|
import { Database } from 'bun:sqlite';
|
|
3
2
|
import {
|
|
4
3
|
CONTENT_TYPES,
|
|
5
|
-
DEFAULT_ROUTES_DIR,
|
|
6
|
-
DEFAULT_WIDGETS_DIR,
|
|
7
|
-
EMROUTE_EXTERNALS,
|
|
8
4
|
type FetchParams,
|
|
9
5
|
type FetchReturn,
|
|
10
6
|
ROUTES_MANIFEST_PATH,
|
|
@@ -12,9 +8,6 @@ import {
|
|
|
12
8
|
type RuntimeConfig,
|
|
13
9
|
WIDGETS_MANIFEST_PATH,
|
|
14
10
|
} from '../../abstract.runtime.ts';
|
|
15
|
-
import { createManifestPlugin } from '../../../server/esbuild-manifest.plugin.ts';
|
|
16
|
-
import { createRuntimeLoaderPlugin, VIRTUAL_ROOT } from '../esbuild-runtime-loader.plugin.ts';
|
|
17
|
-
import { generateMainTs } from '../../../server/codegen.util.ts';
|
|
18
11
|
|
|
19
12
|
export class BunSqliteRuntime extends Runtime {
|
|
20
13
|
private readonly db: Database;
|
|
@@ -24,9 +17,6 @@ export class BunSqliteRuntime extends Runtime {
|
|
|
24
17
|
private readonly stmtHas: ReturnType<Database['prepare']>;
|
|
25
18
|
|
|
26
19
|
constructor(path: string = ':memory:', config: RuntimeConfig = {}) {
|
|
27
|
-
if (config.entryPoint && !config.bundlePaths) {
|
|
28
|
-
config.bundlePaths = { emroute: '/emroute.js', app: '/app.js' };
|
|
29
|
-
}
|
|
30
20
|
super(config);
|
|
31
21
|
this.db = new Database(path);
|
|
32
22
|
this.db.run(`
|
|
@@ -81,11 +71,7 @@ export class BunSqliteRuntime extends Runtime {
|
|
|
81
71
|
|
|
82
72
|
override async loadModule(path: string): Promise<unknown> {
|
|
83
73
|
const source = await this.query(path, { as: 'text' });
|
|
84
|
-
const
|
|
85
|
-
? await BunSqliteRuntime.transpile(source)
|
|
86
|
-
: source;
|
|
87
|
-
|
|
88
|
-
const blob = new Blob([code], { type: 'text/javascript' });
|
|
74
|
+
const blob = new Blob([source], { type: 'text/javascript' });
|
|
89
75
|
const url = URL.createObjectURL(blob);
|
|
90
76
|
try {
|
|
91
77
|
return await import(url);
|
|
@@ -98,79 +84,6 @@ export class BunSqliteRuntime extends Runtime {
|
|
|
98
84
|
this.db.close();
|
|
99
85
|
}
|
|
100
86
|
|
|
101
|
-
// ── Bundling ─────────────────────────────────────────────────────────
|
|
102
|
-
|
|
103
|
-
override async bundle(): Promise<void> {
|
|
104
|
-
if (this.config.spa === 'none') return;
|
|
105
|
-
const paths = this.config.bundlePaths;
|
|
106
|
-
if (!paths) return;
|
|
107
|
-
|
|
108
|
-
const esbuild = await BunSqliteRuntime.esbuild();
|
|
109
|
-
const builds: Promise<{ outputFiles: { path: string; contents: Uint8Array }[] }>[] = [];
|
|
110
|
-
const shared = { bundle: true, write: false, format: 'esm' as const, platform: 'browser' as const };
|
|
111
|
-
const runtimeLoader = createRuntimeLoaderPlugin({ runtime: this, root: VIRTUAL_ROOT });
|
|
112
|
-
|
|
113
|
-
// Emroute SPA bundle — resolve from consumer's node_modules (no runtime loader needed)
|
|
114
|
-
const consumerRequire = createRequire(process.cwd() + '/');
|
|
115
|
-
const spaEntry = consumerRequire.resolve('@emkodev/emroute/spa');
|
|
116
|
-
builds.push(esbuild.build({
|
|
117
|
-
...shared,
|
|
118
|
-
entryPoints: [spaEntry],
|
|
119
|
-
outfile: paths.emroute,
|
|
120
|
-
}));
|
|
121
|
-
|
|
122
|
-
// App bundle — generate main.ts if absent, virtual plugin resolves manifests
|
|
123
|
-
if (this.config.entryPoint) {
|
|
124
|
-
if ((await this.query(this.config.entryPoint)).status === 404) {
|
|
125
|
-
const hasRoutes = (await this.query((this.config.routesDir ?? DEFAULT_ROUTES_DIR) + '/')).status !== 404;
|
|
126
|
-
const hasWidgets = (await this.query((this.config.widgetsDir ?? DEFAULT_WIDGETS_DIR) + '/')).status !== 404;
|
|
127
|
-
const code = generateMainTs('root', hasRoutes, hasWidgets, '@emkodev/emroute');
|
|
128
|
-
await this.command(this.config.entryPoint, { body: code });
|
|
129
|
-
}
|
|
130
|
-
const manifestPlugin = createManifestPlugin({
|
|
131
|
-
runtime: this,
|
|
132
|
-
resolveDir: process.cwd(),
|
|
133
|
-
});
|
|
134
|
-
builds.push(esbuild.build({
|
|
135
|
-
...shared,
|
|
136
|
-
entryPoints: [VIRTUAL_ROOT + this.config.entryPoint],
|
|
137
|
-
outfile: paths.app,
|
|
138
|
-
external: [...EMROUTE_EXTERNALS],
|
|
139
|
-
plugins: [manifestPlugin, runtimeLoader],
|
|
140
|
-
}));
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Widgets bundle
|
|
144
|
-
if (paths.widgets) {
|
|
145
|
-
const widgetsTsPath = paths.widgets.replace('.js', '.ts');
|
|
146
|
-
if ((await this.query(widgetsTsPath)).status !== 404) {
|
|
147
|
-
builds.push(esbuild.build({
|
|
148
|
-
...shared,
|
|
149
|
-
entryPoints: [VIRTUAL_ROOT + widgetsTsPath],
|
|
150
|
-
outfile: paths.widgets,
|
|
151
|
-
external: [...EMROUTE_EXTERNALS],
|
|
152
|
-
plugins: [runtimeLoader],
|
|
153
|
-
}));
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const results = await Promise.all(builds);
|
|
158
|
-
|
|
159
|
-
// Write all output files through the runtime
|
|
160
|
-
for (const result of results) {
|
|
161
|
-
for (const file of result.outputFiles) {
|
|
162
|
-
// outfile paths are relative — ensure leading /
|
|
163
|
-
const runtimePath = file.path.startsWith('/') ? file.path : '/' + file.path;
|
|
164
|
-
await this.command(runtimePath, { body: file.contents as unknown as BodyInit });
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
await this.writeShell(paths);
|
|
169
|
-
|
|
170
|
-
await esbuild.stop();
|
|
171
|
-
BunSqliteRuntime._esbuild = null;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
87
|
// ── Private ─────────────────────────────────────────────────────────
|
|
175
88
|
|
|
176
89
|
private async read(path: string): Promise<Response> {
|
|
@@ -231,30 +144,6 @@ export class BunSqliteRuntime extends Runtime {
|
|
|
231
144
|
return this.stmtHas.get(prefix) !== null;
|
|
232
145
|
}
|
|
233
146
|
|
|
234
|
-
// ── Transpile / esbuild ───────────────────────────────────────────────
|
|
235
|
-
|
|
236
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
237
|
-
private static _esbuild: any = null;
|
|
238
|
-
|
|
239
|
-
private static async esbuild() {
|
|
240
|
-
if (!BunSqliteRuntime._esbuild) {
|
|
241
|
-
BunSqliteRuntime._esbuild = await import('esbuild');
|
|
242
|
-
}
|
|
243
|
-
return BunSqliteRuntime._esbuild;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
static override transpile(source: string): Promise<string> {
|
|
247
|
-
const transpiler = new Bun.Transpiler({ loader: 'ts' });
|
|
248
|
-
return Promise.resolve(transpiler.transformSync(source));
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
static override async stopBundler(): Promise<void> {
|
|
252
|
-
if (BunSqliteRuntime._esbuild) {
|
|
253
|
-
await BunSqliteRuntime._esbuild.stop();
|
|
254
|
-
BunSqliteRuntime._esbuild = null;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
147
|
private parsePath(resource: FetchParams[0]): string {
|
|
259
148
|
if (typeof resource === 'string') return decodeURIComponent(resource);
|
|
260
149
|
if (resource instanceof URL) return decodeURIComponent(resource.pathname);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch Runtime
|
|
3
|
+
*
|
|
4
|
+
* Browser-compatible Runtime that delegates all reads to a remote server
|
|
5
|
+
* via `fetch()`. Used by the thin client in `root` mode — same
|
|
6
|
+
* `createEmrouteServer` runs in the browser, but the Runtime fetches
|
|
7
|
+
* files from the real server instead of reading from disk.
|
|
8
|
+
*
|
|
9
|
+
* No bundling, no transpiling, no filesystem access.
|
|
10
|
+
* No directory scanning — the remote server already has manifests.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
type FetchParams,
|
|
15
|
+
type FetchReturn,
|
|
16
|
+
Runtime,
|
|
17
|
+
type RuntimeConfig,
|
|
18
|
+
} from './abstract.runtime.ts';
|
|
19
|
+
|
|
20
|
+
export class FetchRuntime extends Runtime {
|
|
21
|
+
private readonly origin: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param origin — Server origin, e.g. `'http://localhost:4100'` or `location.origin`.
|
|
25
|
+
*/
|
|
26
|
+
constructor(origin: string, config: RuntimeConfig = {}) {
|
|
27
|
+
super(config);
|
|
28
|
+
this.origin = origin.endsWith('/') ? origin.slice(0, -1) : origin;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
handle(
|
|
32
|
+
resource: FetchParams[0],
|
|
33
|
+
init?: FetchParams[1],
|
|
34
|
+
): FetchReturn {
|
|
35
|
+
const url = this.toUrl(resource);
|
|
36
|
+
return fetch(url, init);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
query(
|
|
40
|
+
resource: FetchParams[0],
|
|
41
|
+
options: FetchParams[1] & { as: 'text' },
|
|
42
|
+
): Promise<string>;
|
|
43
|
+
query(
|
|
44
|
+
resource: FetchParams[0],
|
|
45
|
+
options?: FetchParams[1],
|
|
46
|
+
): FetchReturn;
|
|
47
|
+
query(
|
|
48
|
+
resource: FetchParams[0],
|
|
49
|
+
options?: FetchParams[1] & { as?: 'text' },
|
|
50
|
+
): Promise<Response | string> {
|
|
51
|
+
if (options?.as === 'text') {
|
|
52
|
+
return fetch(this.toUrl(resource)).then((r) => r.text());
|
|
53
|
+
}
|
|
54
|
+
return this.handle(resource, options);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
override async loadModule(path: string): Promise<unknown> {
|
|
58
|
+
const url = `${this.origin}${path}`;
|
|
59
|
+
const response = await fetch(url);
|
|
60
|
+
const js = await response.text();
|
|
61
|
+
const blob = new Blob([js], { type: 'application/javascript' });
|
|
62
|
+
return import(URL.createObjectURL(blob));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private toUrl(resource: FetchParams[0]): string {
|
|
66
|
+
if (typeof resource === 'string') return `${this.origin}${resource}`;
|
|
67
|
+
if (resource instanceof URL) return `${this.origin}${resource.pathname}${resource.search}`;
|
|
68
|
+
return `${this.origin}${new URL(resource.url).pathname}`;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sitemap Generator — Opt-in Submodule
|
|
3
3
|
*
|
|
4
|
-
* Generates sitemap.xml from a
|
|
4
|
+
* Generates sitemap.xml from a RouteNode tree. Pure function over tree data,
|
|
5
5
|
* no filesystem access needed.
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { escapeHtml } from '../src/util/html.util.ts';
|
|
21
|
-
import type {
|
|
21
|
+
import type { RouteNode } from '../src/type/route-tree.type.ts';
|
|
22
22
|
|
|
23
23
|
/** Valid changefreq values per sitemaps.org protocol. */
|
|
24
24
|
export type Changefreq =
|
|
@@ -130,32 +130,57 @@ function serializeEntry(entry: SitemapEntry): string {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
|
-
*
|
|
133
|
+
* Collect page routes from a RouteNode tree as pattern strings.
|
|
134
|
+
* Skips redirect and error boundary nodes.
|
|
135
|
+
*/
|
|
136
|
+
function collectPatterns(node: RouteNode, prefix: string, out: string[]): void {
|
|
137
|
+
// This node has page files — it's a page route
|
|
138
|
+
if (node.files && !node.redirect) {
|
|
139
|
+
out.push(prefix || '/');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (node.children) {
|
|
143
|
+
for (const [segment, child] of Object.entries(node.children)) {
|
|
144
|
+
collectPatterns(child, `${prefix}/${segment}`, out);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (node.dynamic) {
|
|
149
|
+
collectPatterns(node.dynamic.child, `${prefix}/:${node.dynamic.param}`, out);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (node.wildcard) {
|
|
153
|
+
collectPatterns(node.wildcard.child, `${prefix}/*${node.wildcard.param}`, out);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Generate sitemap.xml content from a route tree.
|
|
134
159
|
*
|
|
135
160
|
* Static routes (no :param) are included directly.
|
|
136
161
|
* Dynamic routes are included only if an enumerator is provided.
|
|
137
162
|
* All URLs point to /html/ prefixed paths for SSR HTML rendering.
|
|
138
163
|
*/
|
|
139
164
|
export async function generateSitemap(
|
|
140
|
-
|
|
165
|
+
routeTree: RouteNode,
|
|
141
166
|
options: SitemapOptions,
|
|
142
167
|
): Promise<string> {
|
|
143
168
|
const entries: SitemapEntry[] = [];
|
|
144
169
|
const bp = options.basePath ?? '';
|
|
145
170
|
|
|
146
|
-
|
|
147
|
-
|
|
171
|
+
const patterns: string[] = [];
|
|
172
|
+
collectPatterns(routeTree, '', patterns);
|
|
148
173
|
|
|
149
|
-
for (const
|
|
150
|
-
const routeOpts = resolveOptions(
|
|
174
|
+
for (const pattern of patterns) {
|
|
175
|
+
const routeOpts = resolveOptions(pattern, options);
|
|
151
176
|
|
|
152
|
-
if (isDynamic(
|
|
177
|
+
if (isDynamic(pattern)) {
|
|
153
178
|
// Dynamic route — use enumerator if provided, skip otherwise
|
|
154
|
-
const enumerator = options.enumerators?.[
|
|
179
|
+
const enumerator = options.enumerators?.[pattern];
|
|
155
180
|
if (!enumerator) continue;
|
|
156
181
|
|
|
157
182
|
const values = await enumerator();
|
|
158
|
-
const paths = expandDynamic(
|
|
183
|
+
const paths = expandDynamic(pattern, values);
|
|
159
184
|
|
|
160
185
|
for (const path of paths) {
|
|
161
186
|
entries.push({
|
|
@@ -166,7 +191,7 @@ export async function generateSitemap(
|
|
|
166
191
|
} else {
|
|
167
192
|
// Static route — include directly
|
|
168
193
|
entries.push({
|
|
169
|
-
loc: buildLoc(options.baseUrl,
|
|
194
|
+
loc: buildLoc(options.baseUrl, pattern, bp),
|
|
170
195
|
...routeOpts,
|
|
171
196
|
});
|
|
172
197
|
}
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { readFile, writeFile, stat, readdir, mkdir } from 'node:fs/promises';
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
2
|
import { resolve } from 'node:path';
|
|
4
3
|
import {
|
|
5
4
|
CONTENT_TYPES,
|
|
6
|
-
DEFAULT_ROUTES_DIR,
|
|
7
|
-
DEFAULT_WIDGETS_DIR,
|
|
8
|
-
EMROUTE_EXTERNALS,
|
|
9
5
|
type FetchParams,
|
|
10
6
|
type FetchReturn,
|
|
11
7
|
ROUTES_MANIFEST_PATH,
|
|
@@ -13,9 +9,6 @@ import {
|
|
|
13
9
|
type RuntimeConfig,
|
|
14
10
|
WIDGETS_MANIFEST_PATH,
|
|
15
11
|
} from '../../abstract.runtime.ts';
|
|
16
|
-
import { createManifestPlugin } from '../../../server/esbuild-manifest.plugin.ts';
|
|
17
|
-
import { createRuntimeLoaderPlugin } from '../../bun/esbuild-runtime-loader.plugin.ts';
|
|
18
|
-
import { generateMainTs } from '../../../server/codegen.util.ts';
|
|
19
12
|
|
|
20
13
|
/**
|
|
21
14
|
* Filesystem runtime using only `node:` APIs and esbuild. Works on Node,
|
|
@@ -29,9 +22,6 @@ export class UniversalFsRuntime extends Runtime {
|
|
|
29
22
|
private readonly root: string;
|
|
30
23
|
|
|
31
24
|
constructor(root: string, config: RuntimeConfig = {}) {
|
|
32
|
-
if (config.entryPoint && !config.bundlePaths) {
|
|
33
|
-
config.bundlePaths = { emroute: '/emroute.js', app: '/app.js' };
|
|
34
|
-
}
|
|
35
25
|
super(config);
|
|
36
26
|
const abs = resolve(root);
|
|
37
27
|
this.root = abs.endsWith('/') ? abs.slice(0, -1) : abs;
|
|
@@ -150,103 +140,4 @@ export class UniversalFsRuntime extends Runtime {
|
|
|
150
140
|
return import(this.root + path);
|
|
151
141
|
}
|
|
152
142
|
|
|
153
|
-
// ── Bundling ─────────────────────────────────────────────────────────
|
|
154
|
-
|
|
155
|
-
override async bundle(): Promise<void> {
|
|
156
|
-
if (this.config.spa === 'none') return;
|
|
157
|
-
const paths = this.config.bundlePaths;
|
|
158
|
-
if (!paths) return;
|
|
159
|
-
|
|
160
|
-
const esbuild = await UniversalFsRuntime.esbuild();
|
|
161
|
-
const builds: Promise<{ outputFiles: { path: string; contents: Uint8Array }[] }>[] = [];
|
|
162
|
-
const shared = { bundle: true, write: false, format: 'esm' as const, platform: 'browser' as const };
|
|
163
|
-
const runtimeLoader = createRuntimeLoaderPlugin({ runtime: this, root: this.root });
|
|
164
|
-
|
|
165
|
-
// Emroute SPA bundle — resolve from consumer's node_modules (no runtime loader needed)
|
|
166
|
-
const consumerRequire = createRequire(this.root + '/');
|
|
167
|
-
const spaEntry = consumerRequire.resolve('@emkodev/emroute/spa');
|
|
168
|
-
builds.push(esbuild.build({
|
|
169
|
-
...shared,
|
|
170
|
-
entryPoints: [spaEntry],
|
|
171
|
-
outfile: `${this.root}${paths.emroute}`,
|
|
172
|
-
}));
|
|
173
|
-
|
|
174
|
-
// App bundle — generate main.ts if absent, virtual plugin resolves manifests
|
|
175
|
-
if (this.config.entryPoint) {
|
|
176
|
-
if ((await this.query(this.config.entryPoint)).status === 404) {
|
|
177
|
-
const hasRoutes = (await this.query((this.config.routesDir ?? DEFAULT_ROUTES_DIR) + '/')).status !== 404;
|
|
178
|
-
const hasWidgets = (await this.query((this.config.widgetsDir ?? DEFAULT_WIDGETS_DIR) + '/')).status !== 404;
|
|
179
|
-
const code = generateMainTs('root', hasRoutes, hasWidgets, '@emkodev/emroute');
|
|
180
|
-
await this.command(this.config.entryPoint, { body: code });
|
|
181
|
-
}
|
|
182
|
-
const manifestPlugin = createManifestPlugin({
|
|
183
|
-
runtime: this,
|
|
184
|
-
resolveDir: this.root,
|
|
185
|
-
});
|
|
186
|
-
builds.push(esbuild.build({
|
|
187
|
-
...shared,
|
|
188
|
-
entryPoints: [`${this.root}${this.config.entryPoint}`],
|
|
189
|
-
outfile: `${this.root}${paths.app}`,
|
|
190
|
-
external: [...EMROUTE_EXTERNALS],
|
|
191
|
-
plugins: [manifestPlugin, runtimeLoader],
|
|
192
|
-
}));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Widgets bundle
|
|
196
|
-
if (paths.widgets) {
|
|
197
|
-
const widgetsTsPath = paths.widgets.replace('.js', '.ts');
|
|
198
|
-
if ((await this.query(widgetsTsPath)).status !== 404) {
|
|
199
|
-
builds.push(esbuild.build({
|
|
200
|
-
...shared,
|
|
201
|
-
entryPoints: [`${this.root}${widgetsTsPath}`],
|
|
202
|
-
outfile: `${this.root}${paths.widgets}`,
|
|
203
|
-
external: [...EMROUTE_EXTERNALS],
|
|
204
|
-
plugins: [runtimeLoader],
|
|
205
|
-
}));
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const results = await Promise.all(builds);
|
|
210
|
-
|
|
211
|
-
// Write all output files through the runtime
|
|
212
|
-
for (const result of results) {
|
|
213
|
-
for (const file of result.outputFiles) {
|
|
214
|
-
const runtimePath = file.path.startsWith(this.root)
|
|
215
|
-
? file.path.slice(this.root.length)
|
|
216
|
-
: '/' + file.path;
|
|
217
|
-
await this.command(runtimePath, { body: file.contents as unknown as BodyInit });
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
await this.writeShell(paths);
|
|
222
|
-
|
|
223
|
-
await esbuild.stop();
|
|
224
|
-
UniversalFsRuntime._esbuild = null;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// ── Transpile / esbuild ───────────────────────────────────────────────
|
|
228
|
-
|
|
229
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
230
|
-
private static _esbuild: any = null;
|
|
231
|
-
|
|
232
|
-
private static async esbuild() {
|
|
233
|
-
if (!UniversalFsRuntime._esbuild) {
|
|
234
|
-
const consumerRequire = createRequire(process.cwd() + '/');
|
|
235
|
-
UniversalFsRuntime._esbuild = consumerRequire('esbuild');
|
|
236
|
-
}
|
|
237
|
-
return UniversalFsRuntime._esbuild;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
static override async transpile(source: string): Promise<string> {
|
|
241
|
-
const esbuild = await UniversalFsRuntime.esbuild();
|
|
242
|
-
const result = await esbuild.transform(source, { loader: 'ts' });
|
|
243
|
-
return result.code;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
static override async stopBundler(): Promise<void> {
|
|
247
|
-
if (UniversalFsRuntime._esbuild) {
|
|
248
|
-
await UniversalFsRuntime._esbuild.stop();
|
|
249
|
-
UniversalFsRuntime._esbuild = null;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
143
|
}
|