@ecopages/core 0.2.0-beta.26 → 0.2.0-beta.27

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 (135) hide show
  1. package/package.json +58 -50
  2. package/src/adapters/bun/server-adapter.js +2 -2
  3. package/src/adapters/node/server-adapter.js +2 -2
  4. package/src/adapters/shared/hmr-entrypoint-registrar.d.ts +10 -0
  5. package/src/adapters/shared/hmr-entrypoint-registrar.js +34 -10
  6. package/src/adapters/shared/runtime-server-lifecycle.js +1 -1
  7. package/src/adapters/shared/server-static-builder.d.ts +2 -1
  8. package/src/adapters/shared/server-static-builder.js +16 -14
  9. package/src/adapters/shared/shared-hmr-manager.d.ts +11 -4
  10. package/src/adapters/shared/shared-hmr-manager.js +22 -3
  11. package/src/build/README.md +92 -46
  12. package/src/build/app-build-manifest-runtime.d.ts +28 -2
  13. package/src/build/app-build-manifest-runtime.js +26 -10
  14. package/src/build/{browser-runtime-plugin.d.ts → browser/browser-runtime-plugin.d.ts} +1 -1
  15. package/src/build/{browser-runtime-plugin.js → browser/browser-runtime-plugin.js} +1 -1
  16. package/src/build/{jsx-ownership-plugins.d.ts → browser/jsx-ownership-plugins.d.ts} +2 -2
  17. package/src/build/{jsx-ownership-plugins.js → browser/jsx-ownership-plugins.js} +2 -2
  18. package/src/build/build-adapter.d.ts +18 -21
  19. package/src/build/build-adapter.js +8 -43
  20. package/src/build/{build-input-fingerprint.d.ts → cache/build-input-fingerprint.d.ts} +3 -3
  21. package/src/build/cache/cache-constants.d.ts +4 -0
  22. package/src/build/cache/cache-constants.js +6 -0
  23. package/src/build/cache/cache-keys.d.ts +22 -0
  24. package/src/build/cache/cache-keys.js +63 -0
  25. package/src/build/{dev-browser-script-cache.d.ts → cache/dev-browser-script-cache.d.ts} +2 -2
  26. package/src/build/{dev-browser-script-cache.js → cache/dev-browser-script-cache.js} +4 -4
  27. package/src/build/{pages-unified-graph-build.d.ts → cache/pages-unified-graph-build.d.ts} +1 -1
  28. package/src/build/{pages-unified-graph-build.js → cache/pages-unified-graph-build.js} +18 -26
  29. package/src/build/{production-build-cache.d.ts → cache/production-build-cache.d.ts} +1 -1
  30. package/src/build/{production-build-cache.js → cache/production-build-cache.js} +2 -2
  31. package/src/build/{server-entry-build-cache.d.ts → cache/server-entry-build-cache.d.ts} +3 -4
  32. package/src/build/{server-entry-build-cache.js → cache/server-entry-build-cache.js} +16 -12
  33. package/src/build/{build-contracts.d.ts → contracts/build-contracts.d.ts} +1 -2
  34. package/src/build/contracts/build-manifest.d.ts +90 -0
  35. package/src/build/{build-manifest.js → contracts/build-manifest.js} +2 -2
  36. package/src/build/{rolldown-adapter-helpers.d.ts → rolldown/rolldown-adapter-helpers.d.ts} +20 -3
  37. package/src/build/{rolldown-adapter-helpers.js → rolldown/rolldown-adapter-helpers.js} +26 -6
  38. package/src/build/{rolldown-build-adapter.d.ts → rolldown/rolldown-build-adapter.d.ts} +1 -1
  39. package/src/build/{rolldown-plugin-bridge.d.ts → rolldown/rolldown-plugin-bridge.d.ts} +2 -2
  40. package/src/build/{rolldown-plugin-bridge.js → rolldown/rolldown-plugin-bridge.js} +1 -1
  41. package/src/build/{rolldown-source-transform-pass.d.ts → rolldown/rolldown-source-transform-pass.d.ts} +1 -1
  42. package/src/build/{rolldown-source-transform-pass.js → rolldown/rolldown-source-transform-pass.js} +4 -1
  43. package/src/build/{runtime-build-output-normalizer.js → rolldown/runtime-build-output-normalizer.js} +2 -2
  44. package/src/build/{build-profile-options.d.ts → runtime/build-profile-options.d.ts} +2 -2
  45. package/src/build/runtime/build-request-identity.d.ts +17 -0
  46. package/src/build/runtime/build-request-identity.js +54 -0
  47. package/src/build/runtime/build-request-policy.d.ts +59 -0
  48. package/src/build/runtime/build-request-policy.js +46 -0
  49. package/src/build/{build-runtime.d.ts → runtime/build-runtime.d.ts} +8 -4
  50. package/src/build/{build-runtime.js → runtime/build-runtime.js} +11 -11
  51. package/src/build/{deduping-build-executor.d.ts → runtime/deduping-build-executor.d.ts} +2 -9
  52. package/src/build/runtime/deduping-build-executor.js +34 -0
  53. package/src/build/{parallel-build-executor.d.ts → runtime/parallel-build-executor.d.ts} +1 -1
  54. package/src/build/{serialized-build-executor.d.ts → runtime/serialized-build-executor.d.ts} +1 -1
  55. package/src/config/config-builder.d.ts +1 -1
  56. package/src/diagnostics/startup-trace.d.ts +49 -2
  57. package/src/diagnostics/startup-trace.js +140 -8
  58. package/src/hmr/client/hmr-runtime.js +1 -1
  59. package/src/hmr/hmr-file-change-prep.d.ts +10 -0
  60. package/src/hmr/hmr-file-change-prep.js +17 -0
  61. package/src/plugins/alias-resolver-plugin.d.ts +1 -1
  62. package/src/plugins/eco-component-meta-plugin.d.ts +1 -1
  63. package/src/plugins/foreign-jsx-override-plugin.d.ts +1 -1
  64. package/src/plugins/integration-plugin.d.ts +16 -5
  65. package/src/plugins/integration-plugin.js +11 -1
  66. package/src/plugins/processor.d.ts +28 -8
  67. package/src/plugins/source-transform.d.ts +1 -1
  68. package/src/route-renderer/GRAPH.md +13 -19
  69. package/src/route-renderer/README.md +1 -1
  70. package/src/route-renderer/orchestration/component-render-context.d.ts +4 -4
  71. package/src/route-renderer/orchestration/foreign-subtree-execution.service.js +6 -2
  72. package/src/route-renderer/orchestration/integration-renderer.d.ts +11 -52
  73. package/src/route-renderer/orchestration/integration-renderer.js +38 -91
  74. package/src/route-renderer/orchestration/integration-route-render-adapter.d.ts +1 -8
  75. package/src/route-renderer/orchestration/integration-route-render-adapter.js +0 -2
  76. package/src/route-renderer/orchestration/page-browser-graph-session.d.ts +106 -0
  77. package/src/route-renderer/orchestration/page-browser-graph-session.js +436 -0
  78. package/src/route-renderer/orchestration/page-browser-graph.service.d.ts +9 -3
  79. package/src/route-renderer/orchestration/page-browser-graph.service.js +89 -40
  80. package/src/route-renderer/orchestration/render-output.utils.d.ts +14 -0
  81. package/src/route-renderer/orchestration/render-output.utils.js +30 -0
  82. package/src/route-renderer/orchestration/route-html-finalization.service.d.ts +0 -1
  83. package/src/route-renderer/orchestration/route-html-finalization.service.js +4 -9
  84. package/src/route-renderer/orchestration/route-prepared-options.builder.d.ts +1 -2
  85. package/src/route-renderer/orchestration/route-prepared-options.builder.js +1 -10
  86. package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +3 -12
  87. package/src/route-renderer/orchestration/route-render-orchestrator.js +4 -14
  88. package/src/services/assets/asset-processing-service/asset-processing.service.js +1 -1
  89. package/src/services/assets/asset-processing-service/assets.types.d.ts +1 -1
  90. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.d.ts +1 -1
  91. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.d.ts +0 -2
  92. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.js +2 -15
  93. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js +1 -1
  94. package/src/services/assets/browser-bundle.service.d.ts +27 -31
  95. package/src/services/assets/browser-bundle.service.js +12 -23
  96. package/src/services/module-loading/app-server-module-transpiler.service.d.ts +8 -0
  97. package/src/services/module-loading/app-server-module-transpiler.service.js +1 -7
  98. package/src/services/module-loading/page-module-import.service.d.ts +1 -1
  99. package/src/services/module-loading/page-module-import.service.js +40 -28
  100. package/src/services/module-loading/route-module-build-cache.store.js +1 -1
  101. package/src/services/module-loading/route-module-build-manifest.d.ts +2 -9
  102. package/src/services/module-loading/route-module-build-manifest.js +12 -43
  103. package/src/services/module-loading/server-module-transpiler.service.d.ts +1 -1
  104. package/src/static-site-generator/README.md +1 -1
  105. package/src/static-site-generator/production-page-browser-graph-prebuild.d.ts +28 -0
  106. package/src/static-site-generator/production-page-browser-graph-prebuild.js +22 -0
  107. package/src/static-site-generator/static-build-invalidation.d.ts +2 -2
  108. package/src/static-site-generator/static-build-invalidation.js +3 -3
  109. package/src/static-site-generator/static-site-generator.js +23 -1
  110. package/src/types/internal-types.d.ts +10 -4
  111. package/src/types/public-types.d.ts +58 -9
  112. package/src/watchers/project-watcher.js +12 -2
  113. package/src/build/build-manifest.d.ts +0 -33
  114. package/src/build/deduping-build-executor.js +0 -56
  115. package/src/build/runtime-build-executor.d.ts +0 -14
  116. package/src/build/runtime-build-executor.js +0 -16
  117. /package/src/build/{browser-runtime-manifest.d.ts → browser/browser-runtime-manifest.d.ts} +0 -0
  118. /package/src/build/{browser-runtime-manifest.js → browser/browser-runtime-manifest.js} +0 -0
  119. /package/src/build/{browser-runtime-plugin-helpers.d.ts → browser/browser-runtime-plugin-helpers.d.ts} +0 -0
  120. /package/src/build/{browser-runtime-plugin-helpers.js → browser/browser-runtime-plugin-helpers.js} +0 -0
  121. /package/src/build/{lit-static-render-worker-context.d.ts → browser/lit-static-render-worker-context.d.ts} +0 -0
  122. /package/src/build/{lit-static-render-worker-context.js → browser/lit-static-render-worker-context.js} +0 -0
  123. /package/src/build/{build-input-fingerprint.js → cache/build-input-fingerprint.js} +0 -0
  124. /package/src/build/{build-contracts.js → contracts/build-contracts.js} +0 -0
  125. /package/src/build/{build-types.d.ts → contracts/build-types.d.ts} +0 -0
  126. /package/src/build/{build-types.js → contracts/build-types.js} +0 -0
  127. /package/src/build/{rolldown-build-adapter.js → rolldown/rolldown-build-adapter.js} +0 -0
  128. /package/src/build/{rolldown-build-invocation-metrics.d.ts → rolldown/rolldown-build-invocation-metrics.d.ts} +0 -0
  129. /package/src/build/{rolldown-build-invocation-metrics.js → rolldown/rolldown-build-invocation-metrics.js} +0 -0
  130. /package/src/build/{runtime-build-output-normalizer.d.ts → rolldown/runtime-build-output-normalizer.d.ts} +0 -0
  131. /package/src/build/{server-side-css-shim-plugin.d.ts → rolldown/server-side-css-shim-plugin.d.ts} +0 -0
  132. /package/src/build/{server-side-css-shim-plugin.js → rolldown/server-side-css-shim-plugin.js} +0 -0
  133. /package/src/build/{build-profile-options.js → runtime/build-profile-options.js} +0 -0
  134. /package/src/build/{parallel-build-executor.js → runtime/parallel-build-executor.js} +0 -0
  135. /package/src/build/{serialized-build-executor.js → runtime/serialized-build-executor.js} +0 -0
