@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.
Files changed (97) hide show
  1. package/dist/emroute.js +2757 -0
  2. package/dist/emroute.js.map +7 -0
  3. package/dist/runtime/abstract.runtime.d.ts +0 -28
  4. package/dist/runtime/abstract.runtime.js +10 -58
  5. package/dist/runtime/abstract.runtime.js.map +1 -1
  6. package/dist/runtime/bun/esbuild-runtime-loader.plugin.js +3 -0
  7. package/dist/runtime/bun/esbuild-runtime-loader.plugin.js.map +1 -1
  8. package/dist/runtime/bun/fs/bun-fs.runtime.d.ts +0 -5
  9. package/dist/runtime/bun/fs/bun-fs.runtime.js +1 -95
  10. package/dist/runtime/bun/fs/bun-fs.runtime.js.map +1 -1
  11. package/dist/runtime/bun/sqlite/bun-sqlite.runtime.d.ts +0 -5
  12. package/dist/runtime/bun/sqlite/bun-sqlite.runtime.js +2 -96
  13. package/dist/runtime/bun/sqlite/bun-sqlite.runtime.js.map +1 -1
  14. package/dist/runtime/fetch.runtime.d.ts +26 -0
  15. package/dist/runtime/fetch.runtime.js +55 -0
  16. package/dist/runtime/fetch.runtime.js.map +1 -0
  17. package/dist/runtime/sitemap.generator.d.ts +4 -4
  18. package/dist/runtime/sitemap.generator.js +32 -11
  19. package/dist/runtime/sitemap.generator.js.map +1 -1
  20. package/dist/runtime/universal/fs/universal-fs.runtime.d.ts +0 -5
  21. package/dist/runtime/universal/fs/universal-fs.runtime.js +1 -95
  22. package/dist/runtime/universal/fs/universal-fs.runtime.js.map +1 -1
  23. package/dist/server/build.util.d.ts +38 -0
  24. package/dist/server/build.util.js +133 -0
  25. package/dist/server/build.util.js.map +1 -0
  26. package/dist/server/codegen.util.d.ts +3 -0
  27. package/dist/server/codegen.util.js +28 -10
  28. package/dist/server/codegen.util.js.map +1 -1
  29. package/dist/server/emroute.server.js +53 -29
  30. package/dist/server/emroute.server.js.map +1 -1
  31. package/dist/server/esbuild-manifest.plugin.js +6 -4
  32. package/dist/server/esbuild-manifest.plugin.js.map +1 -1
  33. package/dist/server/server-api.type.d.ts +6 -0
  34. package/dist/src/component/abstract.component.d.ts +5 -3
  35. package/dist/src/component/abstract.component.js.map +1 -1
  36. package/dist/src/element/component.element.js +5 -4
  37. package/dist/src/element/component.element.js.map +1 -1
  38. package/dist/src/renderer/spa/mod.d.ts +2 -3
  39. package/dist/src/renderer/spa/mod.js +2 -3
  40. package/dist/src/renderer/spa/mod.js.map +1 -1
  41. package/dist/src/renderer/spa/thin-client.d.ts +34 -0
  42. package/dist/src/renderer/spa/thin-client.js +138 -0
  43. package/dist/src/renderer/spa/thin-client.js.map +1 -0
  44. package/dist/src/renderer/ssr/html.renderer.d.ts +3 -3
  45. package/dist/src/renderer/ssr/html.renderer.js +6 -6
  46. package/dist/src/renderer/ssr/html.renderer.js.map +1 -1
  47. package/dist/src/renderer/ssr/md.renderer.d.ts +3 -3
  48. package/dist/src/renderer/ssr/md.renderer.js +12 -7
  49. package/dist/src/renderer/ssr/md.renderer.js.map +1 -1
  50. package/dist/src/renderer/ssr/ssr.renderer.d.ts +7 -6
  51. package/dist/src/renderer/ssr/ssr.renderer.js +42 -44
  52. package/dist/src/renderer/ssr/ssr.renderer.js.map +1 -1
  53. package/dist/src/route/route.core.d.ts +16 -6
  54. package/dist/src/route/route.core.js +44 -23
  55. package/dist/src/route/route.core.js.map +1 -1
  56. package/dist/src/type/route-tree.type.d.ts +2 -0
  57. package/dist/src/type/route.type.d.ts +6 -24
  58. package/dist/src/util/md.util.d.ts +8 -0
  59. package/dist/src/util/md.util.js +28 -0
  60. package/dist/src/util/md.util.js.map +1 -0
  61. package/dist/src/util/widget-resolve.util.js +6 -1
  62. package/dist/src/util/widget-resolve.util.js.map +1 -1
  63. package/dist/src/widget/breadcrumb.widget.d.ts +0 -1
  64. package/dist/src/widget/breadcrumb.widget.js +4 -15
  65. package/dist/src/widget/breadcrumb.widget.js.map +1 -1
  66. package/package.json +13 -2
  67. package/runtime/abstract.runtime.ts +9 -82
  68. package/runtime/bun/esbuild-runtime-loader.plugin.ts +2 -0
  69. package/runtime/bun/fs/bun-fs.runtime.ts +0 -109
  70. package/runtime/bun/sqlite/bun-sqlite.runtime.ts +1 -112
  71. package/runtime/fetch.runtime.ts +70 -0
  72. package/runtime/sitemap.generator.ts +37 -12
  73. package/runtime/universal/fs/universal-fs.runtime.ts +0 -109
  74. package/server/build.util.ts +168 -0
  75. package/server/codegen.util.ts +29 -11
  76. package/server/emroute.server.ts +50 -30
  77. package/server/esbuild-manifest.plugin.ts +5 -3
  78. package/server/server-api.type.ts +7 -0
  79. package/src/component/abstract.component.ts +5 -3
  80. package/src/element/component.element.ts +5 -4
  81. package/src/renderer/spa/mod.ts +2 -8
  82. package/src/renderer/spa/thin-client.ts +165 -0
  83. package/src/renderer/ssr/html.renderer.ts +6 -5
  84. package/src/renderer/ssr/md.renderer.ts +12 -6
  85. package/src/renderer/ssr/ssr.renderer.ts +54 -48
  86. package/src/route/route.core.ts +49 -28
  87. package/src/type/route-tree.type.ts +2 -0
  88. package/src/type/route.type.ts +7 -32
  89. package/src/util/md.util.ts +31 -0
  90. package/src/util/widget-resolve.util.ts +6 -1
  91. package/src/widget/breadcrumb.widget.ts +4 -16
  92. package/server/scanner.util.ts +0 -243
  93. package/src/renderer/spa/base.renderer.ts +0 -186
  94. package/src/renderer/spa/hash.renderer.ts +0 -238
  95. package/src/renderer/spa/html.renderer.ts +0 -399
  96. package/src/route/route.matcher.ts +0 -260
  97. 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 code = path.endsWith('.ts')
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 RoutesManifest. Pure function over manifest data,
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 { RoutesManifest } from '../src/type/route.type.ts';
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
- * Generate sitemap.xml content from a routes manifest.
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
- manifest: RoutesManifest,
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
- // Filter to page routes only (exclude error, redirect)
147
- const pages = manifest.routes.filter((r) => r.type === 'page');
171
+ const patterns: string[] = [];
172
+ collectPatterns(routeTree, '', patterns);
148
173
 
149
- for (const route of pages) {
150
- const routeOpts = resolveOptions(route.pattern, options);
174
+ for (const pattern of patterns) {
175
+ const routeOpts = resolveOptions(pattern, options);
151
176
 
152
- if (isDynamic(route.pattern)) {
177
+ if (isDynamic(pattern)) {
153
178
  // Dynamic route — use enumerator if provided, skip otherwise
154
- const enumerator = options.enumerators?.[route.pattern];
179
+ const enumerator = options.enumerators?.[pattern];
155
180
  if (!enumerator) continue;
156
181
 
157
182
  const values = await enumerator();
158
- const paths = expandDynamic(route.pattern, values);
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, route.pattern, bp),
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
  }