@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
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import {
|
|
4
|
-
AssetFactory
|
|
4
|
+
AssetFactory,
|
|
5
|
+
createPagePackage
|
|
5
6
|
} from "../../services/assets/asset-processing-service/index.js";
|
|
6
7
|
import { buildGlobalInjectorBootstrapContent, buildGlobalInjectorMapScript } from "../../eco/global-injector-map.js";
|
|
7
8
|
import { LocalsAccessError } from "../../errors/locals-access-error.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
9
|
+
import { inspectUnresolvedMarkerArtifactHtml } from "./render-output.utils.js";
|
|
10
|
+
import { OwnershipValidationService } from "./ownership-validation.service.js";
|
|
11
|
+
import { OwnershipPlanningService } from "./ownership-planning.service.js";
|
|
10
12
|
import { dedupeProcessedAssets } from "./processed-asset-dedupe.js";
|
|
11
13
|
function createPageLocalsProxy(filePath) {
|
|
12
14
|
const errorMessage = `[ecopages] Request locals are only available during request-time rendering with cache: 'dynamic'. Page: ${filePath}. If you meant to use locals here, set cache: 'dynamic' and provide locals from route middleware/handlers.`;
|
|
@@ -37,83 +39,74 @@ function createPageLocalsProxy(filePath) {
|
|
|
37
39
|
}
|
|
38
40
|
);
|
|
39
41
|
}
|
|
40
|
-
class
|
|
42
|
+
class RouteRenderOrchestrator {
|
|
41
43
|
appConfig;
|
|
42
44
|
assetProcessingService;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Creates the render-preparation orchestrator for one app instance.
|
|
47
|
-
*
|
|
48
|
-
* @remarks
|
|
49
|
-
* The service is app-scoped because it depends on finalized config defaults and
|
|
50
|
-
* the app-owned asset-processing pipeline while remaining renderer-agnostic.
|
|
51
|
-
*/
|
|
45
|
+
ownershipPlanningService;
|
|
46
|
+
ownershipValidationService;
|
|
52
47
|
constructor(appConfig, assetProcessingService, dependencies = {}) {
|
|
53
48
|
this.appConfig = appConfig;
|
|
54
49
|
this.assetProcessingService = assetProcessingService;
|
|
55
|
-
this.
|
|
56
|
-
this.
|
|
50
|
+
this.ownershipPlanningService = dependencies.ownershipPlanningService ?? new OwnershipPlanningService();
|
|
51
|
+
this.ownershipValidationService = dependencies.ownershipValidationService ?? new OwnershipValidationService(appConfig);
|
|
57
52
|
}
|
|
58
53
|
/**
|
|
59
|
-
* Builds
|
|
60
|
-
* renderer.
|
|
61
|
-
*
|
|
62
|
-
* The returned object contains normalized page data, processed dependency
|
|
63
|
-
* state, component render artifacts, and the locals contract expected by the
|
|
64
|
-
* rest of the pipeline.
|
|
54
|
+
* Builds normalized route render options before the integration render runs.
|
|
65
55
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* @param callbacks Renderer-specific hooks used during preparation.
|
|
70
|
-
* @returns Normalized render options.
|
|
56
|
+
* This preparation step validates route-root ownership, resolves page data,
|
|
57
|
+
* collects processed assets, captures optional page-root render metadata, and
|
|
58
|
+
* produces the page package consumed by downstream HTML transformation.
|
|
71
59
|
*/
|
|
72
|
-
async
|
|
73
|
-
const
|
|
74
|
-
const { Page, integrationSpecificProps } =
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
60
|
+
async prepareRenderOptions(routeOptions, adapter) {
|
|
61
|
+
const resolvedInputs = await adapter.resolveRouteRenderInputs(routeOptions);
|
|
62
|
+
const { Page, HtmlTemplate, Layout, props, metadata, integrationSpecificProps } = resolvedInputs;
|
|
63
|
+
const validationErrors = this.ownershipValidationService.validate({
|
|
64
|
+
currentIntegrationName: adapter.name,
|
|
65
|
+
roots: [
|
|
66
|
+
{ component: HtmlTemplate, source: "html-template" },
|
|
67
|
+
...Layout ? [{ component: Layout, source: "layout" }] : [],
|
|
68
|
+
{ component: Page, source: "page" }
|
|
69
|
+
]
|
|
70
|
+
});
|
|
71
|
+
const ownershipPlan = this.ownershipPlanningService.buildPlan({
|
|
79
72
|
routeFile: routeOptions.file,
|
|
80
|
-
currentIntegrationName,
|
|
73
|
+
currentIntegrationName: adapter.name,
|
|
81
74
|
HtmlTemplate,
|
|
82
75
|
Layout,
|
|
83
|
-
Page
|
|
76
|
+
Page,
|
|
77
|
+
validationErrors
|
|
84
78
|
});
|
|
85
79
|
const componentsToResolve = Layout ? [HtmlTemplate, Layout, Page] : [HtmlTemplate, Page];
|
|
86
|
-
const resolvedDependencies = await
|
|
87
|
-
|
|
88
|
-
componentsToResolve
|
|
89
|
-
|
|
90
|
-
);
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
80
|
+
const { resolvedDependencies, pageBrowserGraph } = await adapter.resolveRouteAssets({
|
|
81
|
+
routeOptions,
|
|
82
|
+
components: componentsToResolve
|
|
83
|
+
});
|
|
84
|
+
const usedIntegrationDependencies = this.collectUsedIntegrationDependencies(componentsToResolve, adapter.name);
|
|
85
|
+
const allDependencies = [
|
|
86
|
+
...resolvedDependencies,
|
|
87
|
+
...usedIntegrationDependencies,
|
|
88
|
+
...pageBrowserGraph?.assets ?? []
|
|
89
|
+
];
|
|
90
|
+
const componentRender = await adapter.resolveRoutePageComponentRender({
|
|
91
|
+
Page,
|
|
92
|
+
Layout,
|
|
93
|
+
props,
|
|
94
|
+
routeOptions
|
|
95
|
+
});
|
|
96
|
+
if (componentRender?.assets?.length) {
|
|
97
|
+
allDependencies.push(...componentRender.assets);
|
|
105
98
|
}
|
|
106
99
|
const triggers = this.collectResolvedTriggers(componentsToResolve);
|
|
107
100
|
if (triggers.length > 0) {
|
|
108
|
-
const globalAssets = await this.buildGlobalInjectorAssets(triggers,
|
|
101
|
+
const globalAssets = await this.buildGlobalInjectorAssets(triggers, adapter.name);
|
|
109
102
|
allDependencies.push(...globalAssets);
|
|
110
103
|
}
|
|
111
|
-
const eagerSsrLazyAssets = await this.buildEagerSsrLazyAssets(componentsToResolve,
|
|
104
|
+
const eagerSsrLazyAssets = await this.buildEagerSsrLazyAssets(componentsToResolve, adapter.name);
|
|
112
105
|
if (eagerSsrLazyAssets.length > 0) {
|
|
113
106
|
allDependencies.push(...eagerSsrLazyAssets);
|
|
114
107
|
}
|
|
115
108
|
const dedupedDependencies = dedupeProcessedAssets(allDependencies);
|
|
116
|
-
const pagePackage =
|
|
109
|
+
const pagePackage = createPagePackage(dedupedDependencies);
|
|
117
110
|
const pageProps = {
|
|
118
111
|
...props,
|
|
119
112
|
params: routeOptions.params || {},
|
|
@@ -141,7 +134,7 @@ class RenderPreparationService {
|
|
|
141
134
|
locals,
|
|
142
135
|
pageLocals,
|
|
143
136
|
cacheStrategy,
|
|
144
|
-
|
|
137
|
+
ownershipPlan
|
|
145
138
|
};
|
|
146
139
|
return {
|
|
147
140
|
...integrationSpecificProps,
|
|
@@ -149,15 +142,83 @@ class RenderPreparationService {
|
|
|
149
142
|
};
|
|
150
143
|
}
|
|
151
144
|
/**
|
|
152
|
-
*
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
145
|
+
* Captures one route render body as HTML while preserving a replayable body value.
|
|
146
|
+
*/
|
|
147
|
+
async captureHtmlRender(render) {
|
|
148
|
+
const renderedBody = await render();
|
|
149
|
+
const capturedRender = await this.captureRenderedBody(renderedBody);
|
|
150
|
+
return {
|
|
151
|
+
body: capturedRender.body,
|
|
152
|
+
html: capturedRender.html
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Executes the full route-render flow and returns the final body plus cache strategy.
|
|
160
157
|
*/
|
|
158
|
+
async execute(options, adapter) {
|
|
159
|
+
const renderOptions = await this.prepareRenderOptions(options, adapter);
|
|
160
|
+
return this.executePrepared(renderOptions, adapter);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Executes the route-render finalization path for already prepared render options.
|
|
164
|
+
*/
|
|
165
|
+
async executePrepared(renderOptions, adapter) {
|
|
166
|
+
const renderExecution = await this.captureHtmlRender(async () => adapter.renderRouteBody(renderOptions));
|
|
167
|
+
const unresolvedArtifactInspection = inspectUnresolvedMarkerArtifactHtml(renderExecution.html);
|
|
168
|
+
const htmlFinalization = adapter.getRouteHtmlFinalization(renderOptions);
|
|
169
|
+
const hasUnresolvedMarkerHtml = unresolvedArtifactInspection.hasUnresolvedMarkerArtifacts;
|
|
170
|
+
if (hasUnresolvedMarkerHtml) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
"[ecopages] Route render returned unresolved eco-marker artifact HTML. Full-route unresolved-marker fallback has been removed; resolve mixed foreign children inside renderComponentWithForeignChildren()."
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
const canReuseCapturedBody = !hasUnresolvedMarkerHtml && htmlFinalization.finalizeHtml === void 0;
|
|
176
|
+
if (canReuseCapturedBody) {
|
|
177
|
+
const body2 = await adapter.transformRouteResponse(
|
|
178
|
+
new Response(renderExecution.body, {
|
|
179
|
+
headers: {
|
|
180
|
+
"Content-Type": "text/html"
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
);
|
|
184
|
+
return {
|
|
185
|
+
body: body2,
|
|
186
|
+
cacheStrategy: renderOptions.cacheStrategy
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const finalization = htmlFinalization.finalizeHtml ? htmlFinalization.finalizeHtml(unresolvedArtifactInspection.normalizedHtml) : unresolvedArtifactInspection.normalizedHtml;
|
|
190
|
+
const body = await adapter.transformRouteResponse(
|
|
191
|
+
new Response(finalization, {
|
|
192
|
+
headers: {
|
|
193
|
+
"Content-Type": "text/html"
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
);
|
|
197
|
+
return {
|
|
198
|
+
body,
|
|
199
|
+
cacheStrategy: renderOptions.cacheStrategy
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
async captureRenderedBody(body) {
|
|
203
|
+
const response = new Response(body);
|
|
204
|
+
if (typeof body === "string") {
|
|
205
|
+
return {
|
|
206
|
+
body,
|
|
207
|
+
html: await response.text()
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
if (!response.body) {
|
|
211
|
+
return {
|
|
212
|
+
body,
|
|
213
|
+
html: await response.text()
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
const [capturedBody, replayBody] = response.body.tee();
|
|
217
|
+
return {
|
|
218
|
+
body: replayBody,
|
|
219
|
+
html: await new Response(capturedBody).text()
|
|
220
|
+
};
|
|
221
|
+
}
|
|
161
222
|
collectResolvedTriggers(components, seen = /* @__PURE__ */ new Set()) {
|
|
162
223
|
const triggers = [];
|
|
163
224
|
for (const comp of components) {
|
|
@@ -180,13 +241,6 @@ class RenderPreparationService {
|
|
|
180
241
|
}
|
|
181
242
|
return triggers;
|
|
182
243
|
}
|
|
183
|
-
/**
|
|
184
|
-
* Collects global integration dependencies used by nested components belonging
|
|
185
|
-
* to integrations other than the current renderer.
|
|
186
|
-
*
|
|
187
|
-
* @param components Root component set.
|
|
188
|
-
* @returns Processed integration dependencies contributed by nested integrations.
|
|
189
|
-
*/
|
|
190
244
|
collectUsedIntegrationDependencies(components, currentIntegrationName) {
|
|
191
245
|
const integrationNames = this.collectIntegrationNames(components);
|
|
192
246
|
const dependencies = [];
|
|
@@ -204,13 +258,6 @@ class RenderPreparationService {
|
|
|
204
258
|
}
|
|
205
259
|
return dependencies;
|
|
206
260
|
}
|
|
207
|
-
/**
|
|
208
|
-
* Discovers integration names referenced by the component dependency graph.
|
|
209
|
-
*
|
|
210
|
-
* @param components Root component set.
|
|
211
|
-
* @param seen Internal visited set for shared graphs.
|
|
212
|
-
* @returns Set of integration names found in the graph.
|
|
213
|
-
*/
|
|
214
261
|
collectIntegrationNames(components, seen = /* @__PURE__ */ new Set()) {
|
|
215
262
|
const integrationNames = /* @__PURE__ */ new Set();
|
|
216
263
|
for (const comp of components) {
|
|
@@ -236,32 +283,6 @@ class RenderPreparationService {
|
|
|
236
283
|
}
|
|
237
284
|
return integrationNames;
|
|
238
285
|
}
|
|
239
|
-
/**
|
|
240
|
-
* Renders the page root through the component-level render contract so any
|
|
241
|
-
* integration-specific assets and root attributes are available before the main
|
|
242
|
-
* document render.
|
|
243
|
-
*
|
|
244
|
-
* @param input Page root render inputs.
|
|
245
|
-
* @returns Structured component render result.
|
|
246
|
-
*/
|
|
247
|
-
async renderPageRoot(input) {
|
|
248
|
-
return {
|
|
249
|
-
componentRender: await input.callbacks.renderPageComponent({
|
|
250
|
-
component: input.Page,
|
|
251
|
-
props: {
|
|
252
|
-
...input.props,
|
|
253
|
-
params: input.routeOptions.params || {},
|
|
254
|
-
query: input.routeOptions.query || {}
|
|
255
|
-
}
|
|
256
|
-
})
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Builds the runtime assets needed to bootstrap global lazy trigger execution.
|
|
261
|
-
*
|
|
262
|
-
* @param triggers Fully resolved lazy trigger definitions.
|
|
263
|
-
* @returns Processed assets that should be merged into the final dependency set.
|
|
264
|
-
*/
|
|
265
286
|
async buildGlobalInjectorAssets(triggers, currentIntegrationName) {
|
|
266
287
|
const globalInjectorImportPath = createRequire(import.meta.url).resolve("@ecopages/scripts-injector/global");
|
|
267
288
|
const mapScript = AssetFactory.createInlineContentScript({
|
|
@@ -280,7 +301,10 @@ class RenderPreparationService {
|
|
|
280
301
|
packageRole: "keep-separate",
|
|
281
302
|
bundle: true
|
|
282
303
|
});
|
|
283
|
-
return this.assetProcessingService.processDependencies(
|
|
304
|
+
return this.assetProcessingService.processDependencies(
|
|
305
|
+
[mapScript, bootstrapInlineScript],
|
|
306
|
+
currentIntegrationName
|
|
307
|
+
);
|
|
284
308
|
}
|
|
285
309
|
async buildEagerSsrLazyAssets(components, currentIntegrationName) {
|
|
286
310
|
const dependencies = this.collectEagerSsrLazyDependencies(components);
|
|
@@ -360,5 +384,5 @@ class RenderPreparationService {
|
|
|
360
384
|
}
|
|
361
385
|
}
|
|
362
386
|
export {
|
|
363
|
-
|
|
387
|
+
RouteRenderOrchestrator
|
|
364
388
|
};
|
|
@@ -32,7 +32,14 @@ function resolveDependencyPath(componentDir, pathUrl) {
|
|
|
32
32
|
return path.join(componentDir, pathUrl);
|
|
33
33
|
}
|
|
34
34
|
function collectComponentDependencies(options) {
|
|
35
|
-
const {
|
|
35
|
+
const {
|
|
36
|
+
components,
|
|
37
|
+
integrationName,
|
|
38
|
+
resolveLazyScripts,
|
|
39
|
+
createEcopagesJsxLazyEntryName,
|
|
40
|
+
isEcopagesJsxIntegration,
|
|
41
|
+
errors
|
|
42
|
+
} = options;
|
|
36
43
|
const dependencies = [];
|
|
37
44
|
const lazyScriptsByConfig = /* @__PURE__ */ new Map();
|
|
38
45
|
const lazyDependencyKeys = /* @__PURE__ */ new Set();
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { rapidhash } from "../../utils/hash.js";
|
|
3
3
|
import { AssetFactory } from "../../services/assets/asset-processing-service/index.js";
|
|
4
|
-
import {
|
|
5
|
-
buildResolvedLazyTriggers
|
|
6
|
-
} from "./lazy-trigger-planning.js";
|
|
4
|
+
import { buildResolvedLazyTriggers } from "./lazy-trigger-planning.js";
|
|
7
5
|
import { collectComponentDependencies } from "./component-dependency-collection.js";
|
|
8
|
-
import {
|
|
6
|
+
import { packagePageDependencies } from "./page-dependency-bundling.js";
|
|
9
7
|
const DEPENDENCY_ERRORS = {
|
|
10
8
|
INVALID_STYLESHEET_ENTRY: "Invalid stylesheet dependency entry: expected src or content",
|
|
11
9
|
INVALID_SCRIPT_ENTRY: "Invalid script dependency entry: expected src or content",
|
|
@@ -77,12 +75,12 @@ class DependencyResolverService {
|
|
|
77
75
|
lazyScriptMissingSrc: DEPENDENCY_ERRORS.LAZY_SCRIPT_MISSING_SRC
|
|
78
76
|
}
|
|
79
77
|
});
|
|
80
|
-
const
|
|
81
|
-
const hasLazyDependencies =
|
|
78
|
+
const packagedDependencies = packagePageDependencies(dependencies, integrationName);
|
|
79
|
+
const hasLazyDependencies = packagedDependencies.some(
|
|
82
80
|
(dep) => dep.kind === "script" && dep.excludeFromHtml === true
|
|
83
81
|
);
|
|
84
82
|
const processedDependencies = await this.assetProcessingService.processDependencies(
|
|
85
|
-
|
|
83
|
+
packagedDependencies,
|
|
86
84
|
integrationName
|
|
87
85
|
);
|
|
88
86
|
const lazyKeyToOutputUrl = /* @__PURE__ */ new Map();
|
|
@@ -10,4 +10,4 @@ export declare function shouldBundlePageDependencies(integrationName: string): b
|
|
|
10
10
|
* Only assets with the default attribute shape and without explicit packaging roles are
|
|
11
11
|
* collapsed so integration-specific or lazy behavior keeps its existing ownership model.
|
|
12
12
|
*/
|
|
13
|
-
export declare function
|
|
13
|
+
export declare function packagePageDependencies(dependencies: AssetDefinition[], integrationName: string): AssetDefinition[];
|
|
@@ -16,7 +16,7 @@ function normalizeCssReferenceToken(token) {
|
|
|
16
16
|
return token.trim().replace(/^['"]|['"]$/g, "");
|
|
17
17
|
}
|
|
18
18
|
function isSafeBundledStylesheetContent(content) {
|
|
19
|
-
for (const match of content.matchAll(/@import\s+(?:url\()?['"]?([^'"
|
|
19
|
+
for (const match of content.matchAll(/@import\s+(?:url\()?['"]?([^'")\s]+)['"]?\)?/g)) {
|
|
20
20
|
const reference = normalizeCssReferenceToken(match[1] ?? "");
|
|
21
21
|
if (reference && !isSafeBundledStylesheetReference(reference)) {
|
|
22
22
|
return false;
|
|
@@ -39,11 +39,12 @@ function isBundleableFileScriptAsset(dependency) {
|
|
|
39
39
|
function shouldBundlePageDependencies(integrationName) {
|
|
40
40
|
return integrationName === "react" || integrationName === "ecopages-jsx";
|
|
41
41
|
}
|
|
42
|
-
function
|
|
43
|
-
|
|
44
|
-
return dependencies;
|
|
45
|
-
}
|
|
42
|
+
function createPageDependencyPackagingPlan(dependencies, integrationName) {
|
|
43
|
+
const shouldBundleDependencies = process.env.NODE_ENV === "production";
|
|
46
44
|
const bundleableStyles = dependencies.filter((dependency) => {
|
|
45
|
+
if (!shouldBundleDependencies) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
47
48
|
if (dependency.kind !== "stylesheet" || dependency.inline || dependency.position === "body") {
|
|
48
49
|
return false;
|
|
49
50
|
}
|
|
@@ -59,57 +60,78 @@ function createUnifiedPageDependencies(dependencies, integrationName) {
|
|
|
59
60
|
return isSafeBundledStylesheetContent(readFileSync(dependency.filepath, "utf8"));
|
|
60
61
|
}).filter(isBundleableFileStylesheetAsset);
|
|
61
62
|
const bundleableScripts = dependencies.filter((dependency) => {
|
|
63
|
+
if (!shouldBundleDependencies) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
62
66
|
return dependency.kind === "script" && dependency.source === "file" && !dependency.inline && !dependency.excludeFromHtml && dependency.position !== "body" && !dependency.packageRole && dependency.bundle !== false && hasOnlyExpectedAttributes(dependency.attributes, { type: "module", defer: "" }) && existsSync(dependency.filepath);
|
|
63
67
|
}).filter(isBundleableFileScriptAsset);
|
|
64
68
|
const bundledStylesheet = bundleableStyles.length > 1 ? AssetFactory.createContentStylesheet({
|
|
65
69
|
content: bundleableStyles.map((dependency) => readFileSync(dependency.filepath, "utf8")).join("\n"),
|
|
66
70
|
position: "head",
|
|
67
71
|
attributes: { rel: "stylesheet" },
|
|
68
|
-
packageRole: "page-style"
|
|
72
|
+
packageRole: "page-style",
|
|
73
|
+
bundledSourceFilepaths: bundleableStyles.map((dependency) => dependency.filepath)
|
|
69
74
|
}) : void 0;
|
|
70
|
-
const bundleableStyleFilepaths = new Set(bundleableStyles.map((dependency) => dependency.filepath));
|
|
71
75
|
const pageScriptImports = [...new Set(bundleableScripts.map((dependency) => dependency.filepath))];
|
|
72
|
-
const bundleableScriptFilepaths = new Set(pageScriptImports);
|
|
73
76
|
const shouldBundlePageScript = pageScriptImports.length > 0 && bundleableScripts.length > 1;
|
|
74
77
|
const bundledScript = shouldBundlePageScript ? AssetFactory.createContentScript({
|
|
75
78
|
name: `${integrationName}-page-${rapidhash(pageScriptImports.join("|")).toString(16)}`,
|
|
76
79
|
content: pageScriptImports.map((filepath) => `import ${JSON.stringify(filepath)};`).join("\n"),
|
|
77
80
|
position: "head",
|
|
78
81
|
attributes: { type: "module", defer: "" },
|
|
79
|
-
packageRole: "page-script"
|
|
82
|
+
packageRole: "page-script",
|
|
83
|
+
bundledSourceFilepaths: pageScriptImports
|
|
80
84
|
}) : void 0;
|
|
81
85
|
if (!bundledStylesheet && !bundledScript) {
|
|
82
|
-
return
|
|
86
|
+
return void 0;
|
|
83
87
|
}
|
|
88
|
+
return {
|
|
89
|
+
bundledStylesheet,
|
|
90
|
+
bundledScript,
|
|
91
|
+
bundleableStyleFilepaths: new Set(bundleableStyles.map((dependency) => dependency.filepath)),
|
|
92
|
+
bundleableScriptFilepaths: new Set(pageScriptImports)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function applyPageDependencyPackagingPlan(dependencies, plan) {
|
|
84
96
|
const unifiedDependencies = [];
|
|
85
97
|
let insertedStylesheet = false;
|
|
86
98
|
let insertedScript = false;
|
|
87
99
|
for (const dependency of dependencies) {
|
|
88
|
-
if (bundledStylesheet && dependency.kind === "stylesheet" && dependency.source === "file" && bundleableStyleFilepaths.has(dependency.filepath)) {
|
|
100
|
+
if (plan.bundledStylesheet && dependency.kind === "stylesheet" && dependency.source === "file" && plan.bundleableStyleFilepaths.has(dependency.filepath)) {
|
|
89
101
|
if (!insertedStylesheet) {
|
|
90
|
-
unifiedDependencies.push(bundledStylesheet);
|
|
102
|
+
unifiedDependencies.push(plan.bundledStylesheet);
|
|
91
103
|
insertedStylesheet = true;
|
|
92
104
|
}
|
|
93
105
|
continue;
|
|
94
106
|
}
|
|
95
|
-
if (bundledScript && dependency.kind === "script" && dependency.source === "file" && bundleableScriptFilepaths.has(dependency.filepath)) {
|
|
107
|
+
if (plan.bundledScript && dependency.kind === "script" && dependency.source === "file" && plan.bundleableScriptFilepaths.has(dependency.filepath)) {
|
|
96
108
|
if (!insertedScript) {
|
|
97
|
-
unifiedDependencies.push(bundledScript);
|
|
109
|
+
unifiedDependencies.push(plan.bundledScript);
|
|
98
110
|
insertedScript = true;
|
|
99
111
|
}
|
|
100
112
|
continue;
|
|
101
113
|
}
|
|
102
114
|
unifiedDependencies.push(dependency);
|
|
103
115
|
}
|
|
104
|
-
if (bundledScript && !insertedScript) {
|
|
105
|
-
unifiedDependencies.push(bundledScript);
|
|
116
|
+
if (plan.bundledScript && !insertedScript) {
|
|
117
|
+
unifiedDependencies.push(plan.bundledScript);
|
|
106
118
|
}
|
|
107
|
-
if (bundledStylesheet && !insertedStylesheet) {
|
|
108
|
-
unifiedDependencies.push(bundledStylesheet);
|
|
119
|
+
if (plan.bundledStylesheet && !insertedStylesheet) {
|
|
120
|
+
unifiedDependencies.push(plan.bundledStylesheet);
|
|
109
121
|
}
|
|
110
122
|
return unifiedDependencies;
|
|
111
123
|
}
|
|
124
|
+
function packagePageDependencies(dependencies, integrationName) {
|
|
125
|
+
if (!shouldBundlePageDependencies(integrationName)) {
|
|
126
|
+
return dependencies;
|
|
127
|
+
}
|
|
128
|
+
const plan = createPageDependencyPackagingPlan(dependencies, integrationName);
|
|
129
|
+
if (!plan) {
|
|
130
|
+
return dependencies;
|
|
131
|
+
}
|
|
132
|
+
return applyPageDependencyPackagingPlan(dependencies, plan);
|
|
133
|
+
}
|
|
112
134
|
export {
|
|
113
|
-
|
|
135
|
+
packagePageDependencies,
|
|
114
136
|
shouldBundlePageDependencies
|
|
115
137
|
};
|
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
import type { EcoPagesAppConfig } from '../types/internal-types.js';
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
import type { IntegrationRenderer, RouteModuleLoadOptions } from './orchestration/integration-renderer.js';
|
|
2
|
+
import type { AnyIntegrationPlugin } from '../plugins/integration-plugin.js';
|
|
3
|
+
import type { IntegrationRenderer } from './orchestration/integration-renderer.js';
|
|
5
4
|
/**
|
|
6
|
-
*
|
|
5
|
+
* Narrow route-render contract exposed to higher-level routing code.
|
|
7
6
|
*
|
|
8
7
|
* @remarks
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Higher-level routing code only needs request execution and page-module
|
|
9
|
+
* loading. Returning this narrowed shape avoids a dedicated wrapper class while
|
|
10
|
+
* still keeping callers off the full integration renderer surface.
|
|
12
11
|
*/
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* Loads the route module through the owning integration renderer.
|
|
25
|
-
*/
|
|
26
|
-
loadPageModule(filePath: string, options?: RouteModuleLoadOptions): Promise<EcoPageFile>;
|
|
12
|
+
export type PageRouteRenderer = Pick<IntegrationRenderer<unknown>, 'execute' | 'loadPageModule'>;
|
|
13
|
+
/**
|
|
14
|
+
* Narrow explicit-view render contract exposed to static route handling.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* Explicit static routes only need `renderToResponse()`, so the factory can
|
|
18
|
+
* hide the broader integration renderer surface there as well.
|
|
19
|
+
*/
|
|
20
|
+
export type ExplicitViewRenderer = Pick<IntegrationRenderer<unknown>, 'renderToResponse'>;
|
|
21
|
+
export interface PageRendererResolver {
|
|
22
|
+
getPageRenderer(filePath: string): PageRouteRenderer;
|
|
27
23
|
}
|
|
24
|
+
export interface ExplicitViewRendererResolver {
|
|
25
|
+
getExplicitViewRenderer(integrationName: string): ExplicitViewRenderer | null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Combined renderer-factory contract used by static generation.
|
|
29
|
+
*/
|
|
30
|
+
export type StaticGenerationRendererResolver = PageRendererResolver & ExplicitViewRendererResolver;
|
|
28
31
|
/**
|
|
29
32
|
* Selects and caches integration renderers for route files and explicit views.
|
|
30
33
|
*
|
|
@@ -49,16 +52,15 @@ export declare class RouteRendererFactory {
|
|
|
49
52
|
/**
|
|
50
53
|
* Returns a route renderer for the supplied route file.
|
|
51
54
|
*/
|
|
52
|
-
|
|
55
|
+
getPageRenderer(filePath: string): PageRouteRenderer;
|
|
53
56
|
/**
|
|
54
|
-
*
|
|
55
|
-
* Used for explicit routing where views specify their integration via __eco.integration.
|
|
57
|
+
* Returns a renderer for an explicit view integration.
|
|
56
58
|
*/
|
|
57
|
-
|
|
59
|
+
getExplicitViewRenderer(integrationName: string): ExplicitViewRenderer | null;
|
|
58
60
|
/**
|
|
59
61
|
* Resolves the integration plugin that owns a given route file.
|
|
60
62
|
*/
|
|
61
|
-
getIntegrationPlugin(filePath: string):
|
|
63
|
+
getIntegrationPlugin(filePath: string): AnyIntegrationPlugin;
|
|
62
64
|
/**
|
|
63
65
|
* Returns the cached renderer engine for the file's owning integration,
|
|
64
66
|
* creating it on first use.
|
|
@@ -1,26 +1,5 @@
|
|
|
1
1
|
import { invariant } from "../utils/invariant.js";
|
|
2
2
|
import { PathUtils } from "../utils/path-utils.module.js";
|
|
3
|
-
class RouteRenderer {
|
|
4
|
-
renderer;
|
|
5
|
-
/**
|
|
6
|
-
* Creates a route renderer bound to one integration renderer instance.
|
|
7
|
-
*/
|
|
8
|
-
constructor(renderer) {
|
|
9
|
-
this.renderer = renderer;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Executes the render pipeline for one matched route.
|
|
13
|
-
*/
|
|
14
|
-
async createRoute(options) {
|
|
15
|
-
return this.renderer.execute(options);
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Loads the route module through the owning integration renderer.
|
|
19
|
-
*/
|
|
20
|
-
async loadPageModule(filePath, options) {
|
|
21
|
-
return this.renderer.loadPageModule(filePath, options);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
3
|
class RouteRendererFactory {
|
|
25
4
|
appConfig;
|
|
26
5
|
runtimeOrigin;
|
|
@@ -41,16 +20,15 @@ class RouteRendererFactory {
|
|
|
41
20
|
/**
|
|
42
21
|
* Returns a route renderer for the supplied route file.
|
|
43
22
|
*/
|
|
44
|
-
|
|
23
|
+
getPageRenderer(filePath) {
|
|
45
24
|
const integrationRenderer = this.getRouteRendererEngine(filePath);
|
|
46
25
|
invariant(!!integrationRenderer, `No integration renderer found for file: ${filePath}`);
|
|
47
|
-
return
|
|
26
|
+
return integrationRenderer;
|
|
48
27
|
}
|
|
49
28
|
/**
|
|
50
|
-
*
|
|
51
|
-
* Used for explicit routing where views specify their integration via __eco.integration.
|
|
29
|
+
* Returns a renderer for an explicit view integration.
|
|
52
30
|
*/
|
|
53
|
-
|
|
31
|
+
getExplicitViewRenderer(integrationName) {
|
|
54
32
|
const integrationPlugin = this.appConfig.integrations.find((plugin) => plugin.name === integrationName);
|
|
55
33
|
if (!integrationPlugin) {
|
|
56
34
|
return null;
|
|
@@ -98,6 +76,5 @@ class RouteRendererFactory {
|
|
|
98
76
|
}
|
|
99
77
|
}
|
|
100
78
|
export {
|
|
101
|
-
RouteRenderer,
|
|
102
79
|
RouteRendererFactory
|
|
103
80
|
};
|