@ecopages/core 0.2.0-beta.2 → 0.2.0-beta.21

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 (273) hide show
  1. package/README.md +1 -1
  2. package/package.json +163 -3
  3. package/src/adapters/abstract/application-adapter.d.ts +104 -5
  4. package/src/adapters/abstract/application-adapter.js +125 -1
  5. package/src/adapters/abstract/server-adapter.d.ts +6 -2
  6. package/src/adapters/abstract/ws-pattern-matcher.d.ts +60 -0
  7. package/src/adapters/abstract/ws-pattern-matcher.js +61 -0
  8. package/src/adapters/bun/client-bridge.d.ts +1 -0
  9. package/src/adapters/bun/client-bridge.js +3 -0
  10. package/src/adapters/bun/create-app.d.ts +6 -4
  11. package/src/adapters/bun/create-app.js +65 -43
  12. package/src/adapters/bun/server-adapter.d.ts +90 -22
  13. package/src/adapters/bun/server-adapter.js +266 -84
  14. package/src/adapters/bun/static-preview-host.js +25 -20
  15. package/src/adapters/create-app.d.ts +13 -11
  16. package/src/adapters/create-app.js +2 -51
  17. package/src/adapters/index.d.ts +1 -1
  18. package/src/adapters/index.js +1 -2
  19. package/src/adapters/node/create-app.d.ts +5 -1
  20. package/src/adapters/node/create-app.js +51 -12
  21. package/src/adapters/node/node-client-bridge.d.ts +1 -0
  22. package/src/adapters/node/node-client-bridge.js +3 -0
  23. package/src/adapters/node/server-adapter-dependencies.js +4 -0
  24. package/src/adapters/node/server-adapter.d.ts +62 -20
  25. package/src/adapters/node/server-adapter.js +160 -131
  26. package/src/adapters/node/static-content-server.d.ts +1 -0
  27. package/src/adapters/node/static-content-server.js +10 -2
  28. package/src/adapters/node/static-preview-host.js +27 -12
  29. package/src/adapters/shared/bun-user-websocket-lifecycle.d.ts +22 -0
  30. package/src/adapters/shared/bun-user-websocket-lifecycle.js +96 -0
  31. package/src/adapters/shared/copy-runtime-public-dir.d.ts +6 -0
  32. package/src/adapters/shared/copy-runtime-public-dir.js +24 -0
  33. package/src/adapters/shared/fs-server-response-matcher.d.ts +2 -0
  34. package/src/adapters/shared/fs-server-response-matcher.js +40 -18
  35. package/src/adapters/shared/hmr-entrypoint-registrar.d.ts +21 -30
  36. package/src/adapters/shared/hmr-entrypoint-registrar.js +49 -54
  37. package/src/adapters/shared/hmr-html-response.d.ts +1 -1
  38. package/src/adapters/shared/hmr-html-response.js +4 -1
  39. package/src/adapters/shared/node-http-websocket-upgrades.d.ts +21 -0
  40. package/src/adapters/shared/node-http-websocket-upgrades.js +100 -0
  41. package/src/adapters/shared/port-manager.d.ts +116 -0
  42. package/src/adapters/shared/port-manager.js +196 -0
  43. package/src/adapters/shared/runtime-app-bootstrap.d.ts +2 -1
  44. package/src/adapters/shared/runtime-app-bootstrap.js +3 -6
  45. package/src/adapters/shared/runtime-server-lifecycle.d.ts +46 -0
  46. package/src/adapters/shared/runtime-server-lifecycle.js +50 -0
  47. package/src/adapters/shared/server-adapter.d.ts +2 -1
  48. package/src/adapters/shared/server-adapter.js +13 -6
  49. package/src/adapters/shared/server-route-handler.d.ts +4 -1
  50. package/src/adapters/shared/server-route-handler.js +5 -2
  51. package/src/adapters/shared/server-static-builder.d.ts +23 -32
  52. package/src/adapters/shared/server-static-builder.js +100 -73
  53. package/src/adapters/shared/shared-hmr-manager.d.ts +45 -11
  54. package/src/adapters/shared/shared-hmr-manager.js +204 -58
  55. package/src/adapters/shared/static-preview-host.d.ts +5 -0
  56. package/src/adapters/shared/websocket-lifecycle.d.ts +24 -0
  57. package/src/adapters/shared/websocket-lifecycle.js +55 -0
  58. package/src/build/README.md +77 -14
  59. package/src/build/app-build-manifest-runtime.d.ts +9 -0
  60. package/src/build/app-build-manifest-runtime.js +87 -0
  61. package/src/build/build-adapter.d.ts +17 -319
  62. package/src/build/build-adapter.js +28 -94
  63. package/src/build/build-contracts.d.ts +97 -0
  64. package/src/build/build-contracts.js +0 -0
  65. package/src/build/build-input-fingerprint.d.ts +28 -0
  66. package/src/build/build-input-fingerprint.js +31 -0
  67. package/src/build/build-profile-options.d.ts +7 -0
  68. package/src/build/build-profile-options.js +37 -0
  69. package/src/build/build-runtime.d.ts +24 -0
  70. package/src/build/build-runtime.js +65 -0
  71. package/src/build/deduping-build-executor.d.ts +28 -0
  72. package/src/build/deduping-build-executor.js +56 -0
  73. package/src/build/jsx-ownership-plugins.d.ts +24 -0
  74. package/src/build/jsx-ownership-plugins.js +41 -0
  75. package/src/build/pages-unified-graph-build.d.ts +31 -0
  76. package/src/build/pages-unified-graph-build.js +206 -0
  77. package/src/build/parallel-build-executor.d.ts +21 -0
  78. package/src/build/parallel-build-executor.js +52 -0
  79. package/src/build/production-build-cache.d.ts +20 -0
  80. package/src/build/production-build-cache.js +63 -0
  81. package/src/build/rolldown-adapter-helpers.d.ts +3 -6
  82. package/src/build/rolldown-adapter-helpers.js +71 -6
  83. package/src/build/rolldown-build-adapter.d.ts +2 -6
  84. package/src/build/rolldown-build-adapter.js +3 -3
  85. package/src/build/rolldown-build-invocation-metrics.d.ts +12 -0
  86. package/src/build/rolldown-build-invocation-metrics.js +43 -0
  87. package/src/build/rolldown-plugin-bridge.d.ts +15 -1
  88. package/src/build/rolldown-plugin-bridge.js +20 -5
  89. package/src/build/rolldown-source-transform-pass.d.ts +15 -0
  90. package/src/build/rolldown-source-transform-pass.js +58 -0
  91. package/src/build/runtime-build-executor.d.ts +8 -15
  92. package/src/build/runtime-build-executor.js +12 -15
  93. package/src/build/runtime-build-output-normalizer.d.ts +7 -0
  94. package/src/build/runtime-build-output-normalizer.js +94 -2
  95. package/src/build/server-entry-build-cache.d.ts +44 -0
  96. package/src/build/server-entry-build-cache.js +161 -0
  97. package/src/client/navigation-scripts/classification.d.ts +16 -0
  98. package/src/client/navigation-scripts/classification.js +41 -0
  99. package/src/client/navigation-scripts/index.d.ts +3 -0
  100. package/src/client/navigation-scripts/index.js +23 -0
  101. package/src/client/navigation-scripts/navigation-scripts.test.browser.js +65 -0
  102. package/src/client/navigation-scripts/registry.d.ts +11 -0
  103. package/src/client/navigation-scripts/registry.js +12 -0
  104. package/src/client/navigation-scripts/rerun-queue.d.ts +37 -0
  105. package/src/client/navigation-scripts/rerun-queue.js +73 -0
  106. package/src/client/scroll.d.ts +17 -0
  107. package/src/client/scroll.js +25 -0
  108. package/src/client/scroll.test.browser.js +37 -0
  109. package/src/client/view-transitions.d.ts +16 -0
  110. package/src/client/view-transitions.js +63 -0
  111. package/src/config/config-builder.d.ts +12 -7
  112. package/src/config/config-builder.js +12 -9
  113. package/src/dev/client-bridge-registry.d.ts +5 -0
  114. package/src/dev/client-bridge-registry.js +15 -0
  115. package/src/dev/dev-client-ownership.d.ts +7 -0
  116. package/src/dev/dev-client-ownership.js +6 -0
  117. package/src/dev/hmr-manager-registry.d.ts +8 -0
  118. package/src/dev/hmr-manager-registry.js +15 -0
  119. package/src/dev/host-runtime.d.ts +3 -0
  120. package/src/dev/host-runtime.js +7 -0
  121. package/src/dev/runtime-server-started-message.d.ts +2 -0
  122. package/src/dev/runtime-server-started-message.js +6 -0
  123. package/src/eco/README.md +11 -0
  124. package/src/eco/eco-declared-component.d.ts +14 -0
  125. package/src/eco/eco-declared-component.js +16 -0
  126. package/src/eco/eco.browser.js +5 -6
  127. package/src/eco/eco.js +5 -6
  128. package/src/eco/eco.types.d.ts +2 -2
  129. package/src/eco/page-layout-normalization.d.ts +21 -0
  130. package/src/eco/page-layout-normalization.js +59 -0
  131. package/src/errors/index.d.ts +1 -0
  132. package/src/errors/index.js +7 -1
  133. package/src/errors/undeclared-component-dependency-error.d.ts +11 -0
  134. package/src/errors/undeclared-component-dependency-error.js +17 -0
  135. package/src/hmr/client/hmr-runtime.js +5 -2
  136. package/src/hmr/hmr-entrypoint-output.d.ts +43 -0
  137. package/src/hmr/hmr-entrypoint-output.js +53 -0
  138. package/src/hmr/hmr-strategy.d.ts +15 -0
  139. package/src/hmr/hmr-strategy.js +19 -0
  140. package/src/hmr/strategies/js-hmr-strategy.d.ts +3 -5
  141. package/src/hmr/strategies/js-hmr-strategy.js +67 -19
  142. package/src/hmr/strategies/server-rendered-template-hmr-strategy.d.ts +17 -0
  143. package/src/hmr/strategies/server-rendered-template-hmr-strategy.js +29 -0
  144. package/src/integrations/ghtml/ghtml-renderer.d.ts +2 -6
  145. package/src/integrations/ghtml/ghtml-renderer.js +2 -51
  146. package/src/integrations/ghtml/ghtml.plugin.d.ts +3 -16
  147. package/src/integrations/ghtml/ghtml.plugin.js +7 -14
  148. package/src/plugins/define-integration.d.ts +26 -0
  149. package/src/plugins/define-integration.js +19 -0
  150. package/src/plugins/eco-component-meta-plugin.js +5 -8
  151. package/src/plugins/foreign-jsx-override-plugin.js +4 -6
  152. package/src/plugins/integration-plugin.d.ts +19 -9
  153. package/src/plugins/integration-plugin.js +7 -4
  154. package/src/plugins/jsx-import-source.utils.d.ts +8 -0
  155. package/src/plugins/jsx-import-source.utils.js +26 -0
  156. package/src/plugins/processor.d.ts +19 -4
  157. package/src/plugins/processor.js +22 -4
  158. package/src/plugins/source-transform.d.ts +31 -0
  159. package/src/plugins/source-transform.js +23 -1
  160. package/src/route-renderer/GRAPH.md +8 -8
  161. package/src/route-renderer/README.md +30 -28
  162. package/src/route-renderer/orchestration/component-graph-collectors.d.ts +10 -0
  163. package/src/route-renderer/orchestration/component-graph-collectors.js +143 -0
  164. package/src/route-renderer/orchestration/component-graph.d.ts +36 -0
  165. package/src/route-renderer/orchestration/component-graph.js +98 -0
  166. package/src/route-renderer/orchestration/component-render-context.d.ts +3 -10
  167. package/src/route-renderer/orchestration/component-render-context.js +3 -1
  168. package/src/route-renderer/orchestration/declared-ownership-graph.d.ts +1 -18
  169. package/src/route-renderer/orchestration/declared-ownership-graph.js +5 -31
  170. package/src/route-renderer/orchestration/document-shell-render.service.d.ts +90 -0
  171. package/src/route-renderer/orchestration/document-shell-render.service.js +120 -0
  172. package/src/route-renderer/orchestration/foreign-subtree-execution.service.d.ts +59 -29
  173. package/src/route-renderer/orchestration/foreign-subtree-execution.service.js +164 -35
  174. package/src/route-renderer/orchestration/global-injector-assets.service.d.ts +7 -0
  175. package/src/route-renderer/orchestration/global-injector-assets.service.js +32 -0
  176. package/src/route-renderer/orchestration/integration-renderer.d.ts +18 -54
  177. package/src/route-renderer/orchestration/integration-renderer.js +113 -195
  178. package/src/route-renderer/orchestration/integration-route-render-adapter.d.ts +38 -0
  179. package/src/route-renderer/orchestration/integration-route-render-adapter.js +27 -0
  180. package/src/route-renderer/orchestration/layout-shell-props.service.d.ts +20 -0
  181. package/src/route-renderer/orchestration/layout-shell-props.service.js +31 -0
  182. package/src/route-renderer/orchestration/ownership-validation.service.d.ts +5 -0
  183. package/src/route-renderer/orchestration/ownership-validation.service.js +29 -1
  184. package/src/route-renderer/orchestration/page-browser-graph-contribution.loader.d.ts +9 -0
  185. package/src/route-renderer/orchestration/page-browser-graph-contribution.loader.js +7 -0
  186. package/src/route-renderer/orchestration/page-browser-graph.service.d.ts +25 -0
  187. package/src/route-renderer/orchestration/page-browser-graph.service.js +174 -0
  188. package/src/route-renderer/orchestration/render-output.utils.d.ts +2 -0
  189. package/src/route-renderer/orchestration/render-output.utils.js +4 -0
  190. package/src/route-renderer/orchestration/route-html-finalization.service.d.ts +17 -0
  191. package/src/route-renderer/orchestration/route-html-finalization.service.js +26 -0
  192. package/src/route-renderer/orchestration/route-prepared-options.builder.d.ts +26 -0
  193. package/src/route-renderer/orchestration/route-prepared-options.builder.js +54 -0
  194. package/src/route-renderer/orchestration/route-prepared-options.utils.d.ts +4 -0
  195. package/src/route-renderer/orchestration/route-prepared-options.utils.js +33 -0
  196. package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +8 -39
  197. package/src/route-renderer/orchestration/route-render-orchestrator.js +53 -461
  198. package/src/route-renderer/orchestration/string-markup-renderer.d.ts +14 -0
  199. package/src/route-renderer/orchestration/string-markup-renderer.js +55 -0
  200. package/src/route-renderer/page-loading/component-dependency-collection.js +5 -2
  201. package/src/router/client/link-intent.d.ts +11 -21
  202. package/src/router/client/link-intent.js +10 -0
  203. package/src/router/client/link-intent.test.browser.js +12 -1
  204. package/src/router/client/link-navigation-policy.d.ts +45 -0
  205. package/src/router/client/link-navigation-policy.js +101 -0
  206. package/src/router/client/link-navigation-policy.test.browser.d.ts +1 -0
  207. package/src/router/client/link-navigation-policy.test.browser.js +92 -0
  208. package/src/router/client/navigation-coordinator.d.ts +1 -1
  209. package/src/router/client/navigation-coordinator.js +12 -2
  210. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.d.ts +1 -0
  211. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.js +1 -0
  212. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.d.ts +4 -2
  213. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.js +27 -7
  214. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.d.ts +0 -1
  215. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.js +18 -15
  216. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +17 -3
  217. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.d.ts +0 -3
  218. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.js +2 -31
  219. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.d.ts +0 -3
  220. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.js +2 -30
  221. package/src/services/assets/asset-processing-service/processors/stylesheet/stylesheet-processor-pipeline.d.ts +2 -0
  222. package/src/services/assets/asset-processing-service/processors/stylesheet/stylesheet-processor-pipeline.js +43 -0
  223. package/src/services/assets/browser-bundle.service.d.ts +3 -1
  224. package/src/services/assets/browser-bundle.service.js +17 -5
  225. package/src/services/html/html-transformer.service.d.ts +1 -1
  226. package/src/services/html/html-transformer.service.js +4 -4
  227. package/src/services/invalidation/development-invalidation.service.d.ts +20 -2
  228. package/src/services/invalidation/development-invalidation.service.js +56 -2
  229. package/src/services/module-loading/README.md +49 -0
  230. package/src/services/module-loading/app-server-module-transpiler.service.js +5 -17
  231. package/src/services/module-loading/page-module-import.service.d.ts +5 -1
  232. package/src/services/module-loading/page-module-import.service.js +96 -45
  233. package/src/services/module-loading/route-module-build-cache-registry.d.ts +24 -0
  234. package/src/services/module-loading/route-module-build-cache-registry.js +37 -0
  235. package/src/services/module-loading/route-module-build-cache.d.ts +4 -0
  236. package/src/services/module-loading/route-module-build-cache.js +38 -0
  237. package/src/services/module-loading/route-module-build-cache.store.d.ts +57 -0
  238. package/src/services/module-loading/route-module-build-cache.store.js +207 -0
  239. package/src/services/module-loading/route-module-build-manifest.d.ts +61 -0
  240. package/src/services/module-loading/route-module-build-manifest.js +116 -0
  241. package/src/services/module-loading/route-module-dependency-hasher.d.ts +81 -0
  242. package/src/services/module-loading/route-module-dependency-hasher.js +122 -0
  243. package/src/services/module-loading/server-module-transpiler.service.js +1 -1
  244. package/src/static-site-generator/README.md +33 -0
  245. package/src/static-site-generator/static-build-invalidation.d.ts +10 -0
  246. package/src/static-site-generator/static-build-invalidation.js +59 -0
  247. package/src/static-site-generator/static-export-context.d.ts +22 -0
  248. package/src/static-site-generator/static-export-context.js +0 -0
  249. package/src/static-site-generator/static-site-generator.d.ts +17 -20
  250. package/src/static-site-generator/static-site-generator.js +188 -85
  251. package/src/types/internal-types.d.ts +16 -9
  252. package/src/types/public-types.d.ts +144 -40
  253. package/src/utils/parse-cli-args.d.ts +2 -0
  254. package/src/utils/parse-cli-args.js +6 -0
  255. package/src/utils/server-utils.module.d.ts +3 -2
  256. package/src/utils/server-utils.module.js +5 -6
  257. package/src/utils/static-file-extensions.d.ts +12 -0
  258. package/src/utils/static-file-extensions.js +48 -0
  259. package/src/watchers/project-watcher-ignore.d.ts +11 -0
  260. package/src/watchers/project-watcher-ignore.js +16 -0
  261. package/src/watchers/project-watcher.d.ts +26 -3
  262. package/src/watchers/project-watcher.js +132 -30
  263. package/src/watchers/project-watcher.test-helpers.js +5 -5
  264. package/src/build/rolldown-dev-build-adapter.d.ts +0 -82
  265. package/src/build/rolldown-dev-build-adapter.js +0 -166
  266. package/src/hmr/hmr.postcss.test.e2e.js +0 -31
  267. package/src/hmr/hmr.test.e2e.js +0 -43
  268. package/src/route-renderer/orchestration/ownership-planning.service.d.ts +0 -24
  269. package/src/route-renderer/orchestration/ownership-planning.service.js +0 -63
  270. package/src/route-renderer/orchestration/queued-foreign-subtree-resolution.service.d.ts +0 -91
  271. package/src/route-renderer/orchestration/queued-foreign-subtree-resolution.service.js +0 -170
  272. /package/src/{hmr/hmr.postcss.test.e2e.d.ts → client/navigation-scripts/navigation-scripts.test.browser.d.ts} +0 -0
  273. /package/src/{hmr/hmr.test.e2e.d.ts → client/scroll.test.browser.d.ts} +0 -0
