@ecopages/core 0.2.0-alpha.1 → 0.2.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/CHANGELOG.md +13 -70
  2. package/README.md +213 -12
  3. package/package.json +50 -18
  4. package/src/adapters/README.md +39 -0
  5. package/src/adapters/bun/hmr-manager.d.ts +77 -18
  6. package/src/adapters/bun/hmr-manager.js +159 -52
  7. package/src/adapters/bun/index.d.ts +1 -2
  8. package/src/adapters/bun/index.js +1 -1
  9. package/src/adapters/bun/server-adapter.js +35 -30
  10. package/src/adapters/bun/server-lifecycle.d.ts +24 -13
  11. package/src/adapters/bun/server-lifecycle.js +32 -60
  12. package/src/adapters/index.d.ts +1 -1
  13. package/src/adapters/index.js +1 -1
  14. package/src/adapters/node/bootstrap-dependency-resolver.d.ts +44 -0
  15. package/src/adapters/node/bootstrap-dependency-resolver.js +172 -0
  16. package/src/adapters/node/index.d.ts +2 -0
  17. package/src/adapters/node/index.js +3 -0
  18. package/src/adapters/node/node-hmr-manager.d.ts +87 -16
  19. package/src/adapters/node/node-hmr-manager.js +179 -88
  20. package/src/adapters/node/runtime-adapter.d.ts +46 -0
  21. package/src/adapters/node/runtime-adapter.js +306 -0
  22. package/src/adapters/node/server-adapter.d.ts +2 -31
  23. package/src/adapters/node/server-adapter.js +31 -93
  24. package/src/adapters/node/static-content-server.d.ts +36 -0
  25. package/src/adapters/node/static-content-server.js +28 -0
  26. package/src/adapters/node/write-runtime-manifest.d.ts +26 -0
  27. package/src/adapters/node/write-runtime-manifest.js +12 -0
  28. package/src/{define-api-handler.d.ts → adapters/shared/define-api-handler.d.ts} +1 -1
  29. package/src/adapters/shared/explicit-static-route-matcher.js +4 -1
  30. package/src/adapters/shared/file-route-middleware-pipeline.js +1 -0
  31. package/src/adapters/shared/fs-server-response-matcher.d.ts +9 -5
  32. package/src/adapters/shared/fs-server-response-matcher.js +13 -8
  33. package/src/adapters/shared/hmr-entrypoint-registrar.d.ts +55 -0
  34. package/src/adapters/shared/hmr-entrypoint-registrar.js +87 -0
  35. package/src/adapters/shared/hmr-html-response.d.ts +22 -0
  36. package/src/adapters/shared/hmr-html-response.js +32 -0
  37. package/src/adapters/shared/render-context.js +3 -2
  38. package/src/adapters/shared/runtime-bootstrap.d.ts +38 -0
  39. package/src/adapters/shared/runtime-bootstrap.js +43 -0
  40. package/src/adapters/shared/server-adapter.d.ts +12 -2
  41. package/src/adapters/shared/server-adapter.js +37 -4
  42. package/src/adapters/shared/server-route-handler.d.ts +1 -1
  43. package/src/adapters/shared/server-route-handler.js +4 -13
  44. package/src/adapters/shared/server-static-builder.d.ts +35 -3
  45. package/src/adapters/shared/server-static-builder.js +59 -6
  46. package/src/build/README.md +101 -0
  47. package/src/build/build-adapter.d.ts +114 -2
  48. package/src/build/build-adapter.js +119 -5
  49. package/src/build/build-manifest.d.ts +27 -0
  50. package/src/build/build-manifest.js +30 -0
  51. package/src/build/dev-build-coordinator.d.ts +74 -0
  52. package/src/build/dev-build-coordinator.js +161 -0
  53. package/src/build/esbuild-build-adapter.d.ts +9 -6
  54. package/src/build/esbuild-build-adapter.js +103 -71
  55. package/src/build/runtime-build-executor.d.ts +13 -0
  56. package/src/build/runtime-build-executor.js +20 -0
  57. package/src/build/runtime-specifier-alias-plugin.d.ts +15 -0
  58. package/src/build/runtime-specifier-alias-plugin.js +31 -0
  59. package/src/config/README.md +33 -0
  60. package/src/config/config-builder.d.ts +28 -17
  61. package/src/config/config-builder.js +221 -48
  62. package/src/constants.d.ts +13 -0
  63. package/src/constants.js +4 -0
  64. package/src/declarations.d.ts +18 -13
  65. package/src/eco/README.md +70 -16
  66. package/src/eco/component-render-context.d.ts +1 -1
  67. package/src/eco/component-render-context.js +26 -16
  68. package/src/eco/eco.js +16 -12
  69. package/src/eco/eco.types.d.ts +12 -4
  70. package/src/hmr/README.md +26 -0
  71. package/src/hmr/client/hmr-runtime.d.ts +1 -6
  72. package/src/hmr/client/hmr-runtime.js +30 -7
  73. package/src/hmr/hmr.postcss.test.e2e.d.ts +1 -0
  74. package/src/hmr/hmr.postcss.test.e2e.js +31 -0
  75. package/src/hmr/hmr.test.e2e.js +26 -33
  76. package/src/hmr/strategies/js-hmr-strategy.d.ts +45 -42
  77. package/src/hmr/strategies/js-hmr-strategy.js +70 -71
  78. package/src/index.d.ts +1 -1
  79. package/src/index.js +1 -1
  80. package/src/integrations/ghtml/ghtml-renderer.d.ts +1 -1
  81. package/src/integrations/ghtml/ghtml-renderer.js +3 -1
  82. package/src/internal-types.d.ts +39 -18
  83. package/src/plugins/README.md +34 -0
  84. package/src/plugins/alias-resolver-plugin.js +17 -3
  85. package/src/plugins/eco-component-meta-plugin.d.ts +2 -0
  86. package/src/plugins/eco-component-meta-plugin.js +1 -1
  87. package/src/plugins/integration-plugin.d.ts +38 -4
  88. package/src/plugins/integration-plugin.js +34 -1
  89. package/src/plugins/processor.d.ts +14 -1
  90. package/src/plugins/processor.js +15 -1
  91. package/src/plugins/runtime-capability.d.ts +9 -0
  92. package/src/public-types.d.ts +65 -10
  93. package/src/route-renderer/GRAPH.md +16 -20
  94. package/src/route-renderer/README.md +8 -21
  95. package/src/route-renderer/component-graph/component-reference.d.ts +10 -0
  96. package/src/route-renderer/component-graph/component-reference.js +19 -0
  97. package/src/route-renderer/{marker-graph-resolver.d.ts → component-graph/marker-graph-resolver.d.ts} +6 -5
  98. package/src/route-renderer/{marker-graph-resolver.js → component-graph/marker-graph-resolver.js} +10 -8
  99. package/src/route-renderer/{integration-renderer.d.ts → orchestration/integration-renderer.d.ts} +45 -12
  100. package/src/route-renderer/{integration-renderer.js → orchestration/integration-renderer.js} +79 -16
  101. package/src/route-renderer/{render-execution.service.d.ts → orchestration/render-execution.service.d.ts} +39 -5
  102. package/src/route-renderer/{render-execution.service.js → orchestration/render-execution.service.js} +58 -28
  103. package/src/route-renderer/{render-preparation.service.d.ts → orchestration/render-preparation.service.d.ts} +13 -4
  104. package/src/route-renderer/{render-preparation.service.js → orchestration/render-preparation.service.js} +92 -3
  105. package/src/route-renderer/{dependency-resolver.d.ts → page-loading/dependency-resolver.d.ts} +15 -4
  106. package/src/route-renderer/{dependency-resolver.js → page-loading/dependency-resolver.js} +18 -4
  107. package/src/route-renderer/page-loading/page-module-loader.d.ts +87 -0
  108. package/src/route-renderer/{page-module-loader.js → page-loading/page-module-loader.js} +36 -14
  109. package/src/route-renderer/route-renderer.d.ts +36 -1
  110. package/src/route-renderer/route-renderer.js +19 -0
  111. package/src/router/README.md +26 -0
  112. package/src/router/client/link-intent.d.ts +53 -0
  113. package/src/router/client/link-intent.js +34 -0
  114. package/src/router/client/link-intent.test.browser.d.ts +1 -0
  115. package/src/router/client/link-intent.test.browser.js +43 -0
  116. package/src/router/client/navigation-coordinator.d.ts +149 -0
  117. package/src/router/client/navigation-coordinator.js +215 -0
  118. package/src/router/{fs-router-scanner.d.ts → server/fs-router-scanner.d.ts} +3 -3
  119. package/src/router/{fs-router-scanner.js → server/fs-router-scanner.js} +8 -7
  120. package/src/router/{fs-router.d.ts → server/fs-router.d.ts} +1 -1
  121. package/src/router/{fs-router.js → server/fs-router.js} +1 -1
  122. package/src/services/README.md +29 -0
  123. package/src/services/assets/asset-processing-service/asset-processing.service.d.ts +120 -0
  124. package/src/services/{asset-processing-service → assets/asset-processing-service}/asset-processing.service.js +88 -7
  125. package/src/services/{asset-processing-service → assets/asset-processing-service}/asset.factory.js +2 -2
  126. package/src/services/{asset-processing-service → assets/asset-processing-service}/assets.types.d.ts +2 -1
  127. package/src/services/assets/asset-processing-service/assets.types.js +0 -0
  128. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.d.ts +55 -0
  129. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.js +48 -0
  130. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.d.ts +20 -0
  131. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.js +41 -0
  132. package/src/services/{asset-processing-service → assets/asset-processing-service}/index.d.ts +2 -0
  133. package/src/services/{asset-processing-service → assets/asset-processing-service}/index.js +2 -0
  134. package/src/services/{asset-processing-service → assets/asset-processing-service}/processor.interface.d.ts +1 -1
  135. package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/base/base-processor.d.ts +1 -1
  136. package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/base/base-processor.js +9 -4
  137. package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/base/base-script-processor.d.ts +3 -2
  138. package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/base/base-script-processor.js +14 -22
  139. package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/script/file-script.processor.d.ts +1 -1
  140. package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/script/file-script.processor.js +2 -2
  141. package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/script/node-module-script.processor.js +5 -4
  142. package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/stylesheet/file-stylesheet.processor.js +4 -1
  143. package/src/services/assets/browser-bundle.service.d.ts +32 -0
  144. package/src/services/assets/browser-bundle.service.js +33 -0
  145. package/src/services/{page-request-cache-coordinator.service.d.ts → cache/page-request-cache-coordinator.service.d.ts} +2 -2
  146. package/src/services/{page-request-cache-coordinator.service.js → cache/page-request-cache-coordinator.service.js} +3 -1
  147. package/src/services/html/html-rewriter-provider.service.d.ts +37 -0
  148. package/src/services/html/html-rewriter-provider.service.js +65 -0
  149. package/src/services/html/html-transformer.service.d.ts +77 -0
  150. package/src/services/html/html-transformer.service.js +221 -0
  151. package/src/services/invalidation/development-invalidation.service.d.ts +74 -0
  152. package/src/services/invalidation/development-invalidation.service.js +189 -0
  153. package/src/services/module-loading/app-server-module-transpiler.service.d.ts +16 -0
  154. package/src/services/module-loading/app-server-module-transpiler.service.js +34 -0
  155. package/src/services/module-loading/page-module-import.service.d.ts +71 -0
  156. package/src/services/module-loading/page-module-import.service.js +132 -0
  157. package/src/services/module-loading/server-loader.service.d.ts +96 -0
  158. package/src/services/module-loading/server-loader.service.js +32 -0
  159. package/src/services/module-loading/server-module-transpiler.service.d.ts +69 -0
  160. package/src/services/module-loading/server-module-transpiler.service.js +61 -0
  161. package/src/services/runtime-manifest/node-runtime-manifest.service.d.ts +35 -0
  162. package/src/services/runtime-manifest/node-runtime-manifest.service.js +60 -0
  163. package/src/services/runtime-state/dev-graph.service.d.ts +118 -0
  164. package/src/services/runtime-state/dev-graph.service.js +162 -0
  165. package/src/services/runtime-state/entrypoint-dependency-graph.service.d.ts +41 -0
  166. package/src/services/runtime-state/entrypoint-dependency-graph.service.js +85 -0
  167. package/src/services/runtime-state/runtime-specifier-registry.service.d.ts +69 -0
  168. package/src/services/runtime-state/runtime-specifier-registry.service.js +37 -0
  169. package/src/services/runtime-state/server-invalidation-state.service.d.ts +26 -0
  170. package/src/services/runtime-state/server-invalidation-state.service.js +35 -0
  171. package/src/services/{schema-validation-service.d.ts → validation/schema-validation-service.d.ts} +1 -1
  172. package/src/static-site-generator/README.md +26 -0
  173. package/src/static-site-generator/static-site-generator.d.ts +53 -1
  174. package/src/static-site-generator/static-site-generator.js +86 -5
  175. package/src/utils/resolve-work-dir.d.ts +11 -0
  176. package/src/utils/resolve-work-dir.js +31 -0
  177. package/src/watchers/project-watcher.d.ts +37 -21
  178. package/src/watchers/project-watcher.js +109 -66
  179. package/src/watchers/project-watcher.test-helpers.js +1 -0
  180. package/src/adapters/abstract/application-adapter.ts +0 -337
  181. package/src/adapters/abstract/router-adapter.ts +0 -30
  182. package/src/adapters/abstract/server-adapter.ts +0 -79
  183. package/src/adapters/bun/client-bridge.ts +0 -62
  184. package/src/adapters/bun/create-app.ts +0 -189
  185. package/src/adapters/bun/define-api-handler.d.ts +0 -61
  186. package/src/adapters/bun/define-api-handler.ts +0 -114
  187. package/src/adapters/bun/hmr-manager.ts +0 -281
  188. package/src/adapters/bun/index.ts +0 -3
  189. package/src/adapters/bun/server-adapter.ts +0 -492
  190. package/src/adapters/bun/server-lifecycle.ts +0 -154
  191. package/src/adapters/index.ts +0 -6
  192. package/src/adapters/node/create-app.ts +0 -179
  193. package/src/adapters/node/index.ts +0 -9
  194. package/src/adapters/node/node-client-bridge.ts +0 -79
  195. package/src/adapters/node/node-hmr-manager.ts +0 -271
  196. package/src/adapters/node/server-adapter.ts +0 -561
  197. package/src/adapters/node/static-content-server.ts +0 -203
  198. package/src/adapters/shared/api-response.ts +0 -104
  199. package/src/adapters/shared/application-adapter.ts +0 -199
  200. package/src/adapters/shared/explicit-static-route-matcher.ts +0 -134
  201. package/src/adapters/shared/file-route-middleware-pipeline.ts +0 -123
  202. package/src/adapters/shared/fs-server-response-factory.ts +0 -118
  203. package/src/adapters/shared/fs-server-response-matcher.ts +0 -198
  204. package/src/adapters/shared/render-context.ts +0 -105
  205. package/src/adapters/shared/server-adapter.ts +0 -442
  206. package/src/adapters/shared/server-route-handler.ts +0 -166
  207. package/src/adapters/shared/server-static-builder.ts +0 -82
  208. package/src/build/build-adapter.ts +0 -132
  209. package/src/build/build-types.ts +0 -83
  210. package/src/build/esbuild-build-adapter.ts +0 -510
  211. package/src/config/config-builder.ts +0 -474
  212. package/src/constants.ts +0 -39
  213. package/src/create-app.ts +0 -87
  214. package/src/define-api-handler.js +0 -15
  215. package/src/define-api-handler.ts +0 -66
  216. package/src/dev/sc-server.ts +0 -143
  217. package/src/eco/component-render-context.ts +0 -202
  218. package/src/eco/eco.ts +0 -221
  219. package/src/eco/eco.types.ts +0 -202
  220. package/src/eco/eco.utils.ts +0 -89
  221. package/src/eco/global-injector-map.ts +0 -112
  222. package/src/eco/lazy-injector-map.ts +0 -120
  223. package/src/eco/module-dependencies.ts +0 -75
  224. package/src/errors/http-error.ts +0 -72
  225. package/src/errors/index.ts +0 -2
  226. package/src/errors/locals-access-error.ts +0 -7
  227. package/src/global/app-logger.ts +0 -4
  228. package/src/hmr/client/__screenshots__/hmr-runtime.test.browser.ts/HMR-Runtime-HMR-Server-Integration-should-have-HMR-script-injected-in-page-1.png +0 -0
  229. package/src/hmr/client/__screenshots__/hmr-runtime.test.browser.ts/HMR-Runtime-HMR-Server-Integration-should-load-fixture-app-page-1.png +0 -0
  230. package/src/hmr/client/__screenshots__/hmr-runtime.test.browser.ts/HMR-Runtime-WebSocket-Connection-should-connect-to-correct-HMR-endpoint-1.png +0 -0
  231. package/src/hmr/client/hmr-runtime.ts +0 -121
  232. package/src/hmr/hmr-strategy.ts +0 -172
  233. package/src/hmr/hmr.test.e2e.ts +0 -75
  234. package/src/hmr/strategies/default-hmr-strategy.ts +0 -60
  235. package/src/hmr/strategies/js-hmr-strategy.ts +0 -308
  236. package/src/index.browser.ts +0 -3
  237. package/src/index.ts +0 -5
  238. package/src/integrations/ghtml/ghtml-renderer.ts +0 -93
  239. package/src/integrations/ghtml/ghtml.plugin.ts +0 -32
  240. package/src/internal-types.ts +0 -212
  241. package/src/plugins/alias-resolver-plugin.ts +0 -45
  242. package/src/plugins/eco-component-meta-plugin.ts +0 -474
  243. package/src/plugins/integration-plugin.ts +0 -184
  244. package/src/plugins/processor.ts +0 -220
  245. package/src/public-types.ts +0 -1255
  246. package/src/route-renderer/component-graph-executor.ts +0 -84
  247. package/src/route-renderer/component-graph.ts +0 -159
  248. package/src/route-renderer/component-marker.ts +0 -117
  249. package/src/route-renderer/dependency-resolver.ts +0 -596
  250. package/src/route-renderer/html-post-processing.service.d.ts +0 -40
  251. package/src/route-renderer/html-post-processing.service.js +0 -86
  252. package/src/route-renderer/html-post-processing.service.ts +0 -103
  253. package/src/route-renderer/integration-renderer.ts +0 -696
  254. package/src/route-renderer/marker-graph-resolver.ts +0 -153
  255. package/src/route-renderer/page-module-loader.d.ts +0 -61
  256. package/src/route-renderer/page-module-loader.ts +0 -153
  257. package/src/route-renderer/render-execution.service.ts +0 -158
  258. package/src/route-renderer/render-preparation.service.ts +0 -358
  259. package/src/route-renderer/route-renderer.ts +0 -80
  260. package/src/router/fs-router-scanner.ts +0 -217
  261. package/src/router/fs-router.ts +0 -122
  262. package/src/services/asset-processing-service/asset-processing.service.d.ts +0 -41
  263. package/src/services/asset-processing-service/asset-processing.service.ts +0 -306
  264. package/src/services/asset-processing-service/asset.factory.ts +0 -105
  265. package/src/services/asset-processing-service/assets.types.ts +0 -112
  266. package/src/services/asset-processing-service/index.ts +0 -3
  267. package/src/services/asset-processing-service/processor.interface.ts +0 -27
  268. package/src/services/asset-processing-service/processor.registry.ts +0 -18
  269. package/src/services/asset-processing-service/processors/base/base-processor.ts +0 -76
  270. package/src/services/asset-processing-service/processors/base/base-script-processor.ts +0 -105
  271. package/src/services/asset-processing-service/processors/index.ts +0 -5
  272. package/src/services/asset-processing-service/processors/script/content-script.processor.ts +0 -66
  273. package/src/services/asset-processing-service/processors/script/file-script.processor.ts +0 -88
  274. package/src/services/asset-processing-service/processors/script/node-module-script.processor.ts +0 -84
  275. package/src/services/asset-processing-service/processors/stylesheet/content-stylesheet.processor.ts +0 -27
  276. package/src/services/asset-processing-service/processors/stylesheet/file-stylesheet.processor.ts +0 -77
  277. package/src/services/cache/cache.types.ts +0 -126
  278. package/src/services/cache/index.ts +0 -18
  279. package/src/services/cache/memory-cache-store.ts +0 -130
  280. package/src/services/cache/page-cache-service.ts +0 -202
  281. package/src/services/html-transformer.service.d.ts +0 -50
  282. package/src/services/html-transformer.service.js +0 -163
  283. package/src/services/html-transformer.service.ts +0 -217
  284. package/src/services/page-module-import.service.d.ts +0 -37
  285. package/src/services/page-module-import.service.js +0 -88
  286. package/src/services/page-module-import.service.ts +0 -129
  287. package/src/services/page-request-cache-coordinator.service.ts +0 -128
  288. package/src/services/schema-validation-service.ts +0 -204
  289. package/src/services/validation/standard-schema.types.ts +0 -68
  290. package/src/static-site-generator/static-site-generator.ts +0 -359
  291. package/src/utils/css.ts +0 -5
  292. package/src/utils/deep-merge.ts +0 -47
  293. package/src/utils/hash.ts +0 -5
  294. package/src/utils/html.ts +0 -1
  295. package/src/utils/invariant.ts +0 -15
  296. package/src/utils/locals-utils.ts +0 -37
  297. package/src/utils/parse-cli-args.ts +0 -83
  298. package/src/utils/path-utils.module.ts +0 -14
  299. package/src/utils/runtime.ts +0 -44
  300. package/src/utils/server-utils.module.ts +0 -67
  301. package/src/watchers/project-watcher.test-helpers.ts +0 -40
  302. package/src/watchers/project-watcher.ts +0 -306
  303. /package/src/adapters/{bun → shared}/define-api-handler.js +0 -0
  304. /package/src/{services/asset-processing-service/assets.types.js → plugins/runtime-capability.js} +0 -0
  305. /package/src/route-renderer/{component-graph-executor.d.ts → component-graph/component-graph-executor.d.ts} +0 -0
  306. /package/src/route-renderer/{component-graph-executor.js → component-graph/component-graph-executor.js} +0 -0
  307. /package/src/route-renderer/{component-graph.d.ts → component-graph/component-graph.d.ts} +0 -0
  308. /package/src/route-renderer/{component-graph.js → component-graph/component-graph.js} +0 -0
  309. /package/src/route-renderer/{component-marker.d.ts → component-graph/component-marker.d.ts} +0 -0
  310. /package/src/route-renderer/{component-marker.js → component-graph/component-marker.js} +0 -0
  311. /package/src/services/{asset-processing-service → assets/asset-processing-service}/asset.factory.d.ts +0 -0
  312. /package/src/services/{asset-processing-service → assets/asset-processing-service}/processor.interface.js +0 -0
  313. /package/src/services/{asset-processing-service → assets/asset-processing-service}/processor.registry.d.ts +0 -0
  314. /package/src/services/{asset-processing-service → assets/asset-processing-service}/processor.registry.js +0 -0
  315. /package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/index.d.ts +0 -0
  316. /package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/index.js +0 -0
  317. /package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/script/content-script.processor.d.ts +0 -0
  318. /package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/script/content-script.processor.js +0 -0
  319. /package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/script/node-module-script.processor.d.ts +0 -0
  320. /package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/stylesheet/content-stylesheet.processor.d.ts +0 -0
  321. /package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/stylesheet/content-stylesheet.processor.js +0 -0
  322. /package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/stylesheet/file-stylesheet.processor.d.ts +0 -0
  323. /package/src/services/{schema-validation-service.js → validation/schema-validation-service.js} +0 -0
