@ecopages/core 0.2.0-beta.37 → 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 (125) hide show
  1. package/README.md +1 -0
  2. package/package.json +35 -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 +11 -4
  45. package/src/eco/eco.js +15 -8
  46. package/src/eco/eco.types.d.ts +34 -6
  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 +12 -9
  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 +19 -11
  64. package/src/route-renderer/orchestration/integration-renderer.js +95 -27
  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/grouped-graph-build-plan.d.ts +15 -0
  70. package/src/route-renderer/orchestration/page-browser-graph/grouped-graph-build-plan.js +9 -0
  71. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-contribution.merge.d.ts +5 -0
  72. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-contribution.merge.js +30 -0
  73. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-session.d.ts +23 -6
  74. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-session.js +156 -67
  75. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph.service.d.ts +18 -2
  76. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph.service.js +63 -39
  77. package/src/route-renderer/orchestration/page-browser-graph/route-instance-key.d.ts +29 -0
  78. package/src/route-renderer/orchestration/page-browser-graph/route-instance-key.js +54 -0
  79. package/src/route-renderer/orchestration/route-pipeline/integration-route-render-adapter.d.ts +3 -0
  80. package/src/route-renderer/orchestration/route-pipeline/integration-route-render-adapter.js +3 -2
  81. package/src/route-renderer/orchestration/route-pipeline/route-prepared-options.builder.d.ts +1 -0
  82. package/src/route-renderer/orchestration/route-pipeline/route-prepared-options.builder.js +3 -5
  83. package/src/route-renderer/orchestration/route-pipeline/route-render-orchestrator.d.ts +14 -4
  84. package/src/route-renderer/orchestration/route-pipeline/route-render-orchestrator.js +91 -32
  85. package/src/route-renderer/page-loading/component-dependency-collection.js +3 -2
  86. package/src/route-renderer/page-loading/ecopages-virtual-imports.d.ts +0 -5
  87. package/src/route-renderer/page-loading/ecopages-virtual-imports.js +4 -10
  88. package/src/route-renderer/page-loading/file-scoped-dependency-components.d.ts +42 -0
  89. package/src/route-renderer/page-loading/file-scoped-dependency-components.js +94 -0
  90. package/src/route-renderer/page-loading/lazy-trigger-planning.js +2 -1
  91. package/src/route-renderer/page-loading/page-module-loader.d.ts +2 -1
  92. package/src/route-renderer/page-loading/page-module-loader.js +9 -3
  93. package/src/route-renderer/page-loading/resolved-page-dependencies.d.ts +12 -0
  94. package/src/route-renderer/page-loading/resolved-page-dependencies.js +38 -0
  95. package/src/router/README.md +8 -0
  96. package/src/services/README.md +5 -0
  97. package/src/services/assets/asset-processing-service/asset-processing.service.d.ts +1 -0
  98. package/src/services/assets/asset-processing-service/asset-processing.service.js +22 -5
  99. package/src/services/cache/cache.types.d.ts +4 -2
  100. package/src/services/cache/html-page-cache-dependency-index.d.ts +34 -0
  101. package/src/services/cache/html-page-cache-dependency-index.js +88 -0
  102. package/src/services/cache/index.d.ts +2 -0
  103. package/src/services/cache/index.js +2 -0
  104. package/src/services/cache/page-cache-service.d.ts +31 -1
  105. package/src/services/cache/page-cache-service.js +79 -3
  106. package/src/services/cache/page-request-cache-coordinator.service.js +4 -0
  107. package/src/services/module-loading/README.md +9 -6
  108. package/src/services/module-loading/app-server-module-transpiler.service.js +0 -3
  109. package/src/services/module-loading/collection-server-module-build.service.d.ts +19 -0
  110. package/src/services/module-loading/collection-server-module-build.service.js +83 -0
  111. package/src/services/module-loading/page-module-import.service.d.ts +5 -11
  112. package/src/services/module-loading/page-module-import.service.js +56 -73
  113. package/src/services/module-loading/route-module-build-cache.store.js +6 -6
  114. package/src/services/module-loading/route-module-build-manifest.d.ts +8 -5
  115. package/src/services/module-loading/route-module-build-manifest.js +20 -32
  116. package/src/services/module-loading/server-module-transpiler.service.d.ts +0 -2
  117. package/src/services/module-loading/server-module-transpiler.service.js +0 -3
  118. package/src/static-site-generator/production-page-browser-graph-prebuild.d.ts +10 -6
  119. package/src/static-site-generator/production-page-browser-graph-prebuild.js +29 -10
  120. package/src/static-site-generator/static-site-generator.js +6 -3
  121. package/src/types/internal-types.d.ts +4 -0
  122. package/src/types/public-types.d.ts +27 -19
  123. package/src/watchers/project-watcher.js +14 -0
  124. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-contribution.loader.d.ts +0 -9
  125. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-contribution.loader.js +0 -11
