@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
@@ -8,9 +8,9 @@ The router layer determines what route is being handled and how the client runti
8
8
 
9
9
  It is responsible for:
10
10
 
11
- - filesystem route scanning and classification (`exact`, `dynamic`, `catch-all`)
12
- - matching incoming request URLs to discovered routes
13
- - server-side static-path expansion for dynamic routes
11
+ - filesystem route discovery and classification (`exact`, `dynamic`, `catch-all`)
12
+ - matching incoming request URLs to canonical template routes
13
+ - server-side static-path expansion for dynamic template routes
14
14
  - client-side navigation ownership and cross-runtime handoff
15
15
  - keeping route discovery separate from rendering execution
16
16
 
@@ -18,9 +18,8 @@ It is responsible for:
18
18
 
19
19
  ```
20
20
  router/
21
- ├── server/ # Server-side route scanning and matching
22
- ├── fs-router-scanner.ts # Scans the filesystem and classifies routes
23
- │ └── fs-router.ts # Matches request URLs to discovered routes
21
+ ├── server/ # Server-side route discovery and matching
22
+ └── route-registry.ts # Owns template routes, request matching, static expansion, and reload
24
23
  └── client/ # Browser-side navigation coordination
25
24
  ├── navigation-coordinator.ts # Singleton runtime coordinator
26
25
  └── link-intent.ts # Shared anchor detection and intent recovery helpers
@@ -28,9 +27,9 @@ router/
28
27
 
29
28
  ## `server/`
30
29
 
31
- ### `FSRouterScanner`
30
+ ### `RouteRegistry`
32
31
 
33
- Walks the pages directory and builds a `Routes` map keyed by route pathname.
32
+ Owns the canonical set of filesystem-discovered template routes for one application.
34
33
 
35
34
  File patterns determine route kind:
36
35
 
@@ -40,13 +39,17 @@ File patterns determine route kind:
40
39
  | `[slug].tsx` | `dynamic` | `/blog/[slug]` |
41
40
  | `[...slug].tsx` | `catch-all` | `/docs/[...slug]` |
42
41
 
43
- For `dynamic` routes, the scanner checks whether the page module exports `getStaticPaths`. If present, every returned path is expanded into a concrete `exact`-style route at scan time. In build mode, both `getStaticPaths` and `getStaticProps` are required or an invariant is thrown.
42
+ The registry stores canonical template routes only. It compiles request-time matching metadata during `init()` and `reload()`, but it does not execute `staticPaths()` during discovery.
44
43
 
45
- Catch-all routes are registered but skipped during static generation with a warning.
44
+ Build-time static expansion is a separate operation. The registry invokes `staticPaths()` lazily through an injected page-module adapter and returns concrete static path expansions for the static generator.
46
45
 
47
- ### `FSRouter`
46
+ The public interface is intentionally small:
48
47
 
49
- Holds the scanned `Routes` map and exposes a `match(requestUrl)` method used by adapters.
48
+ - `templateRoutes` ordered readonly template routes
49
+ - `init()` / `reload()` — rebuild discovery state and match metadata
50
+ - `matchRequest(requestUrl)` — request-time matching result with requested pathname, matched template route, params, and query
51
+ - `listStaticPathExpansions()` — build-time expansion for dynamic routes
52
+ - `listStaticGenerationRoutes()` — build-time route planning for static generation across exact and expanded routes
50
53
 
51
54
  Match priority:
52
55
 
@@ -54,12 +57,6 @@ Match priority:
54
57
  2. `dynamic` — the clean (bracket-stripped) prefix must appear in the pathname, and the segment counts must match.
55
58
  3. `catch-all` — the clean prefix must appear in the pathname.
56
59
 
57
- Additional helpers:
58
-
59
- - `getDynamicParams(route, pathname)` — extracts named and spread parameters from a matched dynamic or catch-all route.
60
- - `getSearchParams(url)` — converts `URLSearchParams` to a plain object.
61
- - `setOnReload(cb)` / `reload()` — re-scans routes and fires an optional callback, used during development HMR.
62
-
63
60
  ## `client/`
64
61
 
65
62
  ### Navigation Coordinator (`navigation-coordinator.ts`)
@@ -69,7 +66,7 @@ A singleton browser-side runtime stored on `window.__ECO_PAGES__.navigation`.
69
66
  Access it with:
70
67
 
71
68
  ```ts