@@ -1,15 +1,50 @@
1
1
  import path from "node:path";
2
- import { DEFAULT_ECOPAGES_HOSTNAME, DEFAULT_ECOPAGES_PORT } from "../constants.js";
2
+ import {
3
+ DEFAULT_ECOPAGES_DIST_DIR,
4
+ DEFAULT_ECOPAGES_HOSTNAME,
5
+ DEFAULT_ECOPAGES_PORT,
6
+ DEFAULT_ECOPAGES_WORK_DIR
7
+ } from "../constants.js";
8
+ import {
9
+ collectConfiguredAppBuildManifestContributions,
10
+ createBuildAdapter,
11
+ getAppServerBuildPlugins,
12
+ setAppBuildAdapter,
13
+ setAppBuildExecutor,
14
+ updateAppBuildManifest
15
+ } from "../build/build-adapter.js";
16
+ import { createAppBuildExecutor } from "../build/dev-build-coordinator.js";
3
17
  import { GHTML_PLUGIN_NAME, ghtmlPlugin } from "../integrations/ghtml/ghtml.plugin.js";
4
18
  import { createEcoComponentMetaPlugin } from "../plugins/eco-component-meta-plugin.js";
19
+ import {
20
+ NoopEntrypointDependencyGraph,
21
+ setAppEntrypointDependencyGraph
22
+ } from "../services/runtime-state/entrypoint-dependency-graph.service.js";
23
+ import {
24
+ createNodeRuntimeManifest,
25
+ setAppNodeRuntimeManifest
26
+ } from "../services/runtime-manifest/node-runtime-manifest.service.js";
27
+ import {
28
+ InMemoryRuntimeSpecifierRegistry,
29
+ setAppRuntimeSpecifierRegistry
30
+ } from "../services/runtime-state/runtime-specifier-registry.service.js";
31
+ import {
32
+ CounterServerInvalidationState,
33
+ setAppServerInvalidationState
34
+ } from "../services/runtime-state/server-invalidation-state.service.js";
5
35
  import { invariant } from "../utils/invariant.js";
