@ecopages/core 0.2.0-beta.6 → 0.2.0-beta.8

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 (104) hide show
  1. package/package.json +26 -2
  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 -7
  26. package/src/adapters/shared/server-static-builder.js +89 -49
  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 +8 -2
  31. package/src/build/build-adapter.js +71 -33
  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 +47 -3
  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/runtime-build-executor.d.ts +7 -13
  47. package/src/build/runtime-build-executor.js +31 -7
  48. package/src/build/runtime-build-output-normalizer.d.ts +7 -0
  49. package/src/build/runtime-build-output-normalizer.js +85 -1
  50. package/src/build/server-entry-build-cache.d.ts +44 -0
  51. package/src/build/server-entry-build-cache.js +169 -0
  52. package/src/hmr/hmr.test.e2e.js +15 -4
  53. package/src/plugins/integration-plugin.d.ts +19 -8
  54. package/src/plugins/integration-plugin.js +7 -2
  55. package/src/plugins/processor.d.ts +5 -0
  56. package/src/plugins/processor.js +7 -0
  57. package/src/route-renderer/GRAPH.md +4 -4
  58. package/src/route-renderer/README.md +6 -5
  59. package/src/route-renderer/orchestration/foreign-subtree-execution.service.js +1 -3
  60. package/src/route-renderer/orchestration/integration-renderer.d.ts +10 -2
  61. package/src/route-renderer/orchestration/integration-renderer.js +32 -12
  62. package/src/route-renderer/orchestration/render-output.utils.d.ts +2 -0
  63. package/src/route-renderer/orchestration/render-output.utils.js +4 -0
  64. package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +2 -2
  65. package/src/route-renderer/orchestration/route-render-orchestrator.js +17 -16
  66. package/src/router/client/navigation-coordinator.js +10 -0
  67. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.d.ts +1 -0
  68. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.js +1 -0
  69. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.d.ts +4 -2
  70. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.js +27 -7
  71. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +17 -3
  72. package/src/services/assets/browser-bundle.service.d.ts +1 -0
  73. package/src/services/assets/browser-bundle.service.js +9 -3
  74. package/src/services/html/html-transformer.service.d.ts +1 -1
  75. package/src/services/html/html-transformer.service.js +4 -4
  76. package/src/services/invalidation/development-invalidation.service.d.ts +10 -1
  77. package/src/services/invalidation/development-invalidation.service.js +29 -0
  78. package/src/services/module-loading/README.md +49 -0
  79. package/src/services/module-loading/app-server-module-transpiler.service.js +4 -17
  80. package/src/services/module-loading/page-module-import.service.d.ts +4 -1
  81. package/src/services/module-loading/page-module-import.service.js +52 -29
  82. package/src/services/module-loading/route-module-build-cache-registry.d.ts +24 -0
  83. package/src/services/module-loading/route-module-build-cache-registry.js +37 -0
  84. package/src/services/module-loading/route-module-build-cache.d.ts +4 -0
  85. package/src/services/module-loading/route-module-build-cache.js +38 -0
  86. package/src/services/module-loading/route-module-build-cache.store.d.ts +57 -0
  87. package/src/services/module-loading/route-module-build-cache.store.js +203 -0
  88. package/src/services/module-loading/route-module-build-manifest.d.ts +61 -0
  89. package/src/services/module-loading/route-module-build-manifest.js +124 -0
  90. package/src/services/module-loading/route-module-dependency-hasher.d.ts +81 -0
  91. package/src/services/module-loading/route-module-dependency-hasher.js +121 -0
  92. package/src/services/module-loading/server-module-transpiler.service.js +1 -1
  93. package/src/static-site-generator/README.md +33 -0
  94. package/src/static-site-generator/static-build-invalidation.d.ts +10 -0
  95. package/src/static-site-generator/static-build-invalidation.js +78 -0
  96. package/src/static-site-generator/static-export-context.d.ts +22 -0
  97. package/src/static-site-generator/static-export-context.js +0 -0
  98. package/src/static-site-generator/static-site-generator.d.ts +17 -20
  99. package/src/static-site-generator/static-site-generator.js +188 -85
  100. package/src/types/internal-types.d.ts +8 -0
  101. package/src/utils/parse-cli-args.d.ts +1 -0
  102. package/src/utils/parse-cli-args.js +3 -0
  103. package/src/watchers/project-watcher.d.ts +8 -0
  104. package/src/watchers/project-watcher.js +32 -12
@@ -1,13 +1,14 @@
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 { getJsxOwnershipPlugins } from "./jsx-ownership-plugins.js";
9
9
  import { createRolldownBuildAdapter } from "./rolldown-build-adapter.js";
10
10
  import { createRolldownDevBuildAdapter } from "./rolldown-dev-build-adapter.js";