72
- import { getEcoNavigationRuntime } from '@ecopages/core/router/client/navigation-coordinator';
69
+ import { getEcoNavigationRuntime } from '@ecopages/core/router/navigation-coordinator';
73
70
  const runtime = getEcoNavigationRuntime();
74
71
  ```
75
72
 
@@ -0,0 +1,78 @@
1
+ import type { EcoPagesAppConfig, RouteKind } from '../../types/internal-types.js';
2
+ export type RouteParams = Record<string, string | string[]>;
3
+ export type RouteQuery = Record<string, string>;
4
+ export type TemplateRoute = {
5
+ readonly pathname: string;
6
+ readonly kind: RouteKind;
7
+ readonly filePath: string;
8
+ readonly paramNames: readonly string[];
9
+ };
10
+ export type RouteMatch = {
11
+ readonly requestedPathname: string;
12
+ readonly templateRoute: TemplateRoute;
13
+ readonly params: RouteParams;
14
+ readonly query: RouteQuery;
15
+ };
16
+ export type StaticPathExpansion = {
17
+ readonly pathname: string;
18
+ readonly templateRoute: TemplateRoute;
19
+ readonly params: RouteParams;
20
+ };
21
+ export type StaticGenerationRoute = {
22
+ readonly requestUrl: string;
23
+ readonly pathname: string;
24
+ readonly templateRoute: TemplateRoute;
25
+ readonly params: RouteParams;
26
+ };
27
+ export type StaticPathsContext = {
28
+ readonly appConfig: EcoPagesAppConfig;
29
+ readonly runtimeOrigin: string;
30
+ };
31
+ export type RouteRegistryPageModule = {
32
+ readonly staticPaths?: (context: StaticPathsContext) => Promise<{
33
+ paths: Array<{
34
+ params: RouteParams;
35
+ }>;
36
+ }>;
37
+ readonly staticProps?: unknown;
38
+ };
39
+ export interface RouteRegistryPageModuleAdapter {
40
+ loadPageModule(filePath: string): Promise<RouteRegistryPageModule>;
41
+ }
42
+ export type RouteRegistryOptions = {
43
+ pagesDir: string;
44
+ appConfig: EcoPagesAppConfig;
45
+ origin: string;
46
+ templatesExt: readonly string[];
47
+ buildMode: boolean;
48
+ pageModuleAdapter: RouteRegistryPageModuleAdapter;
49
+ };
50
+ export declare class RouteRegistry {
51
+ readonly origin: string;
52
+ readonly appConfig: EcoPagesAppConfig;
53
+ readonly pagesDir: string;
54
+ readonly templatesExt: readonly string[];
55
+ readonly buildMode: boolean;
56
+ private readonly pageModuleAdapter;
57
+ private templateRouteList;
58
+ private readonly reloadListeners;
59
+ constructor(options: RouteRegistryOptions);
60
+ get templateRoutes(): readonly TemplateRoute[];
61
+ init(): Promise<void>;
62
+ reload(): Promise<void>;
63
+ onReload(listener: () => void): () => void;
64
+ matchRequest(requestUrl: string): RouteMatch | null;
65
+ listStaticPathExpansions(input: {
66
+ runtimeOrigin: string;
67
+ }): Promise<readonly StaticPathExpansion[]>;
68
+ listStaticGenerationRoutes(input: {
69
+ runtimeOrigin: string;
70
+ }): Promise<readonly StaticGenerationRoute[]>;
71
+ private scanTemplateRoutes;
72
+ private getRoutePath;
73
+ private classifyRouteKind;
74
+ private getParamNames;
75
+ private tryExtractParams;
76
+ private getSearchParams;
77
+ private resolveTemplatePath;
78
+ }
@@ -0,0 +1,262 @@
1
+ import path from "node:path";
2
+ import { existsSync } from "node:fs";
3
+ import { fileSystem } from "@ecopages/file-system";
4
+ import { appLogger } from "../../global/app-logger.js";
5
+ import { invariant } from "../../utils/invariant.js";
6
+ const ROUTE_PRIORITY = {
7
+ exact: 0,
8
+ dynamic: 1,
9
+ "catch-all": 2
10
+ };
11
+ class RouteRegistry {
12
+ origin;
13
+ appConfig;
14
+ pagesDir;
15
+ templatesExt;
16
+ buildMode;
17
+ pageModuleAdapter;
18
+ templateRouteList = [];
19
+ reloadListeners = /* @__PURE__ */ new Set();
20
+ constructor(options) {
21
+ this.origin = options.origin;
22
+ this.appConfig = options.appConfig;
23
+ this.pagesDir = options.pagesDir;
24
+ this.templatesExt = options.templatesExt;
25
+ this.buildMode = options.buildMode;
26
+ this.pageModuleAdapter = options.pageModuleAdapter;
27
+ }
28
+ get templateRoutes() {
29
+ return this.templateRouteList;
30
+ }
31
+ async init() {
32
+ this.templateRouteList = await this.scanTemplateRoutes();
33
+ appLogger.debug("RouteRegistry initialized", this.templateRouteList);
34
+ }
35
+ async reload() {
36
+ await this.init();
37
+ for (const listener of this.reloadListeners) {
38
+ listener();
39
+ }
40
+ }
41
+ onReload(listener) {
42
+ this.reloadListeners.add(listener);
43
+ return () => {
44
+ this.reloadListeners.delete(listener);
45
+ };
46
+ }
47
+ matchRequest(requestUrl) {
48
+ const url = new URL(requestUrl);
49
+ const requestedPathname = normalizePathname(url.pathname);
50
+ const query = this.getSearchParams(url);
51
+ for (const route of this.templateRouteList) {
52
+ if (route.kind !== "exact") {
53
+ continue;
54
+ }
55
+ if (requestedPathname === route.pathname || requestedPathname === `${route.pathname}/`) {
56
+ return {
57
+ requestedPathname,
58
+ templateRoute: route,
59
+ params: {},
60
+ query
61
+ };
62
+ }
63
+ }
64
+ for (const route of this.templateRouteList) {
65
+ if (route.kind !== "dynamic") {
66
+ continue;
67
+ }
68
+ const params = this.tryExtractParams(route, requestedPathname);
69
+ if (!params) {
70
+ continue;
71
+ }
72
+ return {
73
+ requestedPathname,
74
+ templateRoute: route,
75
+ params,
76
+ query
77
+ };
78
+ }
79
+ for (const route of this.templateRouteList) {
80
+ if (route.kind !== "catch-all") {
81
+ continue;
82
+ }
83
+ const params = this.tryExtractParams(route, requestedPathname);
84
+ if (!params) {
85
+ continue;
86
+ }
87
+ return {
88
+ requestedPathname,
89
+ templateRoute: route,
90
+ params,
91
+ query
92
+ };
93
+ }
94
+ return null;
95
+ }
96
+ async listStaticPathExpansions(input) {
97
+ const expansions = [];
98
+ for (const route of this.templateRouteList) {
99
+ if (route.kind === "exact") {
100
+ continue;
101
+ }
102
+ const pageModule = await this.pageModuleAdapter.loadPageModule(route.filePath);
103
+ const staticPaths = pageModule.staticPaths;
104
+ if (this.buildMode) {
105
+ invariant(staticPaths !== void 0, `[ecopages] Missing getStaticPaths in ${route.filePath}`);
106
+ invariant(
107
+ pageModule.staticProps !== void 0,
108
+ `[ecopages] Missing getStaticProps in ${route.filePath}`
109
+ );
110
+ }
111
+ if (!staticPaths) {
112
+ continue;
113
+ }
114
+ const result = await staticPaths({
115
+ appConfig: this.appConfig,
116
+ runtimeOrigin: input.runtimeOrigin
117
+ });
118
+ for (const { params } of result.paths) {
119
+ expansions.push({
120
+ pathname: this.resolveTemplatePath(route.pathname, params),
121
+ templateRoute: route,
122
+ params
123
+ });
124
+ }
125
+ }
126
+ return expansions;
127
+ }
128
+ async listStaticGenerationRoutes(input) {
129
+ const staticPathExpansions = await this.listStaticPathExpansions(input);
130
+ return [
131
+ ...this.templateRouteList.filter((route) => route.kind === "exact").map((route) => ({
132
+ requestUrl: `${input.runtimeOrigin}${route.pathname}`,
133
+ pathname: route.pathname,
134
+ templateRoute: route,
135
+ params: {}
136
+ })),
137
+ ...staticPathExpansions.map((route) => ({
138
+ requestUrl: `${input.runtimeOrigin}${route.pathname}`,
139
+ pathname: route.pathname,
140
+ templateRoute: route.templateRoute,
141
+ params: route.params
142
+ }))
143
+ ];
144
+ }
145
+ async scanTemplateRoutes() {
146
+ if (!existsSync(this.pagesDir)) {
147
+ return [];
148
+ }
149
+ const scannedFiles = await fileSystem.glob(
150
+ this.templatesExt.map((ext) => `**/*${ext}`),
151
+ {
152
+ cwd: this.pagesDir
153
+ }
154
+ );
155
+ const templateRoutes = [];
156
+ for await (const file of scannedFiles) {
157
+ if (file.includes(".ecopages-node.")) {
158
+ continue;
159
+ }
160
+ const routePathname = this.getRoutePath(file);
161
+ const filePath = path.join(this.pagesDir, file);
162
+ const kind = this.classifyRouteKind(filePath);
163
+ templateRoutes.push({
164
+ pathname: routePathname,
165
+ kind,
166
+ filePath,
167
+ paramNames: this.getParamNames(routePathname)
168
+ });
169
+ }
170
+ return templateRoutes.sort((left, right) => {
171
+ const priorityDifference = ROUTE_PRIORITY[left.kind] - ROUTE_PRIORITY[right.kind];
172
+ if (priorityDifference !== 0) {
173
+ return priorityDifference;
174
+ }
175
+ if (left.pathname === "/") {
176
+ return -1;
177
+ }
178
+ if (right.pathname === "/") {
179
+ return 1;
180
+ }
181
+ return left.pathname.localeCompare(right.pathname);
182
+ });
183
+ }
184
+ getRoutePath(file) {
185
+ const cleanedRoute = this.templatesExt.reduce((route, ext) => route.replace(ext, ""), file).replace(/\/?index$/, "");
186
+ return normalizePathname(`/${cleanedRoute}`);
187
+ }
188
+ classifyRouteKind(filePath) {
189
+ if (filePath.includes("[...")) {
190
+ return "catch-all";
191
+ }
192
+ if (filePath.includes("[") && filePath.includes("]")) {
193
+ return "dynamic";
194
+ }
195
+ return "exact";
196
+ }
197
+ getParamNames(routePathname) {
198
+ const matches = routePathname.match(/\[(?:\.\.\.)?([^\]]+)\]/g);
199
+ return matches ? matches.map((match) => match.replace(/^\[(?:\.\.\.)?/, "").replace(/\]$/, "")) : [];
200
+ }
201
+ tryExtractParams(route, requestedPathname) {
202
+ const routeParts = route.pathname.split("/");
203
+ const pathnameParts = requestedPathname.split("/");
204
+ if (route.kind === "dynamic" && routeParts.length !== pathnameParts.length) {
205
+ return null;
206
+ }
207
+ const params = {};
208
+ for (let i = 0; i < routeParts.length; i++) {
209
+ const routePart = routeParts[i];
210
+ const pathnamePart = pathnameParts[i];
211
+ if (!routePart) {
212
+ continue;
213
+ }
214
+ if (routePart.startsWith("[...") && routePart.endsWith("]")) {
215
+ const paramName = routePart.slice(4, -1);
216
+ params[paramName] = pathnameParts.slice(i).filter(Boolean);
217
+ return params;
218
+ }
219
+ if (routePart.startsWith("[") && routePart.endsWith("]")) {
220
+ if (pathnamePart === void 0) {
221
+ return null;
222
+ }
223
+ params[routePart.slice(1, -1)] = pathnamePart;
224
+ continue;
225
+ }
226
+ if (routePart !== pathnamePart) {
227
+ return null;
228
+ }
229
+ }
230
+ if (route.kind === "catch-all") {
231
+ const cleanPathname = route.pathname.replace(/\[.*?\]/g, "");
232
+ return requestedPathname.includes(cleanPathname) ? params : null;
233
+ }
234
+ return params;
235
+ }
236
+ getSearchParams(url) {
237
+ const query = {};
238
+ for (const [key, value] of url.searchParams) {
239
+ query[key] = value;
240
+ }
241
+ return query;
242
+ }
243
+ resolveTemplatePath(pathname, params) {
244
+ let resolvedPath = pathname;
245
+ for (const [key, value] of Object.entries(params)) {
246
+ const serializedValue = Array.isArray(value) ? value.join("/") : value;
247
+ resolvedPath = resolvedPath.replace(`[...${key}]`, serializedValue);
248
+ resolvedPath = resolvedPath.replace(`[${key}]`, serializedValue);
249
+ }
250
+ return normalizePathname(resolvedPath);
251
+ }
252
+ }
253
+ function normalizePathname(pathname) {
254
+ if (!pathname || pathname === "/") {
255
+ return "/";
256
+ }
257
+ const normalized = pathname.startsWith("/") ? pathname : `/${pathname}`;
258
+ return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
259
+ }
260
+ export {
261
+ RouteRegistry
262
+ };
@@ -11,7 +11,6 @@ Typical responsibilities include:
11
11
  - server-module loading and transpilation
12
12
  - browser bundle coordination
13
13
  - asset processing and runtime asset declaration helpers
14
- - runtime specifier registry management
15
14
  - server invalidation state and entrypoint dependency graphs
16
15
  - HTML finalization and dependency injection
17
16
 
@@ -20,7 +19,7 @@ Typical responsibilities include:
20
19
  - `module-loading/`: framework-owned config/app bootstrap loading and server-side source loading
21
20
  - `assets/`: shared browser build coordination and processed asset pipelines
22
21
  - `invalidation/`: file-change classification and invalidation policy
23
- - `runtime-state/`: app-owned invalidation state, dependency graphs, and runtime specifier registry
22
+ - `runtime-state/`: app-owned invalidation state and dependency graphs
24
23
  - `runtime-manifest/`: node runtime manifest derivation and persistence
25
24
  - `html/`: final HTML dependency injection and rewriter selection
26
25
 
@@ -13,6 +13,7 @@ export interface BaseAsset {
13
13
  attributes?: Record<string, string>;
14
14
  position?: AssetPosition;
15
15
  packageRole?: AssetPackageRole;
16
+ bundledSourceFilepaths?: string[];
16
17
  }
17
18
  export interface ScriptAsset extends BaseAsset {
18
19
  kind: 'script';
@@ -87,6 +88,7 @@ export interface JsonScriptAsset extends ScriptAsset {
87
88
  }
88
89
  export type ProcessedAsset = {
89
90
  filepath?: string;
91
+ sourceFilepath?: string;
90
92
  srcUrl?: string;
91
93
  content?: string;
92
94
  kind: AssetKind;
@@ -96,5 +98,6 @@ export type ProcessedAsset = {
96
98
  excludeFromHtml?: boolean;
97
99
  packageRole?: AssetPackageRole;
98
100
  groupedBundle?: GroupedScriptBundle;
101
+ bundledSourceFilepaths?: string[];
99
102
  };
100
103
  export type AssetDefinition = ContentScriptAsset | FileScriptAsset | NodeModuleScriptAsset | JsonScriptAsset | ContentStylesheetAsset | FileStylesheetAsset;
@@ -1,4 +1,5 @@
1
1
  export * from './asset.factory.js';
2
+ export * from './page-package.js';
2
3
  export * from './asset-processing.service.js';
3
4
  export * from './assets.types.js';
4
5
  export * from './browser-runtime-asset.factory.js';
@@ -1,4 +1,5 @@
1
1
  export * from "./asset.factory.js";
2
+ export * from "./page-package.js";
2
3
  export * from "./asset-processing.service.js";
3
4
  export * from "./assets.types.js";
4
5
  export * from "./browser-runtime-asset.factory.js";
@@ -0,0 +1,3 @@
1
+ import type { PagePackageResult } from '../../../types/public-types.js';
2
+ import type { ProcessedAsset } from './assets.types.js';
3
+ export declare function createPagePackage(assets: ProcessedAsset[]): PagePackageResult;
@@ -0,0 +1,74 @@
1
+ function getSuppressedSourceFilepaths(assets) {
2
+ const suppressed = /* @__PURE__ */ new Set();
3
+ for (const asset of assets) {
4
+ if ((asset.packageRole === "page-style" || asset.packageRole === "page-script") && Array.isArray(asset.bundledSourceFilepaths)) {
5
+ for (const filepath of asset.bundledSourceFilepaths) {
6
+ suppressed.add(filepath);
7
+ }
8
+ }
9
+ }
10
+ return suppressed;
11
+ }
12
+ function createPagePackage(assets) {
13
+ const inlineAssets = [];
14
+ const separateAssets = [];
15
+ const dynamicChunks = [];
16
+ let pageScript;
17
+ let pageStylesheet;
18
+ const suppressedSourceFilepaths = getSuppressedSourceFilepaths(assets);
19
+ for (const asset of assets) {
20
+ if (asset.inline) {
21
+ inlineAssets.push(asset);
22
+ continue;
23
+ }
24
+ if (asset.packageRole === "dynamic-chunk") {
25
+ dynamicChunks.push(asset);
26
+ continue;
27
+ }
28
+ if (!pageScript && asset.packageRole === "page-script") {
29
+ pageScript = asset;
30
+ continue;
31
+ }
32
+ if (!pageStylesheet && asset.packageRole === "page-style") {
33
+ pageStylesheet = asset;
34
+ continue;
35
+ }
36
+ if (asset.packageRole === "keep-separate" || asset.packageRole === "runtime") {
37
+ separateAssets.push(asset);
38
+ continue;
39
+ }
40
+ if (!pageScript && asset.kind === "script" && !asset.excludeFromHtml) {
41
+ pageScript = asset;
42
+ continue;
43
+ }
44
+ if (!pageStylesheet && asset.kind === "stylesheet") {
45
+ pageStylesheet = asset;
46
+ continue;
47
+ }
48
+ separateAssets.push(asset);
49
+ }
50
+ return {
51
+ assets,
52
+ htmlAssets: assets.filter((asset) => shouldIncludeInHtml(asset, suppressedSourceFilepaths)),
53
+ pageScript,
54
+ pageStylesheet,
55
+ inlineAssets,
56
+ separateAssets,
57
+ dynamicChunks
58
+ };
59
+ }
60
+ function shouldIncludeInHtml(asset, suppressedSourceFilepaths) {
61
+ if (asset.excludeFromHtml) {
62
+ return false;
63
+ }
64
+ if (asset.packageRole === "runtime") {
65
+ return false;
66
+ }
67
+ if (asset.sourceFilepath && suppressedSourceFilepaths.has(asset.sourceFilepath)) {
68
+ return false;
69
+ }
70
+ return true;
71
+ }
72
+ export {
73
+ createPagePackage
74
+ };
@@ -3,9 +3,7 @@ import { getAppBrowserBuildPlugins } from "../../../../../build/build-adapter.js
3
3
  import { fileSystem } from "@ecopages/file-system";