@@ -0,0 +1,19 @@
1
+ import type { EcoPagesAppConfig } from '../../types/internal-types.js';
2
+ export type CollectionServerBuildArtifact = {
3
+ collectionName: string;
4
+ outputPath: string;
5
+ outputUrl: string;
6
+ sourcePaths: string[];
7
+ buildIdentity: string;
8
+ };
9
+ export declare function getCollectionServerBuildArtifact(appConfig: EcoPagesAppConfig, collectionName: string): CollectionServerBuildArtifact | undefined;
10
+ export declare function clearCollectionServerBuildArtifacts(appConfig: EcoPagesAppConfig): void;
11
+ /**
12
+ * Builds one generated collection server module as an independent server unit.
13
+ */
14
+ export declare function buildCollectionServerModule(input: {
15
+ appConfig: EcoPagesAppConfig;
16
+ collectionName: string;
17
+ sourceFilePath: string;
18
+ ownedSourcePaths: readonly string[];
19
+ }): Promise<CollectionServerBuildArtifact>;
@@ -0,0 +1,83 @@
1
+ import { createHash } from 'node:crypto';
2
+ import path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { build } from '../../build/build-adapter.js';
5
+ import { createBuildRequestIdentity } from '../../build/runtime/build-request-identity.js';
6
+ import { createServerBuildRequest } from '../../build/runtime/build-request-policy.js';
7
+ import { recordCollectionBuild } from '../../diagnostics/request-pipeline-metrics.js';
8
+ import { resolveInternalExecutionDir } from '../../utils/resolve-work-dir.js';
9
+ import { fileSystem } from '@ecopages/file-system';
10
+ const artifactsByCollection = new WeakMap();
11
+ function getArtifactMap(appConfig) {
12
+ let artifacts = artifactsByCollection.get(appConfig);
13
+ if (!artifacts) {
14
+ artifacts = new Map();
15
+ artifactsByCollection.set(appConfig, artifacts);
16
+ }
17
+ return artifacts;
18
+ }
19
+ export function getCollectionServerBuildArtifact(appConfig, collectionName) {
20
+ return getArtifactMap(appConfig).get(collectionName);
21
+ }
22
+ export function clearCollectionServerBuildArtifacts(appConfig) {
23
+ getArtifactMap(appConfig).clear();
24
+ }
25
+ function createCollectionBuildIdentity(buildRequestIdentity, sourceFilePath, ownedSourcePaths) {
26
+ const sourcePaths = [
27
+ ...new Set([sourceFilePath, ...ownedSourcePaths].map((sourcePath) => path.resolve(sourcePath))),
28
+ ].sort();
29
+ const sourceIdentity = sourcePaths
30
+ .map((sourcePath) => `${sourcePath}\0${fileSystem.exists(sourcePath) ? fileSystem.hash(sourcePath) : 'missing'}`)
31
+ .join('\0');
32
+ return createHash('sha256')
33
+ .update('collection-server-build-v2\0')
34
+ .update(buildRequestIdentity)
35
+ .update('\0')
36
+ .update(sourceIdentity)
37
+ .digest('hex')
38
+ .slice(0, 16);
39
+ }
40
+ /**
41
+ * Builds one generated collection server module as an independent server unit.
42
+ */
43
+ export async function buildCollectionServerModule(input) {
44
+ const { appConfig, collectionName, sourceFilePath, ownedSourcePaths } = input;
45
+ const outdir = path.join(resolveInternalExecutionDir(appConfig), '.server-collections', collectionName);
46
+ const buildRequest = createServerBuildRequest(appConfig, {
47
+ profile: 'route-module',
48
+ entrypoints: [sourceFilePath],
49
+ outdir,
50
+ naming: '[name].[ext]',
51
+ splitting: false,
52
+ externalPackages: true,
53
+ });
54
+ const buildIdentity = createCollectionBuildIdentity(createBuildRequestIdentity(buildRequest), sourceFilePath, ownedSourcePaths);
55
+ const artifacts = getArtifactMap(appConfig);
56
+ const existing = artifacts.get(collectionName);
57
+ if (existing?.buildIdentity === buildIdentity) {
58
+ return existing;
59
+ }
60
+ const outputFileName = `${collectionName}-${buildIdentity}.mjs`;
61
+ const buildOptions = { ...buildRequest, naming: outputFileName.replace(/\.mjs$/u, '.[ext]') };
62
+ recordCollectionBuild();
63
+ const buildResult = await build(buildOptions);
64
+ if (!buildResult.success) {
65
+ const details = buildResult.logs.map((log) => log.message).join(' | ');
66
+ throw new Error(`Error building collection server module ${collectionName}: ${details}`);
67
+ }
68
+ const preferredOutputPath = path.join(outdir, outputFileName);
69
+ const compiledOutput = buildResult.outputs.find((output) => output.path === preferredOutputPath)?.path ??
70
+ buildResult.outputs.find((output) => /\.(?:[cm]?js)$/u.test(output.path))?.path;
71
+ if (!compiledOutput) {
72
+ throw new Error(`No compiled output generated for collection server module: ${sourceFilePath}`);
73
+ }
74
+ const artifact = {
75
+ collectionName,
76
+ outputPath: compiledOutput,
77
+ outputUrl: pathToFileURL(compiledOutput).href,
78
+ sourcePaths: [...ownedSourcePaths, sourceFilePath],
79
+ buildIdentity,
80
+ };
81
+ artifacts.set(collectionName, artifact);
82
+ return artifact;
83
+ }
@@ -1,12 +1,10 @@
1
- import { build, type BuildExecutor, type BuildResult } from '../../build/build-adapter.js';
1
+ import { build, type BuildExecutor, type BuildOptions, type BuildResult } from '../../build/build-adapter.js';
2
2
  import type { EcoBuildPlugin } from '../../build/contracts/build-types.js';