@@ -0,0 +1,46 @@
1
+ import { getAppBrowserBuildPlugins, getAppServerBuildPlugins, getAppTranspileOptions } from "../build-adapter.js";
2
+ import { resolveBuildProfileOptions } from "./build-profile-options.js";
3
+ import { getJsxOwnershipPlugins } from "../browser/jsx-ownership-plugins.js";
4
+ import { getAppSourceTransforms } from "../../plugins/source-transform.js";
5
+ function resolveServerAppBuildPlugins(appConfig) {
6
+ return [...getAppServerBuildPlugins(appConfig), ...getJsxOwnershipPlugins(appConfig)];
7
+ }
8
+ function mergeCallerBuildPlugins(appPlugins, callerPlugins) {
9
+ if (!callerPlugins || callerPlugins.length === 0) {
10
+ return appPlugins;
11
+ }
12
+ const appByName = new Map(appPlugins.map((plugin) => [plugin.name, plugin]));
13
+ const uniqueCallerPlugins = callerPlugins.filter((plugin) => !appByName.has(plugin.name));
14
+ return [...uniqueCallerPlugins, ...appPlugins];
15
+ }
16
+ function createServerBuildRequest(appConfig, input) {
17
+ const profile = input.profile ?? "route-module";
18
+ const plugins = mergeCallerBuildPlugins(resolveServerAppBuildPlugins(appConfig), input.plugins);
19
+ const { plugins: _callerPlugins, profile: _profile, ...overrides } = input;
20
+ return {
21
+ ...resolveBuildProfileOptions(profile, appConfig, overrides),
22
+ ...overrides,
23
+ entrypoints: input.entrypoints,
24
+ ...plugins.length > 0 ? { plugins } : {}
25
+ };
26
+ }
27
+ function createBrowserBuildRequest(appConfig, input) {
28
+ const { profile, excludeAppBuildPlugins, plugins: callerPlugins, ...overrides } = input;
29
+ const appBrowserPlugins = getAppBrowserBuildPlugins(appConfig);
30
+ const filteredAppBrowserPlugins = excludeAppBuildPlugins && excludeAppBuildPlugins.length > 0 ? appBrowserPlugins.filter((plugin) => !excludeAppBuildPlugins.includes(plugin.name)) : appBrowserPlugins;
31
+ const plugins = mergeCallerBuildPlugins(filteredAppBrowserPlugins, callerPlugins);
32
+ return {
33
+ ...resolveBuildProfileOptions("browser-hmr", appConfig, overrides),
34
+ ...getAppTranspileOptions(appConfig, profile),
35
+ ...overrides,
36
+ entrypoints: input.entrypoints,
37
+ plugins,
38
+ sourceTransforms: getAppSourceTransforms(appConfig)
39
+ };
40
+ }
41
+ export {
42
+ createBrowserBuildRequest,
43
+ createServerBuildRequest,
44
+ mergeCallerBuildPlugins,
45
+ resolveServerAppBuildPlugins
46
+ };
@@ -1,5 +1,5 @@
1
- import type { EcoPagesAppConfig } from '../types/internal-types.js';
2
- import { type BuildExecutor } from './build-adapter.js';
1
+ import type { EcoPagesAppConfig } from '../../types/internal-types.js';
2
+ import { type BuildExecutor } from '../build-adapter.js';
3
3
  export type BuildProfile = 'server-entry' | 'route-module' | 'browser-hmr';