6
36
  import { appLogger } from "../global/app-logger.js";
37
+ import { fileSystem } from "@ecopages/file-system";
7
38
  const CONFIG_BUILDER_ERRORS = {
8
39
  DUPLICATE_INTEGRATION_NAMES: "Integrations names must be unique",
9
40
  DUPLICATE_INTEGRATION_EXTENSIONS: "Integrations extensions must be unique",
10
41
  MIXED_JSX_ENGINES: "Both kitajs and react integrations are enabled. Use per-file JSX import source/pragma consistently (e.g. `/** @jsxImportSource react */` for React files and `/** @jsxImportSource @kitajs/html */` for Kita files).",
11
42
  duplicateProcessorName: (name) => `Processor with name "${name}" already exists`,
12
- duplicateLoaderName: (name) => `Loader with name "${name}" already exists`
43
+ duplicateLoaderName: (name) => `Loader with name "${name}" already exists`,
44
+ duplicateSemanticTemplate: (kind, matches) => `Multiple ${kind} templates found: ${matches.join(", ")}`,
45
+ incompatibleRuntimeCapability: (kind, name, runtime, reason) => `Cannot enable ${kind} "${name}" on ${runtime}: ${reason}`,
46
+ unsupportedRuntimeVersion: (kind, name, runtime, current, min) => `Cannot enable ${kind} "${name}" on ${runtime} ${current}: requires runtime version ${min} or newer`,
47
+ invalidRuntimeVersion: (kind, name, version) => `Cannot validate ${kind} "${name}" runtimeCapability.minRuntimeVersion "${version}" because it is not a dot-separated numeric version`
13
48
  };
