@ecopages/core 0.2.0-alpha.26 → 0.2.0-alpha.28

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 (106) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +63 -7
  3. package/package.json +8 -94
  4. package/src/adapters/bun/create-app.d.ts +1 -0
  5. package/src/adapters/bun/create-app.js +39 -2
  6. package/src/adapters/bun/hmr-manager.d.ts +1 -13
  7. package/src/adapters/bun/hmr-manager.js +1 -22
  8. package/src/adapters/bun/server-adapter.js +23 -4
  9. package/src/adapters/node/node-hmr-manager.d.ts +2 -14
  10. package/src/adapters/node/node-hmr-manager.js +2 -23
  11. package/src/adapters/shared/explicit-static-render-preparation.d.ts +25 -0
  12. package/src/adapters/shared/explicit-static-render-preparation.js +26 -0
  13. package/src/adapters/shared/explicit-static-route-matcher.d.ts +5 -2
  14. package/src/adapters/shared/explicit-static-route-matcher.js +14 -16
  15. package/src/adapters/shared/file-route-middleware-pipeline.d.ts +7 -10
  16. package/src/adapters/shared/file-route-middleware-pipeline.js +2 -11
  17. package/src/adapters/shared/fs-server-response-factory.d.ts +13 -9
  18. package/src/adapters/shared/fs-server-response-factory.js +10 -26
  19. package/src/adapters/shared/fs-server-response-matcher.d.ts +14 -6
  20. package/src/adapters/shared/fs-server-response-matcher.js +67 -28
  21. package/src/adapters/shared/render-context.d.ts +2 -2
  22. package/src/adapters/shared/server-adapter.d.ts +21 -10
  23. package/src/adapters/shared/server-adapter.js +171 -132
  24. package/src/adapters/shared/server-route-handler.d.ts +2 -2
  25. package/src/adapters/shared/server-route-handler.js +1 -1
  26. package/src/adapters/shared/server-static-builder.d.ts +4 -4
  27. package/src/config/README.md +1 -1
  28. package/src/config/config-builder.d.ts +2 -2
  29. package/src/config/config-builder.js +0 -5
  30. package/src/dev/host-runtime.d.ts +10 -0
  31. package/src/dev/host-runtime.js +24 -0
  32. package/src/eco/eco.js +7 -7
  33. package/src/eco/eco.types.d.ts +3 -3
  34. package/src/errors/index.d.ts +1 -0
  35. package/src/errors/index.js +3 -1
  36. package/src/hmr/strategies/js-hmr-strategy.d.ts +0 -5
  37. package/src/integrations/ghtml/ghtml-renderer.d.ts +0 -4
  38. package/src/integrations/ghtml/ghtml-renderer.js +1 -7
  39. package/src/plugins/eco-component-meta-plugin.js +0 -1
  40. package/src/plugins/integration-plugin.d.ts +14 -18
  41. package/src/plugins/integration-plugin.js +14 -21
  42. package/src/plugins/processor.d.ts +2 -0
  43. package/src/plugins/processor.js +6 -1
  44. package/src/route-renderer/GRAPH.md +81 -289
  45. package/src/route-renderer/README.md +67 -105
  46. package/src/route-renderer/orchestration/component-render-context.d.ts +24 -18
  47. package/src/route-renderer/orchestration/component-render-context.js +14 -14
  48. package/src/route-renderer/orchestration/declared-ownership-graph.d.ts +18 -0
  49. package/src/route-renderer/orchestration/declared-ownership-graph.js +34 -0
  50. package/src/route-renderer/orchestration/foreign-subtree-execution.service.d.ts +108 -0
  51. package/src/route-renderer/orchestration/foreign-subtree-execution.service.js +206 -0
  52. package/src/route-renderer/orchestration/integration-renderer.d.ts +96 -136
  53. package/src/route-renderer/orchestration/integration-renderer.js +280 -303
  54. package/src/route-renderer/orchestration/ownership-planning.service.d.ts +24 -0
  55. package/src/route-renderer/orchestration/ownership-planning.service.js +63 -0
  56. package/src/route-renderer/orchestration/ownership-validation.service.d.ts +29 -0
  57. package/src/route-renderer/orchestration/ownership-validation.service.js +53 -0
  58. package/src/route-renderer/orchestration/queued-foreign-subtree-resolution.service.d.ts +90 -0
  59. package/src/route-renderer/orchestration/{queued-boundary-runtime.service.js → queued-foreign-subtree-resolution.service.js} +28 -25
  60. package/src/route-renderer/orchestration/render-output.utils.d.ts +3 -3
  61. package/src/route-renderer/orchestration/render-output.utils.js +6 -6
  62. package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +120 -0
  63. package/src/route-renderer/orchestration/{render-preparation.service.js → route-render-orchestrator.js} +132 -108
  64. package/src/route-renderer/page-loading/component-dependency-collection.js +8 -1
  65. package/src/route-renderer/page-loading/dependency-resolver.js +5 -7
  66. package/src/route-renderer/page-loading/page-dependency-bundling.d.ts +1 -1
  67. package/src/route-renderer/page-loading/page-dependency-bundling.js +41 -19
  68. package/src/route-renderer/route-renderer.d.ts +28 -26
  69. package/src/route-renderer/route-renderer.js +4 -27
  70. package/src/router/README.md +16 -19
  71. package/src/router/server/route-registry.d.ts +78 -0
  72. package/src/router/server/route-registry.js +262 -0
  73. package/src/services/README.md +1 -2
  74. package/src/services/assets/asset-processing-service/assets.types.d.ts +3 -0
  75. package/src/services/assets/asset-processing-service/index.d.ts +1 -0
  76. package/src/services/assets/asset-processing-service/index.js +1 -0
  77. package/src/services/assets/asset-processing-service/page-package.d.ts +3 -0
  78. package/src/services/assets/asset-processing-service/page-package.js +74 -0
  79. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.js +4 -4
  80. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js +6 -3
  81. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.js +9 -3
  82. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +4 -2
  83. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.js +2 -1
  84. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.js +3 -1
  85. package/src/services/module-loading/node-bootstrap-plugin.js +15 -3
  86. package/src/static-site-generator/static-site-generator.d.ts +20 -21
  87. package/src/static-site-generator/static-site-generator.js +107 -140
  88. package/src/types/internal-types.d.ts +13 -12
  89. package/src/types/public-types.d.ts +46 -36
  90. package/src/watchers/project-watcher.test-helpers.js +5 -5
  91. package/src/route-renderer/orchestration/boundary-planning.service.d.ts +0 -25
  92. package/src/route-renderer/orchestration/boundary-planning.service.js +0 -97
  93. package/src/route-renderer/orchestration/page-packaging.service.d.ts +0 -16
  94. package/src/route-renderer/orchestration/page-packaging.service.js +0 -66
  95. package/src/route-renderer/orchestration/queued-boundary-runtime.service.d.ts +0 -89
  96. package/src/route-renderer/orchestration/render-execution.service.d.ts +0 -43
  97. package/src/route-renderer/orchestration/render-execution.service.js +0 -106
  98. package/src/route-renderer/orchestration/render-preparation.service.d.ts +0 -120
  99. package/src/route-renderer/orchestration/route-shell-composer.service.d.ts +0 -50
  100. package/src/route-renderer/orchestration/route-shell-composer.service.js +0 -81
  101. package/src/router/server/fs-router-scanner.d.ts +0 -41
  102. package/src/router/server/fs-router-scanner.js +0 -161
  103. package/src/router/server/fs-router.d.ts +0 -26
  104. package/src/router/server/fs-router.js +0 -100
  105. package/src/services/runtime-state/runtime-specifier-registry.service.d.ts +0 -69
  106. package/src/services/runtime-state/runtime-specifier-registry.service.js +0 -37