11
+ import { appLogger } from "../global/app-logger.js";
11
12
  function mergeBuildExecutorPlugins(existing, appPlugins) {
12
13
  if (!existing || existing.length === 0) {
13
14
  return appPlugins;
@@ -96,7 +97,7 @@ function setAppBuildAdapter(appConfig, buildAdapter) {
96
97
  }
97
98
  function getAppBuildManifest(appConfig) {
98
99
  return appConfig.runtime?.buildManifest ?? createAppBuildManifest({
99
- loaderPlugins: Array.from(appConfig.loaders.values())
100
+ loaderPlugins: Array.from(appConfig.loaders?.values() ?? [])
100
101
  });
101
102
  }
102
103
  function setAppBuildManifest(appConfig, buildManifest) {
@@ -143,27 +144,55 @@ async function collectConfiguredAppBuildManifestContributions(appConfig) {
143
144
  };
144
145
  }
145
146
  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) {
147
+ if (options.appConfig.runtime?.runtimeAssetsPrepared) {
148
+ appLogger.debug("Skipped setupAppRuntimePlugins: runtime assets already prepared");
149
+ for (const loader of options.appConfig.loaders.values()) {
150
+ options.onRuntimePlugin?.(loader);
151
+ }
152
+ for (const processor of options.appConfig.processors.values()) {
153
+ if (processor.plugins) {
154
+ for (const plugin of processor.plugins) {
155
+ options.onRuntimePlugin?.(plugin);
156
+ }
157
+ }
158
+ }
159
+ for (const integration of options.appConfig.integrations) {
160
+ for (const plugin of integration.plugins) {
153
161
  options.onRuntimePlugin?.(plugin);
154
162
  }
155
163
  }
164
+ return;
156
165
  }
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);
166
+ appLogger.debugTime("setupAppRuntimePlugins");
167
+ try {
168
+ for (const loader of options.appConfig.loaders.values()) {
169
+ options.onRuntimePlugin?.(loader);
162
170
  }
163
- await integration.setup();
164
- for (const plugin of integration.plugins) {
165
- options.onRuntimePlugin?.(plugin);
171
+ for (const processor of options.appConfig.processors.values()) {
172
+ await processor.setup();
173
+ if (processor.plugins) {
174
+ for (const plugin of processor.plugins) {
175
+ options.onRuntimePlugin?.(plugin);
176
+ }
177
+ }
166
178
  }
179
+ for (const integration of options.appConfig.integrations) {
180
+ integration.setConfig(options.appConfig);
181
+ integration.setRuntimeOrigin(options.runtimeOrigin);
182
+ if (options.hmrManager) {
183
+ integration.setHmrManager(options.hmrManager);
184
+ }
185
+ await integration.setup();
186
+ for (const plugin of integration.plugins) {
187
+ options.onRuntimePlugin?.(plugin);
188
+ }
189
+ }
190
+ options.appConfig.runtime = {
191
+ ...options.appConfig.runtime ?? {},
192
+ runtimeAssetsPrepared: true
193
+ };
194
+ } finally {
195
+ appLogger.debugTimeEnd("setupAppRuntimePlugins");
167
196
  }
168
197
  }
169
198
  function getAppServerBuildPlugins(appConfig) {
@@ -171,24 +200,16 @@ function getAppServerBuildPlugins(appConfig) {
171
200
  }
172
201
  function getAppBrowserBuildPlugins(appConfig) {
173
202
  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
- })
188
- );
203
+ return [...getBrowserBuildPlugins(manifest), ...getJsxOwnershipPlugins(appConfig)];
189
204
  }
190
205
  function getAppBuildExecutor(appConfig) {
191
- return appConfig.runtime?.buildExecutor ?? getAppBuildAdapter(appConfig);
206
+ return appConfig.runtime?.routeModuleBuildExecutor ?? appConfig.runtime?.buildExecutor ?? getAppBuildAdapter(appConfig);
207
+ }
208
+ function getAppHmrBuildExecutor(appConfig) {
209
+ return appConfig.runtime?.hmrBuildExecutor ?? getAppBuildExecutor(appConfig);
210
+ }
211
+ function getAppRouteModuleBuildExecutor(appConfig) {
212
+ return appConfig.runtime?.routeModuleBuildExecutor ?? getAppBuildExecutor(appConfig);
192
213
  }