3
3
  import type { EcoPagesAppConfig } from '../../types/internal-types.js';
4
4
  import type { SourceModuleLoaderFactory } from './module-loading-types.js';
5
5
  interface PageModuleImportBaseOptions {
6
6
  filePath: string;
7
7
  bypassCache?: boolean;
8
- cacheScope?: string;
9
- invalidationVersion?: number;
10
8
  }
11
9
  /**
12
10
  * Options for imports that must pass through the Ecopages build pipeline.
@@ -21,6 +19,7 @@ export interface PageModuleBuildImportOptions extends PageModuleImportBaseOption
21
19
  buildExecutor?: BuildExecutor;
22
20
  splitting?: boolean;
23
21
  externalPackages?: boolean;
22
+ sourceTransforms?: BuildOptions['sourceTransforms'];
24
23
  jsx?: {
25
24
  development?: boolean;
26
25
  factory?: string;
@@ -64,20 +63,15 @@ export declare class PageModuleImportService {
64
63
  private readonly dependencies;
65
64
  private readonly dependencyHasher;
66
65
  private readonly importCache;
67
- private developmentInvalidationVersion;
66
+ private developmentImportGeneration;
68
67
  constructor(appConfig?: EcoPagesAppConfig, dependencies?: Partial<PageModuleImportDependencies>);
69
68
  /**
70
69
  * Clears the shared import cache used by framework-owned page-module loads.
71
70
  */
72
71
  clearImportCache(): void;
73
72
  /**
74
- * Invalidates all previously imported modules in development by clearing the
75
- * import cache and incrementing the invalidation version included in cache keys.
76
- *
77
- * This forces all modules to be reloaded on the next import, even if their
78
- * source content hasn't changed. This is necessary to ensure that changes to
79
- * non-content aspects of modules (e.g. dependencies, transpilation output)
80
- * are picked up during development.
73
+ * Clears in-memory import promises and dependency-hash memoization so the next
74
+ * import revalidates against current source and transform identity.
81
75
  */
82
76
  invalidateDevelopmentGraph(): void;
83
77
  /**
@@ -6,8 +6,8 @@ import { createServerBuildRequest } from '../../build/runtime/build-request-poli
6
6
  import { resolveBuildProfileOptions } from '../../build/runtime/build-profile-options.js';
7
7
  import { importPagesUnifiedGraphModule, isPagesUnifiedGraphPage, shouldBuildPagesUnifiedGraph, } from '../../build/cache/pages-unified-graph-build.js';
8
8
  import { recordPageModuleBuildInvocation } from '../../build/rolldown/rolldown-build-invocation-metrics.js';
9
- import { resolvePageModuleOutputFileName } from './route-module-build-cache.js';
10
- import { createPluginCacheKey, createJsxCacheKey } from '../../build/cache/cache-keys.js';
9
+ import { recordPageModuleLoad } from '../../diagnostics/request-pipeline-metrics.js';
10
+ import { createRouteModuleReuseIdentity, resolvePageModuleOutputFileName } from './route-module-build-manifest.js';
11
11
  import { getSharedRouteModuleBuildCache } from './route-module-build-cache-registry.js';
12
12
  import { RouteModuleDependencyHasher, resolveRouteModuleDependencyPaths, } from './route-module-dependency-hasher.js';
13
13
  import { supportsSourceModuleLoading } from './source-module-support.js';
@@ -28,7 +28,7 @@ export class PageModuleImportService {
28
28
  dependencies;
29
29
  dependencyHasher;
30
30
  importCache = new Map();
31
- developmentInvalidationVersion = 0;
31
+ developmentImportGeneration = 0;
32
32
  constructor(appConfig, dependencies) {
33
33
  this.appConfig = appConfig;
34
34
  this.dependencies = {
@@ -49,18 +49,13 @@ export class PageModuleImportService {
49
49
  this.importCache.clear();
50
50
  }
51
51
  /**
52
- * Invalidates all previously imported modules in development by clearing the
53
- * import cache and incrementing the invalidation version included in cache keys.
54
- *
55
- * This forces all modules to be reloaded on the next import, even if their
56
- * source content hasn't changed. This is necessary to ensure that changes to
57
- * non-content aspects of modules (e.g. dependencies, transpilation output)
58
- * are picked up during development.
52
+ * Clears in-memory import promises and dependency-hash memoization so the next
53
+ * import revalidates against current source and transform identity.
59
54
  */