@@ -48,7 +48,8 @@ class ContentStylesheetProcessor extends BaseProcessor {
48
48
  position: dep.position,
49
49
  attributes: dep.attributes,
50
50
  inline: dep.inline,
51
- packageRole: dep.packageRole
51
+ packageRole: dep.packageRole,
52
+ bundledSourceFilepaths: dep.bundledSourceFilepaths
52
53
  };
53
54
  });
54
55
  }
@@ -52,12 +52,14 @@ class FileStylesheetProcessor extends BaseProcessor {
52
52
  }
53
53
  return {
54
54
  filepath,
55
+ sourceFilepath: dep.filepath,
55
56
  content: dep.inline ? processedContent : void 0,
56
57
  kind: "stylesheet",
57
58
  position: dep.position,
58
59
  attributes: dep.attributes,
59
60
  inline: dep.inline,
60
- packageRole: dep.packageRole
61
+ packageRole: dep.packageRole,
62
+ bundledSourceFilepaths: dep.bundledSourceFilepaths
61
63
  };
62
64
  });
63
65
  }
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync } from "node:fs";
2
+ import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync, unlinkSync } from "node:fs";
3
3
  import { createRequire } from "node:module";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import { resolveInternalExecutionDir } from "../../utils/resolve-work-dir.js";