14
49
  class ConfigBuilder {
15
50
  config = {
@@ -21,12 +56,6 @@ class ConfigBuilder {
21
56
  componentsDir: "components",
22
57
  layoutsDir: "layouts",
23
58
  publicDir: "public",
24
- includesTemplates: {
25
- head: "head.ghtml.ts",
26
- html: "html.ghtml.ts",
27
- seo: "seo.ghtml.ts"
28
- },
29
- error404Template: "404.ghtml.ts",
30
59
  robotsTxt: {
31
60
  preferences: {
32
61
  "*": []
@@ -34,7 +63,7 @@ class ConfigBuilder {
34
63
  },
35
64
  integrations: [],
36
65
  integrationsDependencies: [],
37
- distDir: ".eco",
66
+ distDir: DEFAULT_ECOPAGES_DIST_DIR,
38
67
  defaultMetadata: {
39
68
  title: "Ecopages",
40
69
  description: "This is a static site generated with Ecopages"
@@ -45,6 +74,7 @@ class ConfigBuilder {
45
74
  config: "",
46
75
  componentsDir: "",
47
76
  distDir: "",
77
+ workDir: "",
48
78
  includesDir: "",
49
79
  layoutsDir: "",
50
80
  pagesDir: "",
@@ -55,7 +85,8 @@ class ConfigBuilder {
55
85
  error404TemplatePath: ""
56
86
  },
57
87
  processors: /* @__PURE__ */ new Map(),
58
- loaders: /* @__PURE__ */ new Map()
88
+ loaders: /* @__PURE__ */ new Map(),
89
+ workDir: DEFAULT_ECOPAGES_WORK_DIR
59
90
  };
60
91
  /**
61
92
  * Sets the base URL for the application.
@@ -145,27 +176,6 @@ class ConfigBuilder {
145
176
  this.config.publicDir = publicDir;
146
177
  return this;
147
178
  }
148
- /**
149
- * Sets the templates used for includes.
150
- * These templates are used to build the HTML structure of pages.
151
- *
152
- * @param includesTemplates - An object containing the template file names
153
- * @returns The ConfigBuilder instance for method chaining
154
- */
155
- setIncludesTemplates(includesTemplates) {
156
- this.config.includesTemplates = includesTemplates;
157
- return this;
158
- }
159
- /**
160
- * Sets the template file for the 404 error page.
161
- *
162
- * @param error404Template - The file name of the 404 error template (default: '404.ghtml.ts')
163
- * @returns The ConfigBuilder instance for method chaining
164
- */
165
- setError404Template(error404Template) {
166
- this.config.error404Template = error404Template;
167
- return this;
168
- }
169
179
  /**
170
180
  * Sets the robots.txt configuration.
171
181
  * This determines which paths are allowed/disallowed for search engines.
@@ -191,13 +201,28 @@ class ConfigBuilder {
191
201
  /**
192
202
  * Sets the output directory for the built application.
193
203
  *
194
- * @param distDir - The distribution directory name (default: '.eco')
204
+ * @param distDir - The distribution directory name (default: 'dist')
195
205
  * @returns The ConfigBuilder instance for method chaining
196
206
  */
197
207
  setDistDir(distDir) {
198
208
  this.config.distDir = distDir;
199
209
  return this;
200
210
  }
211
+ /**
212
+ * Sets the internal work directory for runtime-only artifacts.
213
+ *
214
+ * @remarks
215
+ * Use this when deployable output should stay clean while Ecopages still
216
+ * needs a separate workspace for server transpilation caches, runtime
217
+ * manifests, and other internal build products.
218
+ *
219
+ * @param workDir - The internal work directory name
220
+ * @returns The ConfigBuilder instance for method chaining
221
+ */
222
+ setWorkDir(workDir) {
223
+ this.config.workDir = workDir;
224
+ return this;
225
+ }
201
226
  /**
202
227
  * Sets the default metadata for pages.
203
228
  * This is used when a page doesn't specify its own metadata.
@@ -294,35 +319,44 @@ class ConfigBuilder {
294
319
  return this;
295
320
  }
296
321
  createAbsolutePaths(config) {
297
- const {
298
- srcDir,
299
- componentsDir,
300
- includesDir,
301
- layoutsDir,
302
- pagesDir,
303
- publicDir,
304
- distDir,
305
- includesTemplates,
306
- error404Template
307
- } = config;
322
+ const { srcDir, componentsDir, includesDir, layoutsDir, pagesDir, publicDir, distDir, workDir } = config;
308
323
  const projectDir = config.rootDir;
309
324
  const absoluteSrcDir = path.resolve(projectDir, srcDir);
310
325
  const absoluteDistDir = path.resolve(projectDir, distDir);
326
+ const absoluteWorkDir = path.resolve(projectDir, workDir);
327
+ const absoluteIncludesDir = path.join(absoluteSrcDir, includesDir);
328
+ const absolutePagesDir = path.join(absoluteSrcDir, pagesDir);
311
329
  this.config.absolutePaths = {
312
330
  config: path.join(projectDir, "eco.config.ts"),
313
331
  projectDir,
314
332
  srcDir: absoluteSrcDir,
315
333
  distDir: absoluteDistDir,
334
+ workDir: absoluteWorkDir,
316
335
  componentsDir: path.join(absoluteSrcDir, componentsDir),
317
- includesDir: path.join(absoluteSrcDir, includesDir),
336
+ includesDir: absoluteIncludesDir,
318
337
  layoutsDir: path.join(absoluteSrcDir, layoutsDir),
319
- pagesDir: path.join(absoluteSrcDir, pagesDir),
338
+ pagesDir: absolutePagesDir,
320
339
  publicDir: path.join(absoluteSrcDir, publicDir),
321
- htmlTemplatePath: path.join(absoluteSrcDir, includesDir, includesTemplates.html),
322
- error404TemplatePath: path.join(absoluteSrcDir, pagesDir, error404Template)
340
+ htmlTemplatePath: this.resolveSemanticTemplatePath({
341
+ dirPath: absoluteIncludesDir,
342
+ basename: "html"
343
+ }),
344
+ error404TemplatePath: this.resolveSemanticTemplatePath({
345
+ dirPath: absolutePagesDir,
346
+ basename: "404"
347
+ })
323
348
  };
324
349
  return this;
325
350
  }
351
+ resolveSemanticTemplatePath({ dirPath, basename }) {
352
+ const extensions = this.config.templatesExt.length > 0 ? this.config.templatesExt : [".ghtml.ts"];
353
+ const matches = extensions.map((extension) => path.join(dirPath, `${basename}${extension}`)).filter((candidate) => fileSystem.exists(candidate));
354
+ invariant(matches.length <= 1, CONFIG_BUILDER_ERRORS.duplicateSemanticTemplate(basename, matches));
355
+ if (matches.length === 1) {
356
+ return matches[0];
357
+ }
358
+ return path.join(dirPath, `${basename}${extensions[0]}`);
359
+ }
326
360
  createIntegrationTemplatesExt(integrations) {
327
361
  const integrationName = integrations.map((integration) => integration.name);
328
362
  const uniqueName = new Set(integrationName);
@@ -345,6 +379,129 @@ class ConfigBuilder {
345
379
  processor.setContext(this.config);
346
380
  }
347
381
  }
382
+ validateRuntimeCapabilities() {
383
+ const runtimeEnvironment = this.detectRuntimeEnvironment();
384
+ const contributors = [
385
+ ...this.config.integrations.map((integration) => ({
386
+ kind: "integration",
387
+ name: integration.name,
388
+ runtimeCapability: integration.runtimeCapability
389
+ })),
390
+ ...Array.from(this.config.processors.values(), (processor) => ({
391
+ kind: "processor",
392
+ name: processor.name,
393
+ runtimeCapability: processor.runtimeCapability
394
+ }))
395
+ ];
396
+ for (const contributor of contributors) {
397
+ this.validateRuntimeCapability(contributor, runtimeEnvironment);
398
+ }
399
+ }
400
+ validateRuntimeCapability(contributor, environment) {
401
+ const declaration = contributor.runtimeCapability;
402
+ if (!declaration) {
403
+ return;
404
+ }
405
+ for (const tag of declaration.tags) {
406
+ if (environment.supportedTags.has(tag)) {
407
+ continue;
408
+ }
409
+ throw new Error(
410
+ CONFIG_BUILDER_ERRORS.incompatibleRuntimeCapability(
411
+ contributor.kind,
412
+ contributor.name,
413
+ environment.runtime,
414
+ this.describeUnsupportedRuntimeTag(tag)
415
+ )
416
+ );
417
+ }
418
+ if (!declaration.minRuntimeVersion) {
419
+ return;
420
+ }
421
+ const minVersion = this.parseVersion(declaration.minRuntimeVersion);
422
+ if (!minVersion) {
423
+ throw new Error(
424
+ CONFIG_BUILDER_ERRORS.invalidRuntimeVersion(
425
+ contributor.kind,
426
+ contributor.name,
427
+ declaration.minRuntimeVersion
428
+ )
429
+ );
430
+ }
431
+ const currentVersion = this.parseVersion(environment.version);
432
+ if (!currentVersion) {
433
+ return;
434
+ }
435
+ if (this.compareVersions(currentVersion, minVersion) >= 0) {
436
+ return;
437
+ }
438
+ throw new Error(
439
+ CONFIG_BUILDER_ERRORS.unsupportedRuntimeVersion(
440
+ contributor.kind,
441
+ contributor.name,
442
+ environment.runtime,
443
+ environment.version,
444
+ declaration.minRuntimeVersion
445
+ )
446
+ );
447
+ }
448
+ detectRuntimeEnvironment() {
449
+ const bunVersion = this.getBunVersion();
450
+ if (bunVersion) {
451
+ return {
452
+ runtime: "bun",
453
+ version: bunVersion,
454
+ supportedTags: /* @__PURE__ */ new Set([
455
+ "bun-only",
456
+ "node-compatible",
457
+ "requires-native-bun-api",
458
+ "requires-node-builtins"
459
+ ])
460
+ };
461
+ }
462
+ return {
463
+ runtime: "node",
464
+ version: process.versions.node,
465
+ supportedTags: /* @__PURE__ */ new Set(["node-compatible", "requires-node-builtins"])
466
+ };
467
+ }
468
+ getBunVersion() {
469
+ const bun = globalThis;
470
+ return typeof bun.Bun?.version === "string" ? bun.Bun.version : void 0;
471
+ }
472
+ describeUnsupportedRuntimeTag(tag) {
473
+ switch (tag) {
474
+ case "bun-only":
475
+ return "it is Bun-only";
476
+ case "requires-native-bun-api":
477
+ return "it requires the native Bun API";
478
+ case "requires-node-builtins":
479
+ return "it requires Node builtins";
480
+ case "node-compatible":
481
+ return "it requires a Node-compatible runtime";
482
+ }
483
+ }
484
+ parseVersion(version) {
485
+ const normalized = version.trim().replace(/^v/i, "");
486
+ if (!/^\d+(?:\.\d+)*$/.test(normalized)) {
487
+ return void 0;
488
+ }
489
+ return normalized.split(".").map((segment) => Number(segment));
490
+ }
491
+ compareVersions(left, right) {
492
+ const maxLength = Math.max(left.length, right.length);
493
+ for (let index = 0; index < maxLength; index += 1) {
494
+ const leftValue = left[index] ?? 0;
495
+ const rightValue = right[index] ?? 0;
496
+ if (leftValue > rightValue) {
497
+ return 1;
498
+ }
499
+ if (leftValue < rightValue) {
500
+ return -1;
501
+ }
502
+ }
503
+ return 0;
504
+ }
348
505
  /**
349
506
  * Initializes default loaders that are required for EcoPages to function.
350
507
  * This includes the eco-component-meta-plugin which auto-injects __eco metadata into component configs.
@@ -379,10 +536,26 @@ class ConfigBuilder {
379
536
  if (!this.config.integrations.some((integration) => integration.name === GHTML_PLUGIN_NAME)) {
380
537
  this.config.integrations.push(ghtmlPlugin());
381
538
  }
382
- this.createAbsolutePaths(this.config);
383
539
  this.createIntegrationTemplatesExt(this.config.integrations);
540
+ this.createAbsolutePaths(this.config);
384
541
  await this.initializeDefaultLoaders();
385
542
  this.initializeProcessors();
543
+ this.validateRuntimeCapabilities();
544
+ const buildAdapter = createBuildAdapter();
545
+ setAppBuildAdapter(this.config, buildAdapter);
546
+ updateAppBuildManifest(this.config, await collectConfiguredAppBuildManifestContributions(this.config));
547
+ setAppServerInvalidationState(this.config, new CounterServerInvalidationState());
548
+ setAppEntrypointDependencyGraph(this.config, new NoopEntrypointDependencyGraph());
549
+ setAppRuntimeSpecifierRegistry(this.config, new InMemoryRuntimeSpecifierRegistry());
550
+ setAppBuildExecutor(
551
+ this.config,
552
+ createAppBuildExecutor({
553
+ development: false,
554
+ adapter: buildAdapter,
555
+ getPlugins: () => getAppServerBuildPlugins(this.config)
556
+ })
557
+ );
558
+ setAppNodeRuntimeManifest(this.config, createNodeRuntimeManifest(this.config));
386
559
  return this.config;
387
560
  }
388
561
  }
@@ -30,3 +30,16 @@ export declare const GENERATED_BASE_PATHS: {
30
30
  };
31
31
  export declare const DEFAULT_ECOPAGES_PORT = 3000;
32
32
  export declare const DEFAULT_ECOPAGES_HOSTNAME = "localhost";
33
+ /**
34
+ * Default directory used for deployable output.
35
+ */
36
+ export declare const DEFAULT_ECOPAGES_DIST_DIR = "dist";
37
+ /**
38
+ * Default internal working directory used for runtime-only artifacts.
39
+ *
40
+ * @remarks
41
+ * This directory is a local tool workspace and is not intended for deployment.
42
+ * It owns transpiled server modules, runtime manifests, and processor caches
43
+ * so the export directory can remain a clean deployable tree.
44
+ */
45
+ export declare const DEFAULT_ECOPAGES_WORK_DIR = ".eco";
package/src/constants.js CHANGED
@@ -10,9 +10,13 @@ const GENERATED_BASE_PATHS = {
10
10
  };
11
11
  const DEFAULT_ECOPAGES_PORT = 3e3;
12
12
  const DEFAULT_ECOPAGES_HOSTNAME = "localhost";
13
+ const DEFAULT_ECOPAGES_DIST_DIR = "dist";
14
+ const DEFAULT_ECOPAGES_WORK_DIR = ".eco";
13
15
  export {
16
+ DEFAULT_ECOPAGES_DIST_DIR,
14
17
  DEFAULT_ECOPAGES_HOSTNAME,
15
18
  DEFAULT_ECOPAGES_PORT,
19
+ DEFAULT_ECOPAGES_WORK_DIR,
16
20
  GENERATED_BASE_PATHS,
17
21
  IS_BUN,
18
22
  RESOLVED_ASSETS_DIR,
@@ -1,22 +1,27 @@
1
1
  import type { EcoPagesAppConfig } from './internal-types';
2
+ import type { EcoNavigationRuntime } from './router/client/navigation-coordinator';
2
3
 
3
4
  type HMRHandler = (url: string) => Promise<void>;
4
-
5
- type ReloadPageFunction = (options: { clearCache: boolean }) => Promise<void>;
5
+ type CleanupPageRootFunction = () => void;
6
+ type EcoPageRoot = { render: (node: unknown) => void; unmount: () => void };
7
+ type EcoPageData = {
8
+ module: string;
9
+ props: Record<string, unknown>;
10
+ };
11
+ type EcoPagesWindowRuntime = {
12
+ hmrHandlers?: Record<string, HMRHandler>;
13
+ navigation?: EcoNavigationRuntime;
14
+ react?: {
15
+ cleanupPageRoot?: CleanupPageRootFunction;
16
+ pageRoot?: EcoPageRoot | null;
17
+ };
18
+ page?: EcoPageData;
19
+ };
6
20
 
7
21
  declare global {
8
- var ecoConfig: EcoPagesAppConfig;
9
-
10
22
  interface Window {
11
- /** Registered HMR handlers for specific module paths */
12
- __ecopages_hmr_handlers__?: Record<string, HMRHandler>;
13
- /** Function to reload the current page, used for layout updates */
14
- __ecopages_reload_current_page__?: ReloadPageFunction;
15
- /** Page data registry - contains module path and props for current page */
16
- __ECO_PAGE__?: {
17
- module: string;
18
- props: Record<string, unknown>;
19
- };
23
+ /** Shared Ecopages browser runtime state */
24
+ __ECO_PAGES__?: EcoPagesWindowRuntime;
20
25
  }
21
26
  }
22
27
 
package/src/eco/README.md CHANGED
@@ -7,10 +7,12 @@ A unified API for defining components, pages, and page data in EcoPages.
7
7
  The `eco` namespace provides a consistent, type-safe interface for:
8
8
 
9
9
  1. **`eco.component()`** - Factory for defining reusable components with dependencies and optional lazy-loading
10
- 2. **`eco.page()`** - Factory for defining page components with optional inline `staticPaths`, `staticProps`, and `metadata`
11
- 3. **`eco.metadata()`** - Type-safe wrapper for page metadata (legacy pattern)
12
- 4. **`eco.staticPaths()`** - Type-safe wrapper for dynamic route generation (legacy pattern)
13
- 5. **`eco.staticProps()`** - Type-safe wrapper for static data fetching (legacy pattern)
10
+ 2. **`eco.html()`** - Semantic alias for the document shell component (the outermost HTML wrapper)
11
+ 3. **`eco.layout()`** - Semantic alias for route layout components (page-level wrappers)
12
+ 4. **`eco.page()`** - Factory for defining page components with optional inline `staticPaths`, `staticProps`, and `metadata`
13
+ 5. **`eco.metadata()`** - Type-safe wrapper for page metadata (legacy pattern)
14
+ 6. **`eco.staticPaths()`** - Type-safe wrapper for dynamic route generation (legacy pattern)
15
+ 7. **`eco.staticProps()`** - Type-safe wrapper for static data fetching (legacy pattern)
14
16
 
15
17
  ## Component Patterns
16
18
 
@@ -42,9 +44,9 @@ export function Card({ children, class: className }: CardProps) {
42
44
 
43
45
  > **Note:** If your component requires a dedicated CSS file, use `eco.component()` instead to manage the stylesheet dependency.
44
46
 
45
- ### Plain React Components (With Bun)
47
+ ### Plain React Components
46
48
 
47
- When using Bun, React components with hooks and Tailwind CSS work out of the box without `eco.component()`. Bun auto-imports dependencies, so you can write standard React components:
49
+ React components with hooks and Tailwind CSS work out of the box without `eco.component()`. Standard React components can be used directly:
48
50
 
49
51
  ```tsx
50
52
  import { useEffect, useState } from 'react';
@@ -99,7 +101,6 @@ export function ThemeToggle() {
99
101
  - React components with hooks (`useState`, `useEffect`, etc.)
100
102
  - Components styled with Tailwind CSS classes
101
103
  - Interactive UI that doesn't require external scripts or dedicated stylesheets
102
- - Bun handles all imports automatically
103
104
 
104
105
  > **Note:** Use `eco.component()` only when you need to manage external stylesheets, scripts, or lazy loading. For React components relying solely on hooks and Tailwind, plain functions are simpler and sufficient.
105
106
 
@@ -128,14 +129,14 @@ export const Counter = eco.component({
128
129
 
129
130
  ### Comparison
130
131
 
131
- | Aspect | Simple JSX | Plain React (Bun) | `eco.component()` |
132
- | -------------------- | ---------- | ----------------- | ----------------- |
133
- | React hooks | No | Yes | Yes |
134
- | Scripts/Stylesheets | No | No | Yes |
135
- | Lazy loading | No | No | Yes |
136
- | Hydration strategies | No | No | Yes |
137
- | Runtime cost | Zero | Minimal | Minimal |
138
- | Use case | Static UI | Interactive UI | Advanced UI |
132
+ | Aspect | Simple JSX | Plain React | `eco.component()` |
133
+ | -------------------- | ---------- | -------------- | ----------------- |
134
+ | React hooks | No | Yes | Yes |
135
+ | Scripts/Stylesheets | No | No | Yes |
136
+ | Lazy loading | No | No | Yes |
137
+ | Hydration strategies | No | No | Yes |
138
+ | Runtime cost | Zero | Minimal | Minimal |
139
+ | Use case | Static UI | Interactive UI | Advanced UI |
139
140
 
140
141
  All patterns can coexist in the same project. Use the right tool for the job.
141
142
 
@@ -231,6 +232,53 @@ Both patterns work and can be mixed - the renderer checks for attached propertie
231
232
 
232
233
  ## API Reference
233
234
 
235
+ ### `eco.html()`
236
+
237
+ Creates the document shell component — the outermost HTML wrapper rendered once per page. Semantically equivalent to `eco.component()` but signals intent to tooling and readers that this component owns the full document structure (`<html>`, `<head>`, `<body>`).
238
+
239
+ ```tsx
240
+ import { eco } from '@ecopages/core';
241
+
242
+ export const Document = eco.html({
243
+ dependencies: {
244
+ stylesheets: ['./document.css'],
245
+ },
246
+ render: ({ children, metadata }) => (
247
+ <html lang="en">
248
+ <head>
249
+ <title>{metadata?.title ?? 'EcoPages'}</title>
250
+ </head>
251
+ <body>{children}</body>
252
+ </html>
253
+ ),
254
+ });
255
+ ```
256
+
257
+ ### `eco.layout()`
258
+
259
+ Creates a route layout component — a wrapper rendered around page content. Semantically equivalent to `eco.component()` but clearly communicates that the component is intended to be used as a `layout` in `eco.page()`.
260
+
261
+ ```tsx
262
+ import { eco } from '@ecopages/core';
263
+
264
+ export const BaseLayout = eco.layout({
265
+ dependencies: {
266
+ stylesheets: ['./base-layout.css'],
267
+ scripts: ['./base-layout.script.ts'],
268
+ },
269
+ render: ({ children }) => <main>{children}</main>,
270
+ });
271
+ ```
272
+
273
+ Use `eco.layout()` components as the `layout` option in `eco.page()`:
274
+
275
+ ```tsx
276
+ export default eco.page({
277
+ layout: BaseLayout,
278
+ render: () => <h1>Hello</h1>,
279
+ });
280
+ ```
281
+
234
282
  ### `eco.component()`
235
283
 
236
284
  Define a reusable component with dependencies.
@@ -516,12 +564,18 @@ interface EcoComponentDependencies {
516
564
  components?: EcoComponent[];
517
565
  }
518
566
 
567
+ // Shared base option shape used by component(), html(), and layout()
519
568
  interface ComponentOptions<P, E = EcoPagesElement> {
520
569
  componentDir?: string;
521
570
  dependencies?: EcoComponentDependencies;
522
571
  render: (props: P) => E;
523
572
  }
524
573
 
574
+ // html() and layout() accept the same options as component() but return
575
+ // narrower types to signal intent (EcoHtmlComponent / EcoLayoutComponent).
576
+ type HtmlOptions<E = EcoPagesElement> = ComponentOptions<Record<string, unknown>, E>;
577
+ type LayoutOptions<E = EcoPagesElement> = ComponentOptions<{ children: E }, E>;
578
+
525
579
  interface PageOptions<T, E = EcoPagesElement> {
526
580
  componentDir?: string;
527
581
  dependencies?: EcoComponentDependencies;
@@ -551,7 +605,7 @@ type PagePropsFor<T> =
551
605
  ```tsx
552
606
  import { eco } from '@ecopages/core';
553
607
 
554
- eco. // IDE shows: component, page, metadata, staticPaths, staticProps
608
+ eco. // IDE shows: component, html, layout, page, metadata, staticPaths, staticProps
555
609
  ```
556
610
 
557
611
  ### 2. Type Safety
@@ -1,5 +1,5 @@
1
1
  import type { EcoComponent } from '../public-types.js';
2
- import type { MarkerNodeId } from '../route-renderer/component-marker.js';
2
+ import type { MarkerNodeId } from '../route-renderer/component-graph/component-marker.js';
3
3
  /**
4
4
  * Outcome returned by boundary policy during one component render pass.
5
5
  *
@@ -1,30 +1,39 @@
1
- const contextStack = [];
2
- let nodeContextStorage = null;
3
- let nodeContextStorageLoader = null;
1
+ const GLOBAL_COMPONENT_RENDER_CONTEXT_STATE_KEY = "__ECOPAGES_COMPONENT_RENDER_CONTEXT_STATE__";
2
+ function getComponentRenderContextState() {
3
+ const globalScope = globalThis;
4
+ globalScope[GLOBAL_COMPONENT_RENDER_CONTEXT_STATE_KEY] ??= {
5
+ contextStack: [],
6
+ nodeContextStorage: null,
7
+ nodeContextStorageLoader: null
8
+ };
9
+ return globalScope[GLOBAL_COMPONENT_RENDER_CONTEXT_STATE_KEY];
10
+ }
4
11
  async function getContextStorage() {
5
- if (nodeContextStorage) {
6
- return nodeContextStorage;
12
+ const state = getComponentRenderContextState();
13
+ if (state.nodeContextStorage) {
14
+ return state.nodeContextStorage;
7
15
  }
8
- if (nodeContextStorageLoader) {
9
- return nodeContextStorageLoader;
16
+ if (state.nodeContextStorageLoader) {
17
+ return state.nodeContextStorageLoader;
10
18
  }
11
- nodeContextStorageLoader = import("node:async_hooks").then((module) => {
19
+ state.nodeContextStorageLoader = import("node:async_hooks").then((module) => {
12
20
  const storage = new module.AsyncLocalStorage();
13
- nodeContextStorage = {
21
+ state.nodeContextStorage = {
14
22
  getStore: () => storage.getStore(),
15
23
  run: (store, callback) => storage.run(store, callback)
16
24
  };
17
- return nodeContextStorage;
25
+ return state.nodeContextStorage;
18
26
  }).catch(() => {
19
- nodeContextStorage = null;
27
+ state.nodeContextStorage = null;
20
28
  return null;
21
29
  }).finally(() => {
22
- nodeContextStorageLoader = null;
30
+ state.nodeContextStorageLoader = null;
23
31
  });
24
- return nodeContextStorageLoader;
32
+ return state.nodeContextStorageLoader;
25
33
  }
26
34
  function getComponentRenderContext() {
27
- return nodeContextStorage?.getStore() ?? contextStack[contextStack.length - 1];
35
+ const state = getComponentRenderContextState();
36
+ return state.nodeContextStorage?.getStore() ?? state.contextStack[state.contextStack.length - 1];
28
37
  }
29
38
  function createNodeId(context) {
30
39
  context.nextNodeId += 1;
@@ -53,11 +62,12 @@ async function runWithComponentRenderContext(input, render) {
53
62
  if (storage) {
54
63
  value = await storage.run(context, render);
55
64
  } else {
56
- contextStack.push(context);
65
+ const state = getComponentRenderContextState();
66
+ state.contextStack.push(context);
57
67
  try {
58
68
  value = await render();
59
69
  } finally {
60
- contextStack.pop();
70
+ state.contextStack.pop();
61
71
  }
62
72
  }
63
73
  return {