@ecopages/core 0.2.0-beta.38 → 0.2.0-beta.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/README.md +1 -0
  2. package/package.json +27 -3
  3. package/src/adapters/bun/server-adapter.js +1 -0
  4. package/src/adapters/node/server-adapter.js +1 -0
  5. package/src/adapters/shared/http/explicit-static-render-preparation.js +1 -1
  6. package/src/adapters/shared/http/explicit-static-route-matcher.js +1 -1
  7. package/src/adapters/shared/http/fs-server-response-matcher.js +15 -7
  8. package/src/adapters/shared/runtime/collect-dev-prewarm-plan.d.ts +11 -0
  9. package/src/adapters/shared/runtime/collect-dev-prewarm-plan.js +29 -0
  10. package/src/adapters/shared/runtime/dev-static-route-prewarm.d.ts +16 -0
  11. package/src/adapters/shared/runtime/dev-static-route-prewarm.js +66 -0
  12. package/src/adapters/shared/runtime/render-context.js +1 -1
  13. package/src/adapters/shared/runtime/server-adapter.d.ts +7 -1
  14. package/src/adapters/shared/runtime/server-adapter.js +64 -5
  15. package/src/build/README.md +1 -1
  16. package/src/build/browser/browser-runtime-plugin.js +2 -5
  17. package/src/build/cache/production-build-cache.d.ts +3 -2
  18. package/src/build/cache/production-build-cache.js +2 -2
  19. package/src/build/contracts/content-virtual-modules.d.ts +12 -0
  20. package/src/build/contracts/content-virtual-modules.js +29 -0
  21. package/src/build/rolldown/rolldown-adapter-helpers.js +1 -1
  22. package/src/build/runtime/build-request-policy.d.ts +3 -4
  23. package/src/build/runtime/build-request-policy.js +4 -4
  24. package/src/cache/index.d.ts +2 -1
  25. package/src/cache/index.js +2 -1
  26. package/src/cache/module-parse-cache.d.ts +5 -0
  27. package/src/cache/module-parse-cache.js +27 -3
  28. package/src/cache/module-transform-profiler.d.ts +8 -0
  29. package/src/cache/module-transform-profiler.js +8 -0
  30. package/src/client/view-transitions.d.ts +17 -1
  31. package/src/client/view-transitions.js +45 -1
  32. package/src/client/view-transitions.test.browser.d.ts +1 -0
  33. package/src/client/view-transitions.test.browser.js +56 -0
  34. package/src/config/config-builder.d.ts +16 -9
  35. package/src/config/config-builder.js +24 -14
  36. package/src/dev/transform-server/dev-transform-vendor-registry.d.ts +1 -0
  37. package/src/dev/transform-server/dev-transform-vendor-registry.js +15 -2
  38. package/src/diagnostics/request-pipeline-metrics.d.ts +38 -0
  39. package/src/diagnostics/request-pipeline-metrics.js +121 -0
  40. package/src/eco/component-identity.d.ts +13 -0
  41. package/src/eco/component-identity.js +18 -0
  42. package/src/eco/eco-declared-component.d.ts +3 -2
  43. package/src/eco/eco-declared-component.js +6 -4
  44. package/src/eco/eco.browser.js +2 -2
  45. package/src/eco/eco.js +7 -6
  46. package/src/eco/eco.types.d.ts +6 -5
  47. package/src/errors/http-error.d.ts +8 -0
  48. package/src/errors/http-error.js +16 -0
  49. package/src/errors/undeclared-component-dependency-error.d.ts +1 -1
  50. package/src/errors/undeclared-component-dependency-error.js +2 -2
  51. package/src/index.browser.d.ts +1 -0
  52. package/src/index.browser.js +1 -0
  53. package/src/index.d.ts +1 -0
  54. package/src/index.js +1 -0
  55. package/src/plugins/README.md +2 -1
  56. package/src/plugins/eco-component-meta-plugin.d.ts +6 -102
  57. package/src/plugins/eco-component-meta-plugin.js +110 -391
  58. package/src/plugins/processor.d.ts +10 -0
  59. package/src/plugins/processor.js +9 -0
  60. package/src/route-renderer/README.md +10 -8
  61. package/src/route-renderer/orchestration/document-shell/document-shell-render.service.js +1 -1
  62. package/src/route-renderer/orchestration/foreign-child/foreign-subtree-execution.service.js +1 -1
  63. package/src/route-renderer/orchestration/integration-renderer.d.ts +0 -8
  64. package/src/route-renderer/orchestration/integration-renderer.js +6 -19
  65. package/src/route-renderer/orchestration/ownership-graph/component-graph-collectors.js +5 -4
  66. package/src/route-renderer/orchestration/ownership-graph/component-graph.js +6 -4
  67. package/src/route-renderer/orchestration/ownership-graph/ownership-validation.service.d.ts +0 -1
  68. package/src/route-renderer/orchestration/ownership-graph/ownership-validation.service.js +10 -19
  69. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-session.d.ts +1 -0
  70. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-session.js +12 -0
  71. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph.service.d.ts +9 -0
  72. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph.service.js +7 -0
  73. package/src/route-renderer/orchestration/route-pipeline/route-render-orchestrator.js +65 -33
  74. package/src/route-renderer/page-loading/component-dependency-collection.js +3 -2
  75. package/src/route-renderer/page-loading/ecopages-virtual-imports.d.ts +0 -5
  76. package/src/route-renderer/page-loading/ecopages-virtual-imports.js +4 -10
  77. package/src/route-renderer/page-loading/file-scoped-dependency-components.d.ts +2 -2
  78. package/src/route-renderer/page-loading/file-scoped-dependency-components.js +12 -11
  79. package/src/route-renderer/page-loading/lazy-trigger-planning.js +2 -1
  80. package/src/route-renderer/page-loading/page-module-loader.d.ts +1 -1
  81. package/src/route-renderer/page-loading/page-module-loader.js +8 -3
  82. package/src/router/README.md +8 -0
  83. package/src/services/README.md +5 -0
  84. package/src/services/assets/asset-processing-service/asset-processing.service.d.ts +1 -0
  85. package/src/services/assets/asset-processing-service/asset-processing.service.js +22 -5
  86. package/src/services/cache/cache.types.d.ts +4 -2
  87. package/src/services/cache/html-page-cache-dependency-index.d.ts +34 -0
  88. package/src/services/cache/html-page-cache-dependency-index.js +88 -0
  89. package/src/services/cache/index.d.ts +2 -0
  90. package/src/services/cache/index.js +2 -0
  91. package/src/services/cache/page-cache-service.d.ts +31 -1
  92. package/src/services/cache/page-cache-service.js +79 -3
  93. package/src/services/cache/page-request-cache-coordinator.service.js +4 -0
  94. package/src/services/module-loading/README.md +9 -6
  95. package/src/services/module-loading/app-server-module-transpiler.service.js +0 -3
  96. package/src/services/module-loading/collection-server-module-build.service.d.ts +19 -0
  97. package/src/services/module-loading/collection-server-module-build.service.js +83 -0
  98. package/src/services/module-loading/page-module-import.service.d.ts +5 -11
  99. package/src/services/module-loading/page-module-import.service.js +56 -73
  100. package/src/services/module-loading/route-module-build-cache.store.js +6 -6
  101. package/src/services/module-loading/route-module-build-manifest.d.ts +8 -5
  102. package/src/services/module-loading/route-module-build-manifest.js +20 -32
  103. package/src/services/module-loading/server-module-transpiler.service.d.ts +0 -2
  104. package/src/services/module-loading/server-module-transpiler.service.js +0 -3
  105. package/src/static-site-generator/static-site-generator.js +2 -2
  106. package/src/types/internal-types.d.ts +4 -0
  107. package/src/types/public-types.d.ts +18 -18
  108. package/src/watchers/project-watcher.js +14 -0
