@ecopages/core 0.2.0-beta.31 → 0.2.0-beta.33

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 (343) hide show
  1. package/README.md +1 -0
  2. package/package.json +90 -3
  3. package/src/adapters/abstract/application-adapter.d.ts +0 -8
  4. package/src/adapters/abstract/application-adapter.js +252 -261
  5. package/src/adapters/abstract/router-adapter.js +12 -4
  6. package/src/adapters/abstract/segment-path-matcher.js +132 -134
  7. package/src/adapters/abstract/server-adapter.js +22 -14
  8. package/src/adapters/abstract/ws-pattern-matcher.js +24 -26
  9. package/src/adapters/bun/bun-user-websocket-lifecycle.js +83 -86
  10. package/src/adapters/bun/client-bridge.js +51 -50
  11. package/src/adapters/bun/create-app.js +171 -161
  12. package/src/adapters/bun/hmr-manager.js +62 -57
  13. package/src/adapters/bun/index.js +2 -31
  14. package/src/adapters/bun/runtime-host.js +61 -54
  15. package/src/adapters/bun/server-adapter.d.ts +0 -1
  16. package/src/adapters/bun/server-adapter.js +561 -561
  17. package/src/adapters/bun/static-preview-host.js +43 -46
  18. package/src/adapters/create-app.js +11 -14
  19. package/src/adapters/index.js +2 -30
  20. package/src/adapters/node/create-app.js +123 -129
  21. package/src/adapters/node/http-request-bridge.js +147 -106
  22. package/src/adapters/node/node-client-bridge.js +62 -64
  23. package/src/adapters/node/node-hmr-manager.js +58 -48
  24. package/src/adapters/node/runtime-host.js +96 -89
  25. package/src/adapters/node/server-adapter-dependencies.js +18 -21
  26. package/src/adapters/node/server-adapter.d.ts +0 -1
  27. package/src/adapters/node/server-adapter.js +347 -326
  28. package/src/adapters/node/static-content-server.js +172 -191
  29. package/src/adapters/node/static-preview-host.js +87 -78
  30. package/src/adapters/shared/hmr/dev-transform-entrypoint-registry.d.ts +16 -0
  31. package/src/adapters/shared/hmr/dev-transform-entrypoint-registry.js +32 -0
  32. package/src/adapters/shared/hmr/hmr-html-response.js +46 -32
  33. package/src/adapters/shared/hmr/shared-hmr-manager.d.ts +29 -38
  34. package/src/adapters/shared/hmr/shared-hmr-manager.js +439 -425
  35. package/src/adapters/shared/http/api-request-pipeline.js +122 -119
  36. package/src/adapters/shared/http/api-response.js +77 -77
  37. package/src/adapters/shared/http/create-body-response.js +22 -19
  38. package/src/adapters/shared/http/define-api-handler.js +41 -55
  39. package/src/adapters/shared/http/explicit-static-render-preparation.js +26 -23
  40. package/src/adapters/shared/http/explicit-static-route-matcher.js +55 -62
  41. package/src/adapters/shared/http/file-route-middleware-pipeline.js +81 -75
  42. package/src/adapters/shared/http/fs-server-response-factory.js +101 -96
  43. package/src/adapters/shared/http/fs-server-response-matcher.js +239 -257
  44. package/src/adapters/shared/http/run-middleware-chain.js +15 -15
  45. package/src/adapters/shared/runtime/application-adapter.js +72 -87
  46. package/src/adapters/shared/runtime/copy-runtime-public-dir.js +25 -22
  47. package/src/adapters/shared/runtime/port-manager.js +207 -177
  48. package/src/adapters/shared/runtime/render-context.js +94 -79
  49. package/src/adapters/shared/runtime/runtime-app-bootstrap.js +33 -38
  50. package/src/adapters/shared/runtime/runtime-host.js +1 -0
  51. package/src/adapters/shared/runtime/runtime-server-lifecycle.d.ts +2 -3
  52. package/src/adapters/shared/runtime/runtime-server-lifecycle.js +65 -51
  53. package/src/adapters/shared/runtime/server-adapter.d.ts +1 -0
  54. package/src/adapters/shared/runtime/server-adapter.js +233 -217
  55. package/src/adapters/shared/runtime/server-route-handler.js +74 -70
  56. package/src/adapters/shared/runtime/server-static-builder.js +165 -184
  57. package/src/adapters/shared/runtime/static-preview-host.js +1 -0
  58. package/src/adapters/shared/ws/node-http-websocket-upgrades.js +97 -88
  59. package/src/adapters/shared/ws/websocket-lifecycle.js +57 -50
  60. package/src/build/README.md +52 -1
  61. package/src/build/app-build-manifest-runtime.js +129 -108
  62. package/src/build/browser/browser-runtime-manifest.js +47 -50
  63. package/src/build/browser/browser-runtime-plugin-helpers.js +32 -12
  64. package/src/build/browser/browser-runtime-plugin.js +206 -160
  65. package/src/build/browser/jsx-ownership-plugins.js +51 -37
  66. package/src/build/browser/lit-static-render-worker-context.js +3 -5
  67. package/src/build/build-adapter.js +276 -139
  68. package/src/build/cache/build-input-fingerprint.js +28 -26
  69. package/src/build/cache/cache-constants.js +4 -6
  70. package/src/build/cache/cache-keys.js +64 -56
  71. package/src/build/cache/pages-unified-graph-build.js +179 -178
  72. package/src/build/cache/production-build-cache.js +47 -54
  73. package/src/build/cache/server-entry-build-cache.js +134 -146
  74. package/src/build/contracts/build-contracts.d.ts +4 -4
  75. package/src/build/contracts/build-contracts.js +1 -0
  76. package/src/build/contracts/build-manifest.js +58 -43
  77. package/src/build/contracts/build-types.js +1 -0
  78. package/src/build/rolldown/rolldown-adapter-helpers.d.ts +9 -0
  79. package/src/build/rolldown/rolldown-adapter-helpers.js +405 -298
  80. package/src/build/rolldown/rolldown-build-adapter.js +53 -61
  81. package/src/build/rolldown/rolldown-build-invocation-metrics.js +31 -35
  82. package/src/build/rolldown/rolldown-plugin-bridge.js +264 -194
  83. package/src/build/rolldown/rolldown-source-transform-pass.js +59 -50
  84. package/src/build/rolldown/runtime-build-output-normalizer.js +227 -175
  85. package/src/build/rolldown/server-side-css-shim-plugin.js +54 -31
  86. package/src/build/runtime/build-profile-options.js +36 -36
  87. package/src/build/runtime/build-request-identity.js +70 -45
  88. package/src/build/runtime/build-request-policy.js +80 -42
  89. package/src/build/runtime/build-runtime.js +64 -54
  90. package/src/build/runtime/deduping-build-executor.js +38 -32
  91. package/src/build/runtime/parallel-build-executor.js +58 -50
  92. package/src/build/runtime/serialized-build-executor.js +78 -62
  93. package/src/cache/index.js +6 -6
  94. package/src/cache/module-parse-cache.js +99 -68
  95. package/src/client/navigation-scripts/classification.js +45 -37
  96. package/src/client/navigation-scripts/index.js +3 -23
  97. package/src/client/navigation-scripts/navigation-scripts.test.browser.js +58 -62
  98. package/src/client/navigation-scripts/registry.js +15 -11
  99. package/src/client/navigation-scripts/rerun-queue.js +81 -64
  100. package/src/client/scroll.js +32 -24
  101. package/src/client/scroll.test.browser.js +27 -33
  102. package/src/client/view-transitions.js +68 -55
  103. package/src/config/config-builder.d.ts +8 -0
  104. package/src/config/config-builder.js +637 -642
  105. package/src/config/constants.js +44 -24
  106. package/src/declarations.d.ts +1 -0
  107. package/src/dev/README.md +24 -0
  108. package/src/dev/client-bridge-registry.js +7 -12
  109. package/src/dev/dev-client-ownership.js +7 -5
  110. package/src/dev/hmr-manager-registry.js +7 -12
  111. package/src/dev/host-runtime.js +25 -30
  112. package/src/dev/runtime-server-started-message.js +2 -5
  113. package/src/dev/sc-server.js +106 -103
  114. package/src/dev/transform-server/dev-transform-bundler.d.ts +24 -0
  115. package/src/dev/transform-server/dev-transform-bundler.js +79 -0
  116. package/src/dev/transform-server/dev-transform-externalize-plugin.d.ts +12 -0
  117. package/src/dev/transform-server/dev-transform-externalize-plugin.js +29 -0
  118. package/src/dev/transform-server/dev-transform-import-rewriter.d.ts +16 -0
  119. package/src/dev/transform-server/dev-transform-import-rewriter.js +110 -0
  120. package/src/dev/transform-server/dev-transform-module-kind.d.ts +13 -0
  121. package/src/dev/transform-server/dev-transform-module-kind.js +17 -0
  122. package/src/dev/transform-server/dev-transform-runtime-specifiers.d.ts +5 -0
  123. package/src/dev/transform-server/dev-transform-runtime-specifiers.js +16 -0
  124. package/src/dev/transform-server/dev-transform-server.d.ts +41 -0
  125. package/src/dev/transform-server/dev-transform-server.js +191 -0
  126. package/src/dev/transform-server/dev-transform-url.d.ts +13 -0
  127. package/src/dev/transform-server/dev-transform-url.js +58 -0
  128. package/src/dev/transform-server/dev-transform-vendor-registry.d.ts +27 -0
  129. package/src/dev/transform-server/dev-transform-vendor-registry.js +145 -0
  130. package/src/dev/transform-server/index.d.ts +6 -0
  131. package/src/dev/transform-server/index.js +4 -0
  132. package/src/dev/transform-server/types.d.ts +15 -0
  133. package/src/dev/transform-server/types.js +1 -0
  134. package/src/dev-toolbar/README.md +51 -0
  135. package/src/dev-toolbar/define-dev-tool.d.ts +21 -0
  136. package/src/dev-toolbar/define-dev-tool.js +9 -0
  137. package/src/dev-toolbar/dev-toolbar-config.d.ts +25 -0
  138. package/src/dev-toolbar/dev-toolbar-config.js +19 -0
  139. package/src/dev-toolbar/dev-toolbar-host.d.ts +49 -0
  140. package/src/dev-toolbar/dev-toolbar-host.js +117 -0
  141. package/src/dev-toolbar/dev-toolbar-html-response.d.ts +11 -0
  142. package/src/dev-toolbar/dev-toolbar-html-response.js +14 -0
  143. package/src/dev-toolbar/dev-toolbar-manifest.d.ts +15 -0
  144. package/src/dev-toolbar/dev-toolbar-manifest.js +52 -0
  145. package/src/dev-toolbar/dev-toolbar-package.d.ts +31 -0
  146. package/src/dev-toolbar/dev-toolbar-package.js +72 -0
  147. package/src/dev-toolbar/dev-toolbar-runtime-paths.d.ts +10 -0
  148. package/src/dev-toolbar/dev-toolbar-runtime-paths.js +13 -0
  149. package/src/diagnostics/request-build-dedupe.js +34 -28
  150. package/src/diagnostics/startup-trace.d.ts +10 -1
  151. package/src/diagnostics/startup-trace.js +269 -241
  152. package/src/eco/README.md +6 -0
  153. package/src/eco/eco-declared-component.js +19 -14
  154. package/src/eco/eco.browser.js +56 -69
  155. package/src/eco/eco.js +156 -96
  156. package/src/eco/eco.types.js +5 -0
  157. package/src/eco/eco.utils.js +1 -10
  158. package/src/eco/global-injector-map.js +55 -41
  159. package/src/eco/lazy-injector-map.js +90 -60
  160. package/src/eco/module-dependencies.js +52 -44
  161. package/src/eco/page-layout-normalization.js +59 -49
  162. package/src/errors/http-error.js +50 -49
  163. package/src/errors/index.js +3 -12
  164. package/src/errors/locals-access-error.js +5 -8
  165. package/src/errors/undeclared-component-dependency-error.js +15 -15
  166. package/src/global/app-logger.js +3 -6
  167. package/src/hmr/README.md +10 -0
  168. package/src/hmr/client/hmr-runtime.js +117 -113
  169. package/src/hmr/client/module-update.d.ts +13 -0
  170. package/src/hmr/client/module-update.js +39 -0
  171. package/src/hmr/hmr-asset-paths.d.ts +14 -0
  172. package/src/hmr/hmr-asset-paths.js +22 -0
  173. package/src/hmr/hmr-entrypoint-output.d.ts +11 -39
  174. package/src/hmr/hmr-entrypoint-output.js +28 -54
  175. package/src/hmr/hmr-file-change-prep.js +13 -16
  176. package/src/hmr/hmr-registered-entrypoints-context.d.ts +7 -0
  177. package/src/hmr/hmr-registered-entrypoints-context.js +1 -0
  178. package/src/hmr/hmr-runtime-paths.d.ts +10 -0
  179. package/src/hmr/hmr-runtime-paths.js +13 -0
  180. package/src/hmr/hmr-strategy.d.ts +5 -11
  181. package/src/hmr/hmr-strategy.js +113 -61
  182. package/src/hmr/strategies/default-hmr-strategy.js +55 -33
  183. package/src/hmr/strategies/js-hmr-strategy.d.ts +6 -95
  184. package/src/hmr/strategies/js-hmr-strategy.js +104 -217
  185. package/src/hmr/strategies/server-rendered-template-hmr-strategy.js +33 -27
  186. package/src/index.browser.js +1 -4
  187. package/src/index.d.ts +1 -0
  188. package/src/index.js +6 -47
  189. package/src/integrations/ghtml/ghtml-renderer.js +12 -7
  190. package/src/integrations/ghtml/ghtml.constants.js +1 -4
  191. package/src/integrations/ghtml/ghtml.plugin.js +9 -12
  192. package/src/islands/README.md +25 -0
  193. package/src/islands/island-host.d.ts +37 -0
  194. package/src/islands/island-host.js +55 -0
  195. package/src/plugins/alias-resolver-cache.js +114 -97
  196. package/src/plugins/alias-resolver-plugin.js +33 -47
  197. package/src/plugins/define-integration.js +18 -17
  198. package/src/plugins/eco-component-meta-plugin.js +387 -145
  199. package/src/plugins/foreign-jsx-override-plugin.js +49 -35
  200. package/src/plugins/integration-plugin.js +212 -203
  201. package/src/plugins/jsx-import-source.utils.js +16 -21
  202. package/src/plugins/processor.js +159 -149
  203. package/src/plugins/runtime-capability.js +1 -0
  204. package/src/plugins/source-transform.js +109 -79
  205. package/src/plugins/tsconfig-import-resolver.d.ts +17 -0
  206. package/src/plugins/tsconfig-import-resolver.js +218 -142
  207. package/src/route-renderer/README.md +12 -1
  208. package/src/route-renderer/orchestration/document-shell/document-shell-render.service.js +143 -136
  209. package/src/route-renderer/orchestration/document-shell/layout-shell-props.service.js +33 -27
  210. package/src/route-renderer/orchestration/foreign-child/component-render-context.js +173 -126
  211. package/src/route-renderer/orchestration/foreign-child/foreign-child-output.utils.js +165 -154
  212. package/src/route-renderer/orchestration/foreign-child/foreign-subtree-execution.service.js +329 -341
  213. package/src/route-renderer/orchestration/foreign-child/owning-renderer-resolution.js +33 -32
  214. package/src/route-renderer/orchestration/integration-renderer.d.ts +1 -0
  215. package/src/route-renderer/orchestration/integration-renderer.js +731 -750
  216. package/src/route-renderer/orchestration/integration-renderer.test-fixtures.js +168 -172
  217. package/src/route-renderer/orchestration/ownership-graph/component-graph-collectors.js +119 -131
  218. package/src/route-renderer/orchestration/ownership-graph/component-graph.js +82 -91
  219. package/src/route-renderer/orchestration/ownership-graph/ownership-validation.service.js +82 -76
  220. package/src/route-renderer/orchestration/page-browser-graph/global-injector-assets.service.js +29 -31
  221. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-contribution.loader.js +10 -6
  222. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.js +31 -28
  223. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-session.d.ts +3 -0
  224. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-session.js +430 -413
  225. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph.service.js +191 -212
  226. package/src/route-renderer/orchestration/page-browser-graph/processed-asset-dedupe.js +20 -24
  227. package/src/route-renderer/orchestration/route-pipeline/integration-route-render-adapter.d.ts +4 -0
  228. package/src/route-renderer/orchestration/route-pipeline/integration-route-render-adapter.js +24 -24
  229. package/src/route-renderer/orchestration/route-pipeline/marker-artifact.utils.js +22 -24
  230. package/src/route-renderer/orchestration/route-pipeline/robots-meta.contribution.js +41 -34
  231. package/src/route-renderer/orchestration/route-pipeline/route-html-finalization.service.d.ts +5 -0
  232. package/src/route-renderer/orchestration/route-pipeline/route-html-finalization.service.js +42 -22
  233. package/src/route-renderer/orchestration/route-pipeline/route-prepared-options.builder.js +50 -48
  234. package/src/route-renderer/orchestration/route-pipeline/route-prepared-options.utils.js +29 -32
  235. package/src/route-renderer/orchestration/route-pipeline/route-render-orchestrator.js +150 -156
  236. package/src/route-renderer/orchestration/string-markup-renderer.js +48 -51
  237. package/src/route-renderer/page-loading/component-dependency-collection.js +120 -124
  238. package/src/route-renderer/page-loading/declared-asset-collection.js +66 -94
  239. package/src/route-renderer/page-loading/dependency-resolver.js +101 -104
  240. package/src/route-renderer/page-loading/ecopages-virtual-imports.js +68 -54
  241. package/src/route-renderer/page-loading/lazy-entry-collection.js +75 -98
  242. package/src/route-renderer/page-loading/lazy-trigger-planning.js +46 -36
  243. package/src/route-renderer/page-loading/module-declaration-aggregation.js +38 -31
  244. package/src/route-renderer/page-loading/module-declaration-scripts.js +10 -15
  245. package/src/route-renderer/page-loading/page-dependency-bundling.js +156 -113
  246. package/src/route-renderer/page-loading/page-module-loader.js +130 -124
  247. package/src/route-renderer/route-renderer.js +74 -76
  248. package/src/router/README.md +23 -0
  249. package/src/router/client/link-intent.js +55 -39
  250. package/src/router/client/link-intent.test.browser.js +49 -49
  251. package/src/router/client/link-navigation-policy.js +114 -93
  252. package/src/router/client/link-navigation-policy.test.browser.js +70 -80
  253. package/src/router/client/navigation-coordinator.js +235 -211
  254. package/src/router/client/navigation-lifecycle.d.ts +55 -0
  255. package/src/router/client/navigation-lifecycle.js +50 -0
  256. package/src/router/client/navigation-lifecycle.test.browser.d.ts +1 -0
  257. package/src/router/client/navigation-lifecycle.test.browser.js +118 -0
  258. package/src/router/server/route-registry.js +237 -242
  259. package/src/services/assets/asset-processing-service/asset-dependency-keys.js +64 -53
  260. package/src/services/assets/asset-processing-service/asset-processing.service.js +287 -303
  261. package/src/services/assets/asset-processing-service/asset.factory.js +78 -81
  262. package/src/services/assets/asset-processing-service/assets.types.js +1 -0
  263. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.js +57 -46
  264. package/src/services/assets/asset-processing-service/browser-runtime-entry-resolution.js +106 -69
  265. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.js +53 -53
  266. package/src/services/assets/asset-processing-service/finalize-processed-asset.js +4 -6
  267. package/src/services/assets/asset-processing-service/grouped-content-bundles.js +79 -80
  268. package/src/services/assets/asset-processing-service/index.js +8 -8
  269. package/src/services/assets/asset-processing-service/inline-content-script-body.js +22 -16
  270. package/src/services/assets/asset-processing-service/materialize-content-script-asset.js +21 -17
  271. package/src/services/assets/asset-processing-service/page-package.js +70 -72
  272. package/src/services/assets/asset-processing-service/processor.interface.js +7 -5
  273. package/src/services/assets/asset-processing-service/processor.registry.js +11 -14
  274. package/src/services/assets/asset-processing-service/processors/base/base-processor.js +59 -62
  275. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.js +102 -117
  276. package/src/services/assets/asset-processing-service/processors/index.js +5 -5
  277. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js +110 -115
  278. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.js +95 -97
  279. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +136 -135
  280. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.js +27 -29
  281. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.js +34 -40
  282. package/src/services/assets/asset-processing-service/processors/stylesheet/stylesheet-processor-pipeline.js +36 -35
  283. package/src/services/assets/asset-processing-service/resolve-integration-plugin.js +14 -16
  284. package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.js +27 -39
  285. package/src/services/assets/browser-bundle.service.js +52 -47
  286. package/src/services/cache/cache.types.js +5 -0
  287. package/src/services/cache/index.js +6 -7
  288. package/src/services/cache/memory-cache-store.js +101 -89
  289. package/src/services/cache/page-cache-service.js +156 -145
  290. package/src/services/cache/page-request-cache-coordinator.service.js +102 -102
  291. package/src/services/html/html-rewriter-provider.service.js +61 -61
  292. package/src/services/html/html-transformer.service.js +249 -266
  293. package/src/services/invalidation/development-invalidation.service.js +252 -241
  294. package/src/services/module-loading/app-module-loader.service.js +1 -0
  295. package/src/services/module-loading/app-server-module-transpiler.service.js +107 -101
  296. package/src/services/module-loading/host-module-loader-registry.js +6 -11
  297. package/src/services/module-loading/module-loading-types.js +1 -0
  298. package/src/services/module-loading/page-module-import.service.js +235 -243
  299. package/src/services/module-loading/route-module-build-cache-registry.js +47 -33
  300. package/src/services/module-loading/route-module-build-cache.js +3 -38
  301. package/src/services/module-loading/route-module-build-cache.store.js +195 -200
  302. package/src/services/module-loading/route-module-build-manifest.js +61 -74
  303. package/src/services/module-loading/route-module-dependency-hasher.js +136 -110
  304. package/src/services/module-loading/server-module-transpiler.service.js +68 -62
  305. package/src/services/module-loading/source-module-support.js +15 -7
  306. package/src/services/runtime-state/dev-graph.service.js +165 -157
  307. package/src/services/runtime-state/entrypoint-dependency-graph.service.js +85 -76
  308. package/src/services/runtime-state/server-invalidation-state.service.js +38 -30
  309. package/src/services/validation/schema-validation-service.js +157 -95
  310. package/src/services/validation/standard-schema.types.js +1 -0
  311. package/src/services/validation/validate-standard-schema.js +13 -13
  312. package/src/static-site-generator/production-page-browser-graph-prebuild.js +35 -19
  313. package/src/static-site-generator/sitemap-routes.js +33 -28
  314. package/src/static-site-generator/sitemap.js +38 -29
  315. package/src/static-site-generator/static-build-invalidation.js +34 -56
  316. package/src/static-site-generator/static-export-context.js +1 -0
  317. package/src/static-site-generator/static-site-generator.js +517 -553
  318. package/src/types/internal-types.d.ts +19 -3
  319. package/src/types/internal-types.js +1 -0
  320. package/src/types/public-types.d.ts +16 -46
  321. package/src/types/public-types.js +1 -0
  322. package/src/utils/deep-merge.js +39 -26
  323. package/src/utils/ecopages-route-info.js +27 -23
  324. package/src/utils/hash.js +3 -6
  325. package/src/utils/html-escaping.js +21 -8
  326. package/src/utils/html.js +1 -4
  327. package/src/utils/invariant.js +11 -10
  328. package/src/utils/locals-utils.js +32 -22
  329. package/src/utils/parse-cli-args.js +73 -65
  330. package/src/utils/path-pattern.js +38 -23
  331. package/src/utils/path-utils.module.js +8 -11
  332. package/src/utils/resolve-entry-file.js +70 -38
  333. package/src/utils/resolve-work-dir.js +23 -27
  334. package/src/utils/runtime.js +24 -33
  335. package/src/utils/server-utils.module.js +60 -52
  336. package/src/utils/static-file-extensions.js +52 -46
  337. package/src/watchers/project-watcher-ignore.js +16 -15
  338. package/src/watchers/project-watcher.js +385 -366
  339. package/src/watchers/project-watcher.test-helpers.js +45 -50
  340. package/src/adapters/shared/hmr/hmr-entrypoint-registrar.d.ts +0 -56
  341. package/src/adapters/shared/hmr/hmr-entrypoint-registrar.js +0 -106
  342. package/src/build/cache/dev-browser-script-cache.d.ts +0 -34
  343. package/src/build/cache/dev-browser-script-cache.js +0 -90