@@ -51,7 +51,7 @@ function ensureRuntimePackageLink(nodeModulesDir, specifier, resolvedPath) {
51
51
  if (linkPointsToPackage(linkPath, packageRoot)) {
52
52
  return;
53
53
  }
54
- rmSync(linkPath, { recursive: true, force: true });
54
+ removeRuntimePackageLink(linkPath);
55
55
  }
56
56
  try {
57
57
  symlinkSync(packageRoot, linkPath, "dir");
@@ -62,10 +62,22 @@ function ensureRuntimePackageLink(nodeModulesDir, specifier, resolvedPath) {
62
62
  if (linkPointsToPackage(linkPath, packageRoot)) {
63
63
  return;
64
64
  }
65
- rmSync(linkPath, { recursive: true, force: true });
65
+ removeRuntimePackageLink(linkPath);
66
66
  symlinkSync(packageRoot, linkPath, "dir");
67
67
  }
68
68
  }
69
+ function removeRuntimePackageLink(linkPath) {
70
+ try {
71
+ const stats = lstatSync(linkPath);
72
+ if (stats.isSymbolicLink()) {
73
+ unlinkSync(linkPath);
74
+ return;
75
+ }
76
+ } catch {
77
+ return;
78
+ }
79
+ rmSync(linkPath, { recursive: true, force: true });
80
+ }
69
81
  function getNodeUnsupportedBuiltinError(specifier, importer) {
70
82
  return `Node bootstrap transpilation does not support Bun builtin specifier ${JSON.stringify(specifier)}${importer ? ` imported from ${importer}` : ""}.`;
71
83
  }
@@ -1,7 +1,14 @@
1
1
  import type { EcoPagesAppConfig } from '../types/internal-types.js';
2
2
  import type { StaticRoute } from '../types/public-types.js';