4
4
  import path from "node:path";
5
5
  import { BaseProcessor } from "./base-processor.js";
6
- import {
7
- BrowserBundleService
8
- } from "../../../browser-bundle.service.js";
6
+ import { BrowserBundleService } from "../../../browser-bundle.service.js";
9
7
  class BaseScriptProcessor extends BaseProcessor {
10
8
  browserBundleService;
11
9
  constructor({ appConfig }) {
@@ -113,7 +111,9 @@ class BaseScriptProcessor extends BaseProcessor {
113
111
  entryOutputs.set(entry.entryName, exactOutput);
114
112
  continue;
115
113
  }
116
- const hashedOutput = outputs.find((outputPath) => path.basename(outputPath).startsWith(`${entry.entryName}-`));
114
+ const hashedOutput = outputs.find(
115
+ (outputPath) => path.basename(outputPath).startsWith(`${entry.entryName}-`)
116
+ );
117
117
  if (hashedOutput) {
118
118
  entryOutputs.set(entry.entryName, hashedOutput);
119
119
  continue;
@@ -51,7 +51,8 @@ class ContentScriptProcessor extends BaseScriptProcessor {
51
51
  inline: dep.inline,
52
52
  excludeFromHtml: dep.excludeFromHtml,
53
53
  packageRole: dep.packageRole,
54
- groupedBundle: dep.groupedBundle
54
+ groupedBundle: dep.groupedBundle,
55
+ bundledSourceFilepaths: dep.bundledSourceFilepaths
55
56
  };
56
57
  });
57
58
  } finally {
@@ -73,7 +74,8 @@ class ContentScriptProcessor extends BaseScriptProcessor {
73
74
  attributes: dep.attributes,
74
75
  inline: dep.inline,
75
76
  excludeFromHtml: dep.excludeFromHtml,
76
- packageRole: dep.packageRole
77
+ packageRole: dep.packageRole,
78
+ bundledSourceFilepaths: dep.bundledSourceFilepaths
77
79
  };
78
80
  this.writeCacheFile(filename, unbundledProcessedAsset);
79
81
  return unbundledProcessedAsset;
@@ -102,7 +104,8 @@ class ContentScriptProcessor extends BaseScriptProcessor {
102
104
  inline: dep.inline,
103
105
  excludeFromHtml: dep.excludeFromHtml,
104
106
  packageRole: dep.packageRole,
105
- groupedBundle: dep.groupedBundle
107
+ groupedBundle: dep.groupedBundle,
108
+ bundledSourceFilepaths: dep.bundledSourceFilepaths
106
109
  };
107
110
  fileSystem.remove(tempFileName);
108
111
  this.writeCacheFile(filename, processedAsset);
@@ -24,13 +24,15 @@ class FileScriptProcessor extends BaseScriptProcessor {
24
24
  const outputFilepath = this.resolveHmrOutputFilepath(dep.filepath);
25
25
  return {
26
26
  filepath: outputFilepath ?? dep.filepath,
27
+ sourceFilepath: dep.filepath,
27
28
  srcUrl: outputUrl,
28
29
  kind: "script",
29
30
  position: dep.position,
30
31
  attributes: dep.attributes,
31
32
  inline: false,
32
33
  excludeFromHtml: dep.excludeFromHtml,
33
- packageRole: dep.packageRole
34
+ packageRole: dep.packageRole,
35
+ bundledSourceFilepaths: dep.bundledSourceFilepaths
34
36
  };
35
37
  }
36
38
  const content = fileSystem.readFileSync(dep.filepath);
@@ -53,13 +55,15 @@ class FileScriptProcessor extends BaseScriptProcessor {
53
55
  }
54
56
  return {
55
57
  filepath,
58
+ sourceFilepath: dep.filepath,
56
59
  content,
57
60
  kind: "script",
58
61
  position: dep.position,
59
62
  attributes: dep.attributes,
60
63
  inline: dep.inline,
61
64
  excludeFromHtml: dep.excludeFromHtml,
62
- packageRole: dep.packageRole
65
+ packageRole: dep.packageRole,
66
+ bundledSourceFilepaths: dep.bundledSourceFilepaths
63
67
  };
64
68
  }