package/README.md CHANGED
@@ -18,6 +18,7 @@ The current core package is organized around app-owned runtime state and explici
18
18
  The important ownership rules are:
19
19
 
20
20
  - `ConfigBuilder.build()` finalizes app-owned build and runtime services.
21
+ - Component identity attribution is one shared source transform for server, browser, and HMR compilation paths: after a lexical `eco.` gate, it uses Oxc to wrap supported factory options with `bindComponentIdentity()`, which factories retain as `config.identity`.
21
22
  - browser bundling and server module loading are explicit, separate paths.
22
23
  - runtime hosts stay thin and delegate framework work into core services.
23
24
  - HMR and invalidation use shared graph-aware services instead of runtime-specific ad hoc wiring.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/core",
3
- "version": "0.2.0-beta.38",
3
+ "version": "0.2.0-beta.39",
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-beta.38",
20
+ "@ecopages/file-system": "0.2.0-beta.39",
21
21
  "@ecopages/logger": "^0.2.3",
22
22
  "@ecopages/scripts-injector": "^0.1.5",
23
23
  "@oxc-project/runtime": "0.141.0",
@@ -32,7 +32,7 @@
32
32
  "@standard-schema/utils": "^0.3.0"
33
33
  },
34
34
  "peerDependencies": {
35
- "@ecopages/dev-toolbar": "0.2.0-beta.38"
35
+ "@ecopages/dev-toolbar": "0.2.0-beta.39"
36
36
  },
37
37
  "peerDependenciesMeta": {
38
38
  "@ecopages/dev-toolbar": {
@@ -143,6 +143,10 @@
143
143
  "types": "./src/diagnostics/startup-trace.d.ts",
144
144
  "default": "./src/diagnostics/startup-trace.js"
145
145
  },
146
+ "./diagnostics/request-pipeline-metrics": {
147
+ "types": "./src/diagnostics/request-pipeline-metrics.d.ts",
148
+ "default": "./src/diagnostics/request-pipeline-metrics.js"
149
+ },
146
150
  "./bun": {
147
151
  "types": "./src/adapters/bun/index.d.ts",
148
152
  "default": "./src/adapters/bun/index.js"
@@ -227,6 +231,14 @@
227
231
  "types": "./src/build/contracts/build-contracts.d.ts",
228
232
  "default": "./src/build/contracts/build-contracts.js"
229
233
  },
234
+ "./build/contracts/content-virtual-modules": {
235
+ "types": "./src/build/contracts/content-virtual-modules.d.ts",
236
+ "default": "./src/build/contracts/content-virtual-modules.js"
237
+ },
238
+ "./services/module-loading/collection-server-module-build.service": {
239
+ "types": "./src/services/module-loading/collection-server-module-build.service.d.ts",
240
+ "default": "./src/services/module-loading/collection-server-module-build.service.js"
241
+ },
230
242
  "./build/production-build-cache": {
231
243
  "types": "./src/build/cache/production-build-cache.d.ts",
232
244
  "default": "./src/build/cache/production-build-cache.js"
@@ -424,6 +436,10 @@
424
436
  "types": "./src/diagnostics/startup-trace.d.ts",
425
437
  "default": "./src/diagnostics/startup-trace.js"
426
438
  },
439
+ "./diagnostics/request-pipeline-metrics.ts": {
440
+ "types": "./src/diagnostics/request-pipeline-metrics.d.ts",
441
+ "default": "./src/diagnostics/request-pipeline-metrics.js"
442
+ },
427
443
  "./bun.ts": {
428
444
  "types": "./src/adapters/bun/index.d.ts",
429
445
  "default": "./src/adapters/bun/index.js"
@@ -508,6 +524,14 @@
508
524
  "types": "./src/build/contracts/build-contracts.d.ts",
509
525
  "default": "./src/build/contracts/build-contracts.js"
510
526
  },
527
+ "./build/contracts/content-virtual-modules.ts": {
528
+ "types": "./src/build/contracts/content-virtual-modules.d.ts",
529
+ "default": "./src/build/contracts/content-virtual-modules.js"
530
+ },
531
+ "./services/module-loading/collection-server-module-build.service.ts": {
532
+ "types": "./src/services/module-loading/collection-server-module-build.service.d.ts",
533
+ "default": "./src/services/module-loading/collection-server-module-build.service.js"
534
+ },
511
535
  "./build/production-build-cache.ts": {
512
536
  "types": "./src/build/cache/production-build-cache.d.ts",
513
537
  "default": "./src/build/cache/production-build-cache.js"
@@ -468,6 +468,7 @@ export class BunServerAdapter extends SharedServerAdapter {
468
468
  appConfig: this.appConfig,
469
469
  runtimeOrigin: this.runtimeOrigin,
470
470
  });
471
+ await this.startDevStaticRoutePrewarmWhenReady();
471
472
  }