@@ -1,8 +1,14 @@
1
1
  import path from "node:path";
2
+ import { availableParallelism } from "node:os";
2
3
  import { appLogger } from "../global/app-logger.js";
3
4
  import { fileSystem } from "@ecopages/file-system";
4
- import { PathUtils } from "../utils/path-utils.module.js";
5
5
  import { prepareExplicitStaticRender } from "../adapters/shared/explicit-static-render-preparation.js";
6
+ import { createRouteModuleStaticRenderCacheContext } from "./static-build-invalidation.js";
7
+ import {
8
+ getServerModuleBuildCacheOutdir,
9
+ getSharedRouteModuleBuildCache
10
+ } from "../services/module-loading/route-module-build-cache-registry.js";
11
+ import { ensurePagesUnifiedGraphBuilt, shouldBuildPagesUnifiedGraph } from "../build/pages-unified-graph-build.js";
6
12
  const STATIC_SITE_GENERATOR_ERRORS = {
7
13
  ROUTE_RENDERER_FACTORY_REQUIRED: "RouteRendererFactory is required for render strategy",
8
14
  unsupportedBodyType: (bodyType) => `Unsupported body type for static generation: ${bodyType}`,
@@ -10,50 +16,51 @@ const STATIC_SITE_GENERATOR_ERRORS = {
10
16
  noRendererForIntegration: (integrationName) => `No renderer found for integration: ${integrationName}`,
11
17
  dynamicRouteRequiresStaticPaths: (routePath) => `Dynamic route ${routePath} requires staticPaths to be defined on the view.`
12
18
  };
19
+ function resolveStaticPageConcurrency() {
20
+ return Math.max(1, availableParallelism() - 1);
21
+ }
22
+ async function runWithConcurrency(items, concurrency, worker) {
23
+ if (items.length === 0) {
24
+ return;
25
+ }
26
+ const limit = Math.max(1, concurrency);
27
+ let nextIndex = 0;
28
+ const runners = Array.from({ length: Math.min(limit, items.length) }, async () => {
29
+ while (nextIndex < items.length) {
30
+ const currentIndex = nextIndex++;
31
+ await worker(items[currentIndex]);
32
+ }
33
+ });
34
+ await Promise.all(runners);
35
+ }
13
36
  class StaticSiteGenerator {
14
37
  appConfig;
38
+ routeModuleBuildCacheOverride;
39
+ staticRenderCacheContext;
40
+ forceFullStaticGeneration = false;
15
41
  /**
16
42
  * Creates the static-site generator for one app config.
17
43
  */
18
- constructor({ appConfig }) {
44
+ constructor({
45
+ appConfig,
46
+ routeModuleBuildCache
47
+ }) {
19
48
  this.appConfig = appConfig;
49
+ this.routeModuleBuildCacheOverride = routeModuleBuildCache;
50
+ this.staticRenderCacheContext = createRouteModuleStaticRenderCacheContext(appConfig);
51
+ }
52
+ getRouteModuleBuildCache() {
53
+ return this.routeModuleBuildCacheOverride ?? getSharedRouteModuleBuildCache(getServerModuleBuildCacheOutdir(this.appConfig), this.appConfig);
20
54
  }
21
55
  getExportDir() {
22
56
  return this.appConfig.absolutePaths?.distDir ?? path.join(this.appConfig.rootDir, this.appConfig.distDir);
23
57
  }
24
- /**
25
- * Logs the standardized warning emitted when a dynamic-cache page is skipped.
26
- */
27
- warnDynamicPageSkipped(filePath) {
28
- appLogger.warn(
29
- "Pages with cache: 'dynamic' are not supported in static generation or preview, so they will be skipped\n",
30
- `\u27A4 ${filePath}`
31
- );
32
- }
33
- /**
34
- * Determines whether one filesystem-discovered page should be excluded from
35
- * static generation.
36
- */
37
58
  async shouldSkipStaticPageFile(filePath, routeRendererFactory) {
38
- const module = await routeRendererFactory.getPageRenderer(filePath).loadPageModule(filePath, {
39
- cacheScope: "static-page-probe"
40
- });
41
- if (module.default?.cache !== "dynamic") {
42
- return false;
43
- }
44
- this.warnDynamicPageSkipped(filePath);
45
- return true;
59
+ const module = await routeRendererFactory.getPageRenderer(filePath).loadPageModule(filePath);
60
+ return module.default?.cache === "dynamic";
46
61
  }
47
- /**
48
- * Determines whether one explicit static route view should be excluded from
49
- * static generation.
50
- */
51
- shouldSkipStaticView(routePath, view) {
52
- if (view.cache !== "dynamic") {
53
- return false;
54
- }
55
- this.warnDynamicPageSkipped(routePath);
56
- return true;
62
+ shouldSkipStaticView(_routePath, view) {
63
+ return view.cache === "dynamic";
57
64
  }
58
65
  /**
59
66
  * Writes the robots.txt file declared by the app config.
@@ -100,29 +107,56 @@ class StaticSiteGenerator {
100
107
  fileSystem.write(outputPath, contents);
101
108
  return outputPath;
102
109
  }
103
- getStaticBuildStrategy(filePath) {
104
- const ext = PathUtils.getEcoTemplateExtension(filePath);
105
- const integration = this.appConfig.integrations.find((plugin) => plugin.extensions.includes(ext));
106
- return integration?.staticBuildStep || "render";
110
+ async writeStaticPageArtifact(options) {
111
+ options.activeStaticPathnames?.add(options.pathname);
112
+ const directories = options.directories ?? this.getDirectories([options.pathname]);
113
+ const renderedOutputPath = this.getOutputPath(options.pathname, directories);
114
+ const routeModuleBuildCache = this.getRouteModuleBuildCache();
115
+ if (options.reuseRenderedOutput !== false && options.sourceFile && routeModuleBuildCache.canReuseStaticRender({
116
+ sourceFile: options.sourceFile,
117
+ pathname: options.pathname,
118
+ renderedOutputPath,
119
+ context: this.staticRenderCacheContext,
120
+ force: this.forceFullStaticGeneration
121
+ })) {
122
+ appLogger.debug(`Skipped unchanged static page: ${options.debugLabel ?? options.pathname}`);
123
+ return;
124
+ }
125
+ const contents = await options.createContents();
126
+ if (contents === null) {
127
+ return;
128
+ }
129
+ const outputPath = this.writeStaticOutput(options.pathname, contents, directories);
130
+ if (options.sourceFile) {
131
+ routeModuleBuildCache.recordStaticRender({
132
+ filePath: options.sourceFile,
133
+ pathname: options.pathname,
134
+ sourceHash: fileSystem.hash(options.sourceFile),
135
+ renderedOutputPath: outputPath,
136
+ context: this.staticRenderCacheContext
137
+ });
138
+ }
107
139
  }
108
- async createFilesystemStaticContents(route, baseUrl, routeRendererFactory) {
140
+ pruneStaleStaticOutputs(activeStaticPathnames) {
141
+ const removedOutputPaths = this.getRouteModuleBuildCache().pruneStaleRenderedOutputs(activeStaticPathnames);
142
+ for (const outputPath of removedOutputPaths) {
143
+ const resolvedOutputPath = path.isAbsolute(outputPath) ? outputPath : path.resolve(outputPath);
144
+ if (fileSystem.exists(resolvedOutputPath)) {
145
+ fileSystem.remove(resolvedOutputPath);
146
+ appLogger.debug(`Removed stale static output: ${resolvedOutputPath}`);
147
+ }
148
+ }
149
+ }
150
+ async createFilesystemStaticContents(route, _baseUrl, routeRendererFactory, skipped) {
109
151
  const {
110
152
  templateRoute: { filePath },
111
153
  params
112
154
  } = route;
113
- if (this.getStaticBuildStrategy(filePath) === "fetch") {
114
- const fetchUrl = this.resolveStaticFetchUrl(route.requestUrl, baseUrl);
115
- const response = await fetch(fetchUrl);
116
- if (!response.ok) {
117
- appLogger.error(`Failed to fetch ${fetchUrl}. Status: ${response.status}`);
118
- return null;
119
- }
120
- return response.text();
121
- }
122
155
  if (!routeRendererFactory) {
123
156
  throw new Error(STATIC_SITE_GENERATOR_ERRORS.ROUTE_RENDERER_FACTORY_REQUIRED);
124
157
  }
125
158
  if (await this.shouldSkipStaticPageFile(filePath, routeRendererFactory)) {
159
+ skipped?.push(filePath);
126
160
  return null;
127
161
  }
128
162
  const renderer = routeRendererFactory.getPageRenderer(filePath);
@@ -143,42 +177,51 @@ class StaticSiteGenerator {
143
177
  * Generates static output for all filesystem-discovered routes.
144
178
  *
145
179
  * @remarks
146
- * Routes whose integrations opt into fetch-based static builds are rendered by
147
- * issuing a request against the running server origin. Render-strategy routes
148
- * go through the normal route renderer directly.
180
+ * Routes are rendered through the normal route renderer directly.
149
181
  */
150
- async generateStaticPages(router, baseUrl, routeRendererFactory) {
151
- const routes = await router.listStaticGenerationRoutes({ runtimeOrigin: baseUrl });
182
+ async generateStaticPages(router, baseUrl, routeRendererFactory, skipped, activeStaticPathnames, preloadedRoutes) {
183
+ const routes = preloadedRoutes ?? await router.listStaticGenerationRoutes({ runtimeOrigin: baseUrl });
152
184
  appLogger.debug(
153
185
  "Static Pages",
154
186
  routes.map((route) => route.requestUrl)
155
187
  );
156
188
  const directories = this.getDirectories(routes.map((route) => route.requestUrl));
157
- for (const route of routes) {
189
+ await runWithConcurrency(routes, resolveStaticPageConcurrency(), async (route) => {
158
190
  try {
159
- const contents = await this.createFilesystemStaticContents(route, baseUrl, routeRendererFactory);
160
- if (contents === null) {
161
- continue;
162
- }
163
- this.writeStaticOutput(route.pathname, contents, directories);
191
+ await this.writeStaticPageArtifact({
192
+ pathname: route.pathname,
193
+ sourceFile: route.templateRoute.filePath,
194
+ directories,
195
+ debugLabel: route.requestUrl,
196
+ activeStaticPathnames,
197
+ createContents: () => this.createFilesystemStaticContents(route, baseUrl, routeRendererFactory, skipped)
198
+ });
164
199
  } catch (error) {
165
200
  appLogger.error(
166
201
  `Error generating static page for ${route.requestUrl}:`,
167
202
  error instanceof Error ? error : String(error)
168
203
  );
169
204
  }
170
- }
205
+ });
171
206
  }
172
- resolveStaticFetchUrl(route, baseUrl) {
173
- if (!route.startsWith("http://") && !route.startsWith("https://")) {
174
- return `${baseUrl}${route}`;
207
+ createStaticExportContext(input) {
208
+ return {
209
+ appConfig: this.appConfig,
210
+ router: input.router,
211
+ baseUrl: input.baseUrl,
212
+ routeRendererFactory: input.routeRendererFactory,
213
+ staticRoutes: input.staticRoutes,
214
+ force: input.force,
215
+ preserveExportDirectory: input.preserveExportDirectory
216
+ };
217
+ }
218
+ async invokeStaticExportHook(hook, context) {
219
+ for (const integration of this.appConfig.integrations) {
220
+ const handler = integration[hook];
221
+ if (typeof handler === "function") {
222
+ await handler.call(integration, context);
223
+ }
175
224
  }
176
- const targetUrl = new URL(route);
177
- const buildUrl = new URL(baseUrl);
178
- buildUrl.pathname = targetUrl.pathname;
179
- buildUrl.search = targetUrl.search;
180
- buildUrl.hash = targetUrl.hash;
181
- return buildUrl.href;
182
225
  }
183
226
  /**
184
227
  * Executes the full static-generation workflow for one app run.
@@ -187,19 +230,71 @@ class StaticSiteGenerator {
187
230
  router,
188
231
  baseUrl,
189
232
  routeRendererFactory,
190
- staticRoutes
233
+ staticRoutes,
234
+ force = false,
235
+ preserveExportDirectory = false
191
236
  }) {
192
- this.generateRobotsTxt();
193
- await this.generateStaticPages(router, baseUrl, routeRendererFactory);
194
- if (staticRoutes && staticRoutes.length > 0 && routeRendererFactory) {
195
- await this.generateExplicitStaticPages(staticRoutes, routeRendererFactory);
237
+ const skippedDynamicPages = [];
238
+ const activeStaticPathnames = /* @__PURE__ */ new Set();
239
+ this.forceFullStaticGeneration = force;
240
+ this.staticRenderCacheContext = createRouteModuleStaticRenderCacheContext(this.appConfig);
241
+ if (!force) {
242
+ this.getRouteModuleBuildCache().ensureIncrementalStaticGenerationContext(this.staticRenderCacheContext);
243
+ }
244
+ const routes = await router.listStaticGenerationRoutes({ runtimeOrigin: baseUrl });
245
+ if (shouldBuildPagesUnifiedGraph()) {
246
+ await ensurePagesUnifiedGraphBuilt({
247
+ appConfig: this.appConfig,
248
+ entryPaths: routes.map((route) => route.templateRoute.filePath),
249
+ outdir: getServerModuleBuildCacheOutdir(this.appConfig),
250
+ force
251
+ });
252
+ }
253
+ const staticExportContext = this.createStaticExportContext({
254
+ router,
255
+ baseUrl,
256
+ routeRendererFactory,
257
+ staticRoutes,
258
+ force,
259
+ preserveExportDirectory
260
+ });
261
+ await this.invokeStaticExportHook("beforeStaticExport", staticExportContext);
262
+ try {
263
+ this.generateRobotsTxt();
264
+ await this.generateStaticPages(
265
+ router,
266
+ baseUrl,
267
+ routeRendererFactory,
268
+ skippedDynamicPages,
269
+ activeStaticPathnames,
270
+ routes
271
+ );
272
+ if (staticRoutes && staticRoutes.length > 0 && routeRendererFactory) {
273
+ await this.generateExplicitStaticPages(
274
+ staticRoutes,
275
+ routeRendererFactory,
276
+ skippedDynamicPages,
277
+ activeStaticPathnames
278
+ );
279
+ }
280
+ if (preserveExportDirectory) {
281
+ this.pruneStaleStaticOutputs(activeStaticPathnames);
282
+ }
283
+ } finally {
284
+ await this.invokeStaticExportHook("afterStaticExport", staticExportContext);
285
+ }
286
+ if (skippedDynamicPages.length > 0) {
287
+ appLogger.debug(
288
+ `Skipped ${skippedDynamicPages.length} page(s) with cache: 'dynamic' (not supported in static generation)`,
289
+ skippedDynamicPages
290
+ );
196
291
  }
197
292
  }
198
293
  /**
199
294
  * Generates static pages from explicit static routes registered via app.static().
200
295
  * These routes use eco.page views via loader functions for HMR support.
201
296
  */
202
- async generateExplicitStaticPages(staticRoutes, routeRendererFactory) {
297
+ async generateExplicitStaticPages(staticRoutes, routeRendererFactory, skipped, activeStaticPathnames) {
203
298
  appLogger.debug(
204
299
  "Generating explicit static routes",
205
300
  staticRoutes.map((r) => r.path)
@@ -209,9 +304,10 @@ class StaticSiteGenerator {
209
304
  const mod = await route.loader();
210
305
  const view = mod.default;
211
306
  if (this.shouldSkipStaticView(route.path, view)) {
307
+ skipped?.push(route.path);
212
308
  continue;
213
309
  }
214
- await this.generateExplicitStaticRoute(route.path, view, routeRendererFactory);
310
+ await this.generateExplicitStaticRoute(route.path, view, routeRendererFactory, activeStaticPathnames);
215
311
  } catch (error) {
216
312
  appLogger.error(
217
313
  `Error generating explicit static page for ${route.path}:`,
@@ -220,18 +316,25 @@ class StaticSiteGenerator {
220
316
  }
221
317
  }
222
318
  }
223
- async generateExplicitStaticRoute(routePath, view, routeRendererFactory) {
319
+ resolveExplicitViewSourceFile(view) {
320
+ const sourceFile = view.config?.__eco?.file;
321
+ if (!sourceFile) {
322
+ return void 0;
323
+ }
324
+ return path.isAbsolute(sourceFile) ? sourceFile : path.join(this.appConfig.rootDir, sourceFile);
325
+ }
326
+ async generateExplicitStaticRoute(routePath, view, routeRendererFactory, activeStaticPathnames) {
224
327
  const { renderer, routeEntries } = await this.planExplicitStaticRoute(routePath, view, routeRendererFactory);
328
+ const sourceFile = this.resolveExplicitViewSourceFile(view);
225
329
  for (const { pathname, params } of routeEntries) {
226
- const contents = await this.createExplicitStaticContents(
227
- routePath,
228
- view,
229
- params,
230
- routeRendererFactory,
231
- renderer
232
- );
233
- const outputPath = this.writeStaticOutput(pathname, contents);
234
- appLogger.debug(`Generated static page: ${pathname} -> ${outputPath}`);
330
+ await this.writeStaticPageArtifact({
331
+ pathname,
332
+ sourceFile,
333
+ debugLabel: pathname,
334
+ activeStaticPathnames,
335
+ createContents: () => this.createExplicitStaticContents(routePath, view, params, routeRendererFactory, renderer)
336
+ });
337
+ appLogger.debug(`Generated static page: ${pathname}`);
235
338
  }
236
339
  }
237
340
  async planExplicitStaticRoute(routePath, view, routeRendererFactory) {
@@ -1,6 +1,7 @@
1
1
  import type { EcoBuildPlugin } from '../build/build-types.js';
2
2
  import type { AppBuildManifest } from '../build/build-manifest.js';
3
- import type { BuildAdapter, BuildExecutor, BuildOwnership } from '../build/build-adapter.js';
3
+ import type { BuildAdapter, BuildOwnership } from '../build/build-adapter.js';
4
+ import type { BuildRuntime } from '../build/build-runtime.js';
4
5
  import type { AnyIntegrationPlugin } from '../plugins/integration-plugin.js';
5
6
  import type { Processor } from '../plugins/processor.js';
6
7
  import type { EcoSourceTransform } from '../plugins/source-transform.js';
@@ -13,6 +14,9 @@ import type { SourceModuleLoader } from '../services/module-loading/module-loadi
13
14
  import type { EntrypointDependencyGraph } from '../services/runtime-state/entrypoint-dependency-graph.service.js';
14
15
  import type { ServerInvalidationState } from '../services/runtime-state/server-invalidation-state.service.js';
15
16
  import type { ServerModuleTranspiler } from '../services/module-loading/server-module-transpiler.service.js';
17
+ import type { RouteModuleBuildCache } from '../services/module-loading/route-module-build-cache.store.js';
18
+ /** Integration hook for registered `dependencies.scripts` entrypoint changes in dev. */
19
+ export type RegisteredScriptEntrypointChangeHandler = (filePath: string) => void | Promise<void>;
16
20
  export interface RobotsPreference {
17
21
  /**
18
22
  * The user agent
@@ -149,13 +153,23 @@ export type EcoPagesAppConfig = {
149
153
  buildOwnership?: BuildOwnership;
150
154
  buildAdapter?: BuildAdapter;
151
155
  buildManifest?: AppBuildManifest;
152
- buildExecutor?: BuildExecutor;
153
156
  devGraphService?: DevGraphService;
154
157
  entrypointDependencyGraph?: EntrypointDependencyGraph;
155
158
  hostModuleLoader?: SourceModuleLoader;
156
159
  rendererModuleContext?: unknown;
157
160
  serverInvalidationState?: ServerInvalidationState;
158
161
  serverModuleTranspiler?: ServerModuleTranspiler;
162
+ routeModuleBuildCaches?: Map<string, RouteModuleBuildCache>;
163
+ /** Profile-based build runtime installed by {@link installBuildRuntime}. */
164
+ buildRuntime?: BuildRuntime;
165
+ /** Set after {@link setupAppRuntimePlugins} runs processor/integration setup once per process. */
166
+ runtimeAssetsPrepared?: boolean;
167
+ /** When `'host'`, the embedded dev server owns browser dev-client bootstrap. */
168
+ devClientOwner?: 'core' | 'host';
169
+ /** Integration hooks run when a registered `dependencies.scripts` entrypoint changes. */
170
+ registeredScriptEntrypointChangeHandlers?: RegisteredScriptEntrypointChangeHandler[];
171
+ /** @deprecated Prefer {@link devClientOwner}: `'host'`. */
172
+ delegateBrowserReloadToHost?: boolean;
159
173
  };
160
174
  /**
161
175
  * Experimental features.
@@ -222,11 +236,4 @@ export interface EcoPagesFileSystemServerAdapter<ServerInstanceOptions = unknown
222
236
  server: unknown;
223
237
  }>;
224
238
  }
225
- export interface ProcessorPlugin {
226
- name: string;
227
- description?: string;
228
- setup(): Promise<void>;
229
- process<T = unknown>(input: T): Promise<T>;
230
- teardown?(): Promise<void>;
231
- }
232
239
  export type { ClientBridgeEvent, DefaultHmrContext, IHmrManager, IClientBridge } from './public-types.js';