@ecopages/core 0.2.0-alpha.13 → 0.2.0-alpha.15
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 +6 -0
- package/package.json +2 -2
- package/src/adapters/shared/fs-server-response-matcher.d.ts +3 -11
- package/src/adapters/shared/fs-server-response-matcher.js +6 -19
- package/src/build/build-adapter.d.ts +3 -0
- package/src/build/build-adapter.js +47 -20
- package/src/plugins/integration-plugin.d.ts +17 -0
- package/src/plugins/integration-plugin.js +26 -10
- package/src/route-renderer/orchestration/integration-renderer.d.ts +22 -2
- package/src/route-renderer/orchestration/integration-renderer.js +26 -7
- package/src/route-renderer/orchestration/render-preparation.service.js +6 -0
- package/src/route-renderer/page-loading/page-module-loader.d.ts +1 -0
- package/src/route-renderer/page-loading/page-module-loader.js +1 -0
- package/src/route-renderer/route-renderer.d.ts +6 -2
- package/src/route-renderer/route-renderer.js +6 -0
- package/src/services/module-loading/app-module-loader.service.d.ts +3 -0
- package/src/services/module-loading/app-module-loader.service.js +4 -0
- package/src/services/module-loading/app-server-module-transpiler.service.js +2 -2
- package/src/services/module-loading/node-bootstrap-plugin.d.ts +23 -0
- package/src/services/module-loading/node-bootstrap-plugin.js +23 -6
- package/src/services/module-loading/page-module-import.service.d.ts +1 -0
- package/src/services/module-loading/page-module-import.service.js +20 -8
- package/src/static-site-generator/static-site-generator.d.ts +0 -5
- package/src/static-site-generator/static-site-generator.js +6 -21
package/CHANGELOG.md
CHANGED
|
@@ -14,12 +14,17 @@ All notable changes to `@ecopages/core` are documented here.
|
|
|
14
14
|
|
|
15
15
|
- Consolidated runtime state around shared module-loading services, app-owned build execution, and the universal `createApp()` boundary.
|
|
16
16
|
- Simplified route-renderer orchestration around renderer-owned boundary runtimes, shared string-boundary queue helpers, and a smaller component render context.
|
|
17
|
+
- Centralized shared integration renderer bootstrapping so package integrations only append renderer-specific config instead of duplicating core lifecycle wiring.
|
|
17
18
|
- Removed marker-era compatibility capture, the shared route-level fallback resolver, deprecated `@ecopages/core/node*` escape hatches, and other dead route-renderer internals.
|
|
18
19
|
|
|
19
20
|
### Bug Fixes
|
|
20
21
|
|
|
21
22
|
- Fixed mixed-integration page, layout, document, and component rendering to resolve foreign boundaries inside their owning renderer across the built-in integrations.
|
|
22
23
|
- Fixed host/runtime module loading, published build-helper exports, asset output normalization, explicit render flows, and static or preview build stability across Bun, Node, Vite, and Nitro.
|
|
24
|
+
- Fixed request-time and static-generation page inspection to preserve integration-specific page loading without reusing the normal render module identity.
|
|
25
|
+
- Fixed Node preview and static-generation React runtime resolution so app-owned page modules and server rendering share one React module identity.
|
|
26
|
+
- Fixed Bun HMR script output normalization so multi-entrypoint browser builds preserve the expected outbase-relative source paths for emitted files.
|
|
27
|
+
- Fixed render-preparation graph traversal so sparse component dependency arrays do not break custom 404 rendering or file-system response fallback flows.
|
|
23
28
|
|
|
24
29
|
### Documentation
|
|
25
30
|
|
|
@@ -28,6 +33,7 @@ All notable changes to `@ecopages/core` are documented here.
|
|
|
28
33
|
### Tests
|
|
29
34
|
|
|
30
35
|
- Added regression coverage for app-owned runtime services, Node fallback paths, and cross-runtime invalidation behavior.
|
|
36
|
+
- Strengthened the core ghtml integration tests so route and explicit render paths await real outcomes and cover `renderToResponse` behavior.
|
|
31
37
|
|
|
32
38
|
---
|
|
33
39
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/core",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.15",
|
|
4
4
|
"description": "Core package for Ecopages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"directory": "packages/core"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@ecopages/file-system": "0.2.0-alpha.
|
|
20
|
+
"@ecopages/file-system": "0.2.0-alpha.15",
|
|
21
21
|
"@ecopages/logger": "^0.2.3",
|
|
22
22
|
"@ecopages/scripts-injector": "^0.1.3",
|
|
23
23
|
"@worker-tools/html-rewriter": "0.1.0-pre.19",
|
|
@@ -4,10 +4,6 @@ import type { FSRouter } from '../../router/server/fs-router.js';
|
|
|
4
4
|
import type { PageCacheService } from '../../services/cache/page-cache-service.js';
|
|
5
5
|
import type { CacheStrategy } from '../../services/cache/cache.types.js';
|
|
6
6
|
import type { FileSystemServerResponseFactory } from './fs-server-response-factory.js';
|
|
7
|
-
export declare const FILE_SYSTEM_RESPONSE_MATCHER_ERRORS: {
|
|
8
|
-
readonly transpilePageModuleFailed: (details: string) => string;
|
|
9
|
-
readonly noTranspiledOutputForPageModule: (filePath: string) => string;
|
|
10
|
-
};
|
|
11
7
|
export interface FileSystemResponseMatcherOptions {
|
|
12
8
|
appConfig: EcoPagesAppConfig;
|
|
13
9
|
router: FSRouter;
|
|
@@ -21,7 +17,6 @@ export interface FileSystemResponseMatcherOptions {
|
|
|
21
17
|
/**
|
|
22
18
|
* Matches file-system routes to rendered HTML responses.
|
|
23
19
|
*
|
|
24
|
-
* This class sits at the request-time boundary between router matches and the
|
|
25
20
|
* render pipeline. It coordinates page module inspection, request-local policy,
|
|
26
21
|
* renderer invocation, middleware execution, cache integration, and fallback
|
|
27
22
|
* error translation.
|
|
@@ -31,14 +26,12 @@ export declare class FileSystemResponseMatcher {
|
|
|
31
26
|
private router;
|
|
32
27
|
private routeRendererFactory;
|
|
33
28
|
private fileSystemResponseFactory;
|
|
34
|
-
private serverModuleTranspiler;
|
|
35
29
|
private pageRequestCacheCoordinator;
|
|
36
30
|
private fileRouteMiddlewarePipeline;
|
|
37
31
|
constructor({ appConfig, router, routeRendererFactory, fileSystemResponseFactory, cacheService, defaultCacheStrategy, }: FileSystemResponseMatcherOptions);
|
|
38
32
|
/**
|
|
39
33
|
* Resolves unmatched paths either as static asset requests or as the custom
|
|
40
34
|
* not-found page.
|
|
41
|
-
*
|
|
42
35
|
* @param requestUrl Incoming pathname.
|
|
43
36
|
* @returns Static file response or rendered 404 response.
|
|
44
37
|
*/
|
|
@@ -59,10 +52,9 @@ export declare class FileSystemResponseMatcher {
|
|
|
59
52
|
* Loads the matched page module for request-time inspection.
|
|
60
53
|
*
|
|
61
54
|
* The matcher needs access to page-level metadata such as `cache` and
|
|
62
|
-
* `middleware` before full rendering starts, so it
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* boundary instead of leaking through nested router collaborators.
|
|
55
|
+
* `middleware` before full rendering starts, so it asks the owning route
|
|
56
|
+
* renderer to load the page module. That preserves integration-specific page
|
|
57
|
+
* import setup for request-time inspection as well as for full rendering.
|
|
66
58
|
*
|
|
67
59
|
* @param filePath Absolute page module path.
|
|
68
60
|
* @returns Imported page module.
|
|
@@ -1,22 +1,15 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { appLogger } from "../../global/app-logger.js";
|
|
3
3
|
import { PageRequestCacheCoordinator } from "../../services/cache/page-request-cache-coordinator.service.js";
|
|
4
|
-
import { getAppServerModuleTranspiler } from "../../services/module-loading/app-server-module-transpiler.service.js";
|
|
5
|
-
import { resolveInternalExecutionDir } from "../../utils/resolve-work-dir.js";
|
|
6
4
|
import { ServerUtils } from "../../utils/server-utils.module.js";
|
|
7
5
|
import { FileRouteMiddlewarePipeline } from "./file-route-middleware-pipeline.js";
|
|
8
6
|
import { LocalsAccessError } from "../../errors/locals-access-error.js";
|
|
9
7
|
import { isDevelopmentRuntime } from "../../utils/runtime.js";
|
|
10
|
-
const FILE_SYSTEM_RESPONSE_MATCHER_ERRORS = {
|
|
11
|
-
transpilePageModuleFailed: (details) => `Error transpiling page module: ${details}`,
|
|
12
|
-
noTranspiledOutputForPageModule: (filePath) => `No transpiled output generated for page module: ${filePath}`
|
|
13
|
-
};
|
|
14
8
|
class FileSystemResponseMatcher {
|
|
15
9
|
appConfig;
|
|
16
10
|
router;
|
|
17
11
|
routeRendererFactory;
|
|
18
12
|
fileSystemResponseFactory;
|
|
19
|
-
serverModuleTranspiler;
|
|
20
13
|
pageRequestCacheCoordinator;
|
|
21
14
|
fileRouteMiddlewarePipeline;
|
|
22
15
|
constructor({
|
|
@@ -31,14 +24,12 @@ class FileSystemResponseMatcher {
|
|
|
31
24
|
this.router = router;
|
|
32
25
|
this.routeRendererFactory = routeRendererFactory;
|
|
33
26
|
this.fileSystemResponseFactory = fileSystemResponseFactory;
|
|
34
|
-
this.serverModuleTranspiler = getAppServerModuleTranspiler(appConfig);
|
|
35
27
|
this.pageRequestCacheCoordinator = new PageRequestCacheCoordinator(cacheService, defaultCacheStrategy);
|
|
36
28
|
this.fileRouteMiddlewarePipeline = new FileRouteMiddlewarePipeline(cacheService);
|
|
37
29
|
}
|
|
38
30
|
/**
|
|
39
31
|
* Resolves unmatched paths either as static asset requests or as the custom
|
|
40
32
|
* not-found page.
|
|
41
|
-
*
|
|
42
33
|
* @param requestUrl Incoming pathname.
|
|
43
34
|
* @returns Static file response or rendered 404 response.
|
|
44
35
|
*/
|
|
@@ -131,20 +122,17 @@ class FileSystemResponseMatcher {
|
|
|
131
122
|
* Loads the matched page module for request-time inspection.
|
|
132
123
|
*
|
|
133
124
|
* The matcher needs access to page-level metadata such as `cache` and
|
|
134
|
-
* `middleware` before full rendering starts, so it
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* boundary instead of leaking through nested router collaborators.
|
|
125
|
+
* `middleware` before full rendering starts, so it asks the owning route
|
|
126
|
+
* renderer to load the page module. That preserves integration-specific page
|
|
127
|
+
* import setup for request-time inspection as well as for full rendering.
|
|
138
128
|
*
|
|
139
129
|
* @param filePath Absolute page module path.
|
|
140
130
|
* @returns Imported page module.
|
|
141
131
|
*/
|
|
142
132
|
async importPageModule(filePath) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
transpileErrorMessage: FILE_SYSTEM_RESPONSE_MATCHER_ERRORS.transpilePageModuleFailed,
|
|
147
|
-
noOutputMessage: FILE_SYSTEM_RESPONSE_MATCHER_ERRORS.noTranspiledOutputForPageModule
|
|
133
|
+
const routeRenderer = this.routeRendererFactory.createRenderer(filePath);
|
|
134
|
+
return routeRenderer.loadPageModule(filePath, {
|
|
135
|
+
cacheScope: "request-metadata"
|
|
148
136
|
});
|
|
149
137
|
}
|
|
150
138
|
/**
|
|
@@ -155,6 +143,5 @@ class FileSystemResponseMatcher {
|
|
|
155
143
|
}
|
|
156
144
|
}
|
|
157
145
|
export {
|
|
158
|
-
FILE_SYSTEM_RESPONSE_MATCHER_ERRORS,
|
|
159
146
|
FileSystemResponseMatcher
|
|
160
147
|
};
|
|
@@ -110,6 +110,9 @@ export declare class BunBuildAdapter implements BuildAdapter {
|
|
|
110
110
|
private resolveConcreteOutputPath;
|
|
111
111
|
private resolveTemplatedOutputPath;
|
|
112
112
|
private relocateOutputFile;
|
|
113
|
+
private createDeterministicOutputReference;
|
|
114
|
+
private collectDeterministicOutputReferences;
|
|
115
|
+
private hasJavaScriptExtension;
|
|
113
116
|
private normalizeBunOutputs;
|
|
114
117
|
private rewriteAliasedRuntimeSpecifiers;
|
|
115
118
|
build(options: BuildOptions): Promise<BuildResult>;
|
|
@@ -275,37 +275,64 @@ class BunBuildAdapter {
|
|
|
275
275
|
fs.renameSync(currentPath, targetPath);
|
|
276
276
|
return targetPath;
|
|
277
277
|
}
|
|
278
|
+
createDeterministicOutputReference(outputPath) {
|
|
279
|
+
return path.normalize(outputPath).replace(/\.(?:[cm]?js)$/u, "");
|
|
280
|
+
}
|
|
281
|
+
collectDeterministicOutputReferences(options, entrypointPath) {
|
|
282
|
+
const expectedOutputsByReference = /* @__PURE__ */ new Map();
|
|
283
|
+
const expectedOutputPath = this.resolveTemplatedOutputPath(options, entrypointPath);
|
|
284
|
+
if (!expectedOutputPath) {
|
|
285
|
+
return expectedOutputsByReference;
|
|
286
|
+
}
|
|
287
|
+
expectedOutputsByReference.set(this.createDeterministicOutputReference(expectedOutputPath), expectedOutputPath);
|
|
288
|
+
if (!options.outbase) {
|
|
289
|
+
return expectedOutputsByReference;
|
|
290
|
+
}
|
|
291
|
+
const bunRootRelativeOutputPath = this.resolveTemplatedOutputPath(
|
|
292
|
+
{ ...options, outbase: void 0 },
|
|
293
|
+
entrypointPath
|
|
294
|
+
);
|
|
295
|
+
if (bunRootRelativeOutputPath && bunRootRelativeOutputPath !== expectedOutputPath) {
|
|
296
|
+
expectedOutputsByReference.set(
|
|
297
|
+
this.createDeterministicOutputReference(bunRootRelativeOutputPath),
|
|
298
|
+
expectedOutputPath
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
return expectedOutputsByReference;
|
|
302
|
+
}
|
|
303
|
+
hasJavaScriptExtension(outputPath) {
|
|
304
|
+
return /\.(?:[cm]?js)$/u.test(outputPath);
|
|
305
|
+
}
|
|
278
306
|
normalizeBunOutputs(result, options) {
|
|
279
307
|
if (!result.success || result.outputs.length === 0) {
|
|
280
308
|
return result;
|
|
281
309
|
}
|
|
282
310
|
const normalizedOutputs = [...result.outputs];
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
for (const [
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
normalizedOutputs[index] = {
|
|
292
|
-
path: this.relocateOutputFile(
|
|
293
|
-
concreteOutputPath ?? normalizedOutputs[index].path,
|
|
294
|
-
expectedOutputPath
|
|
295
|
-
)
|
|
296
|
-
};
|
|
311
|
+
const expectedOutputsByReference = /* @__PURE__ */ new Map();
|
|
312
|
+
for (const entrypointPath of options.entrypoints) {
|
|
313
|
+
for (const [reference, expectedOutputPath] of this.collectDeterministicOutputReferences(
|
|
314
|
+
options,
|
|
315
|
+
entrypointPath
|
|
316
|
+
)) {
|
|
317
|
+
expectedOutputsByReference.set(reference, expectedOutputPath);
|
|
297
318
|
}
|
|
298
|
-
return {
|
|
299
|
-
...result,
|
|
300
|
-
outputs: normalizedOutputs
|
|
301
|
-
};
|
|
302
319
|
}
|
|
303
320
|
return {
|
|
304
321
|
...result,
|
|
305
322
|
outputs: normalizedOutputs.map((output) => {
|
|
306
323
|
const concreteOutputPath = this.resolveConcreteOutputPath(output.path) ?? output.path;
|
|
307
|
-
|
|
308
|
-
|
|
324
|
+
const expectedOutputPath = expectedOutputsByReference.get(
|
|
325
|
+
this.createDeterministicOutputReference(concreteOutputPath)
|
|
326
|
+
);
|
|
327
|
+
if (expectedOutputPath) {
|
|
328
|
+
return {
|
|
329
|
+
path: this.relocateOutputFile(concreteOutputPath, expectedOutputPath)
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
if (this.hasJavaScriptExtension(concreteOutputPath)) {
|
|
333
|
+
return {
|
|
334
|
+
path: concreteOutputPath
|
|
335
|
+
};
|
|
309
336
|
}
|
|
310
337
|
const normalizedPath = `${concreteOutputPath}.js`;
|
|
311
338
|
return {
|
|
@@ -165,6 +165,22 @@ export declare abstract class IntegrationPlugin<C = EcoPagesElement> {
|
|
|
165
165
|
* Returns processed global assets resolved during `setup()`.
|
|
166
166
|
*/
|
|
167
167
|
getResolvedIntegrationDependencies(): ProcessedAsset[];
|
|
168
|
+
/**
|
|
169
|
+
* Creates the shared renderer options owned by core lifecycle setup.
|
|
170
|
+
*/
|
|
171
|
+
protected createRendererOptions(options?: {
|
|
172
|
+
rendererModules?: unknown;
|
|
173
|
+
}): {
|
|
174
|
+
appConfig: EcoPagesAppConfig;
|
|
175
|
+
assetProcessingService: AssetProcessingService;
|
|
176
|
+
resolvedIntegrationDependencies: ProcessedAsset[];
|
|
177
|
+
rendererModules: unknown;
|
|
178
|
+
runtimeOrigin: string;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Attaches runtime-only services after a renderer instance has been created.
|
|
182
|
+
*/
|
|
183
|
+
protected attachRendererRuntimeServices<T extends IntegrationRenderer<C>>(renderer: T): T;
|
|
168
184
|
/**
|
|
169
185
|
* Instantiates the integration renderer with app-owned services.
|
|
170
186
|
*
|
|
@@ -172,6 +188,7 @@ export declare abstract class IntegrationPlugin<C = EcoPagesElement> {
|
|
|
172
188
|
* Renderers are cheap runtime objects. They receive the finalized app config,
|
|
173
189
|
* a fresh asset-processing service, integration-global processed assets, and
|
|
174
190
|
* any renderer module context supplied by the active runtime.
|
|
191
|
+
renderer.name ||= this.name;
|
|
175
192
|
*/
|
|
176
193
|
initializeRenderer(options?: {
|
|
177
194
|
rendererModules?: unknown;
|
|
@@ -112,14 +112,9 @@ class IntegrationPlugin {
|
|
|
112
112
|
return this.resolvedIntegrationDependencies;
|
|
113
113
|
}
|
|
114
114
|
/**
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* @remarks
|
|
118
|
-
* Renderers are cheap runtime objects. They receive the finalized app config,
|
|
119
|
-
* a fresh asset-processing service, integration-global processed assets, and
|
|
120
|
-
* any renderer module context supplied by the active runtime.
|
|
115
|
+
* Creates the shared renderer options owned by core lifecycle setup.
|
|
121
116
|
*/
|
|
122
|
-
|
|
117
|
+
createRendererOptions(options) {
|
|
123
118
|
if (!this.appConfig) {
|
|
124
119
|
throw new Error(INTEGRATION_PLUGIN_ERRORS.NOT_INITIALIZED_WITH_APP_CONFIG);
|
|
125
120
|
}
|
|
@@ -127,19 +122,40 @@ class IntegrationPlugin {
|
|
|
127
122
|
if (this.hmrManager) {
|
|
128
123
|
assetProcessingService.setHmrManager(this.hmrManager);
|
|
129
124
|
}
|
|
130
|
-
|
|
125
|
+
return {
|
|
131
126
|
appConfig: this.appConfig,
|
|
132
127
|
assetProcessingService,
|
|
133
128
|
resolvedIntegrationDependencies: this.resolvedIntegrationDependencies,
|
|
134
129
|
rendererModules: options?.rendererModules,
|
|
135
130
|
runtimeOrigin: this.runtimeOrigin
|
|
136
|
-
}
|
|
137
|
-
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Attaches runtime-only services after a renderer instance has been created.
|
|
135
|
+
*/
|
|
136
|
+
attachRendererRuntimeServices(renderer) {
|
|
137
|
+
if (typeof renderer.name !== "string" || renderer.name.length === 0) {
|
|
138
|
+
renderer.name = this.name;
|
|
139
|
+
}
|
|
138
140
|
if (this.hmrManager) {
|
|
139
141
|
renderer.setHmrManager(this.hmrManager);
|
|
140
142
|
}
|
|
141
143
|
return renderer;
|
|
142
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Instantiates the integration renderer with app-owned services.
|
|
147
|
+
*
|
|
148
|
+
* @remarks
|
|
149
|
+
* Renderers are cheap runtime objects. They receive the finalized app config,
|
|
150
|
+
* a fresh asset-processing service, integration-global processed assets, and
|
|
151
|
+
* any renderer module context supplied by the active runtime.
|
|
152
|
+
renderer.name ||= this.name;
|
|
153
|
+
*/
|
|
154
|
+
initializeRenderer(options) {
|
|
155
|
+
const renderer = new this.renderer(this.createRendererOptions(options));
|
|
156
|
+
renderer.name ||= this.name;
|
|
157
|
+
return this.attachRendererRuntimeServices(renderer);
|
|
158
|
+
}
|
|
143
159
|
/**
|
|
144
160
|
* Prepares build-facing contributions before the app build manifest is sealed.
|
|
145
161
|
*
|
|
@@ -18,6 +18,17 @@ type BoundaryRenderDecisionInput = {
|
|
|
18
18
|
currentIntegration: string;
|
|
19
19
|
targetIntegration?: string;
|
|
20
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* Controls how one route module is loaded outside the normal render path.
|
|
23
|
+
*
|
|
24
|
+
* Request-time metadata inspection and static-generation probes use these
|
|
25
|
+
* options to isolate their module identity from the main render cache while
|
|
26
|
+
* still going through the owning integration's import setup.
|
|
27
|
+
*/
|
|
28
|
+
export type RouteModuleLoadOptions = {
|
|
29
|
+
bypassCache?: boolean;
|
|
30
|
+
cacheScope?: string;
|
|
31
|
+
};
|
|
21
32
|
/**
|
|
22
33
|
* Context for renderToResponse method.
|
|
23
34
|
*/
|
|
@@ -47,6 +58,15 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
|
|
|
47
58
|
protected renderExecutionService: RenderExecutionService;
|
|
48
59
|
protected readonly queuedBoundaryRuntimeService: QueuedBoundaryRuntimeService;
|
|
49
60
|
protected DOC_TYPE: string;
|
|
61
|
+
/**
|
|
62
|
+
* Loads one route module through the owning renderer's import path.
|
|
63
|
+
*
|
|
64
|
+
* Request-time infrastructure may need page metadata such as cache strategy or
|
|
65
|
+
* middleware before full rendering starts. Exposing this narrow entrypoint lets
|
|
66
|
+
* those callers reuse integration-specific import setup instead of bypassing it
|
|
67
|
+
* with raw transpiler access.
|
|
68
|
+
*/
|
|
69
|
+
loadPageModule(file: string, options?: RouteModuleLoadOptions): Promise<EcoPageFile>;
|
|
50
70
|
/**
|
|
51
71
|
* Reads the execution-scoped foreign renderer cache from one boundary input.
|
|
52
72
|
*
|
|
@@ -287,7 +307,7 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
|
|
|
287
307
|
*/
|
|
288
308
|
protected getMetadataProps(getMetadata: GetMetadata | undefined, { props, params, query }: GetMetadataContext): Promise<PageMetadataProps>;
|
|
289
309
|
protected usesIntegrationPageImporter(_file: string): boolean;
|
|
290
|
-
protected importIntegrationPageFile(_file: string): Promise<EcoPageFile>;
|
|
310
|
+
protected importIntegrationPageFile(_file: string, _options?: RouteModuleLoadOptions): Promise<EcoPageFile>;
|
|
291
311
|
protected normalizeImportedPageFile<TPageModule extends EcoPageFile>(_file: string, pageModule: TPageModule): TPageModule;
|
|
292
312
|
/**
|
|
293
313
|
* Imports the page file from the specified path.
|
|
@@ -296,7 +316,7 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
|
|
|
296
316
|
* @param file - The file path to import.
|
|
297
317
|
* @returns The imported module.
|
|
298
318
|
*/
|
|
299
|
-
protected importPageFile(file: string): Promise<EcoPageFile>;
|
|
319
|
+
protected importPageFile(file: string, options?: RouteModuleLoadOptions): Promise<EcoPageFile>;
|
|
300
320
|
/**
|
|
301
321
|
* Resolves the dependency path based on the component directory.
|
|
302
322
|
* It combines the component directory with the provided path URL.
|
|
@@ -56,6 +56,17 @@ class IntegrationRenderer {
|
|
|
56
56
|
renderExecutionService;
|
|
57
57
|
queuedBoundaryRuntimeService = new QueuedBoundaryRuntimeService();
|
|
58
58
|
DOC_TYPE = "<!DOCTYPE html>";
|
|
59
|
+
/**
|
|
60
|
+
* Loads one route module through the owning renderer's import path.
|
|
61
|
+
*
|
|
62
|
+
* Request-time infrastructure may need page metadata such as cache strategy or
|
|
63
|
+
* middleware before full rendering starts. Exposing this narrow entrypoint lets
|
|
64
|
+
* those callers reuse integration-specific import setup instead of bypassing it
|
|
65
|
+
* with raw transpiler access.
|
|
66
|
+
*/
|
|
67
|
+
async loadPageModule(file, options) {
|
|
68
|
+
return this.importPageFile(file, options);
|
|
69
|
+
}
|
|
59
70
|
/**
|
|
60
71
|
* Reads the execution-scoped foreign renderer cache from one boundary input.
|
|
61
72
|
*
|
|
@@ -531,7 +542,7 @@ class IntegrationRenderer {
|
|
|
531
542
|
usesIntegrationPageImporter(_file) {
|
|
532
543
|
return false;
|
|
533
544
|
}
|
|
534
|
-
async importIntegrationPageFile(_file) {
|
|
545
|
+
async importIntegrationPageFile(_file, _options) {
|
|
535
546
|
invariant(false, "Integration page importer must be implemented when enabled");
|
|
536
547
|
}
|
|
537
548
|
normalizeImportedPageFile(_file, pageModule) {
|
|
@@ -544,9 +555,15 @@ class IntegrationRenderer {
|
|
|
544
555
|
* @param file - The file path to import.
|
|
545
556
|
* @returns The imported module.
|
|
546
557
|
*/
|
|
547
|
-
async importPageFile(file) {
|
|
548
|
-
const bypassCache = typeof Bun !== "undefined" && process.env.NODE_ENV === "development";
|
|
549
|
-
const pageModule = this.usesIntegrationPageImporter(file) ? await this.importIntegrationPageFile(file
|
|
558
|
+
async importPageFile(file, options) {
|
|
559
|
+
const bypassCache = options?.bypassCache ?? (typeof Bun !== "undefined" && process.env.NODE_ENV === "development");
|
|
560
|
+
const pageModule = this.usesIntegrationPageImporter(file) ? await this.importIntegrationPageFile(file, {
|
|
561
|
+
bypassCache,
|
|
562
|
+
cacheScope: options?.cacheScope
|
|
563
|
+
}) : await this.pageModuleLoaderService.importPageFile(file, {
|
|
564
|
+
bypassCache,
|
|
565
|
+
cacheScope: options?.cacheScope
|
|
566
|
+
});
|
|
550
567
|
return this.normalizeImportedPageFile(file, pageModule);
|
|
551
568
|
}
|
|
552
569
|
/**
|
|
@@ -775,9 +792,11 @@ class IntegrationRenderer {
|
|
|
775
792
|
if (!boundaryOwner) {
|
|
776
793
|
return void 0;
|
|
777
794
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
795
|
+
const owningRenderer = this.getIntegrationRendererForName(boundaryOwner, rendererCache);
|
|
796
|
+
if (owningRenderer === this || owningRenderer.name === this.name) {
|
|
797
|
+
return void 0;
|
|
798
|
+
}
|
|
799
|
+
return await owningRenderer.renderComponentBoundary(this.withBoundaryRendererCache(input, rendererCache));
|
|
781
800
|
}
|
|
782
801
|
/**
|
|
783
802
|
* Renders one component under this integration's boundary runtime and resolves
|
|
@@ -115,6 +115,9 @@ class RenderPreparationService {
|
|
|
115
115
|
collectResolvedTriggers(components, seen = /* @__PURE__ */ new Set()) {
|
|
116
116
|
const triggers = [];
|
|
117
117
|
for (const comp of components) {
|
|
118
|
+
if (!comp) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
118
121
|
const ecoComp = comp;
|
|
119
122
|
if (seen.has(ecoComp)) {
|
|
120
123
|
continue;
|
|
@@ -165,6 +168,9 @@ class RenderPreparationService {
|
|
|
165
168
|
collectIntegrationNames(components, seen = /* @__PURE__ */ new Set()) {
|
|
166
169
|
const integrationNames = /* @__PURE__ */ new Set();
|
|
167
170
|
for (const comp of components) {
|
|
171
|
+
if (!comp) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
168
174
|
const ecoComp = comp;
|
|
169
175
|
if (seen.has(ecoComp)) {
|
|
170
176
|
continue;
|
|
@@ -30,6 +30,7 @@ class PageModuleLoaderService {
|
|
|
30
30
|
rootDir: this.appConfig.rootDir,
|
|
31
31
|
outdir: `${resolveInternalExecutionDir(this.appConfig)}/.server-modules`,
|
|
32
32
|
bypassCache: options?.bypassCache,
|
|
33
|
+
cacheScope: options?.cacheScope,
|
|
33
34
|
transpileErrorMessage: (details) => `Error transpiling page file: ${details}`,
|
|
34
35
|
noOutputMessage: (targetFilePath) => `No transpiled output generated for page: ${targetFilePath}`
|
|
35
36
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { EcoPagesAppConfig } from '../types/internal-types.js';
|
|
2
2
|
import type { IntegrationPlugin } from '../plugins/integration-plugin.js';
|
|
3
|
-
import type { RouteRenderResult, RouteRendererOptions } from '../types/public-types.js';
|
|
4
|
-
import type { IntegrationRenderer } from './orchestration/integration-renderer.js';
|
|
3
|
+
import type { EcoPageFile, RouteRenderResult, RouteRendererOptions } from '../types/public-types.js';
|
|
4
|
+
import type { IntegrationRenderer, RouteModuleLoadOptions } from './orchestration/integration-renderer.js';
|
|
5
5
|
/**
|
|
6
6
|
* Thin wrapper around one initialized integration renderer.
|
|
7
7
|
*
|
|
@@ -20,6 +20,10 @@ export declare class RouteRenderer {
|
|
|
20
20
|
* Executes the render pipeline for one matched route.
|
|
21
21
|
*/
|
|
22
22
|
createRoute(options: RouteRendererOptions): Promise<RouteRenderResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Loads the route module through the owning integration renderer.
|
|
25
|
+
*/
|
|
26
|
+
loadPageModule(filePath: string, options?: RouteModuleLoadOptions): Promise<EcoPageFile>;
|
|
23
27
|
}
|
|
24
28
|
/**
|
|
25
29
|
* Selects and caches integration renderers for route files and explicit views.
|
|
@@ -14,6 +14,12 @@ class RouteRenderer {
|
|
|
14
14
|
async createRoute(options) {
|
|
15
15
|
return this.renderer.execute(options);
|
|
16
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
|
+
}
|
|
17
23
|
}
|
|
18
24
|
class RouteRendererFactory {
|
|
19
25
|
appConfig;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PageModuleImportService, type PageModuleImportDependencies, type PageModuleImportOptions } from './page-module-import.service.js';
|
|
2
2
|
import type { BuildExecutor } from '../../build/build-adapter.js';
|
|
3
|
+
import type { EcoBuildPlugin } from '../../build/build-types.js';
|
|
3
4
|
export type AppModuleLoaderOwner = 'bun' | 'host';
|
|
4
5
|
export interface AppModuleLoader {
|
|
5
6
|
readonly owner: AppModuleLoaderOwner;
|
|
@@ -9,6 +10,7 @@ export interface AppModuleLoader {
|
|
|
9
10
|
export type AppModuleLoaderOptions = {
|
|
10
11
|
dependencies?: Partial<PageModuleImportDependencies>;
|
|
11
12
|
getBuildExecutor?: () => BuildExecutor | undefined;
|
|
13
|
+
getDefaultPlugins?: () => EcoBuildPlugin[];
|
|
12
14
|
getOwner?: () => AppModuleLoaderOwner;
|
|
13
15
|
getInvalidationVersion?: () => number | undefined;
|
|
14
16
|
pageModuleImportService?: PageModuleImportService;
|
|
@@ -16,6 +18,7 @@ export type AppModuleLoaderOptions = {
|
|
|
16
18
|
export declare class RuntimeAppModuleLoader implements AppModuleLoader {
|
|
17
19
|
private readonly pageModuleImportService;
|
|
18
20
|
private readonly getBuildExecutorValue;
|
|
21
|
+
private readonly getDefaultPluginsValue;
|
|
19
22
|
private readonly getOwnerValue;
|
|
20
23
|
private readonly getInvalidationVersionValue;
|
|
21
24
|
constructor(options?: AppModuleLoaderOptions);
|
|
@@ -4,11 +4,13 @@ import {
|
|
|
4
4
|
class RuntimeAppModuleLoader {
|
|
5
5
|
pageModuleImportService;
|
|
6
6
|
getBuildExecutorValue;
|
|
7
|
+
getDefaultPluginsValue;
|
|
7
8
|
getOwnerValue;
|
|
8
9
|
getInvalidationVersionValue;
|
|
9
10
|
constructor(options = {}) {
|
|
10
11
|
this.pageModuleImportService = options.pageModuleImportService ?? new PageModuleImportService(options.dependencies);
|
|
11
12
|
this.getBuildExecutorValue = options.getBuildExecutor ?? (() => void 0);
|
|
13
|
+
this.getDefaultPluginsValue = options.getDefaultPlugins ?? (() => []);
|
|
12
14
|
this.getOwnerValue = options.getOwner ?? (() => "bun");
|
|
13
15
|
this.getInvalidationVersionValue = options.getInvalidationVersion ?? (() => void 0);
|
|
14
16
|
}
|
|
@@ -16,8 +18,10 @@ class RuntimeAppModuleLoader {
|
|
|
16
18
|
return this.getOwnerValue();
|
|
17
19
|
}
|
|
18
20
|
async importModule(options) {
|
|
21
|
+
const mergedPlugins = [...this.getDefaultPluginsValue(), ...options.plugins ?? []];
|
|
19
22
|
return await this.pageModuleImportService.importModule({
|
|
20
23
|
...options,
|
|
24
|
+
...mergedPlugins.length > 0 ? { plugins: mergedPlugins } : {},
|
|
21
25
|
buildExecutor: options.buildExecutor ?? this.getBuildExecutorValue(),
|
|
22
26
|
invalidationVersion: options.invalidationVersion ?? this.getInvalidationVersionValue()
|
|
23
27
|
});
|
|
@@ -49,6 +49,7 @@ function createAppModuleLoader(appConfig) {
|
|
|
49
49
|
getHostModuleLoader: () => getAppHostModuleLoader(appConfig)
|
|
50
50
|
},
|
|
51
51
|
getBuildExecutor: () => getAppBuildExecutor(appConfig),
|
|
52
|
+
getDefaultPlugins: typeof Bun === "undefined" && appConfig.rootDir ? () => [createAppNodeBootstrapPlugin(appConfig)] : void 0,
|
|
52
53
|
getOwner: () => getAppModuleLoaderOwner(appConfig),
|
|
53
54
|
getInvalidationVersion: () => invalidationService.getServerModuleInvalidationVersion()
|
|
54
55
|
});
|
|
@@ -77,8 +78,7 @@ function createAppServerModuleTranspiler(appConfig) {
|
|
|
77
78
|
canLoadSourceModuleFromHost: (filePath) => shouldAppUseHostModuleLoader(appConfig, filePath),
|
|
78
79
|
getInvalidationVersion: () => invalidationService.getServerModuleInvalidationVersion(),
|
|
79
80
|
invalidateModules: (changedFiles) => invalidationService.invalidateServerModules(changedFiles),
|
|
80
|
-
pageModuleImportService: getAppModuleLoader(appConfig)
|
|
81
|
-
getDefaultPlugins: typeof Bun === "undefined" && appConfig.rootDir ? () => [createAppNodeBootstrapPlugin(appConfig)] : void 0
|
|
81
|
+
pageModuleImportService: getAppModuleLoader(appConfig)
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
84
|
function getAppServerModuleTranspiler(appConfig) {
|
|
@@ -6,7 +6,15 @@ import type { EcoPagesAppConfig } from '../../types/internal-types.js';
|
|
|
6
6
|
*/
|
|
7
7
|
export declare function getAppRuntimeNodeModulesDir(appConfig: Pick<EcoPagesAppConfig, 'rootDir' | 'workDir' | 'absolutePaths'>): string;
|
|
8
8
|
export interface NodeBootstrapResolutionOptions {
|
|
9
|
+
/**
|
|
10
|
+
* App root used as the fallback package boundary when an importer does not
|
|
11
|
+
* live under a more specific package.json.
|
|
12
|
+
*/
|
|
9
13
|
projectDir: string;
|
|
14
|
+
/**
|
|
15
|
+
* Runtime-local node_modules directory that receives symlinks to resolved
|
|
16
|
+
* package roots so transpiled Node imports share one package graph.
|
|
17
|
+
*/
|
|
10
18
|
runtimeNodeModulesDir: string;
|
|
11
19
|
preserveImportMetaPaths?: string[];
|
|
12
20
|
}
|
|
@@ -16,7 +24,22 @@ export interface NodeBootstrapResolutionOptions {
|
|
|
16
24
|
*/
|
|
17
25
|
export declare function getNodeUnsupportedBuiltinError(specifier: string, importer?: string): string;
|
|
18
26
|
export declare function resolveNodeBootstrapDependency(args: Pick<EcoBuildOnResolveArgs, 'path' | 'importer'>, options: NodeBootstrapResolutionOptions): EcoBuildOnResolveResult | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Creates the Node bootstrap plugin used by app-owned server module loads.
|
|
29
|
+
*
|
|
30
|
+
* The resolver anchors third-party imports to the nearest package boundary for
|
|
31
|
+
* the importing file, then mirrors the resolved package root into the runtime
|
|
32
|
+
* node_modules directory. That keeps transpiled Node execution aligned with the
|
|
33
|
+
* package graph each source file was authored against.
|
|
34
|
+
*/
|
|
19
35
|
export declare function createNodeBootstrapPlugin(options: NodeBootstrapResolutionOptions): EcoBuildPlugin;
|
|
36
|
+
/**
|
|
37
|
+
* Creates the default Node bootstrap plugin for one Ecopages app runtime.
|
|
38
|
+
*
|
|
39
|
+
* This binds the shared resolution policy to the app's internal execution
|
|
40
|
+
* directory so transpiled server modules can externalize packages into one
|
|
41
|
+
* stable runtime node_modules graph.
|
|
42
|
+
*/
|
|
20
43
|
export declare function createAppNodeBootstrapPlugin(appConfig: Pick<EcoPagesAppConfig, 'rootDir' | 'workDir' | 'absolutePaths'>, options?: {
|
|
21
44
|
preserveImportMetaPaths?: string[];
|
|
22
45
|
}): EcoBuildPlugin;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, symlinkSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync } from "node:fs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import { resolveInternalExecutionDir } from "../../utils/resolve-work-dir.js";
|
|
@@ -32,7 +32,10 @@ function ensureRuntimePackageLink(nodeModulesDir, specifier, resolvedPath) {
|
|
|
32
32
|
const packageRoot = findPackageRoot(resolvedPath);
|
|
33
33
|
const linkPath = path.join(nodeModulesDir, packageName);
|
|
34
34
|
if (existsSync(linkPath)) {
|
|
35
|
-
|
|
35
|
+
if (realpathSync(linkPath) === realpathSync(packageRoot)) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
rmSync(linkPath, { recursive: true, force: true });
|
|
36
39
|
}
|
|
37
40
|
mkdirSync(path.dirname(linkPath), { recursive: true });
|
|
38
41
|
symlinkSync(packageRoot, linkPath, "dir");
|
|
@@ -40,9 +43,6 @@ function ensureRuntimePackageLink(nodeModulesDir, specifier, resolvedPath) {
|
|
|
40
43
|
function getNodeUnsupportedBuiltinError(specifier, importer) {
|
|
41
44
|
return `Node bootstrap transpilation does not support Bun builtin specifier ${JSON.stringify(specifier)}${importer ? ` imported from ${importer}` : ""}.`;
|
|
42
45
|
}
|
|
43
|
-
function shouldResolveFromImporter(importer) {
|
|
44
|
-
return Boolean(importer && importer.includes(`${path.sep}node_modules${path.sep}`));
|
|
45
|
-
}
|
|
46
46
|
function resolveSpecifier(specifier, parentPath) {
|
|
47
47
|
try {
|
|
48
48
|
return createRequire(parentPath).resolve(specifier);
|
|
@@ -53,6 +53,23 @@ function resolveSpecifier(specifier, parentPath) {
|
|
|
53
53
|
function resolveFromCore(specifier) {
|
|
54
54
|
return createRequire(import.meta.url).resolve(specifier);
|
|
55
55
|
}
|
|
56
|
+
function findResolutionParent(importer, projectDir) {
|
|
57
|
+
if (!importer || !path.isAbsolute(importer)) {
|
|
58
|
+
return path.join(projectDir, "package.json");
|
|
59
|
+
}
|
|
60
|
+
let currentPath = path.dirname(importer);
|
|
61
|
+
while (true) {
|
|
62
|
+
const packageJsonPath = path.join(currentPath, "package.json");
|
|
63
|
+
if (existsSync(packageJsonPath)) {
|
|
64
|
+
return packageJsonPath;
|
|
65
|
+
}
|
|
66
|
+
const parentPath = path.dirname(currentPath);
|
|
67
|
+
if (parentPath === currentPath) {
|
|
68
|
+
return path.join(projectDir, "package.json");
|
|
69
|
+
}
|
|
70
|
+
currentPath = parentPath;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
56
73
|
function getBootstrapBuildLoaderForPath(filePath) {
|
|
57
74
|
switch (path.extname(filePath).toLowerCase()) {
|
|
58
75
|
case ".ts":
|
|
@@ -98,7 +115,7 @@ function resolveNodeBootstrapDependency(args, options) {
|
|
|
98
115
|
if (args.path.startsWith("./") || args.path.startsWith("../") || args.path.startsWith("@/") || args.path.startsWith("/") || args.path.startsWith("node:")) {
|
|
99
116
|
return void 0;
|
|
100
117
|
}
|
|
101
|
-
const resolveParent =
|
|
118
|
+
const resolveParent = findResolutionParent(args.importer, options.projectDir);
|
|
102
119
|
if (args.path.startsWith("@ecopages/")) {
|
|
103
120
|
let resolvedPath2;
|
|
104
121
|
try {
|
|
@@ -51,7 +51,7 @@ class PageModuleImportService {
|
|
|
51
51
|
const fileHash = this.dependencies.hashFile(filePath);
|
|
52
52
|
const hostModuleLoader = typeof Bun === "undefined" && process.env.NODE_ENV === "development" && this.dependencies.canLoadSourceModuleFromHost(filePath) ? this.dependencies.getHostModuleLoader() : void 0;
|
|
53
53
|
if (hostModuleLoader) {
|
|
54
|
-
const sourceModuleUrl = createRuntimeModuleUrl(filePath, fileHash, invalidationVersion);
|
|
54
|
+
const sourceModuleUrl = createRuntimeModuleUrl(filePath, fileHash, invalidationVersion, options.cacheScope);
|
|
55
55
|
return await hostModuleLoader(sourceModuleUrl.href);
|
|
56
56
|
}
|
|
57
57
|
if (options.bypassCache) {
|
|
@@ -69,6 +69,7 @@ class PageModuleImportService {
|
|
|
69
69
|
rootDir,
|
|
70
70
|
splitting ?? "default",
|
|
71
71
|
externalPackages ?? "default",
|
|
72
|
+
options.cacheScope ?? "default",
|
|
72
73
|
fileHash,
|
|
73
74
|
invalidationVersion
|
|
74
75
|
].join("::");
|
|
@@ -96,11 +97,12 @@ class PageModuleImportService {
|
|
|
96
97
|
invalidationVersion = this.developmentInvalidationVersion,
|
|
97
98
|
splitting,
|
|
98
99
|
externalPackages,
|
|
100
|
+
cacheScope,
|
|
99
101
|
transpileErrorMessage = (details) => `Error transpiling page module: ${details}`,
|
|
100
102
|
noOutputMessage = (targetFilePath) => `No transpiled output generated for page module: ${targetFilePath}`,
|
|
101
103
|
fileHash
|
|
102
104
|
} = options;
|
|
103
|
-
const sourceModuleUrl = createRuntimeModuleUrl(filePath, fileHash, invalidationVersion);
|
|
105
|
+
const sourceModuleUrl = createRuntimeModuleUrl(filePath, fileHash, invalidationVersion, cacheScope);
|
|
104
106
|
if (typeof Bun !== "undefined") {
|
|
105
107
|
return await import(
|
|
106
108
|
/* @vite-ignore */
|
|
@@ -108,7 +110,8 @@ class PageModuleImportService {
|
|
|
108
110
|
);
|
|
109
111
|
}
|
|
110
112
|
const fileBaseName = path.basename(filePath, path.extname(filePath));
|
|
111
|
-
const
|
|
113
|
+
const cacheScopeSuffix = cacheScope ? `-${sanitizeCacheScope(cacheScope)}` : "";
|
|
114
|
+
const outputFileName = `${fileBaseName}-${fileHash}${cacheScopeSuffix}.js`;
|
|
112
115
|
const buildResult = await this.dependencies.buildModule(
|
|
113
116
|
{
|
|
114
117
|
entrypoints: [filePath],
|
|
@@ -136,8 +139,11 @@ class PageModuleImportService {
|
|
|
136
139
|
throw new Error(noOutputMessage(filePath));
|
|
137
140
|
}
|
|
138
141
|
const compiledOutputUrl = pathToFileURL(compiledOutput);
|
|
139
|
-
if (process.env.NODE_ENV === "development") {
|
|
140
|
-
compiledOutputUrl.searchParams.set(
|
|
142
|
+
if (process.env.NODE_ENV === "development" || cacheScope) {
|
|
143
|
+
compiledOutputUrl.searchParams.set(
|
|
144
|
+
"update",
|
|
145
|
+
[fileHash, invalidationVersion, cacheScope ? sanitizeCacheScope(cacheScope) : void 0].filter((value) => value !== void 0).join("-")
|
|
146
|
+
);
|
|
141
147
|
}
|
|
142
148
|
return await import(
|
|
143
149
|
/* @vite-ignore */
|
|
@@ -145,13 +151,19 @@ class PageModuleImportService {
|
|
|
145
151
|
);
|
|
146
152
|
}
|
|
147
153
|
}
|
|
148
|
-
function createRuntimeModuleUrl(filePath, fileHash, invalidationVersion) {
|
|
154
|
+
function createRuntimeModuleUrl(filePath, fileHash, invalidationVersion, cacheScope) {
|
|
149
155
|
const moduleUrl = pathToFileURL(filePath);
|
|
150
|
-
if (process.env.NODE_ENV === "development") {
|
|
151
|
-
moduleUrl.searchParams.set(
|
|
156
|
+
if (process.env.NODE_ENV === "development" || cacheScope) {
|
|
157
|
+
moduleUrl.searchParams.set(
|
|
158
|
+
"update",
|
|
159
|
+
[fileHash, invalidationVersion, cacheScope ? sanitizeCacheScope(cacheScope) : void 0].filter((value) => value !== void 0).join("-")
|
|
160
|
+
);
|
|
152
161
|
}
|
|
153
162
|
return moduleUrl;
|
|
154
163
|
}
|
|
164
|
+
function sanitizeCacheScope(cacheScope) {
|
|
165
|
+
return cacheScope.replace(/[^a-zA-Z0-9_-]+/g, "-");
|
|
166
|
+
}
|
|
155
167
|
function supportsHostSourceModuleLoading(filePath) {
|
|
156
168
|
const extension = path.extname(filePath);
|
|
157
169
|
return extension === ".js" || extension === ".jsx" || extension === ".ts" || extension === ".tsx" || extension === ".mjs" || extension === ".mts" || extension === ".cjs" || extension === ".cts";
|
|
@@ -20,17 +20,12 @@ export declare const STATIC_SITE_GENERATOR_ERRORS: {
|
|
|
20
20
|
*/
|
|
21
21
|
export declare class StaticSiteGenerator {
|
|
22
22
|
appConfig: EcoPagesAppConfig;
|
|
23
|
-
private serverModuleTranspiler;
|
|
24
23
|
/**
|
|
25
24
|
* Creates the static-site generator for one app config.
|
|
26
25
|
*/
|
|
27
26
|
constructor({ appConfig }: {
|
|
28
27
|
appConfig: EcoPagesAppConfig;
|
|
29
28
|
});
|
|
30
|
-
/**
|
|
31
|
-
* Returns the transpiler output directory used for static page-module probes.
|
|
32
|
-
*/
|
|
33
|
-
private getStaticPageModuleOutdir;
|
|
34
29
|
private getExportDir;
|
|
35
30
|
/**
|
|
36
31
|
* Logs the standardized warning emitted when a dynamic-cache page is skipped.
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { DEFAULT_ECOPAGES_WORK_DIR } from "../config/constants.js";
|
|
3
2
|
import { appLogger } from "../global/app-logger.js";
|
|
4
3
|
import { fileSystem } from "@ecopages/file-system";
|
|
5
4
|
import { PathUtils } from "../utils/path-utils.module.js";
|
|
6
|
-
import { getAppServerModuleTranspiler } from "../services/module-loading/app-server-module-transpiler.service.js";
|
|
7
5
|
const STATIC_SITE_GENERATOR_ERRORS = {
|
|
8
6
|
ROUTE_RENDERER_FACTORY_REQUIRED: "RouteRendererFactory is required for render strategy",
|
|
9
7
|
unsupportedBodyType: (bodyType) => `Unsupported body type for static generation: ${bodyType}`,
|
|
@@ -13,20 +11,11 @@ const STATIC_SITE_GENERATOR_ERRORS = {
|
|
|
13
11
|
};
|
|
14
12
|
class StaticSiteGenerator {
|
|
15
13
|
appConfig;
|
|
16
|
-
serverModuleTranspiler;
|
|
17
14
|
/**
|
|
18
15
|
* Creates the static-site generator for one app config.
|
|
19
16
|
*/
|
|
20
17
|
constructor({ appConfig }) {
|
|
21
18
|
this.appConfig = appConfig;
|
|
22
|
-
this.serverModuleTranspiler = getAppServerModuleTranspiler(appConfig);
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Returns the transpiler output directory used for static page-module probes.
|
|
26
|
-
*/
|
|
27
|
-
getStaticPageModuleOutdir() {
|
|
28
|
-
const workDir = this.appConfig.absolutePaths?.workDir ?? path.join(this.appConfig.rootDir, this.appConfig.workDir ?? DEFAULT_ECOPAGES_WORK_DIR);
|
|
29
|
-
return path.join(workDir, ".server-static-page-modules");
|
|
30
19
|
}
|
|
31
20
|
getExportDir() {
|
|
32
21
|
return this.appConfig.absolutePaths?.distDir ?? path.join(this.appConfig.rootDir, this.appConfig.distDir);
|
|
@@ -44,13 +33,9 @@ class StaticSiteGenerator {
|
|
|
44
33
|
* Determines whether one filesystem-discovered page should be excluded from
|
|
45
34
|
* static generation.
|
|
46
35
|
*/
|
|
47
|
-
async shouldSkipStaticPageFile(filePath) {
|
|
48
|
-
const module = await
|
|
49
|
-
|
|
50
|
-
outdir: this.getStaticPageModuleOutdir(),
|
|
51
|
-
externalPackages: false,
|
|
52
|
-
transpileErrorMessage: (details) => `Error transpiling static page module: ${details}`,
|
|
53
|
-
noOutputMessage: (targetFilePath) => `No transpiled output generated for static page module: ${targetFilePath}`
|
|
36
|
+
async shouldSkipStaticPageFile(filePath, routeRendererFactory) {
|
|
37
|
+
const module = await routeRendererFactory.createRenderer(filePath).loadPageModule(filePath, {
|
|
38
|
+
cacheScope: "static-page-probe"
|
|
54
39
|
});
|
|
55
40
|
if (module.default?.cache !== "dynamic") {
|
|
56
41
|
return false;
|
|
@@ -146,9 +131,6 @@ class StaticSiteGenerator {
|
|
|
146
131
|
for (const route of routes) {
|
|
147
132
|
try {
|
|
148
133
|
const { filePath, pathname: routePathname } = router.routes[route];
|
|
149
|
-
if (await this.shouldSkipStaticPageFile(filePath)) {
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
134
|
const ext = PathUtils.getEcoTemplateExtension(filePath);
|
|
153
135
|
const integration = this.appConfig.integrations.find((plugin) => plugin.extensions.includes(ext));
|
|
154
136
|
const strategy = integration?.staticBuildStep || "render";
|
|
@@ -165,6 +147,9 @@ class StaticSiteGenerator {
|
|
|
165
147
|
if (!routeRendererFactory) {
|
|
166
148
|
throw new Error(STATIC_SITE_GENERATOR_ERRORS.ROUTE_RENDERER_FACTORY_REQUIRED);
|
|
167
149
|
}
|
|
150
|
+
if (await this.shouldSkipStaticPageFile(filePath, routeRendererFactory)) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
168
153
|
let pathname2 = routePathname;
|
|
169
154
|
const pathnameSegments2 = pathname2.split("/").filter(Boolean);
|
|
170
155
|
if (pathname2 === "/") {
|