3
- import type { RouteRendererFactory } from '../route-renderer/route-renderer.js';
4
- import type { FSRouter } from '../router/server/fs-router.js';
3
+ import type { PageRendererResolver, StaticGenerationRendererResolver } from '../route-renderer/route-renderer.js';
4
+ import type { StaticGenerationRoute } from '../router/server/route-registry.js';
5
+ type StaticGenerationRouteSource = {
6
+ listStaticGenerationRoutes(input: {
7
+ runtimeOrigin: string;
8
+ }): Promise<readonly StaticGenerationRoute[]>;
9
+ };
10
+ type StaticPageRouteRendererFactory = PageRendererResolver;
11
+ type StaticGenerationRendererFactory = StaticGenerationRendererResolver;
5
12
  export declare const STATIC_SITE_GENERATOR_ERRORS: {
6
13
  readonly ROUTE_RENDERER_FACTORY_REQUIRED: "RouteRendererFactory is required for render strategy";
7
14
  readonly unsupportedBodyType: (bodyType: string) => string;
@@ -53,14 +60,9 @@ export declare class StaticSiteGenerator {
53
60
  * Collects parent directories that must exist for the generated route set.
54
61
  */
55
62
  getDirectories(routes: string[]): string[];
56
- /**
57
- * Extracts dynamic parameters from the actual path based on the template path.
58
- *
59
- * @param templatePath - The template path (e.g., "/blog/[slug]")
60
- * @param actualPath - The actual path (e.g., "/blog/my-post")
61
- * @returns A record of extracted parameters (e.g., { slug: "my-post" })
62
- */
63
- private extractParams;
63
+ private writeStaticOutput;
64
+ private getStaticBuildStrategy;
65
+ private createFilesystemStaticContents;
64
66
  /**
65
67
  * Generates static output for all filesystem-discovered routes.
66
68
  *
@@ -69,15 +71,15 @@ export declare class StaticSiteGenerator {
69
71
  * issuing a request against the running server origin. Render-strategy routes
70
72
  * go through the normal route renderer directly.
71
73
  */
72
- generateStaticPages(router: FSRouter, baseUrl: string, routeRendererFactory?: RouteRendererFactory): Promise<void>;
74
+ generateStaticPages(router: StaticGenerationRouteSource, baseUrl: string, routeRendererFactory?: StaticPageRouteRendererFactory): Promise<void>;
73
75
  private resolveStaticFetchUrl;
74
76
  /**
75
77
  * Executes the full static-generation workflow for one app run.
76
78
  */
77
79
  run({ router, baseUrl, routeRendererFactory, staticRoutes, }: {
78
- router: FSRouter;
80
+ router: StaticGenerationRouteSource;
79
81
  baseUrl: string;
80
- routeRendererFactory?: RouteRendererFactory;
82
+ routeRendererFactory?: StaticGenerationRendererFactory;
81
83
  staticRoutes?: StaticRoute[];
82
84
  }): Promise<void>;
83
85
  /**
@@ -85,14 +87,10 @@ export declare class StaticSiteGenerator {
85
87
  * These routes use eco.page views via loader functions for HMR support.
86
88
  */
87
89
  private generateExplicitStaticPages;
88
- /**
89
- * Generate a single static page for a non-dynamic route.
90
- */
91
- private generateSingleStaticRoute;
92
- /**
93
- * Generate static pages for a dynamic route using staticPaths.
94
- */
95
- private generateDynamicStaticRoute;
90
+ private generateExplicitStaticRoute;
91
+ private planExplicitStaticRoute;
92
+ private createExplicitStaticContents;
93
+ private listExplicitStaticRouteEntries;
96
94
  /**
97
95
  * Resolve a route path template with actual params.
98
96
  * Supports both :param and [param] syntax.
@@ -103,3 +101,4 @@ export declare class StaticSiteGenerator {
103
101
  */
104
102
  private getOutputPath;
105
103
  }
104
+ export {};
@@ -2,6 +2,7 @@ import path from "node:path";
2
2
  import { appLogger } from "../global/app-logger.js";
3
3
  import { fileSystem } from "@ecopages/file-system";
4
4
  import { PathUtils } from "../utils/path-utils.module.js";
5
+ import { prepareExplicitStaticRender } from "../adapters/shared/explicit-static-render-preparation.js";
5
6
  const STATIC_SITE_GENERATOR_ERRORS = {
6
7
  ROUTE_RENDERER_FACTORY_REQUIRED: "RouteRendererFactory is required for render strategy",
7
8
  unsupportedBodyType: (bodyType) => `Unsupported body type for static generation: ${bodyType}`,
@@ -34,7 +35,7 @@ class StaticSiteGenerator {
34
35
  * static generation.
35
36
  */
36
37
  async shouldSkipStaticPageFile(filePath, routeRendererFactory) {
37
- const module = await routeRendererFactory.createRenderer(filePath).loadPageModule(filePath, {
38
+ const module = await routeRendererFactory.getPageRenderer(filePath).loadPageModule(filePath, {
38
39
  cacheScope: "static-page-probe"
39
40
  });
40
41
  if (module.default?.cache !== "dynamic") {
@@ -93,25 +94,50 @@ class StaticSiteGenerator {
93
94
  }
94
95
  return Array.from(directories);
95
96
  }
96
- /**
97
- * Extracts dynamic parameters from the actual path based on the template path.
98
- *
99
- * @param templatePath - The template path (e.g., "/blog/[slug]")
100
- * @param actualPath - The actual path (e.g., "/blog/my-post")
101
- * @returns A record of extracted parameters (e.g., { slug: "my-post" })
102
- */
103
- extractParams(templatePath, actualPath) {
104
- const templateSegments = templateSegmentsFromPath(templatePath);
105
- const actualSegments = templateSegmentsFromPath(actualPath);
106
- const params = {};
107
- for (let i = 0; i < templateSegments.length; i++) {
108
- const segment = templateSegments[i];
109
- if (segment.startsWith("[") && segment.endsWith("]")) {
110
- const paramName = segment.slice(1, -1).replace("...", "");
111
- params[paramName] = actualSegments[i];
97
+ writeStaticOutput(routePath, contents, directories = []) {
98
+ const outputPath = this.getOutputPath(routePath, directories);
99
+ fileSystem.ensureDir(path.dirname(outputPath));
100
+ fileSystem.write(outputPath, contents);
101
+ return outputPath;
102
+ }
103
+ getStaticBuildStrategy(filePath) {
104
+ const ext = PathUtils.getEcoTemplateExtension(filePath);
105
+ const integration = this.appConfig.integrations.find((plugin) => plugin.extensions.includes(ext));
106
+ return integration?.staticBuildStep || "render";
107
+ }
108
+ async createFilesystemStaticContents(route, baseUrl, routeRendererFactory) {
109
+ const {
110
+ templateRoute: { filePath },
111
+ params
112
+ } = route;
113
+ if (this.getStaticBuildStrategy(filePath) === "fetch") {
114
+ const fetchUrl = this.resolveStaticFetchUrl(route.requestUrl, baseUrl);
115
+ const response = await fetch(fetchUrl);
116
+ if (!response.ok) {
117
+ appLogger.error(`Failed to fetch ${fetchUrl}. Status: ${response.status}`);
118
+ return null;
112
119
  }
120
+ return response.text();
121
+ }
122
+ if (!routeRendererFactory) {
123
+ throw new Error(STATIC_SITE_GENERATOR_ERRORS.ROUTE_RENDERER_FACTORY_REQUIRED);
124
+ }
125
+ if (await this.shouldSkipStaticPageFile(filePath, routeRendererFactory)) {
126
+ return null;
113
127
  }
114
- return params;
128
+ const renderer = routeRendererFactory.getPageRenderer(filePath);
129
+ const result = await renderer.execute({
130
+ file: filePath,
131
+ params
132
+ });
133
+ const body = result.body;
134
+ if (typeof body === "string" || Buffer.isBuffer(body)) {
135
+ return body;
136
+ }
137
+ if (body instanceof ReadableStream) {
138
+ return new Response(body).text();
139
+ }
140
+ throw new Error(STATIC_SITE_GENERATOR_ERRORS.unsupportedBodyType(typeof body));
115
141
  }
116
142
  /**
117
143
  * Generates static output for all filesystem-discovered routes.
@@ -122,76 +148,22 @@ class StaticSiteGenerator {
122
148
  * go through the normal route renderer directly.
123
149
  */
124
150
  async generateStaticPages(router, baseUrl, routeRendererFactory) {
125
- const routes = Object.keys(router.routes).filter((route) => !route.includes("["));
126
- appLogger.debug("Static Pages", routes);
127
- const directories = this.getDirectories(routes);
128
- for (const directory of directories) {
129
- fileSystem.ensureDir(path.join(this.getExportDir(), directory));
130
- }
151
+ const routes = await router.listStaticGenerationRoutes({ runtimeOrigin: baseUrl });
152
+ appLogger.debug(
153
+ "Static Pages",
154
+ routes.map((route) => route.requestUrl)
155
+ );
156
+ const directories = this.getDirectories(routes.map((route) => route.requestUrl));
131
157
  for (const route of routes) {
132
158
  try {
133
- const { filePath, pathname: routePathname } = router.routes[route];
134
- const ext = PathUtils.getEcoTemplateExtension(filePath);
135
- const integration = this.appConfig.integrations.find((plugin) => plugin.extensions.includes(ext));
136
- const strategy = integration?.staticBuildStep || "render";
137
- let contents;
138
- if (strategy === "fetch") {
139
- const fetchUrl = this.resolveStaticFetchUrl(route, baseUrl);
140
- const response = await fetch(fetchUrl);
141
- if (!response.ok) {
142
- appLogger.error(`Failed to fetch ${fetchUrl}. Status: ${response.status}`);
143
- continue;
144
- }
145
- contents = await response.text();
146
- } else {
147
- if (!routeRendererFactory) {
148
- throw new Error(STATIC_SITE_GENERATOR_ERRORS.ROUTE_RENDERER_FACTORY_REQUIRED);
149
- }
150
- if (await this.shouldSkipStaticPageFile(filePath, routeRendererFactory)) {
151
- continue;
152
- }
153
- let pathname2 = routePathname;
154
- const pathnameSegments2 = pathname2.split("/").filter(Boolean);
155
- if (pathname2 === "/") {
156
- pathname2 = "/index.html";
157
- } else if (pathnameSegments2.join("/").includes("[")) {
158
- pathname2 = `${route.replace(router.origin, "")}.html`;
159
- } else if (pathnameSegments2.length >= 1 && directories.includes(`/${pathnameSegments2.join("/")}`)) {
160
- pathname2 = `${pathname2.endsWith("/") ? pathname2 : `${pathname2}/`}index.html`;
161
- } else {
162
- pathname2 += ".html";
163
- }
164
- const renderer = routeRendererFactory.createRenderer(filePath);
165
- const params = this.extractParams(routePathname, pathname2.replace(".html", ""));
166
- const result = await renderer.createRoute({
167
- file: filePath,
168
- params
169
- });
170
- const body = result.body;
171
- if (typeof body === "string" || Buffer.isBuffer(body)) {
172
- contents = body;
173
- } else if (body instanceof ReadableStream) {
174
- contents = await new Response(body).text();
175
- } else {
176
- throw new Error(STATIC_SITE_GENERATOR_ERRORS.unsupportedBodyType(typeof body));
177
- }
178
- }
179
- let pathname = routePathname;
180
- const pathnameSegments = pathname.split("/").filter(Boolean);
181
- if (pathname === "/") {
182
- pathname = "/index.html";
183
- } else if (pathnameSegments.join("/").includes("[")) {
184
- pathname = `${route.replace(router.origin, "")}.html`;
185
- } else if (pathnameSegments.length >= 1 && directories.includes(`/${pathnameSegments.join("/")}`)) {
186
- pathname = `${pathname.endsWith("/") ? pathname : `${pathname}/`}index.html`;
187
- } else {
188
- pathname += ".html";
159
+ const contents = await this.createFilesystemStaticContents(route, baseUrl, routeRendererFactory);
160
+ if (contents === null) {
161
+ continue;
189
162
  }
190
- const outputPath = path.join(this.getExportDir(), pathname);
191
- fileSystem.write(outputPath, contents);
163
+ this.writeStaticOutput(route.pathname, contents, directories);
192
164
  } catch (error) {
193
165
  appLogger.error(
194
- `Error generating static page for ${route}:`,
166
+ `Error generating static page for ${route.requestUrl}:`,
195
167
  error instanceof Error ? error : String(error)
196
168
  );
197
169
  }
@@ -239,12 +211,7 @@ class StaticSiteGenerator {
239
211
  if (this.shouldSkipStaticView(route.path, view)) {
240
212
  continue;
241
213
  }
242
- const isDynamic = route.path.includes(":") || route.path.includes("[");
243
- if (isDynamic) {
244
- await this.generateDynamicStaticRoute(route.path, view, routeRendererFactory);
245
- } else {
246
- await this.generateSingleStaticRoute(route.path, view, routeRendererFactory);
247
- }
214
+ await this.generateExplicitStaticRoute(route.path, view, routeRendererFactory);
248
215
  } catch (error) {
249
216
  appLogger.error(
250
217
  `Error generating explicit static page for ${route.path}:`,
@@ -253,63 +220,64 @@ class StaticSiteGenerator {
253
220
  }
254
221
  }
255
222
  }
256
- /**
257
- * Generate a single static page for a non-dynamic route.
258
- */
259
- async generateSingleStaticRoute(routePath, view, routeRendererFactory) {
260
- const integrationName = view.config?.__eco?.integration;
261
- if (!integrationName) {
262
- throw new Error(STATIC_SITE_GENERATOR_ERRORS.missingIntegration(routePath));
263
- }
264
- const renderer = routeRendererFactory.getRendererByIntegration(integrationName);
265
- if (!renderer) {
266
- throw new Error(STATIC_SITE_GENERATOR_ERRORS.noRendererForIntegration(integrationName));
223
+ async generateExplicitStaticRoute(routePath, view, routeRendererFactory) {
224
+ const { renderer, routeEntries } = await this.planExplicitStaticRoute(routePath, view, routeRendererFactory);
225
+ for (const { pathname, params } of routeEntries) {
226
+ const contents = await this.createExplicitStaticContents(
227
+ routePath,
228
+ view,
229
+ params,
230
+ routeRendererFactory,
231
+ renderer
232
+ );
233
+ const outputPath = this.writeStaticOutput(pathname, contents);
234
+ appLogger.debug(`Generated static page: ${pathname} -> ${outputPath}`);
267
235
  }
268
- const props = view.staticProps ? (await view.staticProps({
269
- pathname: { params: {} },
236
+ }
237
+ async planExplicitStaticRoute(routePath, view, routeRendererFactory) {
238
+ const { renderer } = await prepareExplicitStaticRender({
239
+ routePath,
240
+ view,
241
+ params: {},
270
242
  appConfig: this.appConfig,
271
- runtimeOrigin: this.appConfig.baseUrl
272
- })).props : {};
273
- const response = await renderer.renderToResponse(view, props, {});
274
- const contents = await response.text();
275
- const outputPath = this.getOutputPath(routePath);
276
- fileSystem.ensureDir(path.dirname(outputPath));
277
- fileSystem.write(outputPath, contents);
278
- appLogger.debug(`Generated static page: ${routePath} -> ${outputPath}`);
243
+ runtimeOrigin: this.appConfig.baseUrl,
244
+ routeRendererFactory,
245
+ errors: STATIC_SITE_GENERATOR_ERRORS
246
+ });
247
+ return {
248
+ renderer,
249
+ routeEntries: await this.listExplicitStaticRouteEntries(routePath, view)
250
+ };
279
251
  }
280
- /**
281
- * Generate static pages for a dynamic route using staticPaths.
282
- */
283
- async generateDynamicStaticRoute(routePath, view, routeRendererFactory) {
252
+ async createExplicitStaticContents(routePath, view, params, routeRendererFactory, renderer) {
253
+ const { props, view: renderableView } = await prepareExplicitStaticRender({
254
+ routePath,
255
+ view,
256
+ params,
257
+ appConfig: this.appConfig,
258
+ runtimeOrigin: this.appConfig.baseUrl,
259
+ routeRendererFactory,
260
+ errors: STATIC_SITE_GENERATOR_ERRORS
261
+ });
262
+ const response = await renderer.renderToResponse(renderableView, props, {});
263
+ return response.text();
264
+ }
265
+ async listExplicitStaticRouteEntries(routePath, view) {
266
+ const isDynamic = routePath.includes(":") || routePath.includes("[");
267
+ if (!isDynamic) {
268
+ return [{ pathname: routePath, params: {} }];
269
+ }
284
270
  if (!view.staticPaths) {
285
271
  throw new Error(STATIC_SITE_GENERATOR_ERRORS.dynamicRouteRequiresStaticPaths(routePath));
286
272
  }
287
- const integrationName = view.config?.__eco?.integration;
288
- if (!integrationName) {
289
- throw new Error(STATIC_SITE_GENERATOR_ERRORS.missingIntegration(routePath));
290
- }
291
- const renderer = routeRendererFactory.getRendererByIntegration(integrationName);
292
- if (!renderer) {
293
- throw new Error(STATIC_SITE_GENERATOR_ERRORS.noRendererForIntegration(integrationName));
294
- }
295
273
  const { paths } = await view.staticPaths({
296
274
  appConfig: this.appConfig,
297
275
  runtimeOrigin: this.appConfig.baseUrl
298
276
  });
299
- for (const { params } of paths) {
300
- const resolvedPath = this.resolveRoutePath(routePath, params);
301
- const props = view.staticProps ? (await view.staticProps({
302
- pathname: { params },
303
- appConfig: this.appConfig,
304
- runtimeOrigin: this.appConfig.baseUrl
305
- })).props : {};
306
- const response = await renderer.renderToResponse(view, props, {});
307
- const contents = await response.text();
308
- const outputPath = this.getOutputPath(resolvedPath);
309
- fileSystem.ensureDir(path.dirname(outputPath));
310
- fileSystem.write(outputPath, contents);
311
- appLogger.debug(`Generated static page: ${resolvedPath} -> ${outputPath}`);
312
- }
277
+ return paths.map(({ params }) => ({
278
+ pathname: this.resolveRoutePath(routePath, params),
279
+ params
280
+ }));
313
281
  }
314
282
  /**
315
283
  * Resolve a route path template with actual params.
@@ -328,10 +296,12 @@ class StaticSiteGenerator {
328
296
  /**
329
297
  * Get the output file path for a given route.
330
298
  */
331
- getOutputPath(routePath) {
299
+ getOutputPath(routePath, directories = []) {
332
300
  let outputName;
333
301
  if (routePath === "/") {
334
302
  outputName = "index.html";
303
+ } else if (directories.includes(routePath)) {
304
+ outputName = `${routePath}/index.html`;
335
305
  } else if (routePath.endsWith("/")) {
336
306
  outputName = `${routePath}index.html`;
337
307
  } else {
@@ -340,9 +310,6 @@ class StaticSiteGenerator {
340
310
  return path.join(this.getExportDir(), outputName);
341
311
  }
342
312
  }
343
- function templateSegmentsFromPath(path2) {
344
- return path2.split("/").filter(Boolean);
345
- }
346
313
  export {
347
314
  STATIC_SITE_GENERATOR_ERRORS,
348
315
  StaticSiteGenerator
@@ -1,17 +1,16 @@
1
1
  import type { EcoBuildPlugin } from '../build/build-types.js';
2
2
  import type { AppBuildManifest } from '../build/build-manifest.js';
3
3
  import type { BuildAdapter, BuildExecutor, BuildOwnership } from '../build/build-adapter.js';
4
- import type { IntegrationPlugin } from '../plugins/integration-plugin.js';
4
+ import type { AnyIntegrationPlugin } from '../plugins/integration-plugin.js';
5
5
  import type { Processor } from '../plugins/processor.js';
6
6
  import type { EcoSourceTransform } from '../plugins/source-transform.js';
7
7
  import type { PageMetadataProps } from './public-types.js';
8
- import type { FSRouter } from '../router/server/fs-router.js';
8
+ import type { RouteRegistry } from '../router/server/route-registry.js';
9
9
  import type { CacheConfig } from '../services/cache/cache.types.js';
10
10
  import type { DevGraphService } from '../services/runtime-state/dev-graph.service.js';
11
11
  import type { AppModuleLoader } from '../services/module-loading/app-module-loader.service.js';
12
12
  import type { SourceModuleLoader } from '../services/module-loading/module-loading-types.js';
13
13
  import type { EntrypointDependencyGraph } from '../services/runtime-state/entrypoint-dependency-graph.service.js';
14
- import type { RuntimeSpecifierRegistry } from '../services/runtime-state/runtime-specifier-registry.service.js';
15
14
  import type { ServerInvalidationState } from '../services/runtime-state/server-invalidation-state.service.js';
16
15
  import type { ServerModuleTranspiler } from '../services/module-loading/server-module-transpiler.service.js';
17
16
  export interface RobotsPreference {
@@ -102,7 +101,7 @@ export type EcoPagesAppConfig = {
102
101
  */
103
102
  defaultMetadata: PageMetadataProps;
104
103
  /** Integrations plugins */
105
- integrations: IntegrationPlugin[];
104
+ integrations: AnyIntegrationPlugin[];
106
105
  /** Integrations dependencies */
107
106
  integrationsDependencies: IntegrationDependencyConfig[];
108
107
  /** Derived Paths */
@@ -155,7 +154,6 @@ export type EcoPagesAppConfig = {
155
154
  entrypointDependencyGraph?: EntrypointDependencyGraph;
156
155
  hostModuleLoader?: SourceModuleLoader;
157
156
  rendererModuleContext?: unknown;
158
- runtimeSpecifierRegistry?: RuntimeSpecifierRegistry;
159
157
  serverInvalidationState?: ServerInvalidationState;
160
158
  serverModuleTranspiler?: ServerModuleTranspiler;
161
159
  };
@@ -184,11 +182,14 @@ export type RouteKind = 'exact' | 'catch-all' | 'dynamic';
184
182
  * Represents the result of a route match.
185
183
  */
186
184
  export type MatchResult = {
187
- filePath: string;
188
- kind: RouteKind;
189
- pathname: string;
190
- query?: Record<string, string>;
191
- params?: Record<string, string | string[]>;
185
+ requestedPathname: string;
186
+ templateRoute: {
187
+ filePath: string;
188
+ kind: RouteKind;
189
+ pathname: string;
190
+ };
191
+ query: Record<string, string>;
192
+ params: Record<string, string | string[]>;
192
193
  };
193
194
  /**
194
195
  * Represents a route in EcoPages.
@@ -214,10 +215,10 @@ export type FileSystemServerOptions = {
214
215
  */
215
216
  export interface EcoPagesFileSystemServerAdapter<ServerInstanceOptions = unknown> {
216
217
  startServer(serverOptions: ServerInstanceOptions): {
217
- router: FSRouter;
218
+ router: RouteRegistry;
218
219
  server: unknown;
219
220
  } | Promise<{
220
- router: FSRouter;
221
+ router: RouteRegistry;
221
222
  server: unknown;
222
223
  }>;
223
224
  }