4
4
  export interface BuildRuntime {
5
5
  getProfile(profile: BuildProfile): BuildExecutor;
@@ -14,8 +14,12 @@ export declare function requireBuildRuntime(appConfig: EcoPagesAppConfig): Build
14
14
  * Installs profile-based build executors for one app instance.
15
15
  *
16
16
  * @remarks
17
- * All profiles use one-shot Rolldown. Server-entry stays serialized
18
- * single-flight; route-module and browser-HMR run in parallel.
17
+ * For Rolldown ownership, server-entry stays serialized single-flight while
18
+ * route-module and browser-HMR run in parallel one-shot builds. Vite-host
19
+ * profiles wrap a boundary marker and reject framework-owned build attempts.
20
+ * Profiles do not inject app plugins — assemble complete
21
+ * {@link BuildOptions} with {@link createServerBuildRequest} /
22
+ * {@link createBrowserBuildRequest} (or {@link BrowserBundleService}) first.
19
23
  */
20
24
  export declare function installBuildRuntime(appConfig: EcoPagesAppConfig): BuildRuntime;
21
25
  /**
@@ -1,10 +1,5 @@
1
1
  import { availableParallelism } from "node:os";
2
- import {
3
- getAppBrowserBuildPlugins,
4
- getAppBuildAdapter,
5
- getAppServerBuildPlugins,
6
- withBuildExecutorPlugins
7
- } from "./build-adapter.js";
2
+ import { getAppBuildAdapter } from "../build-adapter.js";
8
3
  import { ParallelBuildExecutor } from "./parallel-build-executor.js";
9
4
  import { DedupingBuildExecutor } from "./deduping-build-executor.js";
10
5
  import { SerializedBuildExecutor } from "./serialized-build-executor.js";
@@ -23,12 +18,10 @@ class AppBuildRuntime {
23
18
  hmrExecutor;
24
19
  constructor(appConfig) {
25
20
  const adapter = getAppBuildAdapter(appConfig);
26
- const serverPlugins = withBuildExecutorPlugins(adapter, () => getAppServerBuildPlugins(appConfig));
27
- const browserPlugins = withBuildExecutorPlugins(adapter, () => getAppBrowserBuildPlugins(appConfig));
28
21
  const limit = resolveParallelismLimit();
29
- this.serverEntryExecutor = new SerializedBuildExecutor(serverPlugins);
30
- this.routeModuleExecutor = new DedupingBuildExecutor(new ParallelBuildExecutor(serverPlugins, limit));
31
- this.hmrExecutor = new DedupingBuildExecutor(new ParallelBuildExecutor(browserPlugins, Math.min(3, limit)));
22
+ this.serverEntryExecutor = new SerializedBuildExecutor(adapter);
23
+ this.routeModuleExecutor = new DedupingBuildExecutor(new ParallelBuildExecutor(adapter, limit));
24
+ this.hmrExecutor = new DedupingBuildExecutor(new ParallelBuildExecutor(adapter, Math.min(3, limit)));
32
25
  }
33
26
  getProfile(profile) {
34
27
  switch (profile) {
@@ -40,6 +33,13 @@ class AppBuildRuntime {
40
33
  return this.hmrExecutor;
41
34
  }
42
35
  }
36
+ /**
37
+ * No-op for profile teardown.
38
+ *
39
+ * @remarks
40
+ * Rolldown-owned profiles use one-shot builds, and Vite-host profiles only
41
+ * wrap a boundary marker. Neither owns a long-lived engine to close.
42
+ */
43
43
  async dispose() {
44
44
  }
45
45
  }