472
473
  this.fullyInitialized = true;
473
474
  if (this.options?.watch)
@@ -314,6 +314,7 @@ export class NodeServerAdapter extends SharedServerAdapter {
314
314
  runtimeOrigin: this.runtimeOrigin,
315
315
  });
316
316
  this.configureSharedResponseHandlers(this.staticRoutes, this.hmrManager);
317
+ await this.startDevStaticRoutePrewarmWhenReady();
317
318
  const watcher = new ProjectWatcher({
318
319
  config: this.appConfig,
319
320
  refreshRouterRoutesCallback: this.createSharedWatchRefreshCallback({
@@ -1,5 +1,5 @@
1
1
  function getViewIntegrationName(view) {
2
- return view.config?.integration ?? view.config?.__eco?.integration;
2
+ return view.config?.integration ?? view.config?.identity?.integration;
3
3
  }
4
4
  /**
5
5
  * Resolves the renderer and static props needed to render one explicit static
@@ -2,7 +2,7 @@ import { appLogger } from '../../../global/app-logger.js';
2
2
  import { prepareExplicitStaticRender } from './explicit-static-render-preparation.js';
3
3
  import { matchExplicitStaticPathPattern } from '../../abstract/segment-path-matcher.js';
4
4
  export const EXPLICIT_STATIC_ROUTE_MATCHER_ERRORS = {
5
- missingIntegration: (routePath) => `View at ${routePath} is missing __eco.integration. Ensure it's defined with eco.page() and exported as default.`,
5
+ missingIntegration: (routePath) => `View at ${routePath} is missing component identity integration. Ensure it's defined with eco.page() and exported as default.`,
6
6
  noRendererForIntegration: (integrationName) => `No renderer found for integration: ${integrationName}`,
7
7
  };
8
8
  /**
@@ -3,6 +3,7 @@ import { appLogger } from '../../../global/app-logger.js';
3
3
  import { PageRequestCacheCoordinator } from '../../../services/cache/page-request-cache-coordinator.service.js';
4
4
  import { ServerUtils } from '../../../utils/server-utils.module.js';
5
5
  import { FileRouteMiddlewarePipeline } from './file-route-middleware-pipeline.js';
6
+ import { HttpError } from '../../../errors/http-error.js';
6
7
  import { LocalsAccessError } from '../../../errors/locals-access-error.js';
7
8
  import { isDevelopmentRuntime } from '../../../utils/runtime.js';
8
9
  /**
@@ -74,13 +75,18 @@ export class FileSystemResponseMatcher {
74
75
  const renderFn = async () => {
75
76
  const result = await routeRenderer.execute({
76
77
  file: executionPlan.pageFilePath,
78
+ pageModule: executionPlan.pageModule,
77
79
  params: match.params,
78
80
  query: match.query,
79
81
  locals: executionPlan.localsForRender,
80
82
  });
81
83
  const html = await this.pageRequestCacheCoordinator.bodyToString(result.body);
82
84
  const strategy = result.cacheStrategy ?? this.pageRequestCacheCoordinator.getDefaultCacheStrategy();
83
- return { html, strategy };
85
+ return {
86
+ html,
87
+ strategy,
88
+ sourceDependencyPaths: result.sourceDependencyPaths,
89
+ };
84
90
  };
85
91
  const renderResponse = async () => {
86
92
  return this.pageRequestCacheCoordinator.render({
@@ -99,6 +105,9 @@ export class FileSystemResponseMatcher {
99
105
  if (error instanceof Response) {
100
106
  return error;
101
107
  }
108
+ if (HttpError.isHttpError(error) && error.status === 404) {
109
+ return await this.renderCustomNotFoundResponseOrServerError(match.requestedPathname);
110
+ }
102
111
  if (error instanceof LocalsAccessError) {
103
112
  return await this.createInternalServerErrorResponse(error.message, match.requestedPathname, error);
104
113
  }
@@ -211,13 +220,14 @@ export class FileSystemResponseMatcher {
211
220
  const localsStore = {};
212
221
  const pageFilePath = match.templateRoute.filePath;
213
222
  const pageModule = await this.importPageModule(pageFilePath);
214
- const Page = pageModule?.default;
215
- const pageMiddleware = (Page?.middleware ?? []);
216
- const pageCacheStrategy = Page?.cache ?? this.pageRequestCacheCoordinator.getDefaultCacheStrategy();
223
+ const pageComponent = pageModule.default;
224
+ const pageMiddleware = pageComponent.middleware ?? [];
225
+ const pageCacheStrategy = pageModule.cache ?? pageComponent.cache ?? this.pageRequestCacheCoordinator.getDefaultCacheStrategy();
217
226
  return {
218
227
  cacheKey,
219
228
  request: resolvedRequest,
220
229
  pageFilePath,
230
+ pageModule,
221
231
  pageMiddleware,
222
232
  pageCacheStrategy,
223
233
  localsStore,
@@ -237,9 +247,7 @@ export class FileSystemResponseMatcher {
237
247
  */
238
248
  async importPageModule(filePath) {
239
249
  const routeRenderer = this.routeRendererFactory.getPageRenderer(filePath);
240
- return routeRenderer.loadPageModule(filePath, {
241
- cacheScope: 'request-metadata',
242
- });
250
+ return routeRenderer.loadPageModule(filePath);
243
251
  }
244
252
  /**
245
253
  * Get the underlying cache service for external invalidation.
@@ -0,0 +1,11 @@
1
+ import type { EcoPagesAppConfig } from '../../../types/internal-types.js';
2
+ import type { DevPrewarmReadiness } from './dev-static-route-prewarm.js';
3
+ export type DevPrewarmPlan = {
4
+ pathnames: readonly string[];
5
+ beforeReadyPathnames: readonly string[];
6
+ readiness: DevPrewarmReadiness;
7
+ };
8
+ /**
9
+ * Aggregates processor-declared dev prewarm pathnames and readiness into one plan.
10
+ */
11
+ export declare function collectAppDevPrewarmPlan(appConfig: EcoPagesAppConfig): Promise<DevPrewarmPlan>;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Aggregates processor-declared dev prewarm pathnames and readiness into one plan.
3
+ */
4
+ export async function collectAppDevPrewarmPlan(appConfig) {
5
+ const pathnames = new Set(appConfig.devPrewarmPaths ?? []);
6
+ const beforeReadyPathnames = new Set(appConfig.devPrewarmBeforeReadyPaths ?? []);
7
+ let readiness = 'background';
8
+ for (const processor of appConfig.processors.values()) {
9
+ const contributor = processor;
10
+ const plan = await contributor.collectDevPrewarmPlan();
11
+ for (const pathname of plan.pathnames) {
12
+ pathnames.add(pathname);
13
+ }
14
+ if (plan.readiness === 'beforeReady') {
15
+ readiness = 'beforeReady';
16
+ for (const pathname of plan.pathnames) {
17
+ beforeReadyPathnames.add(pathname);
18
+ }
19
+ }
20
+ }
21
+ for (const pathname of beforeReadyPathnames) {
22
+ pathnames.add(pathname);
23
+ }
24
+ return {
25
+ pathnames: [...pathnames],
26
+ beforeReadyPathnames: [...beforeReadyPathnames],
27
+ readiness,
28
+ };
29
+ }
@@ -0,0 +1,16 @@
1
+ export type DevPrewarmReadiness = 'background' | 'beforeReady';
2
+ export type DevStaticRoutePrewarmOptions = {
3
+ pathnames: readonly string[];
4
+ readiness?: DevPrewarmReadiness;
5
+ renderPath: (pathname: string) => Promise<void>;
6
+ /** Invoked with the pathname list before rendering begins. */
7
+ onPathnamesResolved?: (pathnames: readonly string[]) => void;
8
+ };
9
+ /**
10
+ * Renders declared pathnames so first client navigations hit warm graphs and scoped watch HTML cache.
11
+ */
12
+ export declare function runDevStaticRoutePrewarm(options: DevStaticRoutePrewarmOptions): Promise<void>;
13
+ /**
14
+ * Fire-and-forget wrapper around {@link runDevStaticRoutePrewarm}.
15
+ */
16
+ export declare function startDevStaticRoutePrewarm(options: DevStaticRoutePrewarmOptions): void;
@@ -0,0 +1,66 @@
1
+ import { appLogger } from '../../../global/app-logger.js';
2
+ const LOG_PREFIX = '[ecopages:dev-prewarm]';
3
+ function resolvePrewarmConcurrency() {
4
+ const fromEnv = process.env.ECOPAGES_DEV_PREWARM_STATIC_ROUTES_PARALLELISM;
5
+ if (fromEnv) {
6
+ const parsed = Number(fromEnv);
7
+ if (Number.isFinite(parsed) && parsed > 0) {
8
+ return Math.floor(parsed);
9
+ }
10
+ }
11
+ return 3;
12
+ }
13
+ async function runWithConcurrency(items, concurrency, worker) {
14
+ if (items.length === 0) {
15
+ return;
16
+ }
17
+ let index = 0;
18
+ const runners = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
19
+ while (index < items.length) {
20
+ const current = items[index];
21
+ index += 1;
22
+ if (current === undefined) {
23
+ continue;
24
+ }
25
+ await worker(current);
26
+ }
27
+ });
28
+ await Promise.all(runners);
29
+ }
30
+ /**
31
+ * Renders declared pathnames so first client navigations hit warm graphs and scoped watch HTML cache.
32
+ */
33
+ export async function runDevStaticRoutePrewarm(options) {
34
+ const pathnames = [...new Set(options.pathnames)];
35
+ if (pathnames.length === 0) {
36
+ return;
37
+ }
38
+ options.onPathnamesResolved?.(pathnames);
39
+ const concurrency = resolvePrewarmConcurrency();
40
+ const readiness = options.readiness ?? 'background';
41
+ appLogger.debug(`${LOG_PREFIX} started (${pathnames.length} path(s), concurrency ${concurrency}, readiness=${readiness})`);
42
+ appLogger.debug(`${LOG_PREFIX} pathnames: ${pathnames.join(', ')}`);
43
+ await runWithConcurrency(pathnames, concurrency, async (pathname) => {
44
+ try {
45
+ await options.renderPath(pathname);
46
+ }
47
+ catch (error) {
48
+ const message = error instanceof Error ? error.message : String(error);
49
+ if (readiness === 'beforeReady') {
50
+ appLogger.warn(`${LOG_PREFIX} skipped ${pathname}: ${message}`);
51
+ }
52
+ else {
53
+ appLogger.debug(`${LOG_PREFIX} skipped ${pathname}: ${message}`);
54
+ }
55
+ }
56
+ });
57
+ appLogger.debug(`${LOG_PREFIX} finished (${pathnames.length} path(s))`);
58
+ }
59
+ /**
60
+ * Fire-and-forget wrapper around {@link runDevStaticRoutePrewarm}.
61
+ */
62
+ export function startDevStaticRoutePrewarm(options) {
63
+ void runDevStaticRoutePrewarm(options).catch((error) => {
64
+ appLogger.error(`${LOG_PREFIX} failed: ${error instanceof Error ? error.message : String(error)}`);
65
+ });
66
+ }
@@ -34,7 +34,7 @@ export function createRenderContext(options) {
34
34
  * Throws an error if the integration cannot be determined or is not found.
35
35
  */
36
36
  const getRendererForView = (view) => {
37
- const integrationName = view.config?.integration ?? view.config?.__eco?.integration;
37
+ const integrationName = view.config?.integration ?? view.config?.identity?.integration;
38
38
  invariant(!!integrationName, 'Cannot determine integration for view. Set view.config.integration explicitly or ensure the view is defined with eco.page() in a file with a recognized extension.');
39
39
  const integration = integrations.find((i) => i.name === integrationName);
40
40
  invariant(!!integration, `No integration found for: ${integrationName}`);
@@ -26,6 +26,12 @@ export declare abstract class SharedServerAdapter<TOptions extends ServerAdapter
26
26
  protected schemaValidator: SchemaValidationService;
27
27
  protected readonly apiRequestPipeline: ApiRequestPipeline<Request, unknown>;
28
28
  protected hostOwnsDevClient: boolean;
29
+ private devStaticRoutePrewarmStarted;
30
+ private sharedPageCacheService;
31
+ /**
32
+ * Warms declared static paths after the final watch-mode response pipeline exists.
33
+ */
34
+ protected startDevStaticRoutePrewarmWhenReady(): Promise<void>;
29
35
  protected initializeSharedRouteHandling(options: {
30
36
  staticRoutes: StaticRoute[];
31
37
  hmrManager?: SharedHmrAssetManager;
@@ -60,7 +66,7 @@ export declare abstract class SharedServerAdapter<TOptions extends ServerAdapter
60
66
  */
61
67
  protected configureSharedResponseHandlers(staticRoutes: StaticRoute[], hmrManager?: IHmrManager): void;
62
68
  private createSharedResponseHandlerDependencies;
63
- private createSharedPageCacheService;
69
+ private getOrCreateSharedPageCacheService;
64
70
  protected getCacheService(): CacheInvalidator | null;
65
71
  protected getRenderContext(): RenderContext;
66
72
  private tryHandleSharedDevClientRequest;
@@ -5,7 +5,7 @@ import { RouteRegistry } from '../../../router/server/route-registry.js';
5
5
  import { startupTrace } from '../../../diagnostics/startup-trace.js';
6
6
  import { requestBuildDedupe } from '../../../diagnostics/request-build-dedupe.js';
7
7
  import { MemoryCacheStore } from '../../../services/cache/memory-cache-store.js';
8
- import { PageCacheService } from '../../../services/cache/page-cache-service.js';
8
+ import { PageCacheService, registerAppPageCacheService } from '../../../services/cache/page-cache-service.js';
9
9
  import { SchemaValidationService } from '../../../services/validation/schema-validation-service.js';
10
10
  import { StaticSiteGenerator } from '../../../static-site-generator/static-site-generator.js';
11
11
  import { ServerStaticBuilder } from './server-static-builder.js';
@@ -13,6 +13,8 @@ import { ExplicitStaticRouteMatcher } from '../http/explicit-static-route-matche
13
13
  import { FileSystemServerResponseFactory } from '../http/fs-server-response-factory.js';
14
14
  import { FileSystemResponseMatcher } from '../http/fs-server-response-matcher.js';
15
15
  import { ServerRouteHandler } from './server-route-handler.js';
16
+ import { startDevStaticRoutePrewarm, runDevStaticRoutePrewarm } from './dev-static-route-prewarm.js';
17
+ import { collectAppDevPrewarmPlan } from './collect-dev-prewarm-plan.js';
16
18
  import { createRenderContext } from './render-context.js';
17
19
  import { ApiRequestPipeline } from '../http/api-request-pipeline.js';
18
20
  import { injectDevToolbarIntoHtmlResponse, shouldInjectDevToolbarHtmlResponse, } from '../../../dev-toolbar/dev-toolbar-html-response.js';
@@ -33,6 +35,40 @@ export class SharedServerAdapter extends AbstractServerAdapter {
33
35
  getCacheService: () => this.getCacheService(),
34
36
  });
35
37
  hostOwnsDevClient = false;
38
+ devStaticRoutePrewarmStarted = false;
39
+ sharedPageCacheService;
40
+ /**
41
+ * Warms declared static paths after the final watch-mode response pipeline exists.
42
+ */
43
+ async startDevStaticRoutePrewarmWhenReady() {
44
+ if (!this.options?.watch || this.devStaticRoutePrewarmStarted) {
45
+ return;
46
+ }
47
+ this.devStaticRoutePrewarmStarted = true;
48
+ const runtimeOrigin = this.runtimeOrigin;
49
+ const plan = await collectAppDevPrewarmPlan(this.appConfig);
50
+ const beforeReadyPathnames = new Set(plan.beforeReadyPathnames);
51
+ const allPathnames = plan.pathnames;
52
+ const options = {
53
+ pathnames: allPathnames,
54
+ readiness: 'background',
55
+ renderPath: async (pathname) => {
56
+ const response = await this.routeHandler.handleResponse(new Request(new URL(pathname, runtimeOrigin).toString(), { method: 'GET' }));
57
+ await response.arrayBuffer();
58
+ },
59
+ };
60
+ if (beforeReadyPathnames.size > 0) {
61
+ await runDevStaticRoutePrewarm({
62
+ ...options,
63
+ pathnames: [...beforeReadyPathnames],
64
+ readiness: 'beforeReady',
65
+ });
66
+ }
67
+ const backgroundPathnames = allPathnames.filter((pathname) => !beforeReadyPathnames.has(pathname));
68
+ if (backgroundPathnames.length > 0) {
69
+ startDevStaticRoutePrewarm({ ...options, pathnames: backgroundPathnames });
70
+ }
71
+ }
36
72
  async initializeSharedRouteHandling(options) {
37
73
  this.ensureRouteRendererFactory();
38
74
  await this.initSharedRouter();
@@ -132,7 +168,7 @@ export class SharedServerAdapter extends AbstractServerAdapter {
132
168
  watchMode: !!this.options?.watch,
133
169
  },
134
170
  });
135
- const cacheService = this.createSharedPageCacheService();
171
+ const cacheService = this.getOrCreateSharedPageCacheService();
136
172
  const fileSystemResponseMatcher = new FileSystemResponseMatcher({
137
173
  appConfig: this.appConfig,
138
174
  assetPrefix: path.join(this.appConfig.rootDir, this.appConfig.distDir),
@@ -154,16 +190,39 @@ export class SharedServerAdapter extends AbstractServerAdapter {
154
190
  : undefined,
155
191
  };
156
192
  }
157
- createSharedPageCacheService() {
193
+ getOrCreateSharedPageCacheService() {
194
+ if (this.sharedPageCacheService !== undefined) {
195
+ return this.sharedPageCacheService;
196
+ }
158
197
  const cacheConfig = this.appConfig.cache;
159
- const isCacheEnabled = cacheConfig?.enabled ?? !this.options?.watch;
198
+ const watch = Boolean(this.options?.watch);
199
+ if (watch) {
200
+ if (cacheConfig?.enabled === false) {
201
+ this.sharedPageCacheService = null;
202
+ registerAppPageCacheService(this.appConfig, null);
203
+ return null;
204
+ }
205
+ const store = cacheConfig?.store === 'memory' || !cacheConfig?.store
206
+ ? new MemoryCacheStore({ maxEntries: cacheConfig?.maxEntries })
207
+ : cacheConfig.store;
208
+ const service = new PageCacheService({ store, enabled: true });
209
+ this.sharedPageCacheService = service;
210
+ registerAppPageCacheService(this.appConfig, service);
211
+ return service;
212
+ }
213
+ const isCacheEnabled = cacheConfig?.enabled ?? true;
160
214
  if (!isCacheEnabled) {
215
+ this.sharedPageCacheService = null;
216
+ registerAppPageCacheService(this.appConfig, null);
161
217
  return null;
162
218
  }
163
219
  const store = cacheConfig?.store === 'memory' || !cacheConfig?.store
164
220
  ? new MemoryCacheStore({ maxEntries: cacheConfig?.maxEntries })
165
221
  : cacheConfig.store;
166
- return new PageCacheService({ store, enabled: true });
222
+ const service = new PageCacheService({ store, enabled: true });
223
+ this.sharedPageCacheService = service;
224
+ registerAppPageCacheService(this.appConfig, service);
225
+ return service;
167
226
  }
168
227
  getCacheService() {
169
228
  return this.fileSystemResponseMatcher?.getCacheService() ?? null;
@@ -201,7 +201,7 @@ Mixed-integration apps need explicit `@jsxImportSource` handling in two differen
201
201
  | ------------------------------------ | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
202
202
  | `getJsxOwnershipPlugins()` | App-wide server/transpile builds | Each JSX integration extension gets its **own** `jsxImportSource` so native files keep their owning runtime. |
203
203
  | `getHostScopedJsxOwnershipPlugins()` | One integration's **client** bundle graph | Foreign `.tsx`/`.jsx` files compile with the **host** integration JSX runtime (for example React bundling `.kita.tsx`). |
204
- | `eco-component-meta-plugin` | Component metadata injection | Prepends the **owning** integration pragma only when injecting `__eco` metadata into native files. |
204
+ | component identity source transform | Component identity attribution | Prepends the **owning** integration pragma when injecting `identity` into native files. |
205
205
 
206
206
  `foreign-jsx-override-plugin.ts` is the shared implementation. Prefer the helpers above instead of calling it directly from integrations.
207
207
 
@@ -21,7 +21,7 @@
21
21
  */
22
22
  import path from 'node:path';
23
23
  import { existsSync, readFileSync } from 'node:fs';
24
- import { cachedParseSync } from '../../cache/module-parse-cache.js';
24
+ import { parseModuleSource } from '../../cache/module-parse-cache.js';
25
25
  import { getBrowserRuntimeSpecifierMap } from './browser-runtime-manifest.js';
26
26
  import { buildSpecifierFilter } from './browser-runtime-plugin-helpers.js';
27
27
  /**
@@ -80,10 +80,7 @@ export function rewriteBrowserRuntimeImports(code, specifierMap, filePath = 'bro
80
80
  }
81
81
  const edits = [];
82
82
  try {
83
- const result = cachedParseSync(filePath, code, {
84
- sourceType: 'module',
85
- lang: path.extname(filePath).endsWith('x') ? 'tsx' : 'ts',
86
- });
83
+ const result = parseModuleSource(filePath, code);
87
84
  const walk = (node) => {
88
85
  if (!isRecord(node)) {
89
86
  return;
@@ -3,7 +3,8 @@ import type { EcoPagesAppConfig } from '../../types/internal-types.js';
3
3
  * Shared manifest fields for persisted production build caches.
4
4
  */
5
5
  export interface ProductionCacheManifestBase {
6
- invalidationVersion: string;
6
+ invalidationVersion?: string;
7
+ corePackageVersion?: string;
7
8
  buildInputsFingerprint?: string;
8
9
  buildKey?: string;
9
10
  builtAt?: number;
@@ -15,6 +16,6 @@ export declare function getProductionBuildCacheManifestPaths(appConfig: EcoPages
15
16
  export declare function clearPersistedProductionBuildCacheManifests(appConfig: EcoPagesAppConfig): void;
16
17
  export declare function readProductionCacheManifest<T extends ProductionCacheManifestBase>(manifestPath: string): T | undefined;
17
18
  export declare function writeProductionCacheManifest(manifestPath: string, manifest: unknown): void;
18
- export declare function isProductionCacheManifestCurrent(manifest: ProductionCacheManifestBase | undefined, invalidationVersion: string): boolean;
19
+ export declare function isProductionCacheManifestCurrent(manifest: ProductionCacheManifestBase | undefined, packageVersion: string): boolean;
19
20
  export declare function matchesProductionCacheFingerprint(manifest: ProductionCacheManifestBase | undefined, buildInputsFingerprint: string): boolean;
20
21
  export declare function matchesProductionCacheBuildKey(manifest: ProductionCacheManifestBase | undefined, buildKey: string): boolean;
@@ -45,8 +45,8 @@ export function writeProductionCacheManifest(manifestPath, manifest) {
45
45
  fileSystem.ensureDir(path.dirname(manifestPath));
46
46
  fileSystem.write(manifestPath, `${JSON.stringify(manifest, null, '\t')}\n`);
47
47
  }
48
- export function isProductionCacheManifestCurrent(manifest, invalidationVersion) {
49
- return manifest?.invalidationVersion === invalidationVersion;
48
+ export function isProductionCacheManifestCurrent(manifest, packageVersion) {
49
+ return (manifest?.corePackageVersion ?? manifest?.invalidationVersion) === packageVersion;
50
50
  }
51
51
  export function matchesProductionCacheFingerprint(manifest, buildInputsFingerprint) {
52
52
  return manifest?.buildInputsFingerprint === buildInputsFingerprint;
@@ -0,0 +1,12 @@
1
+ /** Virtual module prefix for collection imports, e.g. `ecopages:content/docs`. */
2
+ export declare const CONTENT_VIRTUAL_MODULE_PREFIX = "ecopages:content";
3
+ /** Matches `ecopages:content/<collection>` metadata specifiers (no MDX imports). */
4
+ export declare const CONTENT_VIRTUAL_MODULE_PATTERN: RegExp;
5
+ /** Matches `ecopages:content/<collection>/server` component resolver specifiers. */
6
+ export declare const CONTENT_SERVER_VIRTUAL_MODULE_PATTERN: RegExp;
7
+ export type ParsedCollectionSpecifier = {
8
+ collectionName: string;
9
+ variant: 'entries' | 'server';
10
+ };
11
+ export declare function parseCollectionSpecifier(specifier: string): ParsedCollectionSpecifier | null;
12
+ export declare function isContentServerVirtualModule(specifier: string): boolean;
@@ -0,0 +1,29 @@
1
+ /** Virtual module prefix for collection imports, e.g. `ecopages:content/docs`. */
2
+ export const CONTENT_VIRTUAL_MODULE_PREFIX = 'ecopages:content';
3
+ /** Matches `ecopages:content/<collection>` metadata specifiers (no MDX imports). */
4
+ export const CONTENT_VIRTUAL_MODULE_PATTERN = /^ecopages:content\/[a-z][a-z0-9-]*$/;
5
+ /** Matches `ecopages:content/<collection>/server` component resolver specifiers. */
6
+ export const CONTENT_SERVER_VIRTUAL_MODULE_PATTERN = /^ecopages:content\/[a-z][a-z0-9-]+\/server$/;
7
+ export function parseCollectionSpecifier(specifier) {
8
+ const normalized = specifier.startsWith(`${CONTENT_VIRTUAL_MODULE_PREFIX}/`)
9
+ ? specifier.slice(`${CONTENT_VIRTUAL_MODULE_PREFIX}/`.length)
10
+ : specifier.startsWith('content/')
11
+ ? specifier.slice('content/'.length)
12
+ : null;
13
+ if (!normalized) {
14
+ return null;
15
+ }
16
+ if (normalized.endsWith('/server')) {
17
+ return {
18
+ collectionName: normalized.slice(0, -'/server'.length),
19
+ variant: 'server',
20
+ };
21
+ }
22
+ return {
23
+ collectionName: normalized,
24
+ variant: 'entries',
25
+ };
26
+ }
27
+ export function isContentServerVirtualModule(specifier) {
28
+ return CONTENT_SERVER_VIRTUAL_MODULE_PATTERN.test(specifier);
29
+ }
@@ -307,7 +307,7 @@ export function resolveRolldownOptions(options, contextRoot, outdir, appRootRequ
307
307
  transformOptions.target = options.target;
308
308
  }
309
309
  const bundlePlugins = options.plugins ?? [];
310
- const sourceTransforms = options.target === 'browser' ? (options.sourceTransforms ?? []) : [];
310
+ const sourceTransforms = options.sourceTransforms ?? [];
311
311
  const appPlugins = createRolldownPluginBridge(bundlePlugins, contextRoot, sourceTransforms);
312
312
  const allPlugins = [
313
313
  ...(rolldownPlatform === 'node' ? [createNodeBuiltinExternalPlugin()] : []),
@@ -7,7 +7,7 @@ import type { BuildProfile } from './build-runtime.js';
7
7
  *
8
8
  * @remarks
9
9
  * Always goes through {@link getAppServerBuildPlugins} so sealed-manifest and
10
- * loader-fallback apps share one plugin list with the alias resolver. Used by
10
+ * source-transform apps share one plugin list with the alias resolver. Used by
11
11
  * both request assembly and unified-graph cache keys.
12
12
  */
13
13
  export declare function resolveServerAppBuildPlugins(appConfig: EcoPagesAppConfig): EcoBuildPlugin[];
@@ -36,9 +36,8 @@ export type BrowserBuildRequestInput = Partial<BuildOptions> & {
36
36
  * @remarks
37
37
  * Defaults `profile` to `'route-module'`. Plugins are
38
38
  * {@link resolveServerAppBuildPlugins} plus unique caller contributions via
39
- * {@link mergeCallerBuildPlugins}. Does not attach `sourceTransforms` unlike
40
- * {@link createBrowserBuildRequest}. Pass the returned options into
41
- * {@link BuildRuntime.getProfile}; the runtime no longer injects plugins.
39
+ * {@link mergeCallerBuildPlugins}. Source transforms are applied on every
40
+ * compilation target, so server module loading shares browser attribution.
42
41
  */
43
42
  export declare function createServerBuildRequest(appConfig: EcoPagesAppConfig, input: ServerBuildRequestInput): BuildOptions;
44
43
  /**
@@ -7,7 +7,7 @@ import { getAppSourceTransforms } from '../../plugins/source-transform.js';
7
7
  *
8
8
  * @remarks
9
9
  * Always goes through {@link getAppServerBuildPlugins} so sealed-manifest and
10
- * loader-fallback apps share one plugin list with the alias resolver. Used by
10
+ * source-transform apps share one plugin list with the alias resolver. Used by
11
11
  * both request assembly and unified-graph cache keys.
12
12
  */
13
13
  export function resolveServerAppBuildPlugins(appConfig) {
@@ -36,9 +36,8 @@ export function mergeCallerBuildPlugins(appPlugins, callerPlugins) {
36
36
  * @remarks
37
37
  * Defaults `profile` to `'route-module'`. Plugins are
38
38
  * {@link resolveServerAppBuildPlugins} plus unique caller contributions via
39
- * {@link mergeCallerBuildPlugins}. Does not attach `sourceTransforms` unlike
40
- * {@link createBrowserBuildRequest}. Pass the returned options into
41
- * {@link BuildRuntime.getProfile}; the runtime no longer injects plugins.
39
+ * {@link mergeCallerBuildPlugins}. Source transforms are applied on every
40
+ * compilation target, so server module loading shares browser attribution.
42
41
  */
43
42
  export function createServerBuildRequest(appConfig, input) {
44
43
  const profile = input.profile ?? 'route-module';
@@ -49,6 +48,7 @@ export function createServerBuildRequest(appConfig, input) {
49
48
  ...overrides,
50
49
  entrypoints: input.entrypoints,
51
50
  ...(plugins.length > 0 ? { plugins } : {}),
51
+ sourceTransforms: getAppSourceTransforms(appConfig),
52
52
  };
53
53
  }
54
54
  /**
@@ -3,4 +3,5 @@
3
3
  *
4
4
  * @module @ecopages/core/cache
5
5
  */
6
- export { ModuleParseCache, moduleParseCache, cachedParseSync, type ModuleParseOptions } from './module-parse-cache.js';
6
+ export { ModuleParseCache, moduleParseCache, cachedParseSync, parseModuleSource, parserLanguageForFile, type ModuleParseOptions, type ParserLanguage, } from './module-parse-cache.js';
7
+ export { recordModuleTransformProfile } from './module-transform-profiler.js';