@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.
- package/CHANGELOG.md +25 -0
- package/README.md +63 -7
- package/package.json +8 -94
- package/src/adapters/bun/create-app.d.ts +1 -0
- package/src/adapters/bun/create-app.js +39 -2
- package/src/adapters/bun/hmr-manager.d.ts +1 -13
- package/src/adapters/bun/hmr-manager.js +1 -22
- package/src/adapters/bun/server-adapter.js +23 -4
- package/src/adapters/node/node-hmr-manager.d.ts +2 -14
- package/src/adapters/node/node-hmr-manager.js +2 -23
- package/src/adapters/shared/explicit-static-render-preparation.d.ts +25 -0
- package/src/adapters/shared/explicit-static-render-preparation.js +26 -0
- package/src/adapters/shared/explicit-static-route-matcher.d.ts +5 -2
- package/src/adapters/shared/explicit-static-route-matcher.js +14 -16
- package/src/adapters/shared/file-route-middleware-pipeline.d.ts +7 -10
- package/src/adapters/shared/file-route-middleware-pipeline.js +2 -11
- package/src/adapters/shared/fs-server-response-factory.d.ts +13 -9
- package/src/adapters/shared/fs-server-response-factory.js +10 -26
- package/src/adapters/shared/fs-server-response-matcher.d.ts +14 -6
- package/src/adapters/shared/fs-server-response-matcher.js +67 -28
- package/src/adapters/shared/render-context.d.ts +2 -2
- package/src/adapters/shared/server-adapter.d.ts +21 -10
- package/src/adapters/shared/server-adapter.js +171 -132
- package/src/adapters/shared/server-route-handler.d.ts +2 -2
- package/src/adapters/shared/server-route-handler.js +1 -1
- package/src/adapters/shared/server-static-builder.d.ts +4 -4
- package/src/config/README.md +1 -1
- package/src/config/config-builder.d.ts +2 -2
- package/src/config/config-builder.js +0 -5
- package/src/dev/host-runtime.d.ts +10 -0
- package/src/dev/host-runtime.js +24 -0
- package/src/eco/eco.js +7 -7
- package/src/eco/eco.types.d.ts +3 -3
- package/src/errors/index.d.ts +1 -0
- package/src/errors/index.js +3 -1
- package/src/hmr/strategies/js-hmr-strategy.d.ts +0 -5
- package/src/integrations/ghtml/ghtml-renderer.d.ts +0 -4
- package/src/integrations/ghtml/ghtml-renderer.js +1 -7
- package/src/plugins/eco-component-meta-plugin.js +0 -1
- package/src/plugins/integration-plugin.d.ts +14 -18
- package/src/plugins/integration-plugin.js +14 -21
- package/src/plugins/processor.d.ts +2 -0
- package/src/plugins/processor.js +6 -1
- package/src/route-renderer/GRAPH.md +81 -289
- package/src/route-renderer/README.md +67 -105
- package/src/route-renderer/orchestration/component-render-context.d.ts +24 -18
- package/src/route-renderer/orchestration/component-render-context.js +14 -14
- package/src/route-renderer/orchestration/declared-ownership-graph.d.ts +18 -0
- package/src/route-renderer/orchestration/declared-ownership-graph.js +34 -0
- package/src/route-renderer/orchestration/foreign-subtree-execution.service.d.ts +108 -0
- package/src/route-renderer/orchestration/foreign-subtree-execution.service.js +206 -0
- package/src/route-renderer/orchestration/integration-renderer.d.ts +96 -136
- package/src/route-renderer/orchestration/integration-renderer.js +280 -303
- package/src/route-renderer/orchestration/ownership-planning.service.d.ts +24 -0
- package/src/route-renderer/orchestration/ownership-planning.service.js +63 -0
- package/src/route-renderer/orchestration/ownership-validation.service.d.ts +29 -0
- package/src/route-renderer/orchestration/ownership-validation.service.js +53 -0
- package/src/route-renderer/orchestration/queued-foreign-subtree-resolution.service.d.ts +90 -0
- package/src/route-renderer/orchestration/{queued-boundary-runtime.service.js → queued-foreign-subtree-resolution.service.js} +28 -25
- package/src/route-renderer/orchestration/render-output.utils.d.ts +3 -3
- package/src/route-renderer/orchestration/render-output.utils.js +6 -6
- package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +120 -0
- package/src/route-renderer/orchestration/{render-preparation.service.js → route-render-orchestrator.js} +132 -108
- package/src/route-renderer/page-loading/component-dependency-collection.js +8 -1
- package/src/route-renderer/page-loading/dependency-resolver.js +5 -7
- package/src/route-renderer/page-loading/page-dependency-bundling.d.ts +1 -1
- package/src/route-renderer/page-loading/page-dependency-bundling.js +41 -19
- package/src/route-renderer/route-renderer.d.ts +28 -26
- package/src/route-renderer/route-renderer.js +4 -27
- package/src/router/README.md +16 -19
- package/src/router/server/route-registry.d.ts +78 -0
- package/src/router/server/route-registry.js +262 -0
- package/src/services/README.md +1 -2
- package/src/services/assets/asset-processing-service/assets.types.d.ts +3 -0
- package/src/services/assets/asset-processing-service/index.d.ts +1 -0
- package/src/services/assets/asset-processing-service/index.js +1 -0
- package/src/services/assets/asset-processing-service/page-package.d.ts +3 -0
- package/src/services/assets/asset-processing-service/page-package.js +74 -0
- package/src/services/assets/asset-processing-service/processors/base/base-script-processor.js +4 -4
- package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js +6 -3
- package/src/services/assets/asset-processing-service/processors/script/file-script.processor.js +9 -3
- package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +4 -2
- package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.js +2 -1
- package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.js +3 -1
- package/src/services/module-loading/node-bootstrap-plugin.js +15 -3
- package/src/static-site-generator/static-site-generator.d.ts +20 -21
- package/src/static-site-generator/static-site-generator.js +107 -140
- package/src/types/internal-types.d.ts +13 -12
- package/src/types/public-types.d.ts +46 -36
- package/src/watchers/project-watcher.test-helpers.js +5 -5
- package/src/route-renderer/orchestration/boundary-planning.service.d.ts +0 -25
- package/src/route-renderer/orchestration/boundary-planning.service.js +0 -97
- package/src/route-renderer/orchestration/page-packaging.service.d.ts +0 -16
- package/src/route-renderer/orchestration/page-packaging.service.js +0 -66
- package/src/route-renderer/orchestration/queued-boundary-runtime.service.d.ts +0 -89
- package/src/route-renderer/orchestration/render-execution.service.d.ts +0 -43
- package/src/route-renderer/orchestration/render-execution.service.js +0 -106
- package/src/route-renderer/orchestration/render-preparation.service.d.ts +0 -120
- package/src/route-renderer/orchestration/route-shell-composer.service.d.ts +0 -50
- package/src/route-renderer/orchestration/route-shell-composer.service.js +0 -81
- package/src/router/server/fs-router-scanner.d.ts +0 -41
- package/src/router/server/fs-router-scanner.js +0 -161
- package/src/router/server/fs-router.d.ts +0 -26
- package/src/router/server/fs-router.js +0 -100
- package/src/services/runtime-state/runtime-specifier-registry.service.d.ts +0 -69
- package/src/services/runtime-state/runtime-specifier-registry.service.js +0 -37
package/src/router/README.md
CHANGED
|
@@ -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
|
|
12
|
-
- matching incoming request URLs to
|
|
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
|
|
22
|
-
│
|
|
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
|
-
### `
|
|
30
|
+
### `RouteRegistry`
|
|
32
31
|
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
46
|
+
The public interface is intentionally small:
|
|
48
47
|
|
|
49
|
-
|
|
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/
|
|
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
|
+
};
|
package/src/services/README.md
CHANGED
|
@@ -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
|
|
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;
|
|
@@ -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
|
+
};
|
package/src/services/assets/asset-processing-service/processors/base/base-script-processor.js
CHANGED
|
@@ -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(
|
|
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;
|
package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js
CHANGED
|
@@ -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);
|
package/src/services/assets/asset-processing-service/processors/script/file-script.processor.js
CHANGED
|
@@ -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
|
}
|