65
69
  const relativeFilepath = path.relative(this.appConfig.absolutePaths.srcDir, dep.filepath);
@@ -75,13 +79,15 @@ class FileScriptProcessor extends BaseScriptProcessor {
75
79
  });
76
80
  return {
77
81
  filepath: bundledFilePath,
82
+ sourceFilepath: dep.filepath,
78
83
  content: dep.inline ? fileSystem.readFileSync(bundledFilePath).toString() : void 0,
79
84
  kind: "script",
80
85
  position: dep.position,
81
86
  attributes: dep.attributes,
82
87
  inline: dep.inline,
83
88
  excludeFromHtml: dep.excludeFromHtml,
84
- packageRole: dep.packageRole
89
+ packageRole: dep.packageRole,
90
+ bundledSourceFilepaths: dep.bundledSourceFilepaths
85
91
  };
86
92
  });
87
93
  }
@@ -21,7 +21,8 @@ class NodeModuleScriptProcessor extends BaseScriptProcessor {
21
21
  attributes: dep.attributes,
22
22
  inline: true,
23
23
  excludeFromHtml: dep.excludeFromHtml,
24
- packageRole: dep.packageRole
24
+ packageRole: dep.packageRole,
25
+ bundledSourceFilepaths: dep.bundledSourceFilepaths
25
26
  };
26
27
  }
27
28
  const outdir = path.join(this.getAssetsDir(), "vendors");
@@ -40,7 +41,8 @@ class NodeModuleScriptProcessor extends BaseScriptProcessor {
40
41
  attributes: dep.attributes,
41
42
  inline: dep.inline,
42
43
  excludeFromHtml: dep.excludeFromHtml,
43
- packageRole: dep.packageRole
44
+ packageRole: dep.packageRole,
45
+ bundledSourceFilepaths: dep.bundledSourceFilepaths
44
46
  };
45
47
  });
46
48
  }