@@ -1,432 +1,446 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { RESOLVED_ASSETS_DIR } from "../../../config/constants.js";
5
- import {
6
- removeStaleHmrEntrypointOutput,
7
- isRegisteredScriptEntrypoint,
8
- isBrowserOnlyRegisteredScriptEntrypoint
9
- } from "../../../hmr/hmr-entrypoint-output.js";
10
- import { requireBuildRuntime } from "../../../build/runtime/build-runtime.js";
11
- import { fileSystem } from "@ecopages/file-system";
12
- import { HmrStrategyType } from "../../../hmr/hmr-strategy.js";
13
- import { DefaultHmrStrategy } from "../../../hmr/strategies/default-hmr-strategy.js";
14
- import { JsHmrStrategy } from "../../../hmr/strategies/js-hmr-strategy.js";
15
- import { ServerRenderedTemplateHmrStrategy } from "../../../hmr/strategies/server-rendered-template-hmr-strategy.js";
16
- import { DevelopmentInvalidationService } from "../../../services/invalidation/development-invalidation.service.js";
17
- import { appLogger } from "../../../global/app-logger.js";
18
- import { HmrEntrypointRegistrar } from "./hmr-entrypoint-registrar.js";
19
- import { BrowserBundleService } from "../../../services/assets/browser-bundle.service.js";
20
- import { getAppServerModuleTranspiler } from "../../../services/module-loading/app-server-module-transpiler.service.js";
21
- import {
22
- getAppEntrypointDependencyGraph,
23
- setAppEntrypointDependencyGraph
24
- } from "../../../services/runtime-state/entrypoint-dependency-graph.service.js";
25
- import { resolveInternalExecutionDir, resolveInternalWorkDir } from "../../../utils/resolve-work-dir.js";
26
- class SharedHmrManager {
27
- appConfig;
28
- bridge;
29
- watchers = /* @__PURE__ */ new Map();
30
- distDir;
31
- enabled = false;
32
- strategies = [];
33
- entrypointRegistrar;
34
- browserBundleService;
35
- invalidationService;
36
- entrypointDependencyGraph;
37
- serverModuleTranspiler;
38
- runtimeBuildPromise = null;
39
- runtimeReady = false;
40
- constructor({ appConfig, bridge }) {
41
- this.appConfig = appConfig;
42
- this.bridge = bridge;
43
- this.distDir = path.join(resolveInternalWorkDir(this.appConfig), RESOLVED_ASSETS_DIR, "_hmr");
44
- this.entrypointRegistrar = new HmrEntrypointRegistrar({
45
- srcDir: this.appConfig.absolutePaths.srcDir,
46
- distDir: this.distDir,
47
- clearFailedRegistration: (entrypointPath) => this.clearFailedEntrypointRegistration(entrypointPath)
48
- });
49
- this.browserBundleService = new BrowserBundleService(appConfig);
50
- this.invalidationService = new DevelopmentInvalidationService(appConfig);
51
- this.entrypointDependencyGraph = this.createEntrypointDependencyGraph(
52
- getAppEntrypointDependencyGraph(appConfig)
53
- );
54
- setAppEntrypointDependencyGraph(this.appConfig, this.entrypointDependencyGraph);
55
- this.serverModuleTranspiler = getAppServerModuleTranspiler(this.appConfig);
56
- this.ensureDistDir();
57
- this.initializeStrategies();
58
- }
59
- shouldSkipMissingFileChange(_filePath) {
60
- return false;
61
- }
62
- onRuntimeBundleFailure(error) {
63
- appLogger.error("[HMR] Failed to build runtime script:", error);
64
- }
65
- ensureDistDir() {
66
- fileSystem.ensureDir(this.distDir);
67
- }
68
- shouldJsStrategyProcessEntrypoint(entrypointPath) {
69
- return !this.strategies.some((strategy) => {
70
- if (strategy.type !== HmrStrategyType.INTEGRATION || strategy.priority <= HmrStrategyType.SCRIPT) {
71
- return false;
72
- }
73
- try {
74
- return strategy.matches(entrypointPath);
75
- } catch (error) {
76
- appLogger.error(error);
77
- return false;
78
- }
79
- });
80
- }
81
- initializeStrategies() {
82
- const jsContext = {
83
- getWatchedFiles: () => this.entrypointRegistrar.getWatchedFiles(),
84
- getDistDir: () => this.distDir,
85
- getSrcDir: () => this.appConfig.absolutePaths.srcDir,
86
- getPagesDir: () => this.appConfig.absolutePaths.pagesDir,
87
- getLayoutsDir: () => this.appConfig.absolutePaths.layoutsDir,
88
- getTemplateExtensions: () => this.appConfig.templatesExt,
89
- getBrowserBundleService: () => this.browserBundleService,
90
- getEntrypointDependencyGraph: () => this.entrypointDependencyGraph,
91
- shouldProcessEntrypoint: (entrypointPath) => this.shouldJsStrategyProcessEntrypoint(entrypointPath)
92
- };
93
- this.strategies = [
94
- new JsHmrStrategy(jsContext),
95
- new ServerRenderedTemplateHmrStrategy(this.invalidationService),
96
- new DefaultHmrStrategy()
97
- ];
98
- }
99
- registerStrategy(strategy) {
100
- this.strategies.push(strategy);
101
- }
102
- setEnabled(enabled) {
103
- this.enabled = enabled;
104
- }
105
- isEnabled() {
106
- return this.enabled;
107
- }
108
- isRuntimeReady() {
109
- return this.runtimeReady && fileSystem.exists(this.getRuntimePath());
110
- }
111
- /**
112
- * Builds the browser HMR runtime once per manager session and reuses the in-flight build for concurrent callers.
113
- */
114
- async ensureRuntimeReady() {
115
- if (this.runtimeReady) {
116
- return true;
117
- }
118
- if (this.runtimeBuildPromise) {
119
- return this.runtimeBuildPromise;
120
- }
121
- this.runtimeBuildPromise = this.buildRuntimeInternal();
122
- try {
123
- const ready = await this.runtimeBuildPromise;
124
- this.runtimeReady = ready;
125
- return ready;
126
- } finally {
127
- this.runtimeBuildPromise = null;
128
- }
129
- }
130
- async buildRuntime() {
131
- await this.ensureRuntimeReady();
132
- }
133
- getRuntimePath() {
134
- return path.join(this.distDir, "_hmr_runtime.js");
135
- }
136
- async buildRuntimeInternal() {
137
- const runtimeSource = fileURLToPath(import.meta.resolve("@ecopages/core/hmr/client/hmr-runtime"));
138
- const runtimePath = this.getRuntimePath();
139
- removeStaleHmrEntrypointOutput(runtimePath, "HMR");
140
- try {
141
- const result = await this.browserBundleService.bundle({
142
- profile: "hmr-runtime",
143
- entrypoints: [runtimeSource],
144
- outdir: this.distDir,
145
- naming: "_hmr_runtime.js",
146
- minify: false
147
- });
148
- if (!result.success) {
149
- this.onRuntimeBundleFailure(result.logs);
150
- return false;
151
- }
152
- const emittedRuntime = result.outputs.find(
153
- (output) => path.resolve(output.path) === path.resolve(runtimePath)
154
- );
155
- if (!emittedRuntime || !fileSystem.exists(runtimePath)) {
156
- this.onRuntimeBundleFailure(
157
- new Error(`[HMR] Runtime bundle missing expected output at ${runtimePath}`)
158
- );
159
- return false;
160
- }
161
- return true;
162
- } catch (error) {
163
- this.onRuntimeBundleFailure(error);
164
- return false;
165
- }
166
- }
167
- broadcast(event) {
168
- appLogger.debug(
169
- `[HMR] Broadcasting ${event.type} event, path=${event.path || "all"}, subscribers=${this.bridge.subscriberCount}`
170
- );
171
- this.bridge.broadcast(event);
172
- }
173
- async handleFileChange(filePath, options = {}) {
174
- const resolvedFilePath = path.resolve(filePath);
175
- if (this.shouldSkipMissingFileChange(filePath) && !fileSystem.exists(filePath)) {
176
- appLogger.debug(`[${this.constructor.name}] Skipping missing file change: ${filePath}`);
177
- this.clearFailedEntrypointRegistration(filePath);
178
- return;
179
- }
180
- if (isRegisteredScriptEntrypoint(this.entrypointRegistrar.getRegistered(), resolvedFilePath)) {
181
- await this.prepareRegisteredScriptChange(resolvedFilePath);
182
- }
183
- const shouldBroadcast = options.broadcast ?? true;
184
- const strategy = this.selectChangeStrategy(filePath);
185
- if (!strategy) {
186
- appLogger.warn(`[HMR] No strategy found for ${filePath}`);
187
- return;
188
- }
189
- appLogger.debug(`[${this.constructor.name}] Selected strategy: ${strategy.constructor.name}`);
190
- const action = await strategy.process(filePath);
191
- if (shouldBroadcast && action.type === "broadcast" && action.events) {
192
- if (this.bridge.subscriberCount === 0) {
193
- appLogger.debug(
194
- `[${this.constructor.name}] Deferring HMR client broadcast for ${filePath} until a subscriber connects`
195
- );
196
- return;
197
- }
198
- for (const event of action.events) {
199
- const graphIdentities = event.graphIdentities ?? options.graphIdentities;
200
- this.broadcast(graphIdentities === void 0 ? event : { ...event, graphIdentities });
201
- }
202
- }
203
- }
204
- getStrategiesByPriority() {
205
- return [...this.strategies].sort((left, right) => right.priority - left.priority);
206
- }
207
- selectChangeStrategy(filePath) {
208
- return this.getStrategiesByPriority().find((candidate) => {
209
- try {
210
- return candidate.matches(filePath);
211
- } catch (error) {
212
- appLogger.error(error);
213
- return false;
214
- }
215
- });
216
- }
217
- selectEntrypointEmitter(entrypointPath) {
218
- return this.getStrategiesByPriority().find((candidate) => {
219
- if (candidate.type !== HmrStrategyType.INTEGRATION) {
220
- return false;
221
- }
222
- try {
223
- return candidate.canEmitEntrypoint(entrypointPath);
224
- } catch (error) {
225
- appLogger.error(error);
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { HMR_RUNTIME_SCRIPT_URL, resolveHmrRuntimeWorkDir } from '../../../hmr/hmr-runtime-paths.js';
5
+ import { DEV_TOOLBAR_RUNTIME_SCRIPT_URL, resolveDevToolbarRuntimeWorkDir, } from '../../../dev-toolbar/dev-toolbar-runtime-paths.js';
6
+ import { DevToolbarHost } from '../../../dev-toolbar/dev-toolbar-host.js';
7
+ import { isDevTransformModuleUrl } from '../../../hmr/hmr-asset-paths.js';
8
+ import { removeStaleHmrEntrypointOutput, isRegisteredDevTransformEntrypoint, isRegisteredScriptEntrypoint, } from '../../../hmr/hmr-entrypoint-output.js';
9
+ import { fileSystem } from '@ecopages/file-system';
10
+ import { HmrStrategyType } from '../../../hmr/hmr-strategy.js';
11
+ import { DefaultHmrStrategy } from '../../../hmr/strategies/default-hmr-strategy.js';
12
+ import { JsHmrStrategy } from '../../../hmr/strategies/js-hmr-strategy.js';
13
+ import { ServerRenderedTemplateHmrStrategy } from '../../../hmr/strategies/server-rendered-template-hmr-strategy.js';
14
+ import { DevelopmentInvalidationService } from '../../../services/invalidation/development-invalidation.service.js';
15
+ import { appLogger } from '../../../global/app-logger.js';
16
+ import { DevTransformEntrypointRegistry } from './dev-transform-entrypoint-registry.js';
17
+ import { BrowserBundleService } from '../../../services/assets/browser-bundle.service.js';
18
+ import { getAppServerModuleTranspiler } from '../../../services/module-loading/app-server-module-transpiler.service.js';
19
+ import { getAppEntrypointDependencyGraph, setAppEntrypointDependencyGraph, } from '../../../services/runtime-state/entrypoint-dependency-graph.service.js';
20
+ import { resolveInternalExecutionDir, resolveInternalWorkDir } from '../../../utils/resolve-work-dir.js';
21
+ import { DevTransformServer } from '../../../dev/transform-server/dev-transform-server.js';
22
+ export class SharedHmrManager {
23
+ appConfig;
24
+ bridge;
25
+ watchers = new Map();
26
+ runtimeWorkDir;
27
+ enabled = false;
28
+ strategies = [];
29
+ registeredScriptReloadRequired = new Set();
30
+ entrypointRegistry;
31
+ browserBundleService;
32
+ invalidationService;
33
+ entrypointDependencyGraph;
34
+ serverModuleTranspiler;
35
+ runtimeBuildPromise = null;
36
+ runtimeReady = false;
37
+ devToolbarWorkDir;
38
+ devToolbarBuildPromise = null;
39
+ devToolbarReady = false;
40
+ devTransformServer;
41
+ constructor({ appConfig, bridge }) {
42
+ this.appConfig = appConfig;
43
+ this.bridge = bridge;
44
+ this.runtimeWorkDir = resolveHmrRuntimeWorkDir(resolveInternalWorkDir(this.appConfig));
45
+ this.devToolbarWorkDir = resolveDevToolbarRuntimeWorkDir(resolveInternalWorkDir(this.appConfig));
46
+ this.entrypointRegistry = new DevTransformEntrypointRegistry();
47
+ this.browserBundleService = new BrowserBundleService(appConfig);
48
+ this.invalidationService = new DevelopmentInvalidationService(appConfig);
49
+ this.entrypointDependencyGraph = this.createEntrypointDependencyGraph(getAppEntrypointDependencyGraph(appConfig));
50
+ setAppEntrypointDependencyGraph(this.appConfig, this.entrypointDependencyGraph);
51
+ this.serverModuleTranspiler = getAppServerModuleTranspiler(this.appConfig);
52
+ this.devTransformServer = new DevTransformServer({
53
+ appConfig: this.appConfig,
54
+ onModuleDependencies: (modulePath, dependencies) => {
55
+ this.entrypointDependencyGraph.setEntrypointDependencies(modulePath, dependencies);
56
+ },
57
+ });
58
+ this.ensureRuntimeWorkDir();
59
+ this.initializeStrategies();
60
+ }
61
+ shouldSkipMissingFileChange(_filePath) {
226
62
  return false;
227
- }
228
- });
229
- }
230
- createUnownedEntrypointError(entrypointPath) {
231
- return new Error(
232
- `[HMR] No integration owns entrypoint ${entrypointPath}. Page entrypoints must be emitted by their owning integration.`
233
- );
234
- }
235
- /**
236
- * Materializes one integration-owned page entrypoint during cold registration.
237
- *
238
- * @remarks
239
- * Registration uses {@link HmrStrategy.canEmitEntrypoint} / {@link HmrStrategy.emitEntrypoint},
240
- * not {@link HmrStrategy.matches} / {@link HmrStrategy.process}. File-change handling stays
241
- * on `handleFileChange()`.
242
- */
243
- async emitIntegrationEntrypoint(entrypointPath, outputPath) {
244
- const emitter = this.selectEntrypointEmitter(entrypointPath);
245
- if (!emitter) {
246
- throw this.createUnownedEntrypointError(entrypointPath);
247
- }
248
- appLogger.debug(`[${this.constructor.name}] Selected entrypoint emitter: ${emitter.constructor.name}`);
249
- await emitter.emitEntrypoint(entrypointPath, outputPath);
250
- }
251
- /**
252
- * Runs server invalidation and integration hooks before rebuilding a registered script entrypoint.
253
- *
254
- * @remarks
255
- * Browser-only `*.script.ts` entrypoints skip server module invalidation and bypass-cache
256
- * import; only integration change handlers and the downstream strategy rebuild run.
257
- */
258
- async prepareRegisteredScriptChange(filePath) {
259
- const isBrowserOnlyRegisteredScript = isBrowserOnlyRegisteredScriptEntrypoint(filePath);
260
- if (!isBrowserOnlyRegisteredScript) {
261
- this.invalidationService.invalidateServerModules([filePath]);
262
- }
263
- const handlers = this.appConfig.runtime?.registeredScriptEntrypointChangeHandlers ?? [];
264
- for (const handler of handlers) {
265
- try {
266
- await handler(filePath);
267
- } catch (error) {
268
- const message = error instanceof Error ? error.message : `Failed to handle registered script entrypoint change for ${filePath}: ${String(error)}`;
269
- this.broadcast({ type: "error", message });
270
- throw error;
271
- }
272
- }
273
- if (isBrowserOnlyRegisteredScript) {
274
- return;
275
- }
276
- const appModuleLoader = this.appConfig.runtime?.appModuleLoader;
277
- if (!appModuleLoader) {
278
- return;
279
- }
280
- await appModuleLoader.importModule({
281
- filePath,
282
- rootDir: this.appConfig.rootDir,
283
- outdir: path.join(resolveInternalExecutionDir(this.appConfig), ".server-modules"),
284
- externalPackages: true,
285
- bypassCache: true
286
- });
287
- }
288
- getOutputUrl(entrypointPath) {
289
- return this.entrypointRegistrar.getRegistered().get(path.resolve(entrypointPath))?.outputUrl;
290
- }
291
- /**
292
- * Registers an already-materialized HMR entrypoint without rebuilding it.
293
- *
294
- * @remarks
295
- * Cold dev batches build grouped Rolldown passes up front and then seed the
296
- * registrar so the first SSR resolves the artifact from disk. The entrypoint
297
- * is also added to the watched files so subsequent source edits still rebuild.
298
- */
299
- seedResolvedEntrypoint(resolved) {
300
- this.entrypointRegistrar.seedResolvedEntrypoint(resolved);
301
- }
302
- /**
303
- * Returns the emitted HMR script output when the entrypoint is already registered
304
- * and its browser bundle exists on disk.
305
- *
306
- * @remarks
307
- * Disk artifacts alone are not enough: a fresh dev session must still register
308
- * the entrypoint so file watchers can rebuild it on change.
309
- */
310
- getResolvedScriptOutput(entrypointPath) {
311
- const normalizedEntrypoint = path.resolve(entrypointPath);
312
- const registered = this.entrypointRegistrar.getRegistered().get(normalizedEntrypoint);
313
- if (!registered || !fileSystem.exists(registered.outputPath)) {
314
- return void 0;
315
- }
316
- return registered;
317
- }
318
- getWatchedFiles() {
319
- return this.entrypointRegistrar.getWatchedFiles();
320
- }
321
- tryHandleAssetRequest(request) {
322
- const url = new URL(request.url);
323
- if (url.pathname === "/_hmr_runtime.js") {
324
- const runtimePath = this.getRuntimePath();
325
- if (fileSystem.exists(runtimePath)) {
326
- return new Response(fileSystem.readFileAsBuffer(runtimePath), {
327
- headers: {
328
- "Content-Type": "application/javascript",
329
- "Cache-Control": "no-store, must-revalidate"
330
- }
63
+ }
64
+ onRuntimeBundleFailure(error) {
65
+ appLogger.error('[HMR] Failed to build runtime script:', error);
66
+ }
67
+ ensureRuntimeWorkDir() {
68
+ fileSystem.ensureDir(this.runtimeWorkDir);
69
+ if (this.shouldBuildDevToolbar()) {
70
+ fileSystem.ensureDir(this.devToolbarWorkDir);
71
+ }
72
+ }
73
+ shouldBuildDevToolbar() {
74
+ return this.getDevToolbarHost().isEnabled();
75
+ }
76
+ getDevToolbarHost() {
77
+ return DevToolbarHost.forApp(this.appConfig, { watch: this.enabled });
78
+ }
79
+ shouldJsStrategyProcessEntrypoint(entrypointPath) {
80
+ return !this.strategies.some((strategy) => {
81
+ if (strategy.type !== HmrStrategyType.INTEGRATION || strategy.priority <= HmrStrategyType.SCRIPT) {
82
+ return false;
83
+ }
84
+ try {
85
+ return strategy.matches(entrypointPath);
86
+ }
87
+ catch (error) {
88
+ appLogger.error(error);
89
+ return false;
90
+ }
331
91
  });
332
- }
333
- }
334
- if (url.pathname.startsWith("/assets/_hmr/")) {
335
- const relativePath = url.pathname.slice("/assets/_hmr/".length);
336
- const assetPath = path.join(this.distDir, relativePath);
337
- if (fileSystem.exists(assetPath)) {
338
- return new Response(fileSystem.readFileAsBuffer(assetPath), {
339
- headers: {
340
- "Content-Type": "application/javascript",
341
- "Cache-Control": "no-store, must-revalidate"
342
- }
92
+ }
93
+ registerDevTransformContributor(contributor) {
94
+ this.devTransformServer.addContributor(contributor);
95
+ }
96
+ initializeStrategies() {
97
+ const jsContext = {
98
+ getWatchedFiles: () => this.entrypointRegistry.getWatchedOutputUrls(),
99
+ getRegisteredEntrypoints: () => this.entrypointRegistry.getRegisteredEntrypoints(),
100
+ getSrcDir: () => this.appConfig.absolutePaths.srcDir,
101
+ getPagesDir: () => this.appConfig.absolutePaths.pagesDir,
102
+ getLayoutsDir: () => this.appConfig.absolutePaths.layoutsDir,
103
+ getTemplateExtensions: () => this.appConfig.templatesExt,
104
+ getEntrypointDependencyGraph: () => this.entrypointDependencyGraph,
105
+ shouldProcessEntrypoint: (entrypointPath) => this.shouldJsStrategyProcessEntrypoint(entrypointPath),
106
+ invalidateDevTransformSource: (sourcePath) => this.devTransformServer.invalidateSource(sourcePath),
107
+ consumeRegisteredScriptReloadRequired: (sourcePath) => this.consumeRegisteredScriptReloadRequired(sourcePath),
108
+ };
109
+ this.strategies = [
110
+ new JsHmrStrategy(jsContext),
111
+ new ServerRenderedTemplateHmrStrategy(this.invalidationService),
112
+ new DefaultHmrStrategy(),
113
+ ];
114
+ }
115
+ registerStrategy(strategy) {
116
+ this.strategies.push(strategy);
117
+ }
118
+ setEnabled(enabled) {
119
+ this.enabled = enabled;
120
+ }
121
+ isEnabled() {
122
+ return this.enabled;
123
+ }
124
+ isRuntimeReady() {
125
+ return this.runtimeReady && fileSystem.exists(this.getRuntimePath());
126
+ }
127
+ /**
128
+ * Builds the browser HMR runtime once per manager session and reuses the in-flight build for concurrent callers.
129
+ */
130
+ async ensureRuntimeReady() {
131
+ const [hmrReady, devToolbarReady] = await Promise.all([
132
+ this.ensureHmrRuntimeReady(),
133
+ this.ensureDevToolbarRuntimeReady(),
134
+ ]);
135
+ return hmrReady && devToolbarReady;
136
+ }
137
+ async ensureHmrRuntimeReady() {
138
+ if (this.runtimeReady) {
139
+ return true;
140
+ }
141
+ if (this.runtimeBuildPromise) {
142
+ return this.runtimeBuildPromise;
143
+ }
144
+ this.runtimeBuildPromise = this.buildRuntimeInternal();
145
+ try {
146
+ const ready = await this.runtimeBuildPromise;
147
+ this.runtimeReady = ready;
148
+ return ready;
149
+ }
150
+ finally {
151
+ this.runtimeBuildPromise = null;
152
+ }
153
+ }
154
+ async ensureDevToolbarRuntimeReady() {
155
+ if (!this.shouldBuildDevToolbar()) {
156
+ return true;
157
+ }
158
+ if (this.devToolbarReady) {
159
+ return true;
160
+ }
161
+ if (this.devToolbarBuildPromise) {
162
+ return this.devToolbarBuildPromise;
163
+ }
164
+ this.devToolbarBuildPromise = this.buildDevToolbarRuntimeInternal();
165
+ try {
166
+ const ready = await this.devToolbarBuildPromise;
167
+ this.devToolbarReady = ready;
168
+ return ready;
169
+ }
170
+ finally {
171
+ this.devToolbarBuildPromise = null;
172
+ }
173
+ }
174
+ async buildRuntime() {
175
+ await this.ensureRuntimeReady();
176
+ }
177
+ getRuntimePath() {
178
+ return path.join(this.runtimeWorkDir, path.basename(HMR_RUNTIME_SCRIPT_URL));
179
+ }
180
+ async buildDevToolbarRuntimeInternal() {
181
+ return this.getDevToolbarHost().bundleClientRuntime({
182
+ browserBundleService: this.browserBundleService,
183
+ workDir: this.devToolbarWorkDir,
184
+ onFailure: (error) => this.onRuntimeBundleFailure(error),
343
185
  });
344
- }
345
- }
346
- return null;
347
- }
348
- getDistDir() {
349
- return this.distDir;
350
- }
351
- getDefaultContext() {
352
- return {
353
- getWatchedFiles: () => this.entrypointRegistrar.getWatchedFiles(),
354
- getDistDir: () => this.distDir,
355
- getSrcDir: () => this.appConfig.absolutePaths.srcDir,
356
- getLayoutsDir: () => this.appConfig.absolutePaths.layoutsDir,
357
- getPagesDir: () => this.appConfig.absolutePaths.pagesDir,
358
- getBuildExecutor: () => requireBuildRuntime(this.appConfig).getProfile("browser-hmr"),
359
- getBrowserBundleService: () => this.browserBundleService,
360
- getEntrypointDependencyGraph: () => this.entrypointDependencyGraph,
361
- seedResolvedEntrypoint: (resolved) => this.seedResolvedEntrypoint(resolved),
362
- importServerModule: async (filePath) => await this.serverModuleTranspiler.importModule({
363
- filePath,
364
- outdir: path.join(resolveInternalExecutionDir(this.appConfig), ".server-modules"),
365
- externalPackages: true
366
- })
367
- };
368
- }
369
- stop() {
370
- this.runtimeReady = false;
371
- this.entrypointRegistrar.clearAll();
372
- for (const watcher of this.watchers.values()) {
373
- watcher.close();
374
- }
375
- this.watchers.clear();
376
- this.entrypointDependencyGraph.reset();
377
- }
378
- clearFailedEntrypointRegistration(entrypointPath) {
379
- this.entrypointRegistrar.clearRegistration(entrypointPath);
380
- this.entrypointDependencyGraph.clearEntrypointDependencies(entrypointPath);
381
- }
382
- async registerEntrypoint(entrypointPath) {
383
- const resolved = await this.entrypointRegistrar.registerEntrypoint(entrypointPath, {
384
- emit: async (normalizedEntrypoint, outputPath) => await this.emitIntegrationEntrypoint(normalizedEntrypoint, outputPath),
385
- getMissingOutputError: (normalizedEntrypoint, outputPath) => new Error(
386
- `[HMR] Integration failed to emit entrypoint ${normalizedEntrypoint} to ${outputPath}. Page entrypoints must be produced by their owning integration.`
387
- )
388
- });
389
- return resolved.outputUrl;
390
- }
391
- async registerScriptEntrypoint(entrypointPath) {
392
- return await this.entrypointRegistrar.registerEntrypoint(entrypointPath, {
393
- emit: async (normalizedEntrypoint, outputPath) => await this.emitScriptEntrypoint(normalizedEntrypoint, outputPath),
394
- getMissingOutputError: (normalizedEntrypoint, outputPath) => new Error(`[HMR] Failed to register script entrypoint: ${normalizedEntrypoint} to ${outputPath}`)
395
- });
396
- }
397
- async emitScriptEntrypoint(entrypointPath, outputPath) {
398
- const naming = path.relative(this.distDir, outputPath).split(path.sep).join("/");
399
- const buildResult = await this.browserBundleService.bundle({
400
- profile: "hmr-entrypoint",
401
- entrypoints: [entrypointPath],
402
- outdir: this.distDir,
403
- naming,
404
- minify: false
405
- });
406
- if (!buildResult.success) {
407
- throw new Error(
408
- `[HMR] Generic script entrypoint build failed for ${entrypointPath}: ${JSON.stringify(buildResult.logs)}`
409
- );
410
- }
411
- if (!fileSystem.exists(outputPath) && buildResult.outputs.length > 0) {
412
- const resolvedOutputPath = path.resolve(outputPath);
413
- const emittedOutput = buildResult.outputs.find((output) => path.resolve(output.path) === resolvedOutputPath)?.path ?? buildResult.outputs.find((output) => path.basename(output.path) === path.basename(outputPath))?.path;
414
- if (emittedOutput && fileSystem.exists(emittedOutput)) {
415
- fileSystem.ensureDir(path.dirname(outputPath));
416
- if (path.resolve(emittedOutput) !== resolvedOutputPath) {
417
- fileSystem.copyFile(emittedOutput, outputPath);
186
+ }
187
+ getDevToolbarRuntimePath() {
188
+ return path.join(this.devToolbarWorkDir, path.basename(DEV_TOOLBAR_RUNTIME_SCRIPT_URL));
189
+ }
190
+ async buildRuntimeInternal() {
191
+ const runtimeSource = fileURLToPath(import.meta.resolve('@ecopages/core/hmr/client/hmr-runtime'));
192
+ const runtimePath = this.getRuntimePath();
193
+ removeStaleHmrEntrypointOutput(runtimePath, 'HMR');
194
+ try {
195
+ const result = await this.browserBundleService.bundle({
196
+ profile: 'hmr-runtime',
197
+ entrypoints: [runtimeSource],
198
+ outdir: this.runtimeWorkDir,
199
+ naming: path.basename(HMR_RUNTIME_SCRIPT_URL),
200
+ minify: false,
201
+ });
202
+ if (!result.success) {
203
+ this.onRuntimeBundleFailure(result.logs);
204
+ return false;
205
+ }
206
+ const emittedRuntime = result.outputs.find((output) => path.resolve(output.path) === path.resolve(runtimePath));
207
+ if (!emittedRuntime || !fileSystem.exists(runtimePath)) {
208
+ this.onRuntimeBundleFailure(new Error(`[HMR] Runtime bundle missing expected output at ${runtimePath}`));
209
+ return false;
210
+ }
211
+ return true;
212
+ }
213
+ catch (error) {
214
+ this.onRuntimeBundleFailure(error);
215
+ return false;
216
+ }
217
+ }
218
+ broadcast(event) {
219
+ appLogger.debug(`[HMR] Broadcasting ${event.type} event, path=${event.path || 'all'}, subscribers=${this.bridge.subscriberCount}`);
220
+ this.bridge.broadcast(event);
221
+ }
222
+ async handleFileChange(filePath, options = {}) {
223
+ const resolvedFilePath = path.resolve(filePath);
224
+ const isRegisteredDevTransformEdit = isRegisteredDevTransformEntrypoint(this.entrypointRegistry.getRegisteredEntrypoints(), resolvedFilePath);
225
+ if (!isRegisteredDevTransformEdit) {
226
+ this.invalidateDevTransformDependents(resolvedFilePath);
227
+ }
228
+ if (this.shouldSkipMissingFileChange(filePath) && !fileSystem.exists(filePath)) {
229
+ appLogger.debug(`[${this.constructor.name}] Skipping missing file change: ${filePath}`);
230
+ this.clearFailedEntrypointRegistration(filePath);
231
+ return;
232
+ }
233
+ if (isRegisteredDevTransformEdit) {
234
+ await this.prepareRegisteredEntrypointChange(resolvedFilePath);
235
+ const registered = this.entrypointRegistry.getRegisteredEntrypoints().get(resolvedFilePath);
236
+ if (registered && isDevTransformModuleUrl(registered.outputUrl)) {
237
+ this.devTransformServer.invalidateSource(resolvedFilePath);
238
+ }
239
+ }
240
+ const strategy = this.selectChangeStrategy(filePath);
241
+ if (!strategy) {
242
+ appLogger.warn(`[HMR] No strategy found for ${filePath}`);
243
+ this.broadcastStrategyAction(filePath, { type: 'none' }, options, !isRegisteredDevTransformEdit);
244
+ return;
245
+ }
246
+ appLogger.debug(`[${this.constructor.name}] Selected strategy: ${strategy.constructor.name}`);
247
+ this.broadcastStrategyAction(filePath, await strategy.process(filePath), options, !isRegisteredDevTransformEdit);
248
+ }
249
+ broadcastStrategyAction(filePath, action, options, fallbackReload) {
250
+ const shouldBroadcast = options.broadcast ?? true;
251
+ if (!shouldBroadcast) {
252
+ return;
253
+ }
254
+ if (action.type === 'broadcast' && action.events) {
255
+ if (this.bridge.subscriberCount === 0) {
256
+ appLogger.debug(`[${this.constructor.name}] Deferring HMR client broadcast for ${filePath} until a subscriber connects`);
257
+ return;
258
+ }
259
+ for (const event of action.events) {
260
+ const graphIdentities = event.graphIdentities ?? options.graphIdentities;
261
+ this.broadcast(graphIdentities === undefined ? event : { ...event, graphIdentities });
262
+ }
263
+ return;
264
+ }
265
+ if (fallbackReload && this.bridge.subscriberCount > 0) {
266
+ this.broadcast({ type: 'reload' });
267
+ }
268
+ }
269
+ invalidateDevTransformDependents(changedFilePath) {
270
+ const affectedEntrypoints = this.entrypointDependencyGraph.getDependencyEntrypoints(changedFilePath);
271
+ if (affectedEntrypoints.size > 0) {
272
+ for (const entrypointPath of affectedEntrypoints) {
273
+ this.devTransformServer.invalidateSource(entrypointPath);
274
+ }
275
+ this.devTransformServer.invalidateSource(changedFilePath);
276
+ return;
277
+ }
278
+ if (!this.entrypointDependencyGraph.supportsSelectiveInvalidation()) {
279
+ for (const sourcePath of this.entrypointRegistry.getRegisteredEntrypoints().keys()) {
280
+ this.devTransformServer.invalidateSource(sourcePath);
281
+ }
282
+ return;
283
+ }
284
+ this.devTransformServer.invalidateSource(changedFilePath);
285
+ }
286
+ consumeRegisteredScriptReloadRequired(filePath) {
287
+ const resolved = path.resolve(filePath);
288
+ const required = this.registeredScriptReloadRequired.has(resolved);
289
+ this.registeredScriptReloadRequired.delete(resolved);
290
+ return required;
291
+ }
292
+ getStrategiesByPriority() {
293
+ return [...this.strategies].sort((left, right) => right.priority - left.priority);
294
+ }
295
+ selectChangeStrategy(filePath) {
296
+ return this.getStrategiesByPriority().find((candidate) => {
297
+ try {
298
+ return candidate.matches(filePath);
299
+ }
300
+ catch (error) {
301
+ appLogger.error(error);
302
+ return false;
303
+ }
304
+ });
305
+ }
306
+ async prepareRegisteredEntrypointChange(filePath) {
307
+ const isRegisteredScript = isRegisteredScriptEntrypoint(this.entrypointRegistry.getRegisteredEntrypoints(), filePath);
308
+ if (!isRegisteredScript) {
309
+ this.invalidationService.invalidateServerModules([filePath]);
310
+ }
311
+ const handlers = this.appConfig.runtime?.registeredScriptEntrypointChangeHandlers ?? [];
312
+ for (const handler of handlers) {
313
+ try {
314
+ const result = await handler(filePath);
315
+ if (result === true) {
316
+ this.registeredScriptReloadRequired.add(path.resolve(filePath));
317
+ }
318
+ }
319
+ catch (error) {
320
+ const message = error instanceof Error
321
+ ? error.message
322
+ : `Failed to handle registered script entrypoint change for ${filePath}: ${String(error)}`;
323
+ this.broadcast({ type: 'error', message });
324
+ throw error;
325
+ }
326
+ }
327
+ if (isRegisteredScript) {
328
+ return;
329
+ }
330
+ const appModuleLoader = this.appConfig.runtime?.appModuleLoader;
331
+ if (!appModuleLoader) {
332
+ return;
333
+ }
334
+ await appModuleLoader.importModule({
335
+ filePath,
336
+ rootDir: this.appConfig.rootDir,
337
+ outdir: path.join(resolveInternalExecutionDir(this.appConfig), '.server-modules'),
338
+ externalPackages: true,
339
+ bypassCache: true,
340
+ });
341
+ }
342
+ getOutputUrl(entrypointPath) {
343
+ return this.entrypointRegistry.getRegisteredEntrypoints().get(path.resolve(entrypointPath))?.outputUrl;
344
+ }
345
+ /**
346
+ * Returns the registered script output when the entrypoint is already tracked for HMR.
347
+ *
348
+ * @remarks
349
+ * Dev-transform scripts use the source path as `outputPath`. A fresh dev session must
350
+ * still register the entrypoint so file watchers can invalidate it on change.
351
+ */
352
+ getResolvedScriptOutput(entrypointPath) {
353
+ const normalizedEntrypoint = path.resolve(entrypointPath);
354
+ const registered = this.entrypointRegistry.getRegisteredEntrypoints().get(normalizedEntrypoint);
355
+ if (!registered || !fileSystem.exists(registered.outputPath)) {
356
+ return undefined;
357
+ }
358
+ return registered;
359
+ }
360
+ getWatchedFiles() {
361
+ return this.entrypointRegistry.getWatchedOutputUrls();
362
+ }
363
+ getRegisteredEntrypoints() {
364
+ return this.entrypointRegistry.getRegisteredEntrypoints();
365
+ }
366
+ async tryHandleDevClientRequest(request) {
367
+ return this.devTransformServer.tryHandleRequest(request);
368
+ }
369
+ tryHandleAssetRequest(request) {
370
+ const url = new URL(request.url);
371
+ if (url.pathname === HMR_RUNTIME_SCRIPT_URL) {
372
+ const runtimePath = this.getRuntimePath();
373
+ if (fileSystem.exists(runtimePath)) {
374
+ return new Response(fileSystem.readFileAsBuffer(runtimePath), {
375
+ headers: {
376
+ 'Content-Type': 'application/javascript',
377
+ 'Cache-Control': 'no-store, must-revalidate',
378
+ },
379
+ });
380
+ }
381
+ }
382
+ if (url.pathname === DEV_TOOLBAR_RUNTIME_SCRIPT_URL && this.shouldBuildDevToolbar()) {
383
+ const runtimeResponse = this.getDevToolbarHost().readBundledRuntime(this.devToolbarWorkDir);
384
+ if (runtimeResponse) {
385
+ return runtimeResponse;
386
+ }
387
+ }
388
+ return null;
389
+ }
390
+ getRuntimeWorkDir() {
391
+ return this.runtimeWorkDir;
392
+ }
393
+ getDefaultContext() {
394
+ return {
395
+ getWatchedFiles: () => this.entrypointRegistry.getWatchedOutputUrls(),
396
+ getRegisteredEntrypoints: () => this.entrypointRegistry.getRegisteredEntrypoints(),
397
+ getSrcDir: () => this.appConfig.absolutePaths.srcDir,
398
+ getLayoutsDir: () => this.appConfig.absolutePaths.layoutsDir,
399
+ getPagesDir: () => this.appConfig.absolutePaths.pagesDir,
400
+ getEntrypointDependencyGraph: () => this.entrypointDependencyGraph,
401
+ importServerModule: async (filePath) => await this.serverModuleTranspiler.importModule({
402
+ filePath,
403
+ outdir: path.join(resolveInternalExecutionDir(this.appConfig), '.server-modules'),
404
+ externalPackages: true,
405
+ }),
406
+ };
407
+ }
408
+ stop() {
409
+ this.runtimeReady = false;
410
+ this.entrypointRegistry.clearAll();
411
+ this.devTransformServer.reset();
412
+ for (const watcher of this.watchers.values()) {
413
+ watcher.close();
414
+ }
415
+ this.watchers.clear();
416
+ this.entrypointDependencyGraph.reset();
417
+ }
418
+ clearFailedEntrypointRegistration(entrypointPath) {
419
+ this.entrypointRegistry.clearRegistration(entrypointPath);
420
+ this.entrypointDependencyGraph.clearEntrypointDependencies(entrypointPath);
421
+ }
422
+ async registerEntrypoint(entrypointPath) {
423
+ return this.registerDevTransformEntrypoint(entrypointPath, 'page').outputUrl;
424
+ }
425
+ async registerScriptEntrypoint(entrypointPath) {
426
+ const { normalized, outputUrl, role } = this.registerDevTransformEntrypoint(entrypointPath, 'script');
427
+ return {
428
+ sourcePath: normalized,
429
+ outputPath: normalized,
430
+ outputUrl,
431
+ role,
432
+ };
433
+ }
434
+ registerDevTransformEntrypoint(entrypointPath, role) {
435
+ const normalized = path.resolve(entrypointPath);
436
+ if (!fileSystem.exists(normalized)) {
437
+ throw new Error(`[HMR] Failed to register ${role} entrypoint: missing source ${normalized}`);
418
438
  }
419
- }
439
+ const outputUrl = this.devTransformServer.registerModule(entrypointPath);
440
+ this.entrypointRegistry.registerTransformModule(entrypointPath, outputUrl, { role });
441
+ return { normalized, outputUrl, role };
420
442
  }
421
- const entrypointDependencies = buildResult.dependencyGraph?.entrypoints?.[entrypointPath];
422
- if (entrypointDependencies) {
423
- this.entrypointDependencyGraph.setEntrypointDependencies(entrypointPath, entrypointDependencies);
443
+ [Symbol.dispose]() {
444
+ this.stop();
424
445
  }
425
- }
426
- [Symbol.dispose]() {
427
- this.stop();
428
- }
429
446
  }
430
- export {
431
- SharedHmrManager
432
- };