60
55
  invalidateDevelopmentGraph() {
56
+ this.developmentImportGeneration += 1;
61
57
  this.clearImportCache();
62
58
  this.dependencyHasher.clearMemo();
63
- this.developmentInvalidationVersion += 1;
64
59
  }
65
60
  /**
66
61
  * Imports a page-like module from source.
@@ -76,8 +71,6 @@ export class PageModuleImportService {
76
71
  */
77
72
  async importModule(options) {
78
73
  const { filePath } = options;
79
- const invalidationVersion = options.invalidationVersion ?? this.developmentInvalidationVersion;
80
- const { externalPackages, splitting } = options;
81
74
  const fileHash = this.dependencies.hashFile(filePath);
82
75
  const hostModuleLoader = typeof Bun === 'undefined' &&
83
76
  process.env.NODE_ENV === 'development' &&
@@ -85,63 +78,58 @@ export class PageModuleImportService {
85
78
  ? this.dependencies.getHostModuleLoader()
86
79
  : undefined;
87
80
  if (hostModuleLoader) {
88
- const sourceModuleUrl = createRuntimeModuleUrl(filePath, fileHash, invalidationVersion, options.cacheScope);
89
- return (await hostModuleLoader(sourceModuleUrl.href));
81
+ const sourceModuleUrl = createRuntimeModuleUrl(filePath, fileHash, this.developmentImportGeneration);
82
+ const loadedModule = (await hostModuleLoader(sourceModuleUrl.href));
83
+ recordPageModuleLoad('host-loader');
84
+ return loadedModule;
90
85
  }
91
- if (options.bypassCache) {
92
- this.developmentInvalidationVersion += 1;
93
- return await this.loadModule({
86
+ if (!options.bypassCache) {
87
+ const runtime = typeof Bun !== 'undefined' ? 'bun' : 'node-build';
88
+ const cacheKey = [
89
+ runtime,
90
+ filePath,
91
+ createRouteModuleReuseIdentity(options, options.sourceTransforms),
92
+ fileHash,
93
+ ].join('::');
94
+ const cachedModule = this.importCache.get(cacheKey);
95
+ if (cachedModule) {
96
+ if (!cachedModule.dependencyHashes) {
97
+ const loadedModule = (await cachedModule.promise);
98
+ recordPageModuleLoad('import-cache-hit');
99
+ return loadedModule;
100
+ }
101
+ if (this.dependencyHasher.matchesStoredHashes(cachedModule.dependencyHashes, filePath, fileHash)) {
102
+ const loadedModule = (await cachedModule.promise);
103
+ recordPageModuleLoad('import-cache-hit');
104
+ return loadedModule;
105
+ }
106
+ this.importCache.delete(cacheKey);
107
+ }
108
+ const importPromise = this.loadModule({
94
109
  ...options,
95
- invalidationVersion: this.developmentInvalidationVersion,
96
110
  fileHash,
111
+ importCacheKey: cacheKey,
97
112
  });
98
- }
99
- const runtime = typeof Bun !== 'undefined' ? 'bun' : 'node-build';
100
- const cacheKey = [
101
- runtime,
102
- filePath,
103
- options.rootDir,
104
- path.resolve(options.outdir),
105
- splitting ?? 'default',
106
- externalPackages ?? 'default',
107
- options.cacheScope ?? 'default',
108
- createJsxCacheKey(options.jsx),
109
- createPluginCacheKey(options.plugins),
110
- fileHash,
111
- invalidationVersion,
112
- ].join('::');
113
- const cachedModule = this.importCache.get(cacheKey);
114
- if (cachedModule) {
115
- if (!cachedModule.dependencyHashes) {
116
- return (await cachedModule.promise);
113
+ this.importCache.set(cacheKey, { promise: importPromise });
114
+ try {
115
+ return await importPromise;
117
116
  }
118
- if (this.dependencyHasher.matchesStoredHashes(cachedModule.dependencyHashes, filePath, fileHash)) {
119
- return (await cachedModule.promise);
117
+ catch (error) {
118
+ this.importCache.delete(cacheKey);
119
+ throw error;
120
120
  }
121
- this.importCache.delete(cacheKey);
122
121
  }
123
- const importPromise = this.loadModule({
122
+ return await this.loadModule({
124
123
  ...options,
125
124
  fileHash,
126
- importCacheKey: cacheKey,
127
125
  });
128
- this.importCache.set(cacheKey, { promise: importPromise });
129
- try {
130
- return await importPromise;
131
- }
132
- catch (error) {
133
- this.importCache.delete(cacheKey);
134
- throw error;
135
- }
136
126
  }
137
127
  async loadModule(options) {
138
- const { filePath, invalidationVersion = this.developmentInvalidationVersion, cacheScope, fileHash, importCacheKey, } = options;
128
+ const { filePath, fileHash, importCacheKey } = options;
139
129
  const { rootDir, outdir, splitting, externalPackages, transpileErrorMessage = (details) => `Error transpiling page module: ${details}`, noOutputMessage = (targetFilePath) => `No transpiled output generated for page module: ${targetFilePath}`, } = options;
140
130
  const outputFileName = resolvePageModuleOutputFileName({
141
131
  filePath,
142
132
  fileHash,
143
- cacheScope,
144
- invalidationVersion,
145
133
  });
146
134
  const outputNamingTemplate = outputFileName.replace(/\.mjs$/u, '.[ext]');
147
135
  const preferredOutputPath = path.join(outdir, outputFileName);
@@ -178,24 +166,26 @@ export class PageModuleImportService {
178
166
  externalPackages: buildOptions.externalPackages,
179
167
  jsx: buildOptions.jsx,
180
168
  plugins: buildOptions.plugins,
169
+ sourceTransforms: buildOptions.sourceTransforms,
181
170
  fileHash,
182
171
  };
183
172
  const routeModuleBuildCache = this.getRouteModuleBuildCache(outdir);
184
173
  const cachedBuild = routeModuleBuildCache.lookup(cacheBuildOptions);
185
174
  if (cachedBuild) {
186
- return (await import(/* @vite-ignore */ pathToFileURL(cachedBuild.outputPath).href));
175
+ const loadedModule = (await import(/* @vite-ignore */ pathToFileURL(cachedBuild.outputPath).href));
176
+ recordPageModuleLoad('disk-cache-hit');
177
+ return loadedModule;
187
178
  }
188
- if (!cacheScope &&
189
- shouldBuildPagesUnifiedGraph() &&
190
- this.appConfig &&
191
- isPagesUnifiedGraphPage(filePath, this.appConfig)) {
179
+ if (shouldBuildPagesUnifiedGraph() && this.appConfig && isPagesUnifiedGraphPage(filePath, this.appConfig)) {
192
180
  const graphModule = await importPagesUnifiedGraphModule(this.appConfig, filePath);
193
181
  if (graphModule !== undefined) {
182
+ recordPageModuleLoad('unified-graph');
194
183
  return graphModule;
195
184
  }
196
185
  }
197
186
  recordPageModuleBuildInvocation();
198
187
  const buildResult = await this.dependencies.buildModule(buildOptions, options.buildExecutor);
188
+ recordPageModuleLoad('cold-build', buildResult.outputs.length);
199
189
  if (!buildResult.success) {
200
190
  const details = buildResult.logs.map((log) => log.message).join(' | ');
201
191
  throw new Error(transpileErrorMessage(details));
@@ -220,10 +210,8 @@ export class PageModuleImportService {
220
210
  dependencyModulePaths,
221
211
  });
222
212
  const compiledOutputUrl = pathToFileURL(compiledOutput);
223
- if (shouldAddRuntimeUpdateQuery(invalidationVersion, cacheScope)) {
224
- compiledOutputUrl.searchParams.set('update', [fileHash, invalidationVersion, cacheScope ? sanitizeCacheScope(cacheScope) : undefined]
225
- .filter((value) => value !== undefined)
226
- .join('-'));
213
+ if (shouldAddRuntimeUpdateQuery()) {
214
+ compiledOutputUrl.searchParams.set('update', `${fileHash}-${this.developmentImportGeneration}`);
227
215
  }
228
216
  return (await import(/* @vite-ignore */ compiledOutputUrl.href));
229
217
  }
@@ -231,18 +219,13 @@ export class PageModuleImportService {
231
219
  return getSharedRouteModuleBuildCache(outdir, this.appConfig);
232
220
  }
233
221
  }
234
- function createRuntimeModuleUrl(filePath, fileHash, invalidationVersion, cacheScope) {
222
+ function createRuntimeModuleUrl(filePath, fileHash, developmentImportGeneration) {
235
223
  const moduleUrl = pathToFileURL(filePath);
236
- if (shouldAddRuntimeUpdateQuery(invalidationVersion, cacheScope)) {
237
- moduleUrl.searchParams.set('update', [fileHash, invalidationVersion, cacheScope ? sanitizeCacheScope(cacheScope) : undefined]
238
- .filter((value) => value !== undefined)
239
- .join('-'));
224
+ if (shouldAddRuntimeUpdateQuery()) {
225
+ moduleUrl.searchParams.set('update', `${fileHash}-${developmentImportGeneration}`);
240
226
  }
241
227
  return moduleUrl;
242
228
  }
243
- function shouldAddRuntimeUpdateQuery(invalidationVersion, cacheScope) {
244
- return process.env.NODE_ENV === 'development' || invalidationVersion > 0 || !!cacheScope;
245
- }
246
- function sanitizeCacheScope(cacheScope) {
247
- return cacheScope.replace(/[^a-zA-Z0-9_-]+/g, '-');
229
+ function shouldAddRuntimeUpdateQuery() {
230
+ return process.env.NODE_ENV === 'development';
248
231
  }
@@ -28,10 +28,10 @@ export class RouteModuleBuildCache {
28
28
  return undefined;
29
29
  }
30
30
  const manifest = this.loadManifest();
31
- if (manifest.invalidationVersion !== this.dependencies.getCorePackageVersion()) {
31
+ if (manifest.corePackageVersion !== this.dependencies.getCorePackageVersion()) {
32
32
  return undefined;
33
33
  }
34
- const buildKey = createPersistedRouteModuleBuildKey(options);
34
+ const buildKey = createPersistedRouteModuleBuildKey(options, options.sourceTransforms);
35
35
  const cacheFilePath = normalizeRouteModuleCachePath(options.filePath);
36
36
  const entry = manifest.entries[cacheFilePath];
37
37
  if (!entry || entry.sourceHash !== options.fileHash || entry.buildKey !== buildKey) {
@@ -60,12 +60,12 @@ export class RouteModuleBuildCache {
60
60
  dependencyHashes[cacheFilePath] = options.fileHash;
61
61
  const manifest = this.loadManifest();
62
62
  const existingEntry = manifest.entries[cacheFilePath];
63
- manifest.invalidationVersion = this.dependencies.getCorePackageVersion();
63
+ manifest.corePackageVersion = this.dependencies.getCorePackageVersion();
64
64
  manifest.entries[cacheFilePath] = {
65
65
  sourceHash: options.fileHash,
66
66
  outputPath: options.outputPath,
67
67
  builtAt: Date.now(),
68
- buildKey: createPersistedRouteModuleBuildKey(options),
68
+ buildKey: createPersistedRouteModuleBuildKey(options, options.sourceTransforms),
69
69
  dependencyHashes,
70
70
  renderedOutputs: existingEntry?.renderedOutputs,
71
71
  };
@@ -87,7 +87,7 @@ export class RouteModuleBuildCache {
87
87
  return;
88
88
  }
89
89
  const manifest = this.loadManifest();
90
- manifest.invalidationVersion = this.dependencies.getCorePackageVersion();
90
+ manifest.corePackageVersion = this.dependencies.getCorePackageVersion();
91
91
  manifest.configHash = context.configHash;
92
92
  manifest.buildInputsFingerprint = context.buildInputsFingerprint;
93
93
  this.persistManifest(manifest);
@@ -140,7 +140,7 @@ export class RouteModuleBuildCache {
140
140
  const cacheFilePath = normalizeRouteModuleCachePath(options.filePath);
141
141
  const manifest = this.loadManifest();
142
142
  const existingEntry = manifest.entries[cacheFilePath];
143
- manifest.invalidationVersion = this.dependencies.getCorePackageVersion();
143
+ manifest.corePackageVersion = this.dependencies.getCorePackageVersion();
144
144
  manifest.configHash = options.context.configHash;
145
145
  manifest.buildInputsFingerprint = options.context.buildInputsFingerprint;
146
146
  manifest.entries[cacheFilePath] = {
@@ -1,3 +1,4 @@
1
+ import type { BuildOptions } from '../../build/contracts/build-contracts.js';
1
2
  import type { PageModuleBuildImportOptions } from './page-module-import.service.js';
2
3
  import type { RouteModuleDependencyHashes } from './route-module-dependency-hasher.js';
3
4
  export { createJsxCacheKey, createPluginCacheKey, getCorePackageVersion, hashPluginSetup, } from '../../build/cache/cache-keys.js';
@@ -19,7 +20,7 @@ export interface RouteModuleStaticRenderCacheEntry {
19
20
  }
20
21
  /** On-disk manifest describing all cached route-module builds for one server outdir. */
21
22
  export interface RouteModuleBuildCacheManifest {
22
- invalidationVersion: string;
23
+ corePackageVersion: string;
23
24
  configHash?: string;
24
25
  buildInputsFingerprint?: string;
25
26
  entries: Record<string, RouteModuleBuildCacheEntry>;
@@ -38,17 +39,19 @@ export type RouteModuleBuildCacheManifestReader = (manifestPath: string) => Rout
38
39
  export type RouteModuleBuildCacheManifestWriter = (manifestPath: string, manifest: RouteModuleBuildCacheManifest) => void;
39
40
  /** Normalizes filesystem paths used as manifest keys and build-key inputs. */
40
41
  export declare function normalizeRouteModuleCachePath(filePath: string): string;
41
- /** Returns whether route-module disk caching should run for the current import. */
42
+ /**
43
+ * Returns whether route-module disk caching should run for the current import.
44
+ */
42
45
  export declare function shouldPersistRouteModuleBuildCache(options: PageModuleBuildImportOptions): boolean;
46
+ /** Canonical reuse identity for route-module memory and disk caches. */
47
+ export declare function createRouteModuleReuseIdentity(options: PageModuleBuildImportOptions, sourceTransforms?: BuildOptions['sourceTransforms']): string;
43
48
  /** Derives the deterministic on-disk filename for one transpiled route module. */
44
49
  export declare function resolvePageModuleOutputFileName(options: {
45
50
  filePath: string;
46
51
  fileHash: string;
47
- cacheScope?: string;
48
- invalidationVersion?: number;
49
52
  }): string;
50
53
  /** Builds the cache key for persisted production route-module builds. */
51
- export declare function createPersistedRouteModuleBuildKey(options: PageModuleBuildImportOptions): string;
54
+ export declare function createPersistedRouteModuleBuildKey(options: PageModuleBuildImportOptions, sourceTransforms?: BuildOptions['sourceTransforms']): string;
52
55
  export declare function createEmptyRouteModuleBuildCacheManifest(): RouteModuleBuildCacheManifest;
53
56
  export declare function readRouteModuleBuildCacheManifest(manifestPath: string): RouteModuleBuildCacheManifest | undefined;
54
57
  export declare function writeRouteModuleBuildCacheManifest(manifestPath: string, manifest: RouteModuleBuildCacheManifest): void;
@@ -1,39 +1,23 @@
1
1
  import path from 'node:path';
2
2
  import { readProductionCacheManifest, writeProductionCacheManifest } from '../../build/cache/production-build-cache.js';
3
- import { createJsxCacheKey, createPluginCacheKey, getCorePackageVersion } from '../../build/cache/cache-keys.js';
3
+ import { createJsxCacheKey, createPluginCacheKey, createSourceTransformCacheKey, getCorePackageVersion, } from '../../build/cache/cache-keys.js';
4
4
  export { createJsxCacheKey, createPluginCacheKey, getCorePackageVersion, hashPluginSetup, } from '../../build/cache/cache-keys.js';
5
5
  export { ROUTE_MODULE_BUILD_CACHE_FILENAME } from '../../build/cache/cache-constants.js';
6
6
  /** Normalizes filesystem paths used as manifest keys and build-key inputs. */
7
7
  export function normalizeRouteModuleCachePath(filePath) {
8
8
  return path.resolve(filePath);
9
9
  }
10
- /** Returns whether route-module disk caching should run for the current import. */
10
+ /**
11
+ * Returns whether route-module disk caching should run for the current import.
12
+ */
11
13
  export function shouldPersistRouteModuleBuildCache(options) {
12
- if (process.env.NODE_ENV !== 'production') {
14
+ if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'development') {
13
15
  return false;
14
16
  }
15
- if (options.bypassCache) {
16
- return false;
17
- }
18
- if ((options.invalidationVersion ?? 0) > 0) {
19
- return false;
20
- }
21
- if (options.cacheScope) {
22
- return false;
23
- }
24
- return true;
25
- }
26
- /** Derives the deterministic on-disk filename for one transpiled route module. */
27
- export function resolvePageModuleOutputFileName(options) {
28
- const fileBaseName = path.basename(options.filePath, path.extname(options.filePath));
29
- const cacheScopeSuffix = options.cacheScope ? `-${sanitizeCacheScope(options.cacheScope)}` : '';
30
- const invalidationSuffix = shouldVersionBuildOutputPath(options.invalidationVersion ?? 0)
31
- ? `-v${options.invalidationVersion}`
32
- : '';
33
- return `${fileBaseName}-${options.fileHash}${cacheScopeSuffix}${invalidationSuffix}.mjs`;
17
+ return !options.bypassCache;
34
18
  }
35
- /** Builds the cache key for persisted production route-module builds. */
36
- export function createPersistedRouteModuleBuildKey(options) {
19
+ /** Canonical reuse identity for route-module memory and disk caches. */
20
+ export function createRouteModuleReuseIdentity(options, sourceTransforms) {
37
21
  return [
38
22
  path.resolve(options.rootDir),
39
23
  path.resolve(options.outdir),
@@ -41,11 +25,21 @@ export function createPersistedRouteModuleBuildKey(options) {
41
25
  options.externalPackages ?? 'default',
42
26
  createJsxCacheKey(options.jsx),
43
27
  createPluginCacheKey(options.plugins),
28
+ createSourceTransformCacheKey(sourceTransforms),
44
29
  ].join('::');
45
30
  }
31
+ /** Derives the deterministic on-disk filename for one transpiled route module. */
32
+ export function resolvePageModuleOutputFileName(options) {
33
+ const fileBaseName = path.basename(options.filePath, path.extname(options.filePath));
34
+ return `${fileBaseName}-${options.fileHash}.mjs`;
35
+ }
36
+ /** Builds the cache key for persisted production route-module builds. */
37
+ export function createPersistedRouteModuleBuildKey(options, sourceTransforms) {
38
+ return createRouteModuleReuseIdentity(options, sourceTransforms);
39
+ }
46
40
  export function createEmptyRouteModuleBuildCacheManifest() {
47
41
  return {
48
- invalidationVersion: getCorePackageVersion(),
42
+ corePackageVersion: getCorePackageVersion(),
49
43
  entries: {},
50
44
  };
51
45
  }
@@ -55,7 +49,7 @@ export function readRouteModuleBuildCacheManifest(manifestPath) {
55
49
  return undefined;
56
50
  }
57
51
  return {
58
- invalidationVersion: parsed.invalidationVersion ?? '',
52
+ corePackageVersion: parsed.corePackageVersion ?? parsed.invalidationVersion ?? '',
59
53
  configHash: parsed.configHash,
60
54
  buildInputsFingerprint: parsed.buildInputsFingerprint,
61
55
  entries: parsed.entries,
@@ -64,9 +58,3 @@ export function readRouteModuleBuildCacheManifest(manifestPath) {
64
58
  export function writeRouteModuleBuildCacheManifest(manifestPath, manifest) {
65
59
  writeProductionCacheManifest(manifestPath, manifest);
66
60
  }
67
- function shouldVersionBuildOutputPath(invalidationVersion) {
68
- return typeof Bun !== 'undefined' && invalidationVersion > 0;
69
- }
70
- function sanitizeCacheScope(cacheScope) {
71
- return cacheScope.replace(/[^a-zA-Z0-9_-]+/g, '-');
72
- }
@@ -18,7 +18,6 @@ export type ServerModuleTranspilerBootstrapArgs = {
18
18
  getBuildExecutor?: () => BuildExecutor | undefined;
19
19
  canLoadSourceModuleFromHost?: (filePath: string) => boolean;
20
20
  getHostModuleLoader?: SourceModuleLoaderFactory;
21
- getInvalidationVersion?: () => number;
22
21
  invalidateModules?: (changedFiles?: string[]) => void;
23
22
  pageModuleImportService?: ServerModuleImportDependency;
24
23
  /** Factory evaluated on each importModule call to produce plugins applied to every load. */
@@ -35,7 +34,6 @@ export declare class ServerModuleTranspiler {
35
34
  private readonly pageModuleImportService;
36
35
  private readonly getRootDir;
37
36
  private readonly getBuildExecutor;
38
- private readonly getInvalidationVersion;
39
37
  private readonly invalidateModules;
40
38
  private readonly getDefaultPlugins;
41
39
  /**
@@ -10,7 +10,6 @@ export class ServerModuleTranspiler {
10
10
  pageModuleImportService;
11
11
  getRootDir;
12
12
  getBuildExecutor;
13
- getInvalidationVersion;
14
13
  invalidateModules;
15
14
  getDefaultPlugins;
16
15
  /**
@@ -26,7 +25,6 @@ export class ServerModuleTranspiler {
26
25
  });
27
26
  this.getRootDir = () => args.rootDir;
28
27
  this.getBuildExecutor = args.getBuildExecutor ?? (() => undefined);
29
- this.getInvalidationVersion = () => args.getInvalidationVersion?.();
30
28
  this.getDefaultPlugins = args.getDefaultPlugins ?? (() => []);
31
29
  this.invalidateModules = (changedFiles) => {
32
30
  if (args.invalidateModules) {
@@ -48,7 +46,6 @@ export class ServerModuleTranspiler {
48
46
  ...(mergedPlugins.length > 0 ? { plugins: mergedPlugins } : {}),
49
47
  rootDir: this.getRootDir(),
50
48
  buildExecutor: this.getBuildExecutor(),
51
- invalidationVersion: this.getInvalidationVersion(),
52
49
  });
53
50
  }
54
51
  /**
@@ -1,5 +1,10 @@
1
1
  import type { EcoPagesAppConfig } from '../types/internal-types.js';
2
+ import type { PageParams } from '../types/public-types.js';
2
3
  import type { PageRendererResolver } from '../route-renderer/route-renderer.js';
4
+ export type ProductionPageBrowserGraphRouteInstance = {
5
+ routeFile: string;
6
+ params?: PageParams;
7
+ };
3
8
  /**
4
9
  * Returns whether production static export should warm Page Browser Graphs before rendering.
5
10
  *
@@ -17,12 +22,11 @@ export declare function shouldPrebuildProductionPageBrowserGraphs(): boolean;
17
22
  */
18
23
  export declare function clearProductionPageBrowserGraphSession(appConfig: EcoPagesAppConfig): void;
19
24
  /**
20
- * Warms production Page Browser Graphs for the supplied static route files.
25
+ * Warms production Page Browser Graphs for the supplied static route instances.
21
26
  *
22
27
  * @remarks
23
- * Deduplicates and lexicographically sorts absolute route paths, then calls
24
- * each integration renderer's `prebuildProductionPageBrowserGraph` sequentially
25
- * (activates integration runtime, then resolves into
26
- * `page-browser-graph-session`). Does not persist a disk manifest.
28
+ * Deduplicates route file + params pairs, then calls each integration renderer's
29
+ * `prebuildProductionPageBrowserGraph` sequentially (activates integration runtime,
30
+ * then resolves into `page-browser-graph-session`). Does not persist a disk manifest.
27
31
  */
28
- export declare function prebuildProductionPageBrowserGraphs(routeFiles: readonly string[], routeRendererFactory: PageRendererResolver): Promise<void>;
32
+ export declare function prebuildProductionPageBrowserGraphs(routeInstances: readonly ProductionPageBrowserGraphRouteInstance[], routeRendererFactory: PageRendererResolver): Promise<void>;