@ecopages/core 0.2.0-beta.0 → 0.2.0-beta.10

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 (218) hide show
  1. package/package.json +117 -3
  2. package/src/adapters/abstract/application-adapter.d.ts +73 -1
  3. package/src/adapters/abstract/application-adapter.js +89 -0
  4. package/src/adapters/abstract/server-adapter.d.ts +3 -0
  5. package/src/adapters/abstract/ws-pattern-matcher.d.ts +60 -0
  6. package/src/adapters/abstract/ws-pattern-matcher.js +61 -0
  7. package/src/adapters/bun/client-bridge.d.ts +1 -0
  8. package/src/adapters/bun/client-bridge.js +3 -0
  9. package/src/adapters/bun/create-app.d.ts +5 -3
  10. package/src/adapters/bun/create-app.js +44 -39
  11. package/src/adapters/bun/hmr-manager.d.ts +2 -1
  12. package/src/adapters/bun/hmr-manager.js +2 -2
  13. package/src/adapters/bun/server-adapter.d.ts +81 -20
  14. package/src/adapters/bun/server-adapter.js +220 -78
  15. package/src/adapters/bun/static-preview-host.js +25 -20
  16. package/src/adapters/create-app.d.ts +13 -0
  17. package/src/adapters/create-app.js +10 -1
  18. package/src/adapters/node/create-app.d.ts +4 -0
  19. package/src/adapters/node/create-app.js +38 -9
  20. package/src/adapters/node/node-client-bridge.d.ts +1 -0
  21. package/src/adapters/node/node-client-bridge.js +3 -0
  22. package/src/adapters/node/node-hmr-manager.d.ts +2 -1
  23. package/src/adapters/node/node-hmr-manager.js +2 -2
  24. package/src/adapters/node/server-adapter-dependencies.js +2 -0
  25. package/src/adapters/node/server-adapter.d.ts +55 -19
  26. package/src/adapters/node/server-adapter.js +140 -132
  27. package/src/adapters/node/static-content-server.d.ts +1 -0
  28. package/src/adapters/node/static-content-server.js +10 -2
  29. package/src/adapters/node/static-preview-host.js +27 -12
  30. package/src/adapters/shared/bun-user-websocket-lifecycle.d.ts +22 -0
  31. package/src/adapters/shared/bun-user-websocket-lifecycle.js +96 -0
  32. package/src/adapters/shared/copy-runtime-public-dir.d.ts +6 -0
  33. package/src/adapters/shared/copy-runtime-public-dir.js +24 -0
  34. package/src/adapters/shared/hmr-html-response.d.ts +1 -1
  35. package/src/adapters/shared/hmr-html-response.js +4 -1
  36. package/src/adapters/shared/node-http-websocket-upgrades.d.ts +21 -0
  37. package/src/adapters/shared/node-http-websocket-upgrades.js +100 -0
  38. package/src/adapters/shared/port-manager.d.ts +116 -0
  39. package/src/adapters/shared/port-manager.js +196 -0
  40. package/src/adapters/shared/runtime-app-bootstrap.d.ts +2 -1
  41. package/src/adapters/shared/runtime-app-bootstrap.js +3 -6
  42. package/src/adapters/shared/runtime-server-lifecycle.d.ts +38 -0
  43. package/src/adapters/shared/runtime-server-lifecycle.js +39 -0
  44. package/src/adapters/shared/server-adapter.d.ts +2 -1
  45. package/src/adapters/shared/server-adapter.js +12 -4
  46. package/src/adapters/shared/server-route-handler.d.ts +4 -1
  47. package/src/adapters/shared/server-route-handler.js +5 -2
  48. package/src/adapters/shared/server-static-builder.d.ts +31 -23
  49. package/src/adapters/shared/server-static-builder.js +126 -43
  50. package/src/adapters/shared/shared-hmr-manager.d.ts +12 -0
  51. package/src/adapters/shared/shared-hmr-manager.js +64 -7
  52. package/src/adapters/shared/static-preview-host.d.ts +5 -0
  53. package/src/adapters/shared/websocket-lifecycle.d.ts +24 -0
  54. package/src/adapters/shared/websocket-lifecycle.js +55 -0
  55. package/src/build/README.md +77 -14
  56. package/src/build/app-build-manifest-runtime.d.ts +10 -0
  57. package/src/build/app-build-manifest-runtime.js +90 -0
  58. package/src/build/build-adapter.d.ts +17 -319
  59. package/src/build/build-adapter.js +28 -94
  60. package/src/build/build-contracts.d.ts +97 -0
  61. package/src/build/build-contracts.js +0 -0
  62. package/src/build/build-input-fingerprint.d.ts +28 -0
  63. package/src/build/build-input-fingerprint.js +31 -0
  64. package/src/build/build-profile-options.d.ts +7 -0
  65. package/src/build/build-profile-options.js +37 -0
  66. package/src/build/build-runtime.d.ts +24 -0
  67. package/src/build/build-runtime.js +65 -0
  68. package/src/build/deduping-build-executor.d.ts +28 -0
  69. package/src/build/deduping-build-executor.js +56 -0
  70. package/src/build/jsx-ownership-plugins.d.ts +24 -0
  71. package/src/build/jsx-ownership-plugins.js +41 -0
  72. package/src/build/pages-unified-graph-build.d.ts +31 -0
  73. package/src/build/pages-unified-graph-build.js +206 -0
  74. package/src/build/parallel-build-executor.d.ts +21 -0
  75. package/src/build/parallel-build-executor.js +52 -0
  76. package/src/build/production-build-cache.d.ts +20 -0
  77. package/src/build/production-build-cache.js +63 -0
  78. package/src/build/rolldown-adapter-helpers.d.ts +3 -6
  79. package/src/build/rolldown-adapter-helpers.js +71 -6
  80. package/src/build/rolldown-build-adapter.d.ts +2 -6
  81. package/src/build/rolldown-build-adapter.js +3 -3
  82. package/src/build/rolldown-build-invocation-metrics.d.ts +12 -0
  83. package/src/build/rolldown-build-invocation-metrics.js +43 -0
  84. package/src/build/rolldown-plugin-bridge.d.ts +15 -1
  85. package/src/build/rolldown-plugin-bridge.js +20 -5
  86. package/src/build/rolldown-source-transform-pass.d.ts +15 -0
  87. package/src/build/rolldown-source-transform-pass.js +58 -0
  88. package/src/build/runtime-build-executor.d.ts +8 -15
  89. package/src/build/runtime-build-executor.js +12 -15
  90. package/src/build/runtime-build-output-normalizer.d.ts +7 -0
  91. package/src/build/runtime-build-output-normalizer.js +94 -2
  92. package/src/build/server-entry-build-cache.d.ts +44 -0
  93. package/src/build/server-entry-build-cache.js +161 -0
  94. package/src/config/config-builder.d.ts +12 -7
  95. package/src/config/config-builder.js +12 -9
  96. package/src/dev/client-bridge-registry.d.ts +5 -0
  97. package/src/dev/client-bridge-registry.js +15 -0
  98. package/src/dev/dev-client-ownership.d.ts +7 -0
  99. package/src/dev/dev-client-ownership.js +6 -0
  100. package/src/dev/host-runtime.d.ts +3 -0
  101. package/src/dev/host-runtime.js +7 -0
  102. package/src/hmr/client/hmr-runtime.js +5 -2
  103. package/src/hmr/strategies/server-rendered-template-hmr-strategy.d.ts +17 -0
  104. package/src/hmr/strategies/server-rendered-template-hmr-strategy.js +29 -0
  105. package/src/integrations/ghtml/ghtml-renderer.d.ts +2 -6
  106. package/src/integrations/ghtml/ghtml-renderer.js +2 -51
  107. package/src/integrations/ghtml/ghtml.plugin.d.ts +3 -16
  108. package/src/integrations/ghtml/ghtml.plugin.js +7 -14
  109. package/src/plugins/define-integration.d.ts +26 -0
  110. package/src/plugins/define-integration.js +19 -0
  111. package/src/plugins/eco-component-meta-plugin.js +5 -8
  112. package/src/plugins/foreign-jsx-override-plugin.js +4 -6
  113. package/src/plugins/integration-plugin.d.ts +19 -9
  114. package/src/plugins/integration-plugin.js +7 -4
  115. package/src/plugins/jsx-import-source.utils.d.ts +8 -0
  116. package/src/plugins/jsx-import-source.utils.js +26 -0
  117. package/src/plugins/processor.d.ts +19 -4
  118. package/src/plugins/processor.js +22 -4
  119. package/src/plugins/source-transform.d.ts +31 -0
  120. package/src/plugins/source-transform.js +23 -1
  121. package/src/route-renderer/GRAPH.md +8 -8
  122. package/src/route-renderer/README.md +30 -28
  123. package/src/route-renderer/orchestration/component-graph-collectors.d.ts +10 -0
  124. package/src/route-renderer/orchestration/component-graph-collectors.js +143 -0
  125. package/src/route-renderer/orchestration/component-graph.d.ts +36 -0
  126. package/src/route-renderer/orchestration/component-graph.js +90 -0
  127. package/src/route-renderer/orchestration/component-render-context.d.ts +3 -10
  128. package/src/route-renderer/orchestration/component-render-context.js +3 -1
  129. package/src/route-renderer/orchestration/declared-ownership-graph.d.ts +1 -18
  130. package/src/route-renderer/orchestration/declared-ownership-graph.js +5 -31
  131. package/src/route-renderer/orchestration/document-shell-render.service.d.ts +54 -0
  132. package/src/route-renderer/orchestration/document-shell-render.service.js +54 -0
  133. package/src/route-renderer/orchestration/foreign-subtree-execution.service.d.ts +59 -29
  134. package/src/route-renderer/orchestration/foreign-subtree-execution.service.js +164 -35
  135. package/src/route-renderer/orchestration/global-injector-assets.service.d.ts +7 -0
  136. package/src/route-renderer/orchestration/global-injector-assets.service.js +32 -0
  137. package/src/route-renderer/orchestration/integration-renderer.d.ts +18 -54
  138. package/src/route-renderer/orchestration/integration-renderer.js +108 -194
  139. package/src/route-renderer/orchestration/integration-route-render-adapter.d.ts +38 -0
  140. package/src/route-renderer/orchestration/integration-route-render-adapter.js +27 -0
  141. package/src/route-renderer/orchestration/ownership-validation.service.d.ts +4 -0
  142. package/src/route-renderer/orchestration/ownership-validation.service.js +11 -1
  143. package/src/route-renderer/orchestration/page-browser-graph-contribution.loader.d.ts +9 -0
  144. package/src/route-renderer/orchestration/page-browser-graph-contribution.loader.js +7 -0
  145. package/src/route-renderer/orchestration/page-browser-graph.service.d.ts +25 -0
  146. package/src/route-renderer/orchestration/page-browser-graph.service.js +174 -0
  147. package/src/route-renderer/orchestration/render-output.utils.d.ts +2 -0
  148. package/src/route-renderer/orchestration/render-output.utils.js +4 -0
  149. package/src/route-renderer/orchestration/route-html-finalization.service.d.ts +17 -0
  150. package/src/route-renderer/orchestration/route-html-finalization.service.js +26 -0
  151. package/src/route-renderer/orchestration/route-prepared-options.builder.d.ts +24 -0
  152. package/src/route-renderer/orchestration/route-prepared-options.builder.js +52 -0
  153. package/src/route-renderer/orchestration/route-prepared-options.utils.d.ts +4 -0
  154. package/src/route-renderer/orchestration/route-prepared-options.utils.js +33 -0
  155. package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +5 -38
  156. package/src/route-renderer/orchestration/route-render-orchestrator.js +51 -459
  157. package/src/route-renderer/orchestration/string-markup-renderer.d.ts +14 -0
  158. package/src/route-renderer/orchestration/string-markup-renderer.js +54 -0
  159. package/src/router/client/navigation-coordinator.d.ts +1 -1
  160. package/src/router/client/navigation-coordinator.js +12 -2
  161. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.d.ts +1 -0
  162. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.js +1 -0
  163. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.d.ts +4 -2
  164. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.js +27 -7
  165. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.js +32 -14
  166. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +17 -3
  167. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.d.ts +0 -3
  168. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.js +2 -31
  169. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.d.ts +0 -3
  170. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.js +2 -30
  171. package/src/services/assets/asset-processing-service/processors/stylesheet/stylesheet-processor-pipeline.d.ts +2 -0
  172. package/src/services/assets/asset-processing-service/processors/stylesheet/stylesheet-processor-pipeline.js +43 -0
  173. package/src/services/assets/browser-bundle.service.d.ts +3 -1
  174. package/src/services/assets/browser-bundle.service.js +17 -5
  175. package/src/services/html/html-transformer.service.d.ts +1 -1
  176. package/src/services/html/html-transformer.service.js +4 -4
  177. package/src/services/invalidation/development-invalidation.service.d.ts +10 -1
  178. package/src/services/invalidation/development-invalidation.service.js +31 -2
  179. package/src/services/module-loading/README.md +49 -0
  180. package/src/services/module-loading/app-server-module-transpiler.service.js +5 -17
  181. package/src/services/module-loading/page-module-import.service.d.ts +4 -1
  182. package/src/services/module-loading/page-module-import.service.js +61 -41
  183. package/src/services/module-loading/route-module-build-cache-registry.d.ts +24 -0
  184. package/src/services/module-loading/route-module-build-cache-registry.js +37 -0
  185. package/src/services/module-loading/route-module-build-cache.d.ts +4 -0
  186. package/src/services/module-loading/route-module-build-cache.js +38 -0
  187. package/src/services/module-loading/route-module-build-cache.store.d.ts +57 -0
  188. package/src/services/module-loading/route-module-build-cache.store.js +207 -0
  189. package/src/services/module-loading/route-module-build-manifest.d.ts +61 -0
  190. package/src/services/module-loading/route-module-build-manifest.js +116 -0
  191. package/src/services/module-loading/route-module-dependency-hasher.d.ts +81 -0
  192. package/src/services/module-loading/route-module-dependency-hasher.js +121 -0
  193. package/src/services/module-loading/server-module-transpiler.service.js +1 -1
  194. package/src/static-site-generator/README.md +33 -0
  195. package/src/static-site-generator/static-build-invalidation.d.ts +10 -0
  196. package/src/static-site-generator/static-build-invalidation.js +59 -0
  197. package/src/static-site-generator/static-export-context.d.ts +22 -0
  198. package/src/static-site-generator/static-export-context.js +0 -0
  199. package/src/static-site-generator/static-site-generator.d.ts +17 -20
  200. package/src/static-site-generator/static-site-generator.js +188 -85
  201. package/src/types/internal-types.d.ts +12 -9
  202. package/src/types/public-types.d.ts +87 -22
  203. package/src/utils/parse-cli-args.d.ts +1 -0
  204. package/src/utils/parse-cli-args.js +3 -0
  205. package/src/utils/resolve-entry-file.d.ts +72 -0
  206. package/src/utils/resolve-entry-file.js +41 -0
  207. package/src/watchers/project-watcher.d.ts +14 -2
  208. package/src/watchers/project-watcher.js +50 -20
  209. package/src/build/rolldown-dev-build-adapter.d.ts +0 -82
  210. package/src/build/rolldown-dev-build-adapter.js +0 -166
  211. package/src/hmr/hmr.postcss.test.e2e.d.ts +0 -1
  212. package/src/hmr/hmr.postcss.test.e2e.js +0 -31
  213. package/src/hmr/hmr.test.e2e.d.ts +0 -1
  214. package/src/hmr/hmr.test.e2e.js +0 -43
  215. package/src/route-renderer/orchestration/ownership-planning.service.d.ts +0 -24
  216. package/src/route-renderer/orchestration/ownership-planning.service.js +0 -63
  217. package/src/route-renderer/orchestration/queued-foreign-subtree-resolution.service.d.ts +0 -91
  218. package/src/route-renderer/orchestration/queued-foreign-subtree-resolution.service.js +0 -170