193
214
  function setAppBuildExecutor(appConfig, buildExecutor) {
194
215
  appConfig.runtime = {
@@ -196,6 +217,19 @@ function setAppBuildExecutor(appConfig, buildExecutor) {
196
217
  buildExecutor
197
218
  };
198
219
  }
220
+ function setAppHmrBuildExecutor(appConfig, buildExecutor) {
221
+ appConfig.runtime = {
222
+ ...appConfig.runtime ?? {},
223
+ hmrBuildExecutor: buildExecutor
224
+ };
225
+ }
226
+ function setAppRouteModuleBuildExecutor(appConfig, buildExecutor) {
227
+ appConfig.runtime = {
228
+ ...appConfig.runtime ?? {},
229
+ routeModuleBuildExecutor: buildExecutor,
230
+ buildExecutor
231
+ };
232
+ }
199
233
  function build(options, executor = defaultRolldownBuildAdapter) {
200
234
  return executor.build(options).then((result) => {
201
235
  if (result.success) {
@@ -228,6 +262,8 @@ export {
228
262
  getAppBuildExecutor,
229
263
  getAppBuildManifest,
230
264
  getAppBuildOwnership,
265
+ getAppHmrBuildExecutor,
266
+ getAppRouteModuleBuildExecutor,
231
267
  getAppServerBuildPlugins,
232
268
  getAppTranspileOptions,
233
269
  getBuildAdapterOwnership,
@@ -237,6 +273,8 @@ export {
237
273
  setAppBuildExecutor,
238
274
  setAppBuildManifest,
239
275
  setAppBuildOwnership,
276
+ setAppHmrBuildExecutor,
277
+ setAppRouteModuleBuildExecutor,
240
278
  setupAppRuntimePlugins,
241
279
  updateAppBuildManifest,
242
280
  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
+ };
@@ -1,10 +1,41 @@
1
1
  import { createRequire } from "node:module";
2
+ import { readFileSync } from "node:fs";
2
3
  import path from "node:path";
3
4
  import { collectBrowserRuntimeImportRewriteMap, rewriteBrowserRuntimeImports } from "./browser-runtime-plugin.js";
4
5
  import { createServerSideCssShimPlugin } from "./server-side-css-shim-plugin.js";
5
6
  import { createRolldownPluginBridge } from "./rolldown-plugin-bridge.js";
6
- import { isDeclaredAppPackageImport, normalizeNodeRuntimeBuildOutputs } from "./runtime-build-output-normalizer.js";
7
+ import {
8
+ isDeclaredAppPackageImport,
9
+ isWorkspacePackageImport,
10
+ normalizeNodeRuntimeBuildOutputs
11
+ } from "./runtime-build-output-normalizer.js";
7
12
  const corePackageRequire = createRequire(new URL("../../package.json", import.meta.url));
13
+ let corePackageNames;
14
+ function getCorePackageNames() {
15
+ if (corePackageNames) {
16
+ return corePackageNames;
17
+ }
18
+ const packageJsonPath = new URL("../../package.json", import.meta.url);
19
+ corePackageNames = /* @__PURE__ */ new Set();
20
+ try {
21
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
22
+ for (const field of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
23
+ const entries = packageJson[field];
24
+ if (!entries || typeof entries !== "object") {
25
+ continue;
26
+ }
27
+ for (const name of Object.keys(entries)) {
28
+ corePackageNames.add(name);
29
+ }
30
+ }
31
+ } catch {
32
+ }
33
+ return corePackageNames;
34
+ }
35
+ function isCoreDeclaredPackageImport(specifier) {
36
+ const name = specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0] ?? specifier;
37
+ return getCorePackageNames().has(name);
38
+ }
8
39
  function transpileProfileToOptions(profile) {
9
40
  switch (profile) {
10
41
  case "browser-script":
@@ -37,13 +68,21 @@ function getAppRootRequire(cache, contextRoot) {
37
68
  return req;
38
69
  }
39
70
  function shouldBundlePackageImport(id, contextRoot, appRootRequireCache) {
71
+ if (isWorkspacePackageImport(id, contextRoot)) {
72
+ return true;
73
+ }
40
74
  if (isDeclaredAppPackageImport(id, contextRoot)) {
41
75
  const appRootRequire = getAppRootRequire(appRootRequireCache, contextRoot);
42
76
  const appResolvedPath = tryResolveModule(id, appRootRequire);
43
77
  return Boolean(appResolvedPath && /\.(?:[cm]?ts|tsx|jsx)$/u.test(appResolvedPath));
44
78
  }
45
- const coreResolvedPath = tryResolveModule(id, corePackageRequire);
46
- return Boolean(coreResolvedPath && /\.(?:[cm]?ts|tsx|jsx)$/u.test(coreResolvedPath));
79
+ if (isCoreDeclaredPackageImport(id)) {
80
+ const coreResolvedPath = tryResolveModule(id, corePackageRequire);
81
+ if (coreResolvedPath && !/\.(?:[cm]?ts|tsx|jsx)$/u.test(coreResolvedPath)) {
82
+ return false;
83
+ }
84
+ }
85
+ return true;
47
86
  }
48
87
  function createExternalMatcher(options, appRootRequireCache) {
49
88
  const explicitExternals = new Set(options.external ?? []);
@@ -172,6 +211,11 @@ function resolveRolldownOptions(options, contextRoot, outdir, appRootRequireCach
172
211
  transform: Object.keys(transformOptions).length > 0 ? transformOptions : void 0,
173
212
  resolve: options.conditions ? { conditionNames: options.conditions } : void 0,
174
213
  treeshake: typeof options.treeshaking === "boolean" ? options.treeshaking : true,
214
+ ...process.env.ECOPAGES_PROFILE_BUILD === "1" ? {} : {
215
+ checks: {
216
+ pluginTimings: false
217
+ }
218
+ },
175
219
  experimental: {
176
220
  nativeMagicString: true
177
221
  },
@@ -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 ?? [];