@@ -1,12 +1,5 @@
1
- import type { BuildExecutor, BuildOptions, BuildResult } from './build-adapter.js';
2
- /**
3
- * Stable cache key for coalescing concurrent identical build requests.
4
- *
5
- * @remarks
6
- * Reuses the same material as route-module disk cache keys: normalized entrypoints,
7
- * outdir, splitting, JSX, and plugin setup fingerprints.
8
- */
9
- export declare function createBuildOptionsDedupeKey(options: BuildOptions): string;
1
+ import type { BuildExecutor, BuildOptions, BuildResult } from '../build-adapter.js';
2
+ export { createBuildRequestIdentity, createBuildOptionsDedupeKey } from './build-request-identity.js';
10
3
  /**
11
4
  * In-flight build coalescing wrapper.
12
5
  *
@@ -0,0 +1,34 @@
1
+ import { createBuildRequestIdentity } from "./build-request-identity.js";
2
+ import { createBuildRequestIdentity as createBuildRequestIdentity2, createBuildOptionsDedupeKey } from "./build-request-identity.js";
3
+ class DedupingBuildExecutor {
4
+ inner;
5
+ inFlight = /* @__PURE__ */ new Map();
6
+ constructor(inner) {
7
+ this.inner = inner;
8
+ }
9
+ build(options) {
10
+ const key = createBuildRequestIdentity(options);
11
+ const existing = this.inFlight.get(key);
12
+ if (existing) {
13
+ return existing;
14
+ }
15
+ const buildPromise = this.inner.build(options).finally(() => {
16
+ if (this.inFlight.get(key) === buildPromise) {
17
+ this.inFlight.delete(key);
18
+ }
19
+ });
20
+ this.inFlight.set(key, buildPromise);
21
+ return buildPromise;
22
+ }
23
+ unwrap() {
24
+ return this.inner;
25
+ }
26
+ getInFlightCountForTests() {
27
+ return this.inFlight.size;
28
+ }
29
+ }
30
+ export {
31
+ DedupingBuildExecutor,
32
+ createBuildOptionsDedupeKey,
33
+ createBuildRequestIdentity2 as createBuildRequestIdentity
34
+ };
@@ -1,4 +1,4 @@
1
- import type { BuildExecutor, BuildOptions, BuildResult } from './build-adapter.js';
1
+ import type { BuildExecutor, BuildOptions, BuildResult } from '../build-adapter.js';
2
2
  /**
3
3
  * Concurrency-limited build executor wrapper.
4
4
  *
@@ -1,4 +1,4 @@
1
- import type { BuildExecutor, BuildOptions, BuildResult } from './build-adapter.js';
1
+ import type { BuildExecutor, BuildOptions, BuildResult } from '../build-adapter.js';
2
2
  /**
3
3
  * FIFO-serialized build executor wrapper.
4
4
  *
@@ -3,7 +3,7 @@
3
3
  * @module
4
4
  */
5
5
  import { type BuildOwnership } from '../build/build-adapter.js';
6
- import type { EcoBuildPlugin } from '../build/build-types.js';
6
+ import type { EcoBuildPlugin } from '../build/contracts/build-types.js';
7
7
  import type { EcoPagesAppConfig, RobotsPreference } from '../types/internal-types.js';
8
8
  import type { AnyIntegrationPlugin } from '../plugins/integration-plugin.js';
9
9
  import type { Processor } from '../plugins/processor.js';
@@ -1,25 +1,72 @@
1
- export type StartupTracePhase = 'config-ready' | 'setupAppRuntimePlugins' | 'route-registry' | 'server-listen' | 'first-request-ssr';
1
+ export type StartupTracePhase = 'config-ready' | 'setupAppRuntimePlugins' | 'route-registry' | 'server-listen' | 'first-byte' | 'first-page-browser-graph' | 'first-request-ssr' | 'client-transfer' | 'client-hydration';
2
+ type PhaseRecord = {
3
+ durationMs: number;
4
+ wallMs: number;
5
+ };
6
+ export type StartupTraceReport = {
7
+ generatedAt: string;
8
+ runtime: string;
9
+ platform: string;
10
+ phases: Partial<Record<StartupTracePhase, PhaseRecord>>;
11
+ firstRequest?: {
12
+ path: string;
13
+ bundleCount: number;
14
+ clientBundleBytes: number;
15
+ graphBuildCount: number;
16
+ wallMs: number;
17
+ };
18
+ client?: {
19
+ requestCount: number;
20
+ transferredBytes: number;
21
+ hmrConnected: boolean;
22
+ hydrationReadyMs: number | null;
23
+ };
24
+ };
2
25
  declare class StartupTrace {
3
- private readonly phaseStarts;
26
+ private readonly phaseNesting;
4
27
  private readonly phases;
5
28
  private firstRequestPending;
6
29
  private firstRequestPath;
7
30
  private firstRequestBundleCount;
8
31
  private firstRequestClientBytes;
32
+ private firstRequestGraphBuildCount;
9
33
  private summaryEmitted;
34
+ private firstPageBrowserGraphEmitted;
35
+ private clientRequestCount;
36
+ private clientTransferredBytes;
37
+ private hmrConnected;
38
+ private hydrationReadyMs;
10
39
  isEnabled(): boolean;
11
40
  markPhaseStart(phase: StartupTracePhase): void;
12
41
  markPhaseEnd(phase: StartupTracePhase): void;
13
42
  markConfigReady(): void;
14
43
  markServerListening(): void;
15
44
  beginServerListen(): void;
45
+ markFirstByte(): void;
46
+ beginFirstByte(): void;
47
+ markFirstPageBrowserGraphReady(graphBuildCount?: number): void;
16
48
  recordBrowserBundle(outputs: Array<{
17
49
  path: string;
18
50
  }>): void;
51
+ recordClientTransfer(requestCount: number, transferredBytes: number): void;
52
+ beginClientTransfer(): void;
53
+ recordClientHydration(durationMs: number): void;
54
+ beginClientHydration(): void;
55
+ recordHmrConnected(): void;
19
56
  traceFirstRequest<T>(request: Request, handler: () => Promise<T>): Promise<T>;
20
57
  private emitFirstRequestSummary;
58
+ private writeJsonReport;
59
+ createReport(): StartupTraceReport;
21
60
  /** Resets mutable trace state for unit tests. */
22
61
  resetForTests(): void;
62
+ /**
63
+ * Resets only the first-request bundle counters.
64
+ *
65
+ * @remarks
66
+ * Used by tests that need to re-measure first-request browser bundle work
67
+ * without clearing earlier startup phase records.
68
+ */
69
+ resetFirstRequestCounters(): void;
23
70
  }
24
71
  export declare const startupTrace: StartupTrace;
25
72
  export {};
@@ -1,4 +1,6 @@
1
- import { statSync } from "node:fs";
1
+ import { mkdirSync, statSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { availableParallelism } from "node:os";
2
4
  import { fileSystem } from "@ecopages/file-system";
3
5
  const LOG_PREFIX = "[ecopages:startup-trace]";
4
6
  function isStartupTraceEnabled() {
@@ -7,18 +9,35 @@ function isStartupTraceEnabled() {
7
9
  function wallMsSinceProcessStart() {
8
10
  return Math.round(process.uptime() * 1e3);
9
11
  }
12
+ function resolveTraceBuildParallelism() {
13
+ const fromEnv = process.env.ECOPAGES_DEV_BUILD_PARALLELISM ?? process.env.ECOPAGES_DEV_HMR_PARALLELISM;
14
+ if (fromEnv) {
15
+ return fromEnv;
16
+ }
17
+ return String(Math.max(1, availableParallelism()));
18
+ }
10
19
  function writeTraceLine(message) {
11
20
  process.stderr.write(`${LOG_PREFIX} ${message}
12
21
  `);
13
22
  }
23
+ function resolveStartupTraceJsonPath() {
24
+ const configured = process.env.ECOPAGES_STARTUP_TRACE_JSON?.trim();
25
+ return configured && configured.length > 0 ? configured : void 0;
26
+ }
14
27
  class StartupTrace {
15
- phaseStarts = /* @__PURE__ */ new Map();
28
+ phaseNesting = /* @__PURE__ */ new Map();
16
29
  phases = /* @__PURE__ */ new Map();
17
30
  firstRequestPending = true;
18
31
  firstRequestPath;
19
32
  firstRequestBundleCount = 0;
20
33
  firstRequestClientBytes = 0;
34
+ firstRequestGraphBuildCount = 0;
21
35
  summaryEmitted = false;
36
+ firstPageBrowserGraphEmitted = false;
37
+ clientRequestCount = 0;
38
+ clientTransferredBytes = 0;
39
+ hmrConnected = false;
40
+ hydrationReadyMs = null;
22
41
  isEnabled() {
23
42
  return isStartupTraceEnabled();
24
43
  }
@@ -26,23 +45,34 @@ class StartupTrace {
26
45
  if (!isStartupTraceEnabled() || this.phases.has(phase)) {
27
46
  return;
28
47
  }
29
- this.phaseStarts.set(phase, performance.now());
48
+ const nesting = this.phaseNesting.get(phase);
49
+ if (!nesting) {
50
+ this.phaseNesting.set(phase, { outerStart: performance.now(), depth: 1 });
51
+ return;
52
+ }
53
+ nesting.depth += 1;
30
54
  }
31
55
  markPhaseEnd(phase) {
32
56
  if (!isStartupTraceEnabled() || this.phases.has(phase)) {
33
57
  return;
34
58
  }
35
- const startedAt = this.phaseStarts.get(phase);
36
- if (startedAt === void 0) {
59
+ const nesting = this.phaseNesting.get(phase);
60
+ if (!nesting) {
37
61
  return;
38
62
  }
39
- const durationMs = Math.round(performance.now() - startedAt);
63
+ nesting.depth -= 1;
64
+ if (nesting.depth > 0) {
65
+ return;
66
+ }
67
+ const durationMs = Math.round(performance.now() - nesting.outerStart);
40
68
  const record = {
41
69
  durationMs,
42
70
  wallMs: wallMsSinceProcessStart()
43
71
  };
44
72
  this.phases.set(phase, record);
73
+ this.phaseNesting.delete(phase);
45
74
  writeTraceLine(`phase=${phase} durationMs=${durationMs} wallMs=${record.wallMs}`);
75
+ this.writeJsonReport();
46
76
  }
47
77
  markConfigReady() {
48
78
  if (!isStartupTraceEnabled() || this.phases.has("config-ready")) {
@@ -53,7 +83,8 @@ class StartupTrace {
53
83
  wallMs: wallMsSinceProcessStart()
54
84
  };
55
85
  this.phases.set("config-ready", record);
56
- writeTraceLine(`phase=config-ready wallMs=${record.wallMs}`);
86
+ writeTraceLine(`phase=config-ready wallMs=${record.wallMs} buildParallelism=${resolveTraceBuildParallelism()}`);
87
+ this.writeJsonReport();
57
88
  }
58
89
  markServerListening() {
59
90
  this.markPhaseEnd("server-listen");
@@ -61,6 +92,25 @@ class StartupTrace {
61
92
  beginServerListen() {
62
93
  this.markPhaseStart("server-listen");
63
94
  }
95
+ markFirstByte() {
96
+ if (!isStartupTraceEnabled() || this.phases.has("first-byte")) {
97
+ return;
98
+ }
99
+ this.markPhaseEnd("first-byte");
100
+ }
101
+ beginFirstByte() {
102
+ this.markPhaseStart("first-byte");
103
+ }
104
+ markFirstPageBrowserGraphReady(graphBuildCount) {
105
+ if (!isStartupTraceEnabled() || this.firstPageBrowserGraphEmitted) {
106
+ return;
107
+ }
108
+ if (typeof graphBuildCount === "number") {
109
+ this.firstRequestGraphBuildCount = graphBuildCount;
110
+ }
111
+ this.firstPageBrowserGraphEmitted = true;
112
+ this.markPhaseEnd("first-page-browser-graph");
113
+ }
64
114
  recordBrowserBundle(outputs) {
65
115
  if (!isStartupTraceEnabled() || !this.firstRequestPending) {
66
116
  return;
@@ -75,11 +125,39 @@ class StartupTrace {
75
125
  }
76
126
  }
77
127
  }
128
+ recordClientTransfer(requestCount, transferredBytes) {
129
+ if (!isStartupTraceEnabled()) {
130
+ return;
131
+ }
132
+ this.clientRequestCount = requestCount;
133
+ this.clientTransferredBytes = transferredBytes;
134
+ this.markPhaseEnd("client-transfer");
135
+ }
136
+ beginClientTransfer() {
137
+ this.markPhaseStart("client-transfer");
138
+ }
139
+ recordClientHydration(durationMs) {
140
+ if (!isStartupTraceEnabled()) {
141
+ return;
142
+ }
143
+ this.hydrationReadyMs = durationMs;
144
+ this.markPhaseEnd("client-hydration");
145
+ }
146
+ beginClientHydration() {
147
+ this.markPhaseStart("client-hydration");
148
+ }
149
+ recordHmrConnected() {
150
+ if (!isStartupTraceEnabled()) {
151
+ return;
152
+ }
153
+ this.hmrConnected = true;
154
+ }
78
155
  async traceFirstRequest(request, handler) {
79
156
  if (!isStartupTraceEnabled() || !this.firstRequestPending) {
80
157
  return handler();
81
158
  }
82
159
  this.firstRequestPath = new URL(request.url).pathname;
160
+ this.markPhaseStart("first-page-browser-graph");
83
161
  this.markPhaseStart("first-request-ssr");
84
162
  try {
85
163
  return await handler();
@@ -87,6 +165,7 @@ class StartupTrace {
87
165
  this.markPhaseEnd("first-request-ssr");
88
166
  this.emitFirstRequestSummary();
89
167
  this.firstRequestPending = false;
168
+ this.writeJsonReport();
90
169
  }
91
170
  }
92
171
  emitFirstRequestSummary() {
@@ -100,19 +179,72 @@ class StartupTrace {
100
179
  `path=${this.firstRequestPath ?? "unknown"}`,
101
180
  `bundleCount=${this.firstRequestBundleCount}`,
102
181
  `clientBundleBytes=${this.firstRequestClientBytes}`,
182
+ `graphBuildCount=${this.firstRequestGraphBuildCount}`,
103
183
  `wallMs=${wallMsSinceProcessStart()}`
104
184
  ].join(" ")
105
185
  );
106
186
  }
187
+ writeJsonReport() {
188
+ const outputPath = resolveStartupTraceJsonPath();
189
+ if (!outputPath) {
190
+ return;
191
+ }
192
+ const report = this.createReport();
193
+ mkdirSync(path.dirname(outputPath), { recursive: true });
194
+ writeFileSync(outputPath, JSON.stringify(report, null, 2), "utf-8");
195
+ }
196
+ createReport() {
197
+ const phases = {};
198
+ for (const [phase, record] of this.phases) {
199
+ phases[phase] = record;
200
+ }
201
+ return {
202
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
203
+ runtime: typeof Bun !== "undefined" ? `bun ${Bun.version}` : `node ${process.version}`,
204
+ platform: process.platform,
205
+ phases,
206
+ firstRequest: this.firstRequestPath ? {
207
+ path: this.firstRequestPath,
208
+ bundleCount: this.firstRequestBundleCount,
209
+ clientBundleBytes: this.firstRequestClientBytes,
210
+ graphBuildCount: this.firstRequestGraphBuildCount,
211
+ wallMs: wallMsSinceProcessStart()
212
+ } : void 0,
213
+ client: this.clientRequestCount > 0 || this.hydrationReadyMs !== null ? {
214
+ requestCount: this.clientRequestCount,
215
+ transferredBytes: this.clientTransferredBytes,
216
+ hmrConnected: this.hmrConnected,
217
+ hydrationReadyMs: this.hydrationReadyMs
218
+ } : void 0
219
+ };
220
+ }
107
221
  /** Resets mutable trace state for unit tests. */
108
222
  resetForTests() {
109
- this.phaseStarts.clear();
223
+ this.phaseNesting.clear();
110
224
  this.phases.clear();
111
225
  this.firstRequestPending = true;
112
226
  this.firstRequestPath = void 0;
113
227
  this.firstRequestBundleCount = 0;
114
228
  this.firstRequestClientBytes = 0;
229
+ this.firstRequestGraphBuildCount = 0;
115
230
  this.summaryEmitted = false;
231
+ this.firstPageBrowserGraphEmitted = false;
232
+ this.clientRequestCount = 0;
233
+ this.clientTransferredBytes = 0;
234
+ this.hmrConnected = false;
235
+ this.hydrationReadyMs = null;
236
+ }
237
+ /**
238
+ * Resets only the first-request bundle counters.
239
+ *
240
+ * @remarks
241
+ * Used by tests that need to re-measure first-request browser bundle work
242
+ * without clearing earlier startup phase records.
243
+ */
244
+ resetFirstRequestCounters() {
245
+ this.firstRequestBundleCount = 0;
246
+ this.firstRequestClientBytes = 0;
247
+ this.firstRequestGraphBuildCount = 0;
116
248
  }
117
249
  }
118
250
  const startupTrace = new StartupTrace();
@@ -68,7 +68,7 @@ import { getEcoNavigationRuntime } from "../../router/client/navigation-coordina
68
68
  return;
69
69
  }
70
70
  await import(url);
71
- if (await navigationRuntime.reloadCurrentPage({ clearCache: false })) {
71
+ if (await navigationRuntime.reloadCurrentPage({ clearCache: false, moduleUrl: url })) {
72
72
  }
73
73
  } catch (e) {
74
74
  console.error("[ecopages] Failed to apply HMR update:", e);
@@ -0,0 +1,10 @@
1
+ import type { EcoPagesAppConfig } from '../types/internal-types.js';
2
+ import { type AffectedGraphIdentity } from '../route-renderer/orchestration/page-browser-graph-session.js';
3
+ export type HmrFileChangePreparation = {
4
+ affectedGraphIdentities: AffectedGraphIdentity[];
5
+ invalidatedGraphCount: number;
6
+ };
7
+ /**
8
+ * Invalidates affected Page Browser Graph records before HMR strategy dispatch.
9
+ */
10
+ export declare function prepareHmrFileChange(appConfig: EcoPagesAppConfig, filePath: string): HmrFileChangePreparation;
@@ -0,0 +1,17 @@
1
+ import path from "node:path";
2
+ import {
3
+ getAffectedPageBrowserGraphIdentities,
4
+ invalidatePageBrowserGraphSession
5
+ } from "../route-renderer/orchestration/page-browser-graph-session.js";
6
+ function prepareHmrFileChange(appConfig, filePath) {
7
+ const resolvedFilePath = path.resolve(filePath);
8
+ const affectedGraphIdentities = getAffectedPageBrowserGraphIdentities(appConfig, resolvedFilePath);
9
+ const invalidatedGraphCount = invalidatePageBrowserGraphSession(appConfig, resolvedFilePath);
10
+ return {
11
+ affectedGraphIdentities,
12
+ invalidatedGraphCount
13
+ };
14
+ }
15
+ export {
16
+ prepareHmrFileChange
17
+ };
@@ -1,4 +1,4 @@
1
- import type { EcoBuildPlugin } from '../build/build-types.js';
1
+ import type { EcoBuildPlugin } from '../build/contracts/build-types.js';
2
2
  import { AliasResolverCache } from './alias-resolver-cache.js';
3
3
  export declare function createAliasResolverPlugin(projectRoot: string, options?: {
4
4
  cache?: AliasResolverCache;
@@ -25,7 +25,7 @@
25
25
  *
26
26
  * @module eco-component-meta-plugin
27
27
  */
28
- import type { EcoBuildPlugin } from '../build/build-types.js';
28
+ import type { EcoBuildPlugin } from '../build/contracts/build-types.js';
29
29
  import type { EcoPagesAppConfig } from '../types/internal-types.js';
30
30
  import { type EcoSourceTransform, type EcoViteCompatiblePlugin } from './source-transform.js';
31
31
  /**
@@ -1,4 +1,4 @@
1
- import type { EcoBuildPlugin } from '../build/build-types.js';
1
+ import type { EcoBuildPlugin } from '../build/contracts/build-types.js';
2
2
  /**
3
3
  * Options for the shared foreign-JSX override build plugin.
4
4
  */
@@ -1,5 +1,5 @@
1
- import type { EcoBuildPlugin } from '../build/build-types.js';
2
- import { type BrowserRuntimeManifest } from '../build/browser-runtime-manifest.js';
1
+ import type { EcoBuildPlugin } from '../build/contracts/build-types.js';
2
+ import { type BrowserRuntimeManifest } from '../build/browser/browser-runtime-manifest.js';
3
3
  import type { EcoPagesAppConfig, IHmrManager } from '../types/internal-types.js';
4
4
  import type { HmrStrategy } from '../hmr/hmr-strategy.js';
5
5
  import type { EcoPagesElement } from '../types/public-types.js';
@@ -9,7 +9,7 @@ import type { AssetDefinition, ProcessedAsset } from '../services/assets/asset-p
9
9
  import type { StaticExportContext } from '../static-site-generator/static-export-context.js';
10
10
  import type { RuntimeCapabilityDeclaration } from './runtime-capability.js';
11
11
  export type { RuntimeCapabilityDeclaration, RuntimeCapabilityTag } from './runtime-capability.js';
12
- export type { EcoBuildLoader, EcoBuildOnLoadArgs, EcoBuildOnLoadResult, EcoBuildOnResolveArgs, EcoBuildOnResolveResult, EcoBuildPlugin, EcoBuildPluginBuilder, } from '../build/build-types.js';
12
+ export type { EcoBuildLoader, EcoBuildOnLoadArgs, EcoBuildOnLoadResult, EcoBuildOnResolveArgs, EcoBuildOnResolveResult, EcoBuildPlugin, EcoBuildPluginBuilder, } from '../build/contracts/build-types.js';
13
13
  export type { PageBrowserGraphContribution, PageBrowserGraphContributionContext } from '../types/public-types.js';
14
14
  export type { StaticExportContext } from '../static-site-generator/static-export-context.js';
15
15
  export type { HtmlDocumentContribution, HtmlDocumentContributionContext, } from '../route-renderer/orchestration/integration-renderer.js';
@@ -82,8 +82,11 @@ type RendererClass<C> = new (options: {
82
82
  *
83
83
  * Core owns lifecycle ordering. Integrations declare contributions through the
84
84
  * hooks on this class, while `ConfigBuilder.build()` and app startup decide when
85
- * those hooks run. For page-browser and document shaping, integrations should
86
- * prefer the contribution contracts re-exported from this module:
85
+ * those hooks run. Build plugins map to {@link AppBuildManifest} buckets:
86
+ * `plugins` `runtimePlugins`, `browserBuildPlugins` `browserBundlePlugins`,
87
+ * `browserRuntimeManifest` → client import rewrite map. For page-browser and
88
+ * document shaping, integrations should prefer the contribution contracts
89
+ * re-exported from this module:
87
90
  * `PageBrowserGraphContribution` / `PageBrowserGraphContributionContext` and
88
91
  * `HtmlDocumentContribution` / `HtmlDocumentContributionContext`.
89
92
  */
@@ -100,6 +103,14 @@ export declare abstract class IntegrationPlugin<C = EcoPagesElement> {
100
103
  protected assetProcessingService?: AssetProcessingService;
101
104
  protected hmrManager?: IHmrManager;
102
105
  runtimeOrigin: string;
106
+ /**
107
+ * Returns build plugins shared by server-oriented and browser-oriented builds.
108
+ *
109
+ * @remarks
110
+ * Collected into {@link AppBuildManifest.runtimePlugins} during config finalization.
111
+ * MDX loaders, virtual-module resolvers, and other transforms that must run during
112
+ * route-module transpile belong here—not in {@link browserBuildPlugins}.
113
+ */
103
114
  get plugins(): EcoBuildPlugin[];
104
115
  /**
105
116
  * Returns build plugins that should only apply to browser-oriented bundles.
@@ -1,4 +1,6 @@
1
- import { createBrowserRuntimeManifest } from "../build/browser-runtime-manifest.js";
1
+ import {
2
+ createBrowserRuntimeManifest
3
+ } from "../build/browser/browser-runtime-manifest.js";
2
4
  import { AssetProcessingService } from "../services/assets/asset-processing-service/asset-processing.service.js";
3
5
  import { deepMerge } from "../utils/deep-merge.js";
4
6
  import { invariant } from "../utils/invariant.js";
@@ -23,6 +25,14 @@ class IntegrationPlugin {
23
25
  appConfig;
24
26
  assetProcessingService;
25
27
  hmrManager;
28
+ /**
29
+ * Returns build plugins shared by server-oriented and browser-oriented builds.
30
+ *
31
+ * @remarks
32
+ * Collected into {@link AppBuildManifest.runtimePlugins} during config finalization.
33
+ * MDX loaders, virtual-module resolvers, and other transforms that must run during
34
+ * route-module transpile belong here—not in {@link browserBuildPlugins}.
35
+ */
26
36
  get plugins() {
27
37
  return [];
28
38
  }