@@ -0,0 +1,39 @@
1
+ import path from "node:path";
2
+ import { fileSystem } from "@ecopages/file-system";
3
+ import { RESOLVED_ASSETS_DIR } from "../../config/constants.js";
4
+ import { disposeAppBuildRuntime } from "../../build/build-runtime.js";
5
+ import { getAppBrowserBuildPlugins } from "../../build/build-adapter.js";
6
+ import { copyRuntimePublicDirIfChanged } from "./copy-runtime-public-dir.js";
7
+ import { injectHmrRuntimeIntoHtmlResponse, isHtmlResponse, shouldInjectHmrHtmlResponse } from "./hmr-html-response.js";
8
+ function prepareRuntimePublicDir(appConfig) {
9
+ const srcPublicDir = path.join(appConfig.rootDir, appConfig.srcDir, appConfig.publicDir);
10
+ if (fileSystem.exists(srcPublicDir)) {
11
+ copyRuntimePublicDirIfChanged(srcPublicDir, path.join(appConfig.rootDir, appConfig.distDir));
12
+ }
13
+ fileSystem.ensureDir(path.join(appConfig.absolutePaths.distDir, RESOLVED_ASSETS_DIR));
14
+ }
15
+ function wireIntegrationHmrManagers(appConfig, hmrManager) {
16
+ hmrManager.setPlugins(getAppBrowserBuildPlugins(appConfig));
17
+ for (const integration of appConfig.integrations) {
18
+ integration.setHmrManager(hmrManager);
19
+ }
20
+ }
21
+ async function maybeInjectAdapterHmrHtmlResponse(response, options) {
22
+ if (shouldInjectHmrHtmlResponse(options.watch, options.hmrManager, options.hostOwnsDevClient) && isHtmlResponse(response)) {
23
+ return injectHmrRuntimeIntoHtmlResponse(response);
24
+ }
25
+ return response;
26
+ }
27
+ async function disposeDevResources(options) {
28
+ await options.projectWatcher?.close();
29
+ await disposeAppBuildRuntime(options.appConfig);
30
+ options.hmrManager?.stop();
31
+ options.bridge?.destroy();
32
+ await options.previewHost.stop();
33
+ }
34
+ export {
35
+ disposeDevResources,
36
+ maybeInjectAdapterHmrHtmlResponse,
37
+ prepareRuntimePublicDir,
38
+ wireIntegrationHmrManagers
39
+ };
@@ -21,7 +21,8 @@ export declare abstract class SharedServerAdapter<TOptions extends ServerAdapter
21
21
  protected routeHandler: ServerRouteHandler;
22
22
  protected staticSiteGenerator: StaticSiteGenerator;
23
23
  protected staticBuilder: ServerStaticBuilder;
24
- protected readonly schemaValidator: SchemaValidationService;
24
+ protected schemaValidator: SchemaValidationService;
25
+ protected hostOwnsDevClient: boolean;
25
26
  protected initializeSharedRouteHandling(options: {
26
27
  staticRoutes: StaticRoute[];
27
28
  hmrManager?: any;
@@ -27,6 +27,7 @@ class SharedServerAdapter extends AbstractServerAdapter {
27
27
  staticSiteGenerator;
28
28
  staticBuilder;
29
29
  schemaValidator = new SchemaValidationService();
30
+ hostOwnsDevClient = false;
30
31
  async initializeSharedRouteHandling(options) {
31
32
  await this.initSharedRouter();
32
33
  this.configureSharedResponseHandlers(options.staticRoutes, options.hmrManager);
@@ -76,7 +77,7 @@ class SharedServerAdapter extends AbstractServerAdapter {
76
77
  const module = await serverModuleTranspiler.importModule({
77
78
  filePath,
78
79
  outdir: path.join(resolveInternalExecutionDir(this.appConfig), ".server-route-modules"),
79
- externalPackages: false,
80
+ externalPackages: true,
80
81
  transpileErrorMessage: (details) => `Error transpiling route module: ${details}`,
81
82
  noOutputMessage: (targetFilePath) => `No transpiled output generated for route module: ${targetFilePath}`
82
83
  });
@@ -115,7 +116,8 @@ class SharedServerAdapter extends AbstractServerAdapter {
115
116
  fileSystemResponseMatcher: this.fileSystemResponseMatcher,
116
117
  explicitStaticRouteMatcher,
117
118
  watch: !!this.options?.watch,
118
- hmrManager
119
+ hmrManager,
120
+ hostOwnsDevClient: this.hostOwnsDevClient
119
121
  });
120
122
  }
121
123
  createSharedResponseHandlerDependencies(staticRoutes) {
@@ -380,7 +382,10 @@ class SharedServerAdapter extends AbstractServerAdapter {
380
382
  const runtimePath = context.hmrManager.getRuntimePath();
381
383
  if (fileSystem.exists(runtimePath)) {
382
384
  return new Response(fileSystem.readFileAsBuffer(runtimePath), {
383
- headers: { "Content-Type": "application/javascript" }
385
+ headers: {
386
+ "Content-Type": "application/javascript",
387
+ "Cache-Control": "no-store, must-revalidate"
388
+ }
384
389
  });
385
390
  }
386
391
  }
@@ -389,7 +394,10 @@ class SharedServerAdapter extends AbstractServerAdapter {
389
394
  const assetPath = path.join(context.hmrManager.getDistDir(), relativePath);
390
395
  if (fileSystem.exists(assetPath)) {
391
396
  return new Response(fileSystem.readFileAsBuffer(assetPath), {
392
- headers: { "Content-Type": "application/javascript" }
397
+ headers: {
398
+ "Content-Type": "application/javascript",
399
+ "Cache-Control": "no-store, must-revalidate"
400
+ }
393
401
  });
394
402
  }
395
403
  }
@@ -16,6 +16,8 @@ export interface ServerRouteHandlerParams {
16
16
  watch?: boolean;
17
17
  /** HMR manager for broadcasting hot module reload events in development. */
18
18
  hmrManager?: IHmrManager;
19
+ /** When true, the host dev server owns browser dev-client bootstrap. */
20
+ hostOwnsDevClient?: boolean;
19
21
  }
20
22
  /**
21
23
  * Handles HTTP requests and routing for the server.
@@ -33,12 +35,13 @@ export declare class ServerRouteHandler {
33
35
  private readonly explicitStaticRouteMatcher?;
34
36
  private readonly watch;
35
37
  private readonly hmrManager?;
38
+ private readonly hostOwnsDevClient;
36
39
  /**
37
40
  * Creates a new ServerRouteHandler instance.
38
41
  *
39
42
  * @param params - Configuration parameters
40
43
  */
41
- constructor({ router, fileSystemResponseMatcher, explicitStaticRouteMatcher, watch, hmrManager, }: ServerRouteHandlerParams);
44
+ constructor({ router, fileSystemResponseMatcher, explicitStaticRouteMatcher, watch, hmrManager, hostOwnsDevClient, }: ServerRouteHandlerParams);
42
45
  /**
43
46
  * Determines if HMR script should be injected.
44
47
  *
@@ -7,6 +7,7 @@ class ServerRouteHandler {
7
7
  explicitStaticRouteMatcher;
8
8
  watch;
9
9
  hmrManager;
10
+ hostOwnsDevClient;
10
11
  /**
11
12
  * Creates a new ServerRouteHandler instance.
12
13
  *
@@ -17,13 +18,15 @@ class ServerRouteHandler {
17
18
  fileSystemResponseMatcher,
18
19
  explicitStaticRouteMatcher,
19
20
  watch = false,
20
- hmrManager
21
+ hmrManager,
22
+ hostOwnsDevClient = false
21
23
  }) {
22
24
  this.router = router;
23
25
  this.fileSystemResponseMatcher = fileSystemResponseMatcher;
24
26
  this.explicitStaticRouteMatcher = explicitStaticRouteMatcher;
25
27
  this.watch = watch;
26
28
  this.hmrManager = hmrManager;
29
+ this.hostOwnsDevClient = hostOwnsDevClient;
27
30
  }
28
31
  /**
29
32
  * Determines if HMR script should be injected.
@@ -31,7 +34,7 @@ class ServerRouteHandler {
31
34
  * @returns true if in watch mode and HMR manager is enabled
32
35
  */
33
36
  shouldInjectHmrScript() {
34
- return shouldInjectHmrHtmlResponse(this.watch, this.hmrManager);
37
+ return shouldInjectHmrHtmlResponse(this.watch, this.hmrManager, this.hostOwnsDevClient);
35
38
  }
36
39
  /**
37
40
  * Checks if a response contains HTML content.
@@ -1,11 +1,12 @@
1
- import type { EcoPagesAppConfig } from '../../types/internal-types.js';
1
+ import type { EcoPagesAppConfig, IHmrManager } from '../../types/internal-types.js';
2
+ import type { EcoBuildPlugin } from '../../build/build-types.js';
2
3
  import type { ApiHandler, StaticRoute } from '../../types/public-types.js';
3
4
  import type { RouteRegistry } from '../../router/server/route-registry.js';
4
5
  import type { StaticSiteGenerator } from '../../static-site-generator/static-site-generator.js';
5
6
  import type { StaticGenerationRendererResolver } from '../../route-renderer/route-renderer.js';
6
7
  export interface StaticBuildOptions {
7
- preview?: boolean;
8
8
  baseUrl?: string;
9
+ force?: boolean;
9
10
  }
10
11
  export interface ServeOptions {
11
12
  hostname?: string;
@@ -15,9 +16,12 @@ export interface ServerStaticBuilderParams {
15
16
  appConfig: EcoPagesAppConfig;
16
17
  staticSiteGenerator: StaticSiteGenerator;
17
18
  serveOptions: ServeOptions;
19
+ runtimeOrigin: string;
18
20
  apiHandlers?: ApiHandler[];
19
21
  logger?: ServerStaticBuilderLogger;
20
- previewServerFactory?: ServerStaticPreviewServerFactory;
22
+ entryFile?: string;
23
+ hmrManager?: IHmrManager;
24
+ onRuntimePlugin?: (plugin: EcoBuildPlugin) => void;
21
25
  }
22
26
  /**
23
27
  * Minimal logger dependency used by the static builder.
@@ -28,37 +32,41 @@ export interface ServerStaticBuilderLogger {
28
32
  error(message: string): unknown;
29
33
  }
30
34
  /**
31
- * Preview server factory dependency used when static preview mode is enabled.
32
- */
33
- export interface ServerStaticPreviewServerFactory {
34
- createServer(args: {
35
- appConfig: EcoPagesAppConfig;
36
- options: {
37
- port: number;
38
- };
39
- }): {
40
- server?: {
41
- port?: number;
42
- } | null;
43
- };
44
- }
45
- /**
46
- * Handles static site generation and previews.
35
+ * Handles static site generation.
47
36
  */
48
37
  export declare class ServerStaticBuilder {
49
38
  private readonly appConfig;
50
39
  private readonly staticSiteGenerator;
51
40
  private readonly serveOptions;
41
+ private readonly runtimeOrigin;
52
42
  private readonly apiHandlers;
53
43
  private readonly logger;
54
- private readonly previewServerFactory;
55
- constructor({ appConfig, staticSiteGenerator, serveOptions, apiHandlers, logger, previewServerFactory, }: ServerStaticBuilderParams);
56
- private warnApiHandlersUnavailableInStaticMode;
44
+ private readonly entryFile;
45
+ private readonly hmrManager?;
46
+ private readonly onRuntimePlugin?;
47
+ constructor({ appConfig, staticSiteGenerator, serveOptions, runtimeOrigin, apiHandlers, logger, entryFile, hmrManager, onRuntimePlugin, }: ServerStaticBuilderParams);
57
48
  private prepareExportDirectory;
58
49
  private refreshRuntimeAssets;
50
+ /**
51
+ * Bundles the server entry file for production use.
52
+ *
53
+ * @remarks
54
+ * Every production build emits a runnable server entry for `ecopages start`,
55
+ * including static-only and websocket-only apps. Incremental builds may skip
56
+ * Rolldown when the `.eco/.server-entry` cache is still valid.
57
+ *
58
+ * Package imports remain external so native addons and runtime-owned
59
+ * dependencies continue to load through the app's installed
60
+ * `node_modules` tree. Only the app entry graph is bundled.
61
+ *
62
+ * Throws if the build adapter is unavailable, is owned by a host
63
+ * runtime, or bundling fails.
64
+ *
65
+ * @throws If the build adapter is unavailable, is host-owned, or bundling fails.
66
+ */
67
+ private bundleServerEntry;
59
68
  /**
60
69
  * Generates a static build of the site for deployment.
61
- * @param options.preview - If true, starts a preview server after build
62
70
  * @param dependencies.router - The initialized router
63
71
  * @param dependencies.routeRendererFactory - The route renderer factory
64
72
  * @param dependencies.staticRoutes - Explicit static routes registered via app.static()
@@ -1,48 +1,65 @@
1
1
  import path from "node:path";
2
2
  import { fileSystem } from "@ecopages/file-system";
3
3
  import { DEFAULT_ECOPAGES_HOSTNAME, DEFAULT_ECOPAGES_PORT } from "../../config/constants.js";
4
- import { StaticContentServer } from "../../dev/sc-server.js";
5
4
  import { appLogger } from "../../global/app-logger.js";
5
+ import { build, getAppBuildAdapter, setupAppRuntimePlugins } from "../../build/build-adapter.js";
6
+ import { resolveBuildProfileOptions } from "../../build/build-profile-options.js";
7
+ import {
8
+ getServerBundleOutputPaths,
9
+ lookupServerEntryBuildCache,
10
+ recordServerEntryBuildCache,
11
+ writeServerBundleDeployManifest
12
+ } from "../../build/server-entry-build-cache.js";
13
+ import { getInstalledServerEntryBuildExecutor } from "../../build/runtime-build-executor.js";
14
+ import {
15
+ clearProductionBuildCaches,
16
+ shouldResetStaticExportDirectory
17
+ } from "../../static-site-generator/static-build-invalidation.js";
18
+ import { resolveEntryFile, SERVER_BUNDLE_FILENAME } from "../../utils/resolve-entry-file.js";
6
19
  class ServerStaticBuilder {
7
20
  appConfig;
8
21
  staticSiteGenerator;
9
22
  serveOptions;
23
+ runtimeOrigin;
10
24
  apiHandlers;
11
25
  logger;
12
- previewServerFactory;
26
+ entryFile;
27
+ hmrManager;
28
+ onRuntimePlugin;
13
29
  constructor({
14
30
  appConfig,
15
31
  staticSiteGenerator,
16
32
  serveOptions,
33
+ runtimeOrigin,
17
34
  apiHandlers,
18
35
  logger,
19
- previewServerFactory
36
+ entryFile,
37
+ hmrManager,
38
+ onRuntimePlugin
20
39
  }) {
21
40
  this.appConfig = appConfig;
22
41
  this.staticSiteGenerator = staticSiteGenerator;
23
42
  this.serveOptions = serveOptions;
43
+ this.runtimeOrigin = runtimeOrigin;
24
44
  this.apiHandlers = apiHandlers ?? [];
25
45
  this.logger = logger ?? appLogger;
26
- this.previewServerFactory = previewServerFactory ?? StaticContentServer;
46
+ this.entryFile = resolveEntryFile({ entryFile });
47
+ this.hmrManager = hmrManager;
48
+ this.onRuntimePlugin = onRuntimePlugin;
27
49
  }
28
- warnApiHandlersUnavailableInStaticMode() {
29
- if (this.apiHandlers.length === 0) {
30
- return;
50
+ prepareExportDirectory(force) {
51
+ if (force) {
52
+ clearProductionBuildCaches(this.appConfig);
31
53
  }
32
- const uniqueHandlers = Array.from(
33
- new Set(this.apiHandlers.map((handler) => `${handler.method} ${handler.path}`))
34
- );
35
- const visibleHandlers = uniqueHandlers.slice(0, 5).join(", ");
36
- const remainingCount = uniqueHandlers.length - Math.min(uniqueHandlers.length, 5);
37
- const summary = remainingCount > 0 ? `${visibleHandlers}, +${remainingCount} more` : visibleHandlers;
38
- this.logger.warn(
39
- "Registered API endpoints are not available in static build or preview modes because no server runtime is started. They are excluded from the generated output.\n",
40
- `\u27A4 ${summary}`
41
- );
42
- }
43
- prepareExportDirectory() {
44
54
  const exportDir = this.appConfig.absolutePaths?.distDir ?? path.join(this.appConfig.rootDir, this.appConfig.distDir);
45
- fileSystem.ensureDir(exportDir, true);
55
+ const shouldCleanDist = shouldResetStaticExportDirectory(this.appConfig, force);
56
+ fileSystem.ensureDir(exportDir, shouldCleanDist);
57
+ if (shouldCleanDist) {
58
+ this.appConfig.runtime = {
59
+ ...this.appConfig.runtime ?? {},
60
+ runtimeAssetsPrepared: false
61
+ };
62
+ }
46
63
  const srcPublicDir = path.join(
47
64
  this.appConfig.rootDir,
48
65
  this.appConfig.srcDir ?? "src",
@@ -51,48 +68,114 @@ class ServerStaticBuilder {
51
68
  if (fileSystem.exists(srcPublicDir)) {
52
69
  fileSystem.copyDir(srcPublicDir, exportDir);
53
70
  }
71
+ return !shouldCleanDist;
54
72
  }
55
73
  async refreshRuntimeAssets() {
56
- for (const processor of this.appConfig.processors.values()) {
57
- await processor.setup();
74
+ appLogger.debugTime("refreshRuntimeAssets");
75
+ try {
76
+ await setupAppRuntimePlugins({
77
+ appConfig: this.appConfig,
78
+ runtimeOrigin: this.runtimeOrigin,
79
+ hmrManager: this.hmrManager,
80
+ onRuntimePlugin: this.onRuntimePlugin
81
+ });
82
+ } finally {
83
+ appLogger.debugTimeEnd("refreshRuntimeAssets");
84
+ }
85
+ }
86
+ /**
87
+ * Bundles the server entry file for production use.
88
+ *
89
+ * @remarks
90
+ * Every production build emits a runnable server entry for `ecopages start`,
91
+ * including static-only and websocket-only apps. Incremental builds may skip
92
+ * Rolldown when the `.eco/.server-entry` cache is still valid.
93
+ *
94
+ * Package imports remain external so native addons and runtime-owned
95
+ * dependencies continue to load through the app's installed
96
+ * `node_modules` tree. Only the app entry graph is bundled.
97
+ *
98
+ * Throws if the build adapter is unavailable, is owned by a host
99
+ * runtime, or bundling fails.
100
+ *
101
+ * @throws If the build adapter is unavailable, is host-owned, or bundling fails.
102
+ */
103
+ async bundleServerEntry(options) {
104
+ const buildAdapter = getAppBuildAdapter(this.appConfig);
105
+ if (buildAdapter.ownership === "vite-host") {
106
+ throw new Error(
107
+ 'Cannot bundle the server entry file: build ownership is "vite-host". The host runtime is expected to produce its own server bundle.'
108
+ );
109
+ }
110
+ const entryPath = path.isAbsolute(this.entryFile) ? this.entryFile : path.join(this.appConfig.rootDir, this.entryFile);
111
+ if (!fileSystem.exists(entryPath)) {
112
+ throw new Error(
113
+ `Cannot bundle server entry: file "${this.entryFile}" not found in "${this.appConfig.rootDir}".`
114
+ );
58
115
  }
59
- for (const integration of this.appConfig.integrations) {
60
- await integration.setup();
116
+ const { serverOutdir, serverEntryPath } = getServerBundleOutputPaths(this.appConfig);
117
+ const cached = lookupServerEntryBuildCache({
118
+ appConfig: this.appConfig,
119
+ entryPath,
120
+ force: options?.force
121
+ });
122
+ if (cached) {
123
+ const hasBundle = cached.outputPaths.some(
124
+ (outputPath) => path.resolve(outputPath) === path.resolve(serverEntryPath) && fileSystem.exists(outputPath)
125
+ );
126
+ if (hasBundle) {
127
+ this.logger.info("Reusing cached server entry bundle");
128
+ writeServerBundleDeployManifest(this.appConfig, serverEntryPath);
129
+ return;
130
+ }
131
+ }
132
+ this.logger.info("Bundling server entry file...");
133
+ const buildOptions = {
134
+ ...resolveBuildProfileOptions("server-entry", this.appConfig, {
135
+ entrypoints: [entryPath],
136
+ outdir: serverOutdir,
137
+ naming: SERVER_BUNDLE_FILENAME,
138
+ sourcemap: "hidden"
139
+ }),
140
+ entrypoints: [entryPath]
141
+ };
142
+ const result = await build(buildOptions, getInstalledServerEntryBuildExecutor(this.appConfig));
143
+ if (!result.success) {
144
+ const errorMessages = result.logs.map((log) => log.message).join("\n");
145
+ throw new Error(`Failed to bundle server entry file:
146
+ ${errorMessages}`);
61
147
  }
148
+ const outputPaths = result.outputs.length > 0 ? result.outputs.map((output) => output.path) : fileSystem.exists(serverEntryPath) ? [serverEntryPath] : [];
149
+ recordServerEntryBuildCache({
150
+ appConfig: this.appConfig,
151
+ entryPath,
152
+ buildResult: result,
153
+ outputPaths
154
+ });
155
+ writeServerBundleDeployManifest(this.appConfig, serverEntryPath);
156
+ this.logger.info("Server entry file bundled successfully");
62
157
  }
63
158
  /**
64
159
  * Generates a static build of the site for deployment.
65
- * @param options.preview - If true, starts a preview server after build
66
160
  * @param dependencies.router - The initialized router
67
161
  * @param dependencies.routeRendererFactory - The route renderer factory
68
162
  * @param dependencies.staticRoutes - Explicit static routes registered via app.static()
69
163
  */
70
164
  async build(options, dependencies) {
71
- const { preview = false, baseUrl: explicitBaseUrl } = options ?? {};
165
+ const { baseUrl: explicitBaseUrl, force = false } = options ?? {};
72
166
  const baseUrl = explicitBaseUrl ?? `http://${this.serveOptions.hostname || DEFAULT_ECOPAGES_HOSTNAME}:${this.serveOptions.port || DEFAULT_ECOPAGES_PORT}`;
73
- this.warnApiHandlersUnavailableInStaticMode();
74
- this.prepareExportDirectory();
167
+ const preserveExportDirectory = this.prepareExportDirectory(force);
75
168
  await this.refreshRuntimeAssets();
169
+ await this.bundleServerEntry({ force });
76
170
  await this.staticSiteGenerator.run({
77
171
  router: dependencies.router,
78
172
  baseUrl,
79
173
  routeRendererFactory: dependencies.routeRendererFactory,
80
- staticRoutes: dependencies.staticRoutes
174
+ staticRoutes: dependencies.staticRoutes,
175
+ force,
176
+ preserveExportDirectory
81
177
  });
82
- if (!preview) {
83
- this.logger.info("Build completed");
84
- return;
85
- }
86
- const previewPort = this.serveOptions.port || DEFAULT_ECOPAGES_PORT;
87
- const { server } = this.previewServerFactory.createServer({
88
- appConfig: this.appConfig,
89
- options: { port: Number(previewPort) }
90
- });
91
- if (server?.port) {
92
- this.logger.info(`Preview running at http://localhost:${server.port}`);
93
- } else {
94
- this.logger.error("Failed to start preview server");
95
- }
178
+ this.logger.info("Build completed");
96
179
  }
97
180
  }
98
181
  export {
@@ -10,6 +10,7 @@ import type { ServerModuleTranspiler } from '../../services/module-loading/serve
10
10
  type HandleFileChangeOptions = {
11
11
  broadcast?: boolean;
12
12
  };
13
+ export declare function resolveHmrRegistrationTimeoutMs(explicitTimeoutMs?: number): number;
13
14
  type SharedHmrManagerParams = {
14
15
  appConfig: EcoPagesAppConfig;
15
16
  bridge: IClientBridge;
@@ -45,6 +46,16 @@ export declare abstract class SharedHmrManager implements IHmrManager {
45
46
  broadcast(event: ClientBridgeEvent): void;
46
47
  handleFileChange(filePath: string, options?: HandleFileChangeOptions): Promise<void>;
47
48
  getOutputUrl(entrypointPath: string): string | undefined;
49
+ /**
50
+ * Returns the emitted HMR script output when the artifact already exists on disk.
51
+ *
52
+ * SSR must not block on entrypoint registration when a previous build already
53
+ * produced the browser bundle.
54
+ */
55
+ getResolvedScriptOutput(entrypointPath: string): {
56
+ outputUrl: string;
57
+ outputPath: string;
58
+ } | undefined;
48
59
  getWatchedFiles(): Map<string, string>;
49
60
  getDistDir(): string;
50
61
  getPlugins(): EcoBuildPlugin[];
@@ -55,5 +66,6 @@ export declare abstract class SharedHmrManager implements IHmrManager {
55
66
  protected emitStrictEntrypoint(entrypointPath: string): Promise<void>;
56
67
  protected emitScriptEntrypoint(entrypointPath: string, outputPath: string): Promise<void>;
57
68
  stop(): void;
69
+ [Symbol.dispose](): void;
58
70
  }
59
71
  export {};
@@ -2,11 +2,13 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { RESOLVED_ASSETS_DIR } from "../../config/constants.js";
5
- import { getAppBuildExecutor } from "../../build/build-adapter.js";
5
+ import { requireBuildRuntime } from "../../build/build-runtime.js";
6
6
  import { fileSystem } from "@ecopages/file-system";
7
7
  import { HmrStrategyType } from "../../hmr/hmr-strategy.js";
8
8
  import { DefaultHmrStrategy } from "../../hmr/strategies/default-hmr-strategy.js";
9
9
  import { JsHmrStrategy } from "../../hmr/strategies/js-hmr-strategy.js";
10
+ import { ServerRenderedTemplateHmrStrategy } from "../../hmr/strategies/server-rendered-template-hmr-strategy.js";
11
+ import { DevelopmentInvalidationService } from "../../services/invalidation/development-invalidation.service.js";
10
12
  import { appLogger } from "../../global/app-logger.js";
11
13
  import { HmrEntrypointRegistrar } from "./hmr-entrypoint-registrar.js";
12
14
  import { BrowserBundleService } from "../../services/assets/browser-bundle.service.js";
@@ -16,6 +18,24 @@ import {
16
18
  setAppEntrypointDependencyGraph
17
19
  } from "../../services/runtime-state/entrypoint-dependency-graph.service.js";
18
20
  import { resolveInternalExecutionDir, resolveInternalWorkDir } from "../../utils/resolve-work-dir.js";
21
+ const DEFAULT_HMR_REGISTRATION_TIMEOUT_MS = 4e3;
22
+ const DEVELOPMENT_HMR_REGISTRATION_TIMEOUT_MS = 15e3;
23
+ function resolveHmrRegistrationTimeoutMs(explicitTimeoutMs) {
24
+ if (explicitTimeoutMs !== void 0) {
25
+ return explicitTimeoutMs;
26
+ }
27
+ const envTimeoutMs = process.env.ECOPAGES_HMR_REGISTRATION_TIMEOUT_MS;
28
+ if (envTimeoutMs !== void 0 && envTimeoutMs !== "") {
29
+ const parsedTimeoutMs = Number(envTimeoutMs);
30
+ if (Number.isFinite(parsedTimeoutMs) && parsedTimeoutMs > 0) {
31
+ return parsedTimeoutMs;
32
+ }
33
+ }
34
+ if (process.env.NODE_ENV === "development") {
35
+ return DEVELOPMENT_HMR_REGISTRATION_TIMEOUT_MS;
36
+ }
37
+ return DEFAULT_HMR_REGISTRATION_TIMEOUT_MS;
38
+ }
19
39
  class SharedHmrManager {
20
40
  appConfig;
21
41
  bridge;
@@ -24,13 +44,13 @@ class SharedHmrManager {
24
44
  entrypointRegistrations = /* @__PURE__ */ new Map();
25
45
  distDir;
26
46
  plugins = [];
27
- enabled = true;
47
+ enabled = false;
28
48
  strategies = [];
29
49
  entrypointRegistrar;
30
50
  browserBundleService;
31
51
  entrypointDependencyGraph;
32
52
  serverModuleTranspiler;
33
- constructor({ appConfig, bridge, registrationTimeoutMs = 4e3 }) {
53
+ constructor({ appConfig, bridge, registrationTimeoutMs }) {
34
54
  this.appConfig = appConfig;
35
55
  this.bridge = bridge;
36
56
  this.distDir = path.join(resolveInternalWorkDir(this.appConfig), RESOLVED_ASSETS_DIR, "_hmr");
@@ -40,7 +60,7 @@ class SharedHmrManager {
40
60
  entrypointRegistrations: this.entrypointRegistrations,
41
61
  watchedFiles: this.watchedFiles,
42
62
  clearFailedRegistration: (entrypointPath) => this.clearFailedEntrypointRegistration(entrypointPath),
43
- registrationTimeoutMs
63
+ registrationTimeoutMs: resolveHmrRegistrationTimeoutMs(registrationTimeoutMs)
44
64
  });
45
65
  this.browserBundleService = new BrowserBundleService(appConfig);
46
66
  this.entrypointDependencyGraph = this.createEntrypointDependencyGraph(
@@ -86,7 +106,12 @@ class SharedHmrManager {
86
106
  getEntrypointDependencyGraph: () => this.entrypointDependencyGraph,
87
107
  shouldProcessEntrypoint: (entrypointPath) => this.shouldJsStrategyProcessEntrypoint(entrypointPath)
88
108
  };
89
- this.strategies = [new JsHmrStrategy(jsContext), new DefaultHmrStrategy()];
109
+ const invalidationService = new DevelopmentInvalidationService(this.appConfig);
110
+ this.strategies = [
111
+ new JsHmrStrategy(jsContext),
112
+ new ServerRenderedTemplateHmrStrategy(invalidationService),
113
+ new DefaultHmrStrategy()
114
+ ];
90
115
  }
91
116
  registerStrategy(strategy) {
92
117
  this.strategies.push(strategy);
@@ -158,6 +183,24 @@ class SharedHmrManager {
158
183
  getOutputUrl(entrypointPath) {
159
184
  return this.watchedFiles.get(entrypointPath);
160
185
  }
186
+ /**
187
+ * Returns the emitted HMR script output when the artifact already exists on disk.
188
+ *
189
+ * SSR must not block on entrypoint registration when a previous build already
190
+ * produced the browser bundle.
191
+ */
192
+ getResolvedScriptOutput(entrypointPath) {
193
+ const normalizedEntrypoint = path.resolve(entrypointPath);
194
+ const relativePath = path.relative(this.appConfig.absolutePaths.srcDir, normalizedEntrypoint);
195
+ const relativePathJs = relativePath.replace(/\.(tsx?|jsx?|mdx?)$/, ".js").replace(/\[([^\]]+)\]/g, "_$1_");
196
+ const urlPath = relativePathJs.split(path.sep).join("/");
197
+ const outputPath = path.join(this.distDir, urlPath);
198
+ if (!fileSystem.exists(outputPath)) {
199
+ return void 0;
200
+ }
201
+ const outputUrl = this.watchedFiles.get(normalizedEntrypoint) ?? `/${path.join(RESOLVED_ASSETS_DIR, "_hmr", urlPath).split(path.sep).join("/")}`;
202
+ return { outputUrl, outputPath };
203
+ }
161
204
  getWatchedFiles() {
162
205
  return this.watchedFiles;
163
206
  }
@@ -175,7 +218,7 @@ class SharedHmrManager {
175
218
  getSrcDir: () => this.appConfig.absolutePaths.srcDir,
176
219
  getLayoutsDir: () => this.appConfig.absolutePaths.layoutsDir,
177
220
  getPagesDir: () => this.appConfig.absolutePaths.pagesDir,
178
- getBuildExecutor: () => getAppBuildExecutor(this.appConfig),
221
+ getBuildExecutor: () => requireBuildRuntime(this.appConfig).getProfile("browser-hmr"),
179
222
  getBrowserBundleService: () => this.browserBundleService,
180
223
  getEntrypointDependencyGraph: () => this.entrypointDependencyGraph,
181
224
  importServerModule: async (filePath) => await this.serverModuleTranspiler.importModule({
@@ -220,6 +263,16 @@ class SharedHmrManager {
220
263
  appLogger.error(`[HMR] Generic script entrypoint build failed for ${entrypointPath}:`, buildResult.logs);
221
264
  return;
222
265
  }
266
+ if (!fileSystem.exists(outputPath) && buildResult.outputs.length > 0) {
267
+ const resolvedOutputPath = path.resolve(outputPath);
268
+ const emittedOutput = buildResult.outputs.find((output) => path.resolve(output.path) === resolvedOutputPath)?.path ?? buildResult.outputs.find((output) => path.basename(output.path) === path.basename(outputPath))?.path;
269
+ if (emittedOutput && fileSystem.exists(emittedOutput)) {
270
+ fileSystem.ensureDir(path.dirname(outputPath));
271
+ if (path.resolve(emittedOutput) !== resolvedOutputPath) {
272
+ fileSystem.copyFile(emittedOutput, outputPath);
273
+ }
274
+ }
275
+ }
223
276
  const entrypointDependencies = buildResult.dependencyGraph?.entrypoints?.[entrypointPath];
224
277
  if (entrypointDependencies) {
225
278
  this.entrypointDependencyGraph.setEntrypointDependencies(entrypointPath, entrypointDependencies);
@@ -235,7 +288,11 @@ class SharedHmrManager {
235
288
  this.entrypointDependencyGraph.reset();
236
289
  this.plugins = [];
237
290
  }
291
+ [Symbol.dispose]() {
292
+ this.stop();
293
+ }
238
294
  }
239
295
  export {
240
- SharedHmrManager
296
+ SharedHmrManager,
297
+ resolveHmrRegistrationTimeoutMs
241
298
  };
@@ -7,4 +7,9 @@ export interface StaticPreviewHostStartOptions {
7
7
  appConfig: EcoPagesAppConfig;
8
8
  hostname: string;
9
9
  port: number;
10
+ /**
11
+ * When true, bind to the next available port if the preferred one is busy.
12
+ * Should remain false when the port was explicitly configured.
13
+ */
14
+ allowPortFallback?: boolean;
10
15
  }