@ecopages/core 0.2.0-beta.7 → 0.2.0-beta.9

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 (110) hide show
  1. package/package.json +27 -3
  2. package/src/adapters/abstract/application-adapter.d.ts +12 -1
  3. package/src/adapters/abstract/application-adapter.js +13 -0
  4. package/src/adapters/abstract/server-adapter.d.ts +3 -0
  5. package/src/adapters/bun/client-bridge.d.ts +1 -0
  6. package/src/adapters/bun/client-bridge.js +3 -0
  7. package/src/adapters/bun/create-app.d.ts +2 -0
  8. package/src/adapters/bun/create-app.js +28 -9
  9. package/src/adapters/bun/server-adapter.d.ts +13 -0
  10. package/src/adapters/bun/server-adapter.js +29 -4
  11. package/src/adapters/create-app.d.ts +13 -0
  12. package/src/adapters/create-app.js +10 -1
  13. package/src/adapters/node/create-app.d.ts +1 -0
  14. package/src/adapters/node/create-app.js +17 -7
  15. package/src/adapters/node/node-client-bridge.d.ts +1 -0
  16. package/src/adapters/node/node-client-bridge.js +3 -0
  17. package/src/adapters/node/server-adapter.d.ts +10 -13
  18. package/src/adapters/node/server-adapter.js +37 -82
  19. package/src/adapters/node/static-content-server.d.ts +1 -0
  20. package/src/adapters/node/static-content-server.js +3 -0
  21. package/src/adapters/shared/copy-runtime-public-dir.d.ts +6 -0
  22. package/src/adapters/shared/copy-runtime-public-dir.js +24 -0
  23. package/src/adapters/shared/runtime-app-bootstrap.d.ts +0 -1
  24. package/src/adapters/shared/runtime-app-bootstrap.js +1 -6
  25. package/src/adapters/shared/server-static-builder.d.ts +4 -6
  26. package/src/adapters/shared/server-static-builder.js +90 -34
  27. package/src/adapters/shared/shared-hmr-manager.d.ts +1 -0
  28. package/src/adapters/shared/shared-hmr-manager.js +15 -2
  29. package/src/build/README.md +44 -4
  30. package/src/build/build-adapter.d.ts +27 -2
  31. package/src/build/build-adapter.js +75 -32
  32. package/src/build/build-input-fingerprint.d.ts +28 -0
  33. package/src/build/build-input-fingerprint.js +31 -0
  34. package/src/build/jsx-ownership-plugins.d.ts +10 -0
  35. package/src/build/jsx-ownership-plugins.js +17 -0
  36. package/src/build/pages-unified-graph-build.d.ts +31 -0
  37. package/src/build/pages-unified-graph-build.js +208 -0
  38. package/src/build/parallel-build-executor.d.ts +21 -0
  39. package/src/build/parallel-build-executor.js +52 -0
  40. package/src/build/rolldown-adapter-helpers.js +7 -1
  41. package/src/build/rolldown-build-adapter.js +2 -0
  42. package/src/build/rolldown-build-invocation-metrics.d.ts +12 -0
  43. package/src/build/rolldown-build-invocation-metrics.js +43 -0
  44. package/src/build/rolldown-dev-build-adapter.d.ts +2 -2
  45. package/src/build/rolldown-dev-build-adapter.js +30 -14
  46. package/src/build/rolldown-plugin-bridge.d.ts +10 -1
  47. package/src/build/rolldown-plugin-bridge.js +17 -5
  48. package/src/build/rolldown-source-transform-pass.d.ts +15 -0
  49. package/src/build/rolldown-source-transform-pass.js +58 -0
  50. package/src/build/runtime-build-executor.d.ts +7 -13
  51. package/src/build/runtime-build-executor.js +31 -7
  52. package/src/build/server-entry-build-cache.d.ts +44 -0
  53. package/src/build/server-entry-build-cache.js +169 -0
  54. package/src/config/config-builder.d.ts +11 -1
  55. package/src/config/config-builder.js +11 -1
  56. package/src/hmr/hmr.test.e2e.js +15 -4
  57. package/src/plugins/integration-plugin.d.ts +19 -8
  58. package/src/plugins/integration-plugin.js +7 -2
  59. package/src/plugins/processor.d.ts +5 -0
  60. package/src/plugins/processor.js +7 -0
  61. package/src/plugins/source-transform.d.ts +31 -0
  62. package/src/plugins/source-transform.js +23 -1
  63. package/src/route-renderer/GRAPH.md +4 -4
  64. package/src/route-renderer/README.md +6 -5
  65. package/src/route-renderer/orchestration/foreign-subtree-execution.service.js +1 -3
  66. package/src/route-renderer/orchestration/integration-renderer.d.ts +10 -2
  67. package/src/route-renderer/orchestration/integration-renderer.js +32 -12
  68. package/src/route-renderer/orchestration/render-output.utils.d.ts +2 -0
  69. package/src/route-renderer/orchestration/render-output.utils.js +4 -0
  70. package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +2 -2
  71. package/src/route-renderer/orchestration/route-render-orchestrator.js +17 -16
  72. package/src/router/client/navigation-coordinator.js +10 -0
  73. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.d.ts +1 -0
  74. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.js +1 -0
  75. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.d.ts +4 -2
  76. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.js +27 -7
  77. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +17 -3
  78. package/src/services/assets/browser-bundle.service.d.ts +3 -1
  79. package/src/services/assets/browser-bundle.service.js +14 -5
  80. package/src/services/html/html-transformer.service.d.ts +1 -1
  81. package/src/services/html/html-transformer.service.js +4 -4
  82. package/src/services/invalidation/development-invalidation.service.d.ts +10 -1
  83. package/src/services/invalidation/development-invalidation.service.js +29 -0
  84. package/src/services/module-loading/README.md +49 -0
  85. package/src/services/module-loading/app-server-module-transpiler.service.js +4 -17
  86. package/src/services/module-loading/page-module-import.service.d.ts +4 -1
  87. package/src/services/module-loading/page-module-import.service.js +52 -29
  88. package/src/services/module-loading/route-module-build-cache-registry.d.ts +24 -0
  89. package/src/services/module-loading/route-module-build-cache-registry.js +37 -0
  90. package/src/services/module-loading/route-module-build-cache.d.ts +4 -0
  91. package/src/services/module-loading/route-module-build-cache.js +38 -0
  92. package/src/services/module-loading/route-module-build-cache.store.d.ts +57 -0
  93. package/src/services/module-loading/route-module-build-cache.store.js +203 -0
  94. package/src/services/module-loading/route-module-build-manifest.d.ts +61 -0
  95. package/src/services/module-loading/route-module-build-manifest.js +124 -0
  96. package/src/services/module-loading/route-module-dependency-hasher.d.ts +81 -0
  97. package/src/services/module-loading/route-module-dependency-hasher.js +121 -0
  98. package/src/services/module-loading/server-module-transpiler.service.js +1 -1
  99. package/src/static-site-generator/README.md +33 -0
  100. package/src/static-site-generator/static-build-invalidation.d.ts +10 -0
  101. package/src/static-site-generator/static-build-invalidation.js +78 -0
  102. package/src/static-site-generator/static-export-context.d.ts +22 -0
  103. package/src/static-site-generator/static-export-context.js +0 -0
  104. package/src/static-site-generator/static-site-generator.d.ts +17 -8
  105. package/src/static-site-generator/static-site-generator.js +176 -62
  106. package/src/types/internal-types.d.ts +8 -0
  107. package/src/utils/parse-cli-args.d.ts +1 -0
  108. package/src/utils/parse-cli-args.js +3 -0
  109. package/src/watchers/project-watcher.d.ts +8 -0
  110. package/src/watchers/project-watcher.js +32 -12
@@ -1,13 +1,15 @@
1
1
  import { mergeBrowserRuntimeManifests } from "./browser-runtime-manifest.js";
2
2
  import { normalizeNodeRuntimeBuildOutputs } from "./runtime-build-output-normalizer.js";
3
- import { createForeignJsxOverridePlugin } from "../plugins/foreign-jsx-override-plugin.js";
4
3
  import {
5
4
  createAppBuildManifest,
6
5
  getBrowserBuildPlugins,
7
6
  getServerBuildPlugins
8
7
  } from "./build-manifest.js";
8
+ import { getAppSourceTransforms } from "../plugins/source-transform.js";
9
+ import { getJsxOwnershipPlugins } from "./jsx-ownership-plugins.js";
9
10
  import { createRolldownBuildAdapter } from "./rolldown-build-adapter.js";
10
11
  import { createRolldownDevBuildAdapter } from "./rolldown-dev-build-adapter.js";
12
+ import { appLogger } from "../global/app-logger.js";
11
13
  function mergeBuildExecutorPlugins(existing, appPlugins) {
12
14
  if (!existing || existing.length === 0) {
13
15
  return appPlugins;
@@ -96,7 +98,7 @@ function setAppBuildAdapter(appConfig, buildAdapter) {
96
98
  }
97
99
  function getAppBuildManifest(appConfig) {
98
100
  return appConfig.runtime?.buildManifest ?? createAppBuildManifest({
99
- loaderPlugins: Array.from(appConfig.loaders.values())
101
+ loaderPlugins: Array.from(appConfig.loaders?.values() ?? [])
100
102
  });
101
103
  }
102
104
  function setAppBuildManifest(appConfig, buildManifest) {
@@ -143,27 +145,55 @@ async function collectConfiguredAppBuildManifestContributions(appConfig) {
143
145
  };
144
146
  }
145
147
  async function setupAppRuntimePlugins(options) {
146
- for (const loader of options.appConfig.loaders.values()) {
147
- options.onRuntimePlugin?.(loader);
148
- }
149
- for (const processor of options.appConfig.processors.values()) {
150
- await processor.setup();
151
- if (processor.plugins) {
152
- for (const plugin of processor.plugins) {
148
+ if (options.appConfig.runtime?.runtimeAssetsPrepared) {
149
+ appLogger.debug("Skipped setupAppRuntimePlugins: runtime assets already prepared");
150
+ for (const loader of options.appConfig.loaders.values()) {
151
+ options.onRuntimePlugin?.(loader);
152
+ }
153
+ for (const processor of options.appConfig.processors.values()) {
154
+ if (processor.plugins) {
155
+ for (const plugin of processor.plugins) {
156
+ options.onRuntimePlugin?.(plugin);
157
+ }
158
+ }
159
+ }
160
+ for (const integration of options.appConfig.integrations) {
161
+ for (const plugin of integration.plugins) {
153
162
  options.onRuntimePlugin?.(plugin);
154
163
  }
155
164
  }
165
+ return;
156
166
  }
157
- for (const integration of options.appConfig.integrations) {
158
- integration.setConfig(options.appConfig);
159
- integration.setRuntimeOrigin(options.runtimeOrigin);
160
- if (options.hmrManager) {
161
- integration.setHmrManager(options.hmrManager);
167
+ appLogger.debugTime("setupAppRuntimePlugins");
168
+ try {
169
+ for (const loader of options.appConfig.loaders.values()) {
170
+ options.onRuntimePlugin?.(loader);
162
171
  }
163
- await integration.setup();
164
- for (const plugin of integration.plugins) {
165
- options.onRuntimePlugin?.(plugin);
172
+ for (const processor of options.appConfig.processors.values()) {
173
+ await processor.setup();
174
+ if (processor.plugins) {
175
+ for (const plugin of processor.plugins) {
176
+ options.onRuntimePlugin?.(plugin);
177
+ }
178
+ }
166
179
  }
180
+ for (const integration of options.appConfig.integrations) {
181
+ integration.setConfig(options.appConfig);
182
+ integration.setRuntimeOrigin(options.runtimeOrigin);
183
+ if (options.hmrManager) {
184
+ integration.setHmrManager(options.hmrManager);
185
+ }
186
+ await integration.setup();
187
+ for (const plugin of integration.plugins) {
188
+ options.onRuntimePlugin?.(plugin);
189
+ }
190
+ }
191
+ options.appConfig.runtime = {
192
+ ...options.appConfig.runtime ?? {},
193
+ runtimeAssetsPrepared: true
194
+ };
195
+ } finally {
196
+ appLogger.debugTimeEnd("setupAppRuntimePlugins");
167
197
  }
168
198
  }
169
199
  function getAppServerBuildPlugins(appConfig) {
@@ -171,24 +201,20 @@ function getAppServerBuildPlugins(appConfig) {
171
201
  }
172
202
  function getAppBrowserBuildPlugins(appConfig) {
173
203
  const manifest = getAppBuildManifest(appConfig);
174
- const jsxOwnershipPlugins = collectJsxOwnershipPlugins(appConfig);
175
- return [...getBrowserBuildPlugins(manifest), ...jsxOwnershipPlugins];
176
- }
177
- function collectJsxOwnershipPlugins(appConfig) {
178
- const jsxExtensions = (appConfig.integrations ?? []).filter((integration) => integration.jsxImportSource).flatMap(
179
- (integration) => integration.extensions.filter((extension) => extension.endsWith(".tsx") || extension.endsWith(".jsx")).map((extension) => ({ integration, extension }))
180
- ).sort((left, right) => right.extension.length - left.extension.length);
181
- return jsxExtensions.map(
182
- ({ integration, extension }) => createForeignJsxOverridePlugin({
183
- hostJsxImportSource: integration.jsxImportSource,
184
- foreignExtensions: [extension],
185
- excludeExtensions: jsxExtensions.filter((candidate) => candidate.extension.length > extension.length).filter((candidate) => candidate.extension.endsWith(extension)).map((candidate) => candidate.extension),
186
- name: `ecopages-jsx-ownership-${integration.name}-${extension.replace(/[^a-zA-Z0-9]+/g, "-")}`
187
- })
204
+ const sourceTransformNames = new Set(getAppSourceTransforms(appConfig).map((transform) => transform.name));
205
+ const browserPlugins = getBrowserBuildPlugins(manifest).filter(
206
+ (plugin) => !sourceTransformNames.has(plugin.name)
188
207
  );
208
+ return [...browserPlugins, ...getJsxOwnershipPlugins(appConfig)];
189
209
  }
190
210
  function getAppBuildExecutor(appConfig) {
191
- return appConfig.runtime?.buildExecutor ?? getAppBuildAdapter(appConfig);
211
+ return appConfig.runtime?.routeModuleBuildExecutor ?? appConfig.runtime?.buildExecutor ?? getAppBuildAdapter(appConfig);
212
+ }
213
+ function getAppHmrBuildExecutor(appConfig) {
214
+ return appConfig.runtime?.hmrBuildExecutor ?? getAppBuildExecutor(appConfig);
215
+ }
216
+ function getAppRouteModuleBuildExecutor(appConfig) {
217
+ return appConfig.runtime?.routeModuleBuildExecutor ?? getAppBuildExecutor(appConfig);
192
218
  }
193
219
  function setAppBuildExecutor(appConfig, buildExecutor) {
194
220
  appConfig.runtime = {
@@ -196,6 +222,19 @@ function setAppBuildExecutor(appConfig, buildExecutor) {
196
222
  buildExecutor
197
223
  };
198
224
  }
225
+ function setAppHmrBuildExecutor(appConfig, buildExecutor) {
226
+ appConfig.runtime = {
227
+ ...appConfig.runtime ?? {},
228
+ hmrBuildExecutor: buildExecutor
229
+ };
230
+ }
231
+ function setAppRouteModuleBuildExecutor(appConfig, buildExecutor) {
232
+ appConfig.runtime = {
233
+ ...appConfig.runtime ?? {},
234
+ routeModuleBuildExecutor: buildExecutor,
235
+ buildExecutor
236
+ };
237
+ }
199
238
  function build(options, executor = defaultRolldownBuildAdapter) {
200
239
  return executor.build(options).then((result) => {
201
240
  if (result.success) {
@@ -228,6 +267,8 @@ export {
228
267
  getAppBuildExecutor,
229
268
  getAppBuildManifest,
230
269
  getAppBuildOwnership,
270
+ getAppHmrBuildExecutor,
271
+ getAppRouteModuleBuildExecutor,
231
272
  getAppServerBuildPlugins,
232
273
  getAppTranspileOptions,
233
274
  getBuildAdapterOwnership,
@@ -237,6 +278,8 @@ export {
237
278
  setAppBuildExecutor,
238
279
  setAppBuildManifest,
239
280
  setAppBuildOwnership,
281
+ setAppHmrBuildExecutor,
282
+ setAppRouteModuleBuildExecutor,
240
283
  setupAppRuntimePlugins,
241
284
  updateAppBuildManifest,
242
285
  withBuildExecutorPlugins
@@ -0,0 +1,28 @@
1
+ import type { IntegrationPlugin } from '../plugins/integration-plugin.js';
2
+ import type { Processor } from '../plugins/processor.js';
3
+ import type { EcoPagesAppConfig } from '../types/internal-types.js';
4
+ /**
5
+ * Optional contributor surface for build-time cache invalidation.
6
+ *
7
+ * @remarks
8
+ * Processors and integrations may override {@link didChange} to report that
9
+ * their build inputs changed independently of route source files.
10
+ */
11
+ export interface BuildInputChangeContributor {
12
+ /**
13
+ * Returns `true` when this contributor's build inputs changed since the last
14
+ * persisted incremental build metadata was written.
15
+ */
16
+ didChange?(): boolean;
17
+ }
18
+ /** Returns whether a processor or integration signals changed build inputs. */
19
+ export declare function didBuildInputContributorChange(contributor: BuildInputChangeContributor): boolean;
20
+ /** Hashes the app's eco.config.ts file when present. */
21
+ export declare function hashAppConfigFile(appConfig: EcoPagesAppConfig): string;
22
+ /** Fingerprints processor/integration build-input state for cache invalidation. */
23
+ export declare function createBuildInputsFingerprint(appConfig: EcoPagesAppConfig): string;
24
+ /** Returns true when any processor or integration reports changed build inputs. */
25
+ export declare function haveBuildInputsChanged(appConfig: EcoPagesAppConfig): boolean;
26
+ /** Collects every processor and integration that can invalidate incremental builds. */
27
+ export declare function collectBuildInputContributors(appConfig: EcoPagesAppConfig): BuildInputChangeContributor[];
28
+ export type { Processor, IntegrationPlugin };
@@ -0,0 +1,31 @@
1
+ import { fileSystem } from "@ecopages/file-system";
2
+ function didBuildInputContributorChange(contributor) {
3
+ return contributor.didChange?.() === true;
4
+ }
5
+ function hashAppConfigFile(appConfig) {
6
+ const configPath = appConfig.absolutePaths?.config;
7
+ if (!configPath || !fileSystem.exists(configPath)) {
8
+ return "missing";
9
+ }
10
+ return fileSystem.hash(configPath);
11
+ }
12
+ function createBuildInputsFingerprint(appConfig) {
13
+ const changedContributors = [
14
+ ...Array.from(appConfig.processors?.values() ?? []).filter((processor) => didBuildInputContributorChange(processor)).map((processor) => `processor:${processor.getName()}`),
15
+ ...(appConfig.integrations ?? []).filter((integration) => didBuildInputContributorChange(integration)).map((integration) => `integration:${integration.name}`)
16
+ ];
17
+ return changedContributors.length > 0 ? changedContributors.sort().join("|") : "stable";
18
+ }
19
+ function haveBuildInputsChanged(appConfig) {
20
+ return createBuildInputsFingerprint(appConfig) !== "stable";
21
+ }
22
+ function collectBuildInputContributors(appConfig) {
23
+ return [...Array.from(appConfig.processors?.values() ?? []), ...appConfig.integrations ?? []];
24
+ }
25
+ export {
26
+ collectBuildInputContributors,
27
+ createBuildInputsFingerprint,
28
+ didBuildInputContributorChange,
29
+ hashAppConfigFile,
30
+ haveBuildInputsChanged
31
+ };
@@ -0,0 +1,10 @@
1
+ import type { EcoBuildPlugin } from './build-types.js';
2
+ import type { EcoPagesAppConfig } from '../types/internal-types.js';
3
+ /**
4
+ * Foreign JSX override plugins for mixed-integration apps (Kita + React, etc.).
5
+ *
6
+ * Each plugin rewrites files for one `(integration, extension)` pair with a
7
+ * `@jsxImportSource` pragma so the bundler compiles against the owning runtime.
8
+ * Extensions are sorted longest-first so `.page.react.tsx` wins over `.tsx`.
9
+ */
10
+ export declare function getJsxOwnershipPlugins(appConfig: EcoPagesAppConfig): EcoBuildPlugin[];
@@ -0,0 +1,17 @@
1
+ import { createForeignJsxOverridePlugin } from "../plugins/foreign-jsx-override-plugin.js";
2
+ function getJsxOwnershipPlugins(appConfig) {
3
+ const jsxExtensions = (appConfig.integrations ?? []).filter((integration) => integration.jsxImportSource).flatMap(
4
+ (integration) => integration.extensions.filter((extension) => extension.endsWith(".tsx") || extension.endsWith(".jsx")).map((extension) => ({ integration, extension }))
5
+ ).sort((left, right) => right.extension.length - left.extension.length);
6
+ return jsxExtensions.map(
7
+ ({ integration, extension }) => createForeignJsxOverridePlugin({
8
+ hostJsxImportSource: integration.jsxImportSource,
9
+ foreignExtensions: [extension],
10
+ excludeExtensions: jsxExtensions.filter((candidate) => candidate.extension.length > extension.length).filter((candidate) => candidate.extension.endsWith(extension)).map((candidate) => candidate.extension),
11
+ name: `ecopages-jsx-ownership-${integration.name}-${extension.replace(/[^a-zA-Z0-9]+/g, "-")}`
12
+ })
13
+ );
14
+ }
15
+ export {
16
+ getJsxOwnershipPlugins
17
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Unified pages Rolldown graph (Phase A build + Phase C import).
3
+ *
4
+ * Enabled by default in production builds. Opt out with
5
+ * `ECOPAGES_UNIFIED_PAGES_GRAPH=0`. Covers all static page template extensions
6
+ * registered on the app config.
7
+ */
8
+ import type { EcoPagesAppConfig } from '../types/internal-types.js';
9
+ export declare const PAGES_UNIFIED_GRAPH_CACHE_DIR = ".server-pages-graph";
10
+ export declare const PAGES_UNIFIED_GRAPH_CACHE_FILENAME = ".build-cache.json";
11
+ export interface PagesUnifiedGraphCacheManifest {
12
+ invalidationVersion: string;
13
+ buildInputsFingerprint: string;
14
+ buildKey: string;
15
+ builtAt: number;
16
+ outputs: Record<string, string>;
17
+ }
18
+ export declare function isPagesUnifiedGraphEnabled(): boolean;
19
+ /** True when the unified graph should build or serve prebuilt page modules. */
20
+ export declare function shouldBuildPagesUnifiedGraph(): boolean;
21
+ export declare function isPagesUnifiedGraphPage(filePath: string, appConfig: EcoPagesAppConfig): boolean;
22
+ /**
23
+ * Builds eligible static pages in one Rolldown invocation and seeds route-module disk cache.
24
+ */
25
+ export declare function ensurePagesUnifiedGraphBuilt(options: {
26
+ appConfig: EcoPagesAppConfig;
27
+ entryPaths: readonly string[];
28
+ outdir: string;
29
+ force?: boolean;
30
+ }): Promise<PagesUnifiedGraphCacheManifest | undefined>;
31
+ export declare function importPagesUnifiedGraphModule<T>(appConfig: EcoPagesAppConfig, filePath: string): Promise<T | undefined>;
@@ -0,0 +1,208 @@
1
+ import path from "node:path";
2
+ import { pathToFileURL } from "node:url";
3
+ import { fileSystem } from "@ecopages/file-system";
4
+ import { appLogger } from "../global/app-logger.js";
5
+ import { build, getAppRouteModuleBuildExecutor, getAppServerBuildPlugins } from "./build-adapter.js";
6
+ import { createBuildInputsFingerprint, hashAppConfigFile } from "./build-input-fingerprint.js";
7
+ import { getCorePackageVersion } from "../services/module-loading/route-module-build-manifest.js";
8
+ import { resolveInternalExecutionDir } from "../utils/resolve-work-dir.js";
9
+ import { getSharedRouteModuleBuildCache } from "../services/module-loading/route-module-build-cache-registry.js";
10
+ import { normalizeNodeRuntimeBuildOutputFile } from "./runtime-build-output-normalizer.js";
11
+ import { resolveRouteModuleDependencyPaths } from "../services/module-loading/route-module-dependency-hasher.js";
12
+ import { getJsxOwnershipPlugins } from "./jsx-ownership-plugins.js";
13
+ const PAGES_UNIFIED_GRAPH_CACHE_DIR = ".server-pages-graph";
14
+ const PAGES_UNIFIED_GRAPH_CACHE_FILENAME = ".build-cache.json";
15
+ function isPagesUnifiedGraphEnabled() {
16
+ if (process.env.ECOPAGES_UNIFIED_PAGES_GRAPH === "0") {
17
+ return false;
18
+ }
19
+ if (process.env.ECOPAGES_UNIFIED_PAGES_GRAPH === "1") {
20
+ return true;
21
+ }
22
+ return process.env.NODE_ENV === "production";
23
+ }
24
+ function shouldBuildPagesUnifiedGraph() {
25
+ return isPagesUnifiedGraphEnabled() && process.env.NODE_ENV === "production";
26
+ }
27
+ function isPagesUnifiedGraphPage(filePath, appConfig) {
28
+ const normalizedPath = path.normalize(filePath);
29
+ return appConfig.templatesExt.some((extension) => normalizedPath.endsWith(extension));
30
+ }
31
+ function getPagesUnifiedGraphCachePath(appConfig) {
32
+ return path.join(
33
+ resolveInternalExecutionDir(appConfig),
34
+ PAGES_UNIFIED_GRAPH_CACHE_DIR,
35
+ PAGES_UNIFIED_GRAPH_CACHE_FILENAME
36
+ );
37
+ }
38
+ function createPagesUnifiedGraphBuildKey(appConfig, outdir) {
39
+ const plugins = appConfig.runtime?.buildManifest ? getAppServerBuildPlugins(appConfig) : [];
40
+ const pluginNames = [...plugins, ...getJsxOwnershipPlugins(appConfig)].map((plugin) => plugin.name).sort().join("|");
41
+ const templateExtensions = [...appConfig.templatesExt].sort().join("|");
42
+ return [
43
+ path.resolve(outdir),
44
+ hashAppConfigFile(appConfig),
45
+ templateExtensions,
46
+ pluginNames,
47
+ "es2022",
48
+ "esm",
49
+ "external-packages"
50
+ ].join("::");
51
+ }
52
+ function readPagesUnifiedGraphManifest(appConfig) {
53
+ const manifestPath = getPagesUnifiedGraphCachePath(appConfig);
54
+ if (!fileSystem.exists(manifestPath)) {
55
+ return void 0;
56
+ }
57
+ try {
58
+ const parsed = JSON.parse(fileSystem.readFileSync(manifestPath));
59
+ if (!parsed?.outputs) {
60
+ return void 0;
61
+ }
62
+ return parsed;
63
+ } catch {
64
+ return void 0;
65
+ }
66
+ }
67
+ function writePagesUnifiedGraphManifest(appConfig, manifest) {
68
+ const manifestPath = getPagesUnifiedGraphCachePath(appConfig);
69
+ fileSystem.ensureDir(path.dirname(manifestPath));
70
+ fileSystem.write(manifestPath, `${JSON.stringify(manifest, null, " ")}
71
+ `);
72
+ }
73
+ function createSafeGraphEntryKey(entryPath, rootDir) {
74
+ const relativePath = path.relative(rootDir, entryPath).replace(/\.[^.]+$/u, "");
75
+ const safeKey = relativePath.replace(/[^a-zA-Z0-9]+/gu, "-").replace(/^-+|-+$/gu, "");
76
+ return safeKey.length > 0 ? safeKey : "page";
77
+ }
78
+ function resolveOutputForEntrypoint(entryKey, buildResult) {
79
+ const outputs = buildResult.outputs.map((output) => output.path);
80
+ for (const outputPath of outputs) {
81
+ const outputBaseName = path.basename(outputPath);
82
+ if (outputBaseName.startsWith(`${entryKey}-`) && /\.(?:m?js)$/u.test(outputBaseName)) {
83
+ return outputPath;
84
+ }
85
+ if (outputBaseName === `${entryKey}.js` || outputBaseName === `${entryKey}.mjs`) {
86
+ return outputPath;
87
+ }
88
+ }
89
+ return void 0;
90
+ }
91
+ function isManifestValidForEntries(manifest, appConfig, outdir, entryPaths) {
92
+ if (manifest.invalidationVersion !== getCorePackageVersion()) {
93
+ return false;
94
+ }
95
+ if (manifest.buildInputsFingerprint !== createBuildInputsFingerprint(appConfig)) {
96
+ return false;
97
+ }
98
+ if (manifest.buildKey !== createPagesUnifiedGraphBuildKey(appConfig, outdir)) {
99
+ return false;
100
+ }
101
+ return entryPaths.every((entryPath) => {
102
+ const resolvedEntryPath = path.resolve(entryPath);
103
+ const outputPath = manifest.outputs[resolvedEntryPath];
104
+ return outputPath ? fileSystem.exists(outputPath) : false;
105
+ });
106
+ }
107
+ async function ensurePagesUnifiedGraphBuilt(options) {
108
+ if (!shouldBuildPagesUnifiedGraph()) {
109
+ return void 0;
110
+ }
111
+ const eligibleEntryPaths = options.entryPaths.map((entryPath) => path.resolve(entryPath)).filter((entryPath) => isPagesUnifiedGraphPage(entryPath, options.appConfig));
112
+ if (eligibleEntryPaths.length === 0) {
113
+ return void 0;
114
+ }
115
+ const outdir = path.resolve(options.outdir);
116
+ const existingManifest = options.force ? void 0 : readPagesUnifiedGraphManifest(options.appConfig);
117
+ if (existingManifest && isManifestValidForEntries(existingManifest, options.appConfig, outdir, eligibleEntryPaths)) {
118
+ appLogger.debug("Reusing pages unified graph cache");
119
+ return existingManifest;
120
+ }
121
+ const plugins = options.appConfig.runtime?.buildManifest ? getAppServerBuildPlugins(options.appConfig) : [];
122
+ const mergedPlugins = [...plugins, ...getJsxOwnershipPlugins(options.appConfig)];
123
+ const routeModuleBuildCache = getSharedRouteModuleBuildCache(outdir, options.appConfig);
124
+ const entryRecord = {};
125
+ const entryKeysByPath = /* @__PURE__ */ new Map();
126
+ for (const entryPath of eligibleEntryPaths) {
127
+ const entryKey = createSafeGraphEntryKey(entryPath, options.appConfig.rootDir);
128
+ entryRecord[entryKey] = entryPath;
129
+ entryKeysByPath.set(entryPath, entryKey);
130
+ }
131
+ appLogger.debugTime("pagesUnifiedGraphBuild");
132
+ const buildResult = await build(
133
+ {
134
+ entrypoints: entryRecord,
135
+ root: options.appConfig.rootDir,
136
+ outdir,
137
+ target: "es2022",
138
+ format: "esm",
139
+ sourcemap: "none",
140
+ splitting: true,
141
+ minify: false,
142
+ naming: "[name]-[hash].[ext]",
143
+ externalPackages: true,
144
+ plugins: mergedPlugins.length > 0 ? mergedPlugins : void 0
145
+ },
146
+ getAppRouteModuleBuildExecutor(options.appConfig)
147
+ );
148
+ appLogger.debugTimeEnd("pagesUnifiedGraphBuild");
149
+ if (!buildResult.success) {
150
+ const details = buildResult.logs.map((log) => log.message).join(" | ");
151
+ throw new Error(`Pages unified graph build failed: ${details}`);
152
+ }
153
+ const outputs = {};
154
+ for (const entryPath of eligibleEntryPaths) {
155
+ const fileHash = fileSystem.hash(entryPath);
156
+ const entryKey = entryKeysByPath.get(entryPath);
157
+ const compiledOutput = entryKey ? resolveOutputForEntrypoint(entryKey, buildResult) : void 0;
158
+ if (!compiledOutput) {
159
+ throw new Error(`Pages unified graph build produced no output for ${entryPath}`);
160
+ }
161
+ normalizeNodeRuntimeBuildOutputFile(compiledOutput, options.appConfig.rootDir);
162
+ outputs[entryPath] = compiledOutput;
163
+ routeModuleBuildCache.recordBuild({
164
+ filePath: entryPath,
165
+ rootDir: options.appConfig.rootDir,
166
+ outdir,
167
+ fileHash,
168
+ outputPath: compiledOutput,
169
+ dependencyModulePaths: resolveRouteModuleDependencyPaths(buildResult, entryPath, options.appConfig.rootDir),
170
+ externalPackages: true,
171
+ plugins: mergedPlugins
172
+ });
173
+ }
174
+ const manifest = {
175
+ invalidationVersion: getCorePackageVersion(),
176
+ buildInputsFingerprint: createBuildInputsFingerprint(options.appConfig),
177
+ buildKey: createPagesUnifiedGraphBuildKey(options.appConfig, outdir),
178
+ builtAt: Date.now(),
179
+ outputs
180
+ };
181
+ writePagesUnifiedGraphManifest(options.appConfig, manifest);
182
+ appLogger.debug(`Built pages unified graph for ${eligibleEntryPaths.length} entries`);
183
+ return manifest;
184
+ }
185
+ async function importPagesUnifiedGraphModule(appConfig, filePath) {
186
+ const manifest = readPagesUnifiedGraphManifest(appConfig);
187
+ if (!manifest) {
188
+ return void 0;
189
+ }
190
+ const resolvedFilePath = path.resolve(filePath);
191
+ const outputPath = manifest.outputs[resolvedFilePath];
192
+ if (!outputPath || !fileSystem.exists(outputPath)) {
193
+ return void 0;
194
+ }
195
+ return await import(
196
+ /* @vite-ignore */
197
+ pathToFileURL(outputPath).href
198
+ );
199
+ }
200
+ export {
201
+ PAGES_UNIFIED_GRAPH_CACHE_DIR,
202
+ PAGES_UNIFIED_GRAPH_CACHE_FILENAME,
203
+ ensurePagesUnifiedGraphBuilt,
204
+ importPagesUnifiedGraphModule,
205
+ isPagesUnifiedGraphEnabled,
206
+ isPagesUnifiedGraphPage,
207
+ shouldBuildPagesUnifiedGraph
208
+ };
@@ -0,0 +1,21 @@
1
+ import type { BuildExecutor, BuildOptions, BuildResult } from './build-adapter.js';
2
+ /**
3
+ * Concurrency-limited build executor wrapper.
4
+ *
5
+ * @remarks
6
+ * Runs up to `limit` builds concurrently. Use for independent route-module
7
+ * and HMR browser builds where FIFO serialization is unnecessary.
8
+ */
9
+ export declare class ParallelBuildExecutor implements BuildExecutor {
10
+ private readonly inner;
11
+ private readonly limit;
12
+ private activeCount;
13
+ private queue;
14
+ constructor(inner: BuildExecutor, limit: number);
15
+ build(options: BuildOptions): Promise<BuildResult>;
16
+ private drainQueue;
17
+ unwrap(): BuildExecutor;
18
+ getConcurrencyLimitForTests(): number;
19
+ getActiveCountForTests(): number;
20
+ getQueuedCountForTests(): number;
21
+ }
@@ -0,0 +1,52 @@
1
+ class ParallelBuildExecutor {
2
+ inner;
3
+ limit;
4
+ activeCount = 0;
5
+ queue = [];
6
+ constructor(inner, limit) {
7
+ this.inner = inner;
8
+ this.limit = Math.max(1, limit);
9
+ }
10
+ build(options) {
11
+ return new Promise((resolve, reject) => {
12
+ this.queue.push({
13
+ operation: () => this.inner.build(options),
14
+ resolve,
15
+ reject
16
+ });
17
+ this.drainQueue();
18
+ });
19
+ }
20
+ drainQueue() {
21
+ while (this.activeCount < this.limit && this.queue.length > 0) {
22
+ const next = this.queue.shift();
23
+ if (!next) {
24
+ return;
25
+ }
26
+ this.activeCount += 1;
27
+ void next.operation().then((result) => {
28
+ next.resolve(result);
29
+ }).catch((error) => {
30
+ next.reject(error);
31
+ }).finally(() => {
32
+ this.activeCount -= 1;
33
+ this.drainQueue();
34
+ });
35
+ }
36
+ }
37
+ unwrap() {
38
+ return this.inner;
39
+ }
40
+ getConcurrencyLimitForTests() {
41
+ return this.limit;
42
+ }
43
+ getActiveCountForTests() {
44
+ return this.activeCount;
45
+ }
46
+ getQueuedCountForTests() {
47
+ return this.queue.length;
48
+ }
49
+ }
50
+ export {
51
+ ParallelBuildExecutor
52
+ };
@@ -201,7 +201,8 @@ function resolveRolldownOptions(options, contextRoot, outdir, appRootRequireCach
201
201
  transformOptions.target = options.target;
202
202
  }
203
203
  const bundlePlugins = options.plugins ?? [];
204
- const appPlugins = createRolldownPluginBridge(bundlePlugins, contextRoot);
204
+ const sourceTransforms = options.target === "browser" ? options.sourceTransforms ?? [] : [];
205
+ const appPlugins = createRolldownPluginBridge(bundlePlugins, contextRoot, sourceTransforms);
205
206
  const allPlugins = [...options.target !== "browser" ? [createServerSideCssShimPlugin()] : [], ...appPlugins];
206
207
  const inputOptions = {
207
208
  input: options.entrypoints,
@@ -211,6 +212,11 @@ function resolveRolldownOptions(options, contextRoot, outdir, appRootRequireCach
211
212
  transform: Object.keys(transformOptions).length > 0 ? transformOptions : void 0,
212
213
  resolve: options.conditions ? { conditionNames: options.conditions } : void 0,
213
214
  treeshake: typeof options.treeshaking === "boolean" ? options.treeshaking : true,
215
+ ...process.env.ECOPAGES_PROFILE_BUILD === "1" ? {} : {
216
+ checks: {
217
+ pluginTimings: false
218
+ }
219
+ },
214
220
  experimental: {
215
221
  nativeMagicString: true
216
222
  },
@@ -9,6 +9,7 @@ import {
9
9
  toBuildLogs,
10
10
  transpileProfileToOptions
11
11
  } from "./rolldown-adapter-helpers.js";
12
+ import { recordRolldownBuildInvocation } from "./rolldown-build-invocation-metrics.js";
12
13
  const moduleRequire = createRequire(import.meta.url);
13
14
  class RolldownBuildAdapter {
14
15
  ownership = "rolldown";
@@ -20,6 +21,7 @@ class RolldownBuildAdapter {
20
21
  * entrypoints, use {@link RolldownDevBuildAdapter}.
21
22
  */
22
23
  async buildOrThrow(options) {
24
+ recordRolldownBuildInvocation("rolldown");
23
25
  const contextRoot = options.root ? path.resolve(options.root) : process.cwd();
24
26
  const outdir = path.resolve(options.outdir ?? "dist/assets");
25
27
  const plugins = options.plugins ?? [];
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Process-wide Rolldown build invocation counters for benchmarks and spikes.
3
+ *
4
+ * Gated behind {@link isRolldownBuildMetricsEnabled} so production paths stay quiet.
5
+ */
6
+ export declare function isRolldownBuildMetricsEnabled(): boolean;
7
+ export declare function recordRolldownBuildInvocation(adapter?: 'rolldown' | 'rolldown-dev'): void;
8
+ export declare function getRolldownBuildInvocationCounts(): Readonly<Record<string, number>>;
9
+ export declare function resetRolldownBuildInvocationCounts(): void;
10
+ export declare function recordPageModuleBuildInvocation(): void;
11
+ export declare function getPageModuleRolldownBuildInvocations(): number;
12
+ export declare function getTotalRolldownBuildInvocations(): number;
@@ -0,0 +1,43 @@
1
+ const invocationCounts = /* @__PURE__ */ new Map();
2
+ let pageModuleBuildInvocations = 0;
3
+ function isRolldownBuildMetricsEnabled() {
4
+ return process.env.ECOPAGES_ROLLDOWN_BUILD_METRICS === "1" || process.env.ECOPAGES_LOGGER_DEBUG === "true";
5
+ }
6
+ function recordRolldownBuildInvocation(adapter = "rolldown") {
7
+ if (!isRolldownBuildMetricsEnabled()) {
8
+ return;
9
+ }
10
+ invocationCounts.set(adapter, (invocationCounts.get(adapter) ?? 0) + 1);
11
+ }
12
+ function getRolldownBuildInvocationCounts() {
13
+ return Object.fromEntries(invocationCounts);
14
+ }
15
+ function resetRolldownBuildInvocationCounts() {
16
+ invocationCounts.clear();
17
+ pageModuleBuildInvocations = 0;
18
+ }
19
+ function recordPageModuleBuildInvocation() {
20
+ if (!isRolldownBuildMetricsEnabled()) {
21
+ return;
22
+ }
23
+ pageModuleBuildInvocations += 1;
24
+ }
25
+ function getPageModuleRolldownBuildInvocations() {
26
+ return pageModuleBuildInvocations;
27
+ }
28
+ function getTotalRolldownBuildInvocations() {
29
+ let total = 0;
30
+ for (const count of invocationCounts.values()) {
31
+ total += count;
32
+ }
33
+ return total;
34
+ }
35
+ export {
36
+ getPageModuleRolldownBuildInvocations,
37
+ getRolldownBuildInvocationCounts,
38
+ getTotalRolldownBuildInvocations,
39
+ isRolldownBuildMetricsEnabled,
40
+ recordPageModuleBuildInvocation,
41
+ recordRolldownBuildInvocation,
42
+ resetRolldownBuildInvocationCounts
43
+ };