@ecopages/core 0.2.0-alpha.27 → 0.2.0-alpha.28

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 (546) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/package.json +162 -72
  3. package/src/adapters/abstract/application-adapter.d.ts +194 -0
  4. package/src/adapters/abstract/application-adapter.js +121 -0
  5. package/src/adapters/abstract/router-adapter.d.ts +26 -0
  6. package/src/adapters/abstract/router-adapter.js +5 -0
  7. package/src/adapters/abstract/server-adapter.d.ts +69 -0
  8. package/src/adapters/abstract/server-adapter.js +15 -0
  9. package/src/adapters/bun/client-bridge.d.ts +34 -0
  10. package/src/adapters/bun/client-bridge.js +48 -0
  11. package/src/adapters/bun/create-app.d.ts +53 -0
  12. package/src/adapters/bun/create-app.js +153 -0
  13. package/src/adapters/bun/hmr-manager.d.ts +131 -0
  14. package/src/adapters/bun/hmr-manager.js +312 -0
  15. package/src/adapters/bun/index.d.ts +2 -0
  16. package/src/adapters/bun/index.js +8 -0
  17. package/src/adapters/bun/server-adapter.d.ts +155 -0
  18. package/src/adapters/bun/server-adapter.js +393 -0
  19. package/src/adapters/bun/server-lifecycle.d.ts +63 -0
  20. package/src/adapters/bun/server-lifecycle.js +92 -0
  21. package/src/adapters/create-app.d.ts +20 -0
  22. package/src/adapters/create-app.js +66 -0
  23. package/src/adapters/index.d.ts +2 -0
  24. package/src/adapters/index.js +8 -0
  25. package/src/adapters/node/create-app.d.ts +18 -0
  26. package/src/adapters/node/create-app.js +149 -0
  27. package/src/adapters/node/node-client-bridge.d.ts +26 -0
  28. package/src/adapters/node/node-client-bridge.js +66 -0
  29. package/src/adapters/node/node-hmr-manager.d.ts +121 -0
  30. package/src/adapters/node/node-hmr-manager.js +290 -0
  31. package/src/adapters/node/server-adapter.d.ts +162 -0
  32. package/src/adapters/node/server-adapter.js +368 -0
  33. package/src/adapters/node/static-content-server.d.ts +60 -0
  34. package/src/adapters/node/static-content-server.js +194 -0
  35. package/src/adapters/shared/api-response.d.ts +52 -0
  36. package/src/adapters/shared/api-response.js +96 -0
  37. package/src/adapters/shared/application-adapter.d.ts +18 -0
  38. package/src/adapters/shared/application-adapter.js +90 -0
  39. package/src/adapters/shared/define-api-handler.d.ts +25 -0
  40. package/src/adapters/shared/define-api-handler.js +15 -0
  41. package/src/adapters/shared/explicit-static-render-preparation.d.ts +25 -0
  42. package/src/adapters/shared/explicit-static-render-preparation.js +26 -0
  43. package/src/adapters/shared/explicit-static-route-matcher.d.ts +41 -0
  44. package/src/adapters/shared/explicit-static-route-matcher.js +101 -0
  45. package/src/adapters/shared/file-route-middleware-pipeline.d.ts +62 -0
  46. package/src/adapters/shared/file-route-middleware-pipeline.js +90 -0
  47. package/src/adapters/shared/fs-server-response-factory.d.ts +23 -0
  48. package/src/adapters/shared/fs-server-response-factory.js +81 -0
  49. package/src/adapters/shared/fs-server-response-matcher.d.ts +75 -0
  50. package/src/adapters/shared/fs-server-response-matcher.js +186 -0
  51. package/src/adapters/shared/hmr-entrypoint-registrar.d.ts +55 -0
  52. package/src/adapters/shared/hmr-entrypoint-registrar.js +87 -0
  53. package/src/adapters/shared/hmr-html-response.d.ts +22 -0
  54. package/src/adapters/shared/hmr-html-response.js +32 -0
  55. package/src/adapters/shared/render-context.d.ts +15 -0
  56. package/src/adapters/shared/render-context.js +72 -0
  57. package/src/adapters/shared/runtime-bootstrap.d.ts +38 -0
  58. package/src/adapters/shared/runtime-bootstrap.js +43 -0
  59. package/src/adapters/shared/server-adapter.d.ts +108 -0
  60. package/src/adapters/shared/server-adapter.js +429 -0
  61. package/src/adapters/shared/server-route-handler.d.ts +89 -0
  62. package/src/adapters/shared/server-route-handler.js +111 -0
  63. package/src/adapters/shared/server-static-builder.d.ts +71 -0
  64. package/src/adapters/shared/server-static-builder.js +100 -0
  65. package/src/build/build-adapter.d.ts +239 -0
  66. package/src/build/build-adapter.js +642 -0
  67. package/src/build/build-manifest.d.ts +27 -0
  68. package/src/build/build-manifest.js +30 -0
  69. package/src/build/build-types.d.ts +57 -0
  70. package/src/build/build-types.js +0 -0
  71. package/src/build/dev-build-coordinator.d.ts +72 -0
  72. package/src/build/dev-build-coordinator.js +154 -0
  73. package/src/build/esbuild-build-adapter.d.ts +78 -0
  74. package/src/build/esbuild-build-adapter.js +505 -0
  75. package/src/build/runtime-build-executor.d.ts +14 -0
  76. package/src/build/runtime-build-executor.js +22 -0
  77. package/src/build/runtime-specifier-alias-plugin.d.ts +15 -0
  78. package/src/build/runtime-specifier-alias-plugin.js +35 -0
  79. package/src/build/runtime-specifier-aliases.d.ts +5 -0
  80. package/src/build/runtime-specifier-aliases.js +95 -0
  81. package/src/config/config-builder.d.ts +252 -0
  82. package/src/config/config-builder.js +598 -0
  83. package/src/config/{constants.ts → constants.d.ts} +13 -22
  84. package/src/config/constants.js +25 -0
  85. package/src/dev/host-runtime.d.ts +10 -0
  86. package/src/dev/host-runtime.js +24 -0
  87. package/src/dev/sc-server.d.ts +30 -0
  88. package/src/dev/sc-server.js +111 -0
  89. package/src/eco/eco.browser.d.ts +2 -0
  90. package/src/eco/eco.browser.js +83 -0
  91. package/src/eco/eco.d.ts +9 -0
  92. package/src/eco/eco.js +85 -0
  93. package/src/eco/eco.types.d.ts +178 -0
  94. package/src/eco/eco.types.js +0 -0
  95. package/src/eco/eco.utils.d.ts +1 -0
  96. package/src/eco/eco.utils.js +10 -0
  97. package/src/eco/global-injector-map.d.ts +16 -0
  98. package/src/eco/global-injector-map.js +80 -0
  99. package/src/eco/lazy-injector-map.d.ts +8 -0
  100. package/src/eco/lazy-injector-map.js +70 -0
  101. package/src/eco/module-dependencies.d.ts +18 -0
  102. package/src/eco/module-dependencies.js +49 -0
  103. package/src/errors/http-error.d.ts +31 -0
  104. package/src/errors/http-error.js +50 -0
  105. package/src/errors/index.d.ts +3 -0
  106. package/src/errors/index.js +6 -0
  107. package/src/errors/locals-access-error.d.ts +4 -0
  108. package/src/errors/locals-access-error.js +9 -0
  109. package/src/global/app-logger.d.ts +2 -0
  110. package/src/global/app-logger.js +6 -0
  111. package/src/hmr/client/hmr-runtime.d.ts +5 -0
  112. package/src/hmr/client/hmr-runtime.js +117 -0
  113. package/src/hmr/hmr-strategy.d.ts +162 -0
  114. package/src/hmr/hmr-strategy.js +44 -0
  115. package/src/hmr/hmr.postcss.test.e2e.d.ts +1 -0
  116. package/src/hmr/hmr.postcss.test.e2e.js +31 -0
  117. package/src/hmr/hmr.test.e2e.d.ts +1 -0
  118. package/src/hmr/hmr.test.e2e.js +43 -0
  119. package/src/hmr/strategies/default-hmr-strategy.d.ts +43 -0
  120. package/src/hmr/strategies/default-hmr-strategy.js +34 -0
  121. package/src/hmr/strategies/js-hmr-strategy.d.ts +134 -0
  122. package/src/hmr/strategies/js-hmr-strategy.js +178 -0
  123. package/src/index.browser.d.ts +3 -0
  124. package/src/index.browser.js +4 -0
  125. package/src/index.d.ts +6 -0
  126. package/src/index.js +21 -0
  127. package/src/integrations/ghtml/ghtml-renderer.d.ts +16 -0
  128. package/src/integrations/ghtml/ghtml-renderer.js +57 -0
  129. package/src/integrations/ghtml/ghtml.constants.d.ts +1 -0
  130. package/src/integrations/ghtml/ghtml.constants.js +4 -0
  131. package/src/integrations/ghtml/ghtml.plugin.d.ts +16 -0
  132. package/src/integrations/ghtml/ghtml.plugin.js +20 -0
  133. package/src/plugins/alias-resolver-plugin.d.ts +2 -0
  134. package/src/plugins/alias-resolver-plugin.js +53 -0
  135. package/src/plugins/eco-component-meta-plugin.d.ts +108 -0
  136. package/src/plugins/eco-component-meta-plugin.js +162 -0
  137. package/src/plugins/foreign-jsx-override-plugin.d.ts +31 -0
  138. package/src/plugins/foreign-jsx-override-plugin.js +35 -0
  139. package/src/plugins/integration-plugin.d.ts +215 -0
  140. package/src/plugins/integration-plugin.js +189 -0
  141. package/src/plugins/processor.d.ts +97 -0
  142. package/src/plugins/processor.js +141 -0
  143. package/src/plugins/{runtime-capability.ts → runtime-capability.d.ts} +3 -8
  144. package/src/plugins/runtime-capability.js +0 -0
  145. package/src/plugins/source-transform.d.ts +46 -0
  146. package/src/plugins/source-transform.js +71 -0
  147. package/src/route-renderer/orchestration/component-render-context.d.ts +89 -0
  148. package/src/route-renderer/orchestration/component-render-context.js +147 -0
  149. package/src/route-renderer/orchestration/declared-ownership-graph.d.ts +18 -0
  150. package/src/route-renderer/orchestration/declared-ownership-graph.js +34 -0
  151. package/src/route-renderer/orchestration/foreign-subtree-execution.service.d.ts +108 -0
  152. package/src/route-renderer/orchestration/foreign-subtree-execution.service.js +206 -0
  153. package/src/route-renderer/orchestration/integration-renderer.d.ts +516 -0
  154. package/src/route-renderer/orchestration/integration-renderer.js +909 -0
  155. package/src/route-renderer/orchestration/ownership-planning.service.d.ts +24 -0
  156. package/src/route-renderer/orchestration/ownership-planning.service.js +63 -0
  157. package/src/route-renderer/orchestration/ownership-validation.service.d.ts +29 -0
  158. package/src/route-renderer/orchestration/ownership-validation.service.js +53 -0
  159. package/src/route-renderer/orchestration/processed-asset-dedupe.d.ts +2 -0
  160. package/src/route-renderer/orchestration/processed-asset-dedupe.js +23 -0
  161. package/src/route-renderer/orchestration/queued-foreign-subtree-resolution.service.d.ts +90 -0
  162. package/src/route-renderer/orchestration/queued-foreign-subtree-resolution.service.js +158 -0
  163. package/src/route-renderer/orchestration/render-output.utils.d.ts +66 -0
  164. package/src/route-renderer/orchestration/render-output.utils.js +171 -0
  165. package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +120 -0
  166. package/src/route-renderer/orchestration/route-render-orchestrator.js +388 -0
  167. package/src/route-renderer/orchestration/template-serialization.d.ts +38 -0
  168. package/src/route-renderer/orchestration/template-serialization.js +45 -0
  169. package/src/route-renderer/page-loading/component-dependency-collection.d.ts +37 -0
  170. package/src/route-renderer/page-loading/component-dependency-collection.js +132 -0
  171. package/src/route-renderer/page-loading/declared-asset-collection.d.ts +24 -0
  172. package/src/route-renderer/page-loading/declared-asset-collection.js +106 -0
  173. package/src/route-renderer/page-loading/dependency-resolver.d.ts +35 -0
  174. package/src/route-renderer/page-loading/dependency-resolver.js +115 -0
  175. package/src/route-renderer/page-loading/ecopages-virtual-imports.d.ts +11 -0
  176. package/src/route-renderer/page-loading/ecopages-virtual-imports.js +57 -0
  177. package/src/route-renderer/page-loading/lazy-entry-collection.d.ts +45 -0
  178. package/src/route-renderer/page-loading/lazy-entry-collection.js +105 -0
  179. package/src/route-renderer/page-loading/lazy-trigger-planning.d.ts +19 -0
  180. package/src/route-renderer/page-loading/lazy-trigger-planning.js +40 -0
  181. package/src/route-renderer/page-loading/module-declaration-aggregation.d.ts +5 -0
  182. package/src/route-renderer/page-loading/module-declaration-aggregation.js +33 -0
  183. package/src/route-renderer/page-loading/module-declaration-scripts.d.ts +3 -0
  184. package/src/route-renderer/page-loading/module-declaration-scripts.js +18 -0
  185. package/src/route-renderer/page-loading/page-dependency-bundling.d.ts +13 -0
  186. package/src/route-renderer/page-loading/page-dependency-bundling.js +137 -0
  187. package/src/route-renderer/page-loading/page-module-loader.d.ts +90 -0
  188. package/src/route-renderer/page-loading/page-module-loader.js +127 -0
  189. package/src/route-renderer/route-renderer.d.ts +69 -0
  190. package/src/route-renderer/route-renderer.js +80 -0
  191. package/src/router/client/link-intent.js +34 -0
  192. package/src/router/client/link-intent.test.browser.d.ts +1 -0
  193. package/src/router/client/link-intent.test.browser.js +43 -0
  194. package/src/router/client/navigation-coordinator.d.ts +169 -0
  195. package/src/router/client/navigation-coordinator.js +215 -0
  196. package/src/router/server/route-registry.d.ts +78 -0
  197. package/src/router/server/route-registry.js +262 -0
  198. package/src/services/assets/asset-processing-service/asset-dependency-keys.d.ts +3 -0
  199. package/src/services/assets/asset-processing-service/asset-dependency-keys.js +56 -0
  200. package/src/services/assets/asset-processing-service/asset-processing.service.d.ts +103 -0
  201. package/src/services/assets/asset-processing-service/asset-processing.service.js +285 -0
  202. package/src/services/assets/asset-processing-service/asset.factory.d.ts +17 -0
  203. package/src/services/assets/asset-processing-service/asset.factory.js +82 -0
  204. package/src/services/assets/asset-processing-service/assets.types.d.ts +103 -0
  205. package/src/services/assets/asset-processing-service/assets.types.js +0 -0
  206. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.d.ts +55 -0
  207. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.js +49 -0
  208. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.d.ts +20 -0
  209. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.js +41 -0
  210. package/src/services/assets/asset-processing-service/grouped-content-bundles.d.ts +30 -0
  211. package/src/services/assets/asset-processing-service/grouped-content-bundles.js +65 -0
  212. package/src/services/assets/asset-processing-service/index.d.ts +6 -0
  213. package/src/services/assets/asset-processing-service/index.js +6 -0
  214. package/src/services/assets/asset-processing-service/page-package.d.ts +3 -0
  215. package/src/services/assets/asset-processing-service/page-package.js +74 -0
  216. package/src/services/assets/asset-processing-service/{processor.interface.ts → processor.interface.d.ts} +5 -10
  217. package/src/services/assets/asset-processing-service/processor.interface.js +6 -0
  218. package/src/services/assets/asset-processing-service/processor.registry.d.ts +8 -0
  219. package/src/services/assets/asset-processing-service/processor.registry.js +15 -0
  220. package/src/services/assets/asset-processing-service/processors/base/base-processor.d.ts +24 -0
  221. package/src/services/assets/asset-processing-service/processors/base/base-processor.js +65 -0
  222. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.d.ts +22 -0
  223. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.js +136 -0
  224. package/src/services/assets/asset-processing-service/processors/index.d.ts +5 -0
  225. package/src/services/assets/asset-processing-service/processors/index.js +5 -0
  226. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.d.ts +6 -0
  227. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js +119 -0
  228. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.d.ts +9 -0
  229. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.js +97 -0
  230. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.d.ts +7 -0
  231. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +79 -0
  232. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.d.ts +8 -0
  233. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.js +59 -0
  234. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.d.ts +9 -0
  235. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.js +69 -0
  236. package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.d.ts +18 -0
  237. package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.js +45 -0
  238. package/src/services/assets/browser-bundle.service.d.ts +73 -0
  239. package/src/services/assets/browser-bundle.service.js +41 -0
  240. package/src/services/cache/cache.types.d.ts +107 -0
  241. package/src/services/cache/cache.types.js +0 -0
  242. package/src/services/cache/index.d.ts +7 -0
  243. package/src/services/cache/index.js +7 -0
  244. package/src/services/cache/memory-cache-store.d.ts +42 -0
  245. package/src/services/cache/memory-cache-store.js +98 -0
  246. package/src/services/cache/page-cache-service.d.ts +70 -0
  247. package/src/services/cache/page-cache-service.js +152 -0
  248. package/src/services/cache/page-request-cache-coordinator.service.d.ts +75 -0
  249. package/src/services/cache/page-request-cache-coordinator.service.js +109 -0
  250. package/src/services/html/html-rewriter-provider.service.d.ts +37 -0
  251. package/src/services/html/html-rewriter-provider.service.js +68 -0
  252. package/src/services/html/html-transformer.service.d.ts +87 -0
  253. package/src/services/html/html-transformer.service.js +216 -0
  254. package/src/services/invalidation/development-invalidation.service.d.ts +74 -0
  255. package/src/services/invalidation/development-invalidation.service.js +190 -0
  256. package/src/services/module-loading/app-module-loader.service.d.ts +7 -0
  257. package/src/services/module-loading/app-module-loader.service.js +0 -0
  258. package/src/services/module-loading/app-server-module-transpiler.service.d.ts +24 -0
  259. package/src/services/module-loading/app-server-module-transpiler.service.js +115 -0
  260. package/src/services/module-loading/host-module-loader-registry.d.ts +4 -0
  261. package/src/services/module-loading/host-module-loader-registry.js +15 -0
  262. package/src/services/module-loading/{module-loading-types.ts → module-loading-types.d.ts} +0 -1
  263. package/src/services/module-loading/module-loading-types.js +0 -0
  264. package/src/services/module-loading/node-bootstrap-plugin.d.ts +42 -0
  265. package/src/services/module-loading/node-bootstrap-plugin.js +216 -0
  266. package/src/services/module-loading/page-module-import.service.d.ts +76 -0
  267. package/src/services/module-loading/page-module-import.service.js +170 -0
  268. package/src/services/module-loading/server-module-transpiler.service.d.ts +63 -0
  269. package/src/services/module-loading/server-module-transpiler.service.js +64 -0
  270. package/src/services/module-loading/source-module-support.d.ts +5 -0
  271. package/src/services/module-loading/source-module-support.js +8 -0
  272. package/src/services/runtime-state/dev-graph.service.d.ts +118 -0
  273. package/src/services/runtime-state/dev-graph.service.js +162 -0
  274. package/src/services/runtime-state/entrypoint-dependency-graph.service.d.ts +41 -0
  275. package/src/services/runtime-state/entrypoint-dependency-graph.service.js +85 -0
  276. package/src/services/runtime-state/server-invalidation-state.service.d.ts +26 -0
  277. package/src/services/runtime-state/server-invalidation-state.service.js +35 -0
  278. package/src/services/validation/schema-validation-service.d.ts +122 -0
  279. package/src/services/validation/schema-validation-service.js +101 -0
  280. package/src/services/validation/{standard-schema.types.ts → standard-schema.types.d.ts} +17 -20
  281. package/src/services/validation/standard-schema.types.js +0 -0
  282. package/src/static-site-generator/static-site-generator.d.ts +104 -0
  283. package/src/static-site-generator/static-site-generator.js +316 -0
  284. package/src/types/internal-types.d.ts +232 -0
  285. package/src/types/internal-types.js +0 -0
  286. package/src/types/public-types.d.ts +1267 -0
  287. package/src/types/public-types.js +0 -0
  288. package/src/utils/deep-merge.d.ts +14 -0
  289. package/src/utils/deep-merge.js +32 -0
  290. package/src/utils/hash.d.ts +1 -0
  291. package/src/utils/hash.js +7 -0
  292. package/src/utils/html-escaping.d.ts +7 -0
  293. package/src/utils/html-escaping.js +6 -0
  294. package/src/utils/html.js +4 -0
  295. package/src/utils/invariant.d.ts +5 -0
  296. package/src/utils/invariant.js +11 -0
  297. package/src/utils/locals-utils.d.ts +15 -0
  298. package/src/utils/locals-utils.js +24 -0
  299. package/src/utils/parse-cli-args.d.ts +27 -0
  300. package/src/utils/parse-cli-args.js +62 -0
  301. package/src/utils/path-utils.module.d.ts +5 -0
  302. package/src/utils/path-utils.module.js +14 -0
  303. package/src/utils/resolve-work-dir.d.ts +11 -0
  304. package/src/utils/resolve-work-dir.js +31 -0
  305. package/src/utils/runtime.d.ts +11 -0
  306. package/src/utils/runtime.js +40 -0
  307. package/src/utils/server-utils.module.d.ts +19 -0
  308. package/src/utils/server-utils.module.js +56 -0
  309. package/src/watchers/project-watcher.d.ts +136 -0
  310. package/src/watchers/project-watcher.js +275 -0
  311. package/src/watchers/project-watcher.test-helpers.d.ts +4 -0
  312. package/src/watchers/project-watcher.test-helpers.js +52 -0
  313. package/src/adapters/abstract/application-adapter.test.ts +0 -172
  314. package/src/adapters/abstract/application-adapter.ts +0 -379
  315. package/src/adapters/abstract/router-adapter.ts +0 -30
  316. package/src/adapters/abstract/server-adapter.ts +0 -79
  317. package/src/adapters/bun/client-bridge.ts +0 -62
  318. package/src/adapters/bun/create-app.ts +0 -232
  319. package/src/adapters/bun/hmr-manager.test.ts +0 -265
  320. package/src/adapters/bun/hmr-manager.ts +0 -383
  321. package/src/adapters/bun/index.ts +0 -2
  322. package/src/adapters/bun/server-adapter.ts +0 -526
  323. package/src/adapters/bun/server-lifecycle.ts +0 -124
  324. package/src/adapters/create-app.test.ts +0 -10
  325. package/src/adapters/create-app.ts +0 -91
  326. package/src/adapters/index.ts +0 -2
  327. package/src/adapters/node/create-app.test.ts +0 -53
  328. package/src/adapters/node/create-app.ts +0 -183
  329. package/src/adapters/node/node-client-bridge.test.ts +0 -198
  330. package/src/adapters/node/node-client-bridge.ts +0 -79
  331. package/src/adapters/node/node-hmr-manager.test.ts +0 -320
  332. package/src/adapters/node/node-hmr-manager.ts +0 -355
  333. package/src/adapters/node/server-adapter.ts +0 -502
  334. package/src/adapters/node/static-content-server.test.ts +0 -60
  335. package/src/adapters/node/static-content-server.ts +0 -239
  336. package/src/adapters/shared/api-response.test.ts +0 -97
  337. package/src/adapters/shared/api-response.ts +0 -104
  338. package/src/adapters/shared/application-adapter.ts +0 -199
  339. package/src/adapters/shared/define-api-handler.ts +0 -66
  340. package/src/adapters/shared/explicit-static-render-preparation.ts +0 -58
  341. package/src/adapters/shared/explicit-static-route-matcher.test.ts +0 -381
  342. package/src/adapters/shared/explicit-static-route-matcher.ts +0 -131
  343. package/src/adapters/shared/file-route-middleware-pipeline.test.ts +0 -85
  344. package/src/adapters/shared/file-route-middleware-pipeline.ts +0 -118
  345. package/src/adapters/shared/fs-server-response-factory.test.ts +0 -176
  346. package/src/adapters/shared/fs-server-response-factory.ts +0 -96
  347. package/src/adapters/shared/fs-server-response-matcher.test.ts +0 -311
  348. package/src/adapters/shared/fs-server-response-matcher.ts +0 -240
  349. package/src/adapters/shared/hmr-entrypoint-registrar.ts +0 -149
  350. package/src/adapters/shared/hmr-html-response.ts +0 -52
  351. package/src/adapters/shared/hmr-manager.contract.test.ts +0 -228
  352. package/src/adapters/shared/hmr-manager.dispatch.test.ts +0 -220
  353. package/src/adapters/shared/render-context.test.ts +0 -150
  354. package/src/adapters/shared/render-context.ts +0 -123
  355. package/src/adapters/shared/runtime-bootstrap.ts +0 -79
  356. package/src/adapters/shared/server-adapter.test.ts +0 -130
  357. package/src/adapters/shared/server-adapter.ts +0 -562
  358. package/src/adapters/shared/server-route-handler.test.ts +0 -111
  359. package/src/adapters/shared/server-route-handler.ts +0 -153
  360. package/src/adapters/shared/server-static-builder.test.ts +0 -338
  361. package/src/adapters/shared/server-static-builder.ts +0 -170
  362. package/src/build/build-adapter-serialization.test.ts +0 -281
  363. package/src/build/build-adapter.test.ts +0 -1240
  364. package/src/build/build-adapter.ts +0 -1012
  365. package/src/build/build-manifest.ts +0 -54
  366. package/src/build/build-types.ts +0 -83
  367. package/src/build/dev-build-coordinator.ts +0 -220
  368. package/src/build/esbuild-build-adapter.ts +0 -660
  369. package/src/build/runtime-build-executor.test.ts +0 -81
  370. package/src/build/runtime-build-executor.ts +0 -40
  371. package/src/build/runtime-specifier-alias-plugin.test.ts +0 -67
  372. package/src/build/runtime-specifier-alias-plugin.ts +0 -62
  373. package/src/build/runtime-specifier-aliases.ts +0 -135
  374. package/src/config/config-builder.test.ts +0 -442
  375. package/src/config/config-builder.ts +0 -737
  376. package/src/config/config-builder.typecheck.test.ts +0 -96
  377. package/src/dev/host-runtime.ts +0 -34
  378. package/src/dev/sc-server.ts +0 -143
  379. package/src/eco/eco.browser.test.ts +0 -43
  380. package/src/eco/eco.browser.ts +0 -118
  381. package/src/eco/eco.test.ts +0 -654
  382. package/src/eco/eco.ts +0 -205
  383. package/src/eco/eco.types.ts +0 -221
  384. package/src/eco/eco.utils.test.ts +0 -219
  385. package/src/eco/eco.utils.ts +0 -5
  386. package/src/eco/global-injector-map.test.ts +0 -42
  387. package/src/eco/global-injector-map.ts +0 -112
  388. package/src/eco/lazy-injector-map.test.ts +0 -66
  389. package/src/eco/lazy-injector-map.ts +0 -120
  390. package/src/eco/module-dependencies.test.ts +0 -30
  391. package/src/eco/module-dependencies.ts +0 -75
  392. package/src/errors/http-error.test.ts +0 -134
  393. package/src/errors/http-error.ts +0 -72
  394. package/src/errors/index.ts +0 -3
  395. package/src/errors/locals-access-error.ts +0 -7
  396. package/src/global/app-logger.ts +0 -4
  397. package/src/global/utils.test.ts +0 -12
  398. 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
  399. package/src/hmr/client/__screenshots__/hmr-runtime.test.browser.ts/HMR-Runtime-HMR-Server-Integration-should-load-fixture-app-page-1.png +0 -0
  400. package/src/hmr/client/__screenshots__/hmr-runtime.test.browser.ts/HMR-Runtime-WebSocket-Connection-should-connect-to-correct-HMR-endpoint-1.png +0 -0
  401. package/src/hmr/client/hmr-runtime.ts +0 -162
  402. package/src/hmr/hmr-strategy.test.ts +0 -124
  403. package/src/hmr/hmr-strategy.ts +0 -177
  404. package/src/hmr/hmr.postcss.test.e2e.ts +0 -41
  405. package/src/hmr/hmr.test.e2e.ts +0 -66
  406. package/src/hmr/strategies/default-hmr-strategy.ts +0 -60
  407. package/src/hmr/strategies/js-hmr-strategy.test.ts +0 -334
  408. package/src/hmr/strategies/js-hmr-strategy.ts +0 -314
  409. package/src/index.browser.ts +0 -3
  410. package/src/index.ts +0 -15
  411. package/src/integrations/ghtml/ghtml-renderer.test.ts +0 -253
  412. package/src/integrations/ghtml/ghtml-renderer.ts +0 -87
  413. package/src/integrations/ghtml/ghtml.constants.ts +0 -1
  414. package/src/integrations/ghtml/ghtml.plugin.ts +0 -28
  415. package/src/plugins/alias-resolver-plugin.test.ts +0 -41
  416. package/src/plugins/alias-resolver-plugin.ts +0 -63
  417. package/src/plugins/eco-component-meta-plugin.test.ts +0 -406
  418. package/src/plugins/eco-component-meta-plugin.ts +0 -494
  419. package/src/plugins/foreign-jsx-override-plugin.test.ts +0 -65
  420. package/src/plugins/foreign-jsx-override-plugin.ts +0 -67
  421. package/src/plugins/integration-plugin.test.ts +0 -151
  422. package/src/plugins/integration-plugin.ts +0 -323
  423. package/src/plugins/processor.test.ts +0 -148
  424. package/src/plugins/processor.ts +0 -257
  425. package/src/plugins/source-transform.test.ts +0 -82
  426. package/src/plugins/source-transform.ts +0 -123
  427. package/src/route-renderer/orchestration/component-render-context.ts +0 -325
  428. package/src/route-renderer/orchestration/declared-ownership-graph.ts +0 -62
  429. package/src/route-renderer/orchestration/foreign-subtree-execution.service.ts +0 -383
  430. package/src/route-renderer/orchestration/integration-renderer.test.ts +0 -2085
  431. package/src/route-renderer/orchestration/integration-renderer.ts +0 -1244
  432. package/src/route-renderer/orchestration/ownership-planning.service.ts +0 -97
  433. package/src/route-renderer/orchestration/ownership-validation.service.ts +0 -76
  434. package/src/route-renderer/orchestration/processed-asset-dedupe.ts +0 -25
  435. package/src/route-renderer/orchestration/queued-foreign-subtree-resolution.service.test.ts +0 -324
  436. package/src/route-renderer/orchestration/queued-foreign-subtree-resolution.service.ts +0 -294
  437. package/src/route-renderer/orchestration/render-output.utils.ts +0 -310
  438. package/src/route-renderer/orchestration/route-render-orchestrator.prepare-render-options.test.ts +0 -644
  439. package/src/route-renderer/orchestration/route-render-orchestrator.test.ts +0 -265
  440. package/src/route-renderer/orchestration/route-render-orchestrator.ts +0 -592
  441. package/src/route-renderer/orchestration/template-serialization.test.ts +0 -110
  442. package/src/route-renderer/orchestration/template-serialization.ts +0 -117
  443. package/src/route-renderer/page-loading/component-dependency-collection.ts +0 -202
  444. package/src/route-renderer/page-loading/declared-asset-collection.ts +0 -153
  445. package/src/route-renderer/page-loading/dependency-resolver.test.ts +0 -761
  446. package/src/route-renderer/page-loading/dependency-resolver.ts +0 -144
  447. package/src/route-renderer/page-loading/ecopages-virtual-imports.ts +0 -75
  448. package/src/route-renderer/page-loading/lazy-entry-collection.ts +0 -167
  449. package/src/route-renderer/page-loading/lazy-trigger-planning.ts +0 -74
  450. package/src/route-renderer/page-loading/module-declaration-aggregation.ts +0 -60
  451. package/src/route-renderer/page-loading/module-declaration-scripts.ts +0 -16
  452. package/src/route-renderer/page-loading/page-dependency-bundling.ts +0 -244
  453. package/src/route-renderer/page-loading/page-module-loader.test.ts +0 -183
  454. package/src/route-renderer/page-loading/page-module-loader.ts +0 -184
  455. package/src/route-renderer/route-renderer.ts +0 -133
  456. package/src/router/client/link-intent.test.browser.ts +0 -51
  457. package/src/router/client/link-intent.ts +0 -92
  458. package/src/router/client/navigation-coordinator.test.ts +0 -237
  459. package/src/router/client/navigation-coordinator.ts +0 -453
  460. package/src/router/server/route-registry.test.ts +0 -176
  461. package/src/router/server/route-registry.ts +0 -382
  462. package/src/services/assets/asset-processing-service/asset-dependency-keys.ts +0 -66
  463. package/src/services/assets/asset-processing-service/asset-processing.service.test.ts +0 -473
  464. package/src/services/assets/asset-processing-service/asset-processing.service.ts +0 -344
  465. package/src/services/assets/asset-processing-service/asset.factory.test.ts +0 -63
  466. package/src/services/assets/asset-processing-service/asset.factory.ts +0 -105
  467. package/src/services/assets/asset-processing-service/assets.types.ts +0 -128
  468. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.test.ts +0 -74
  469. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.ts +0 -96
  470. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.test.ts +0 -67
  471. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.ts +0 -78
  472. package/src/services/assets/asset-processing-service/grouped-content-bundles.ts +0 -104
  473. package/src/services/assets/asset-processing-service/index.ts +0 -6
  474. package/src/services/assets/asset-processing-service/page-package.test.ts +0 -100
  475. package/src/services/assets/asset-processing-service/page-package.ts +0 -93
  476. package/src/services/assets/asset-processing-service/processor.registry.ts +0 -18
  477. package/src/services/assets/asset-processing-service/processors/base/base-processor.test.ts +0 -59
  478. package/src/services/assets/asset-processing-service/processors/base/base-processor.ts +0 -83
  479. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.ts +0 -173
  480. package/src/services/assets/asset-processing-service/processors/index.ts +0 -5
  481. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.test.ts +0 -195
  482. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.ts +0 -137
  483. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.test.ts +0 -326
  484. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.ts +0 -116
  485. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.test.ts +0 -227
  486. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.ts +0 -89
  487. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.test.ts +0 -261
  488. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.ts +0 -72
  489. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.ts +0 -83
  490. package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.ts +0 -65
  491. package/src/services/assets/browser-bundle.service.test.ts +0 -66
  492. package/src/services/assets/browser-bundle.service.ts +0 -109
  493. package/src/services/cache/cache.types.ts +0 -126
  494. package/src/services/cache/index.ts +0 -18
  495. package/src/services/cache/memory-cache-store.test.ts +0 -225
  496. package/src/services/cache/memory-cache-store.ts +0 -130
  497. package/src/services/cache/page-cache-service.test.ts +0 -175
  498. package/src/services/cache/page-cache-service.ts +0 -202
  499. package/src/services/cache/page-request-cache-coordinator.service.test.ts +0 -79
  500. package/src/services/cache/page-request-cache-coordinator.service.ts +0 -131
  501. package/src/services/html/html-rewriter-provider.service.test.ts +0 -183
  502. package/src/services/html/html-rewriter-provider.service.ts +0 -104
  503. package/src/services/html/html-transformer.service.test.ts +0 -476
  504. package/src/services/html/html-transformer.service.ts +0 -275
  505. package/src/services/invalidation/development-invalidation.service.test.ts +0 -87
  506. package/src/services/invalidation/development-invalidation.service.ts +0 -262
  507. package/src/services/module-loading/app-module-loader.service.ts +0 -9
  508. package/src/services/module-loading/app-server-module-transpiler.service.test.ts +0 -130
  509. package/src/services/module-loading/app-server-module-transpiler.service.ts +0 -141
  510. package/src/services/module-loading/host-module-loader-registry.ts +0 -15
  511. package/src/services/module-loading/node-bootstrap-plugin.test.ts +0 -335
  512. package/src/services/module-loading/node-bootstrap-plugin.ts +0 -311
  513. package/src/services/module-loading/page-module-import.service.test.ts +0 -504
  514. package/src/services/module-loading/page-module-import.service.ts +0 -251
  515. package/src/services/module-loading/server-module-transpiler.service.test.ts +0 -243
  516. package/src/services/module-loading/server-module-transpiler.service.ts +0 -104
  517. package/src/services/module-loading/source-module-support.ts +0 -19
  518. package/src/services/runtime-state/dev-graph.service.ts +0 -217
  519. package/src/services/runtime-state/entrypoint-dependency-graph.service.ts +0 -136
  520. package/src/services/runtime-state/server-invalidation-state.service.ts +0 -68
  521. package/src/services/validation/schema-validation-service.test.ts +0 -223
  522. package/src/services/validation/schema-validation-service.ts +0 -204
  523. package/src/static-site-generator/static-site-generator.test.ts +0 -408
  524. package/src/static-site-generator/static-site-generator.ts +0 -445
  525. package/src/types/internal-types.ts +0 -243
  526. package/src/types/public-types.ts +0 -1459
  527. package/src/utils/deep-merge.test.ts +0 -114
  528. package/src/utils/deep-merge.ts +0 -47
  529. package/src/utils/hash.ts +0 -5
  530. package/src/utils/html-escaping.ts +0 -9
  531. package/src/utils/invariant.test.ts +0 -22
  532. package/src/utils/invariant.ts +0 -15
  533. package/src/utils/locals-utils.ts +0 -37
  534. package/src/utils/parse-cli-args.test.ts +0 -69
  535. package/src/utils/parse-cli-args.ts +0 -105
  536. package/src/utils/path-utils.module.ts +0 -14
  537. package/src/utils/path-utils.test.ts +0 -15
  538. package/src/utils/resolve-work-dir.ts +0 -45
  539. package/src/utils/runtime.ts +0 -44
  540. package/src/utils/server-utils.module.ts +0 -67
  541. package/src/utils/server-utils.test.ts +0 -38
  542. package/src/watchers/project-watcher.integration.test.ts +0 -337
  543. package/src/watchers/project-watcher.test-helpers.ts +0 -42
  544. package/src/watchers/project-watcher.test.ts +0 -768
  545. package/src/watchers/project-watcher.ts +0 -357
  546. /package/src/utils/{html.ts → html.d.ts} +0 -0
@@ -1,2085 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { eco } from '../../eco/eco.ts';
3
- import { IntegrationRenderer, type RenderToResponseContext } from './integration-renderer.ts';
4
- import type { EcoPagesAppConfig } from '../../types/internal-types.ts';
5
- import type { AssetProcessingService, ProcessedAsset } from '../../services/assets/asset-processing-service/index.ts';
6
- import { getComponentRenderContext, type ForeignChildRuntime } from './component-render-context.ts';
7
- import type {
8
- BaseIntegrationContext,
9
- ComponentRenderInput,
10
- ComponentRenderResult,
11
- ForeignSubtreeRenderPayload,
12
- RouteRendererBody,
13
- EcoPagesElement,
14
- IntegrationRendererRenderOptions,
15
- EcoPageFile,
16
- RouteRendererOptions,
17
- EcoComponent,
18
- HtmlTemplateProps,
19
- } from '../../types/public-types.ts';
20
- import type { EcoPageComponent } from '../../eco/eco.types.ts';
21
- import { runWithComponentRenderContext } from './component-render-context.ts';
22
-
23
- function createUnresolvedMarkerArtifact(nodeId: string, componentRef: string, propsRef: string): string {
24
- return `<eco-marker data-eco-node-id="${nodeId}" data-eco-component-ref="${componentRef}" data-eco-props-ref="${propsRef}"></eco-marker>`;
25
- }
26
-
27
- /**
28
- * Concrete implementation with ed file loading for testing purposes.
29
- */
30
- class TestIntegrationRenderer extends IntegrationRenderer<EcoPagesElement> {
31
- name = 'test-renderer';
32
- ForeignChildRuntimeCreationCount = 0;
33
- ForeignChildRenderCount = 0;
34
- UseFailFastForeignChildRuntime = false;
35
-
36
- /** Mock data container for page module */
37
- PageModule: EcoPageFile | null = null;
38
- /** Mock HTML template container */
39
- HtmlTemplate: EcoComponent<HtmlTemplateProps> | null = null;
40
- RenderedBody: RouteRendererBody = '<html><body>Test Page</body></html>';
41
- RenderBodyFactory:
42
- | ((context: ReturnType<typeof getComponentRenderContext>) => RouteRendererBody | Promise<RouteRendererBody>)
43
- | null = null;
44
- MockComponentRenderResult: ComponentRenderResult | null = null;
45
- ImportedFiles: string[] = [];
46
-
47
- async render(_options: IntegrationRendererRenderOptions<EcoPagesElement>): Promise<RouteRendererBody> {
48
- if (this.RenderBodyFactory) {
49
- return await this.RenderBodyFactory(getComponentRenderContext());
50
- }
51
-
52
- return this.RenderedBody;
53
- }
54
-
55
- override async renderComponent(_input: ComponentRenderInput): Promise<ComponentRenderResult> {
56
- if (this.MockComponentRenderResult) {
57
- return this.MockComponentRenderResult;
58
- }
59
-
60
- return super.renderComponent(_input);
61
- }
62
-
63
- override async renderComponentWithForeignChildren(input: ComponentRenderInput): Promise<ComponentRenderResult> {
64
- this.ForeignChildRenderCount += 1;
65
- return await super.renderComponentWithForeignChildren(input);
66
- }
67
-
68
- async renderToResponse<P>(view: EcoComponent<P>, props: P, ctx: RenderToResponseContext): Promise<Response> {
69
- const viewFn = view as (props: P) => EcoPagesElement;
70
- const content = viewFn(props);
71
-
72
- let body: string;
73
- if (ctx.partial) {
74
- body = content as string;
75
- } else {
76
- const Layout = view.config?.layout as ((props: { children: string }) => string) | undefined;
77
- const children = Layout ? Layout({ children: content as string }) : content;
78
- body = `<!DOCTYPE html><html><body>${children}</body></html>`;
79
- }
80
-
81
- const headers = new Headers({ 'Content-Type': 'text/html; charset=utf-8' });
82
- if (ctx.headers) {
83
- for (const [key, value] of Object.entries(ctx.headers)) {
84
- headers.set(key, value);
85
- }
86
- }
87
-
88
- return new Response(body, {
89
- status: ctx.status ?? 200,
90
- headers,
91
- });
92
- }
93
-
94
- /**
95
- * Override protected methods to return data.
96
- */
97
- protected override async importPageFile(_file: string): Promise<EcoPageFile> {
98
- this.ImportedFiles.push(_file);
99
- if (!this.PageModule) throw new Error('Mock page module not set');
100
- return this.PageModule;
101
- }
102
-
103
- protected override async getHtmlTemplate(): Promise<EcoComponent<HtmlTemplateProps>> {
104
- if (!this.HtmlTemplate) throw new Error('Mock HTML template not set');
105
- return this.HtmlTemplate;
106
- }
107
-
108
- /**
109
- * Override to avoid asset processing service dependency in tests.
110
- */
111
- protected override async resolveDependencies(
112
- _components: (EcoComponent | Partial<EcoComponent>)[],
113
- ): Promise<ProcessedAsset[]> {
114
- return [];
115
- }
116
-
117
- protected override async buildPageBrowserGraph(_file: string) {
118
- return { assets: [] as ProcessedAsset[] };
119
- }
120
-
121
- /**
122
- * Expose protected method for testing.
123
- */
124
- public async testPrepareRenderOptions(options: RouteRendererOptions) {
125
- return this.prepareRenderOptions(options);
126
- }
127
-
128
- public async testProcessComponentDependencies(components: (EcoComponent | Partial<EcoComponent>)[]) {
129
- return this.processComponentDependencies(components);
130
- }
131
-
132
- public testShouldDelegateForeignChild(input: { currentIntegration: string; targetIntegration?: string }) {
133
- return this.foreignSubtreeExecutionService.shouldDelegateForeignChild(input);
134
- }
135
-
136
- public testHasForeignChildDescendants(component: EcoComponent) {
137
- return this.hasForeignChildDescendants(component);
138
- }
139
-
140
- public async testGetHtmlTemplate() {
141
- return this.getHtmlTemplate();
142
- }
143
-
144
- public async testBaseGetHtmlTemplate() {
145
- return super.getHtmlTemplate();
146
- }
147
-
148
- public testGetRendererBootstrapDependencies(partial = false) {
149
- return this.getRendererBootstrapDependencies(partial);
150
- }
151
-
152
- public async testFinalizeResolvedHtml(options: { html: string; partial?: boolean }) {
153
- return this.finalizeResolvedHtml({
154
- html: options.html,
155
- partial: options.partial,
156
- });
157
- }
158
-
159
- public async testRenderPartialViewResponse<P>(input: {
160
- view: EcoComponent<P>;
161
- props: P;
162
- ctx?: RenderToResponseContext;
163
- renderInline?: () => Promise<BodyInit>;
164
- transformHtml?: (html: string) => string;
165
- }) {
166
- return this.renderPartialViewResponse({
167
- view: input.view,
168
- props: input.props,
169
- ctx: input.ctx ?? { partial: true },
170
- renderInline: input.renderInline,
171
- transformHtml: input.transformHtml,
172
- });
173
- }
174
-
175
- public async testRenderViewWithDocumentShell<P>(input: {
176
- view: EcoComponent<P>;
177
- props: P;
178
- ctx?: RenderToResponseContext;
179
- layout?: EcoComponent;
180
- }) {
181
- return this.renderViewWithDocumentShell({
182
- view: input.view,
183
- props: input.props,
184
- ctx: input.ctx ?? {},
185
- layout: input.layout,
186
- });
187
- }
188
-
189
- public async testRenderForeignSubtree(input: ComponentRenderInput) {
190
- return this.renderForeignSubtree(input);
191
- }
192
-
193
- protected override createForeignChildRuntime(options: {
194
- renderInput: ComponentRenderInput;
195
- rendererCache: Map<string, IntegrationRenderer<any>>;
196
- }): ForeignChildRuntime {
197
- this.ForeignChildRuntimeCreationCount += 1;
198
-
199
- if (this.UseFailFastForeignChildRuntime) {
200
- return this.createFailFastForeignChildRuntime();
201
- }
202
-
203
- return super.createForeignChildRuntime(options);
204
- }
205
- }
206
-
207
- describe('IntegrationRenderer', () => {
208
- const AppConfig = {
209
- absolutePaths: {
210
- pagesDir: '/app/pages',
211
- htmlTemplatePath: '/app/index.ghtml.ts',
212
- },
213
- integrations: [],
214
- defaultMetadata: {
215
- title: 'Default Title',
216
- description: 'Default Description',
217
- },
218
- srcDir: '/app',
219
- } as unknown as EcoPagesAppConfig;
220
-
221
- const AssetService = {
222
- processDependencies: vi.fn(() => Promise.resolve([])),
223
- } as unknown as AssetProcessingService;
224
-
225
- it('should extract cache strategy from page component (static property)', async () => {
226
- const renderer = new TestIntegrationRenderer({
227
- appConfig: AppConfig,
228
- assetProcessingService: AssetService,
229
- runtimeOrigin: 'http://localhost:3000',
230
- });
231
-
232
- /** Mock page with cache strategy */
233
- const PageIdx = (() => 'Page Content') as EcoPageComponent<any>;
234
- /** Simulate eco.page() attaching cache config */
235
- PageIdx.cache = { revalidate: 60 };
236
-
237
- renderer.PageModule = {
238
- default: PageIdx,
239
- };
240
- renderer.HtmlTemplate = (() => 'HTML Template') as EcoComponent<HtmlTemplateProps>;
241
-
242
- const options: RouteRendererOptions = {
243
- file: '/app/pages/cached-page.ts',
244
- params: {},
245
- query: {},
246
- };
247
-
248
- const result = await renderer.testPrepareRenderOptions(options);
249
-
250
- expect(result.cacheStrategy).toEqual({ revalidate: 60 });
251
- });
252
-
253
- it('should return undefined cache strategy if not present', async () => {
254
- const renderer = new TestIntegrationRenderer({
255
- appConfig: AppConfig,
256
- assetProcessingService: AssetService,
257
- runtimeOrigin: 'http://localhost:3000',
258
- });
259
-
260
- /** Mock page without cache strategy */
261
- const PageIdx = (() => 'Page Content') as EcoPageComponent<any>;
262
-
263
- renderer.PageModule = {
264
- default: PageIdx,
265
- };
266
- renderer.HtmlTemplate = (() => 'HTML Template') as EcoComponent<HtmlTemplateProps>;
267
-
268
- const options: RouteRendererOptions = {
269
- file: '/app/pages/simple-page.ts',
270
- params: {},
271
- query: {},
272
- };
273
-
274
- const result = await renderer.testPrepareRenderOptions(options);
275
-
276
- expect(result.cacheStrategy).toBeUndefined();
277
- });
278
-
279
- it('should resolve static props and metadata correctly', async () => {
280
- const renderer = new TestIntegrationRenderer({
281
- appConfig: AppConfig,
282
- assetProcessingService: AssetService,
283
- runtimeOrigin: 'http://localhost:3000',
284
- });
285
-
286
- const PageIdx = (() => 'Page Content') as EcoPageComponent<any>;
287
- /** Attached static methods */
288
- PageIdx.staticProps = async () => ({ props: { title: 'Dynamic Title' } });
289
- PageIdx.metadata = async ({ props }: { props: Record<string, unknown> }) => ({
290
- title: props.title as string,
291
- description: 'Dynamic Description',
292
- });
293
-
294
- renderer.PageModule = {
295
- default: PageIdx,
296
- };
297
- renderer.HtmlTemplate = (() => 'HTML Template') as EcoComponent<HtmlTemplateProps>;
298
-
299
- const options: RouteRendererOptions = {
300
- file: '/app/pages/props-page.ts',
301
- params: {},
302
- query: {},
303
- };
304
-
305
- const result = await renderer.testPrepareRenderOptions(options);
306
-
307
- expect(result.props).toEqual({ title: 'Dynamic Title' });
308
- expect(result.metadata?.title).toBe('Dynamic Title');
309
- });
310
-
311
- it('should prefer renderer module html template path when provided', async () => {
312
- const renderer = new TestIntegrationRenderer({
313
- appConfig: {
314
- ...AppConfig,
315
- runtime: {
316
- rendererModuleContext: {
317
- htmlTemplateModulePath: '/virtual/includes/html.kita.tsx',
318
- },
319
- },
320
- } as EcoPagesAppConfig,
321
- assetProcessingService: AssetService,
322
- runtimeOrigin: 'http://localhost:3000',
323
- });
324
-
325
- renderer.PageModule = {
326
- default: (() => 'HTML Template') as EcoComponent<HtmlTemplateProps>,
327
- };
328
-
329
- await renderer.testBaseGetHtmlTemplate();
330
-
331
- expect(renderer.ImportedFiles).toContain('/virtual/includes/html.kita.tsx');
332
- });
333
-
334
- it('should expose island bootstrap dependencies from renderer modules', () => {
335
- const renderer = new TestIntegrationRenderer({
336
- appConfig: {
337
- ...AppConfig,
338
- runtime: {
339
- rendererModuleContext: {
340
- islandClientModuleId: 'virtual:ecopages/island-client.ts',
341
- },
342
- },
343
- } as EcoPagesAppConfig,
344
- assetProcessingService: AssetService,
345
- runtimeOrigin: 'http://localhost:3000',
346
- });
347
-
348
- expect(renderer.testGetRendererBootstrapDependencies()).toEqual([
349
- {
350
- attributes: {
351
- crossorigin: 'anonymous',
352
- 'data-ecopages-runtime': 'islands',
353
- type: 'module',
354
- },
355
- content: 'import "virtual:ecopages/island-client.ts";',
356
- inline: true,
357
- kind: 'script',
358
- packageRole: 'keep-separate',
359
- position: 'body',
360
- },
361
- ]);
362
- expect(renderer.testGetRendererBootstrapDependencies(true)).toEqual([]);
363
- });
364
-
365
- it('should inject the island client bootstrap into finalized full-document HTML', async () => {
366
- const renderer = new TestIntegrationRenderer({
367
- appConfig: {
368
- ...AppConfig,
369
- runtime: {
370
- rendererModuleContext: {
371
- islandClientModuleId: 'virtual:ecopages/island-client.ts',
372
- },
373
- },
374
- } as EcoPagesAppConfig,
375
- assetProcessingService: AssetService,
376
- runtimeOrigin: 'http://localhost:3000',
377
- });
378
-
379
- const html = await renderer.testFinalizeResolvedHtml({
380
- html: '<!DOCTYPE html><html><body><main>hello</main></body></html>',
381
- });
382
-
383
- expect(html).toContain('data-ecopages-runtime="islands"');
384
- expect(html).toContain('import "virtual:ecopages/island-client.ts";');
385
- });
386
-
387
- it('should keep layout locals safe and page locals guarded on static pages', async () => {
388
- const renderer = new TestIntegrationRenderer({
389
- appConfig: AppConfig,
390
- assetProcessingService: AssetService,
391
- runtimeOrigin: 'http://localhost:3000',
392
- });
393
-
394
- const PageIdx = (() => 'Page Content') as EcoPageComponent<any>;
395
-
396
- renderer.PageModule = {
397
- default: PageIdx,
398
- };
399
- renderer.HtmlTemplate = (() => 'HTML Template') as EcoComponent<HtmlTemplateProps>;
400
-
401
- const result = await renderer.testPrepareRenderOptions({
402
- file: '/app/pages/static-page.ts',
403
- params: {},
404
- query: {},
405
- });
406
-
407
- expect(result.locals).toBeUndefined();
408
- expect(() => (result.pageLocals as Record<string, unknown>).session).toThrow();
409
- });
410
-
411
- it('should provide both locals and pageLocals on dynamic pages', async () => {
412
- const renderer = new TestIntegrationRenderer({
413
- appConfig: AppConfig,
414
- assetProcessingService: AssetService,
415
- runtimeOrigin: 'http://localhost:3000',
416
- });
417
-
418
- const PageIdx = (() => 'Page Content') as EcoPageComponent<any>;
419
- PageIdx.cache = 'dynamic';
420
-
421
- renderer.PageModule = {
422
- default: PageIdx,
423
- };
424
- renderer.HtmlTemplate = (() => 'HTML Template') as EcoComponent<HtmlTemplateProps>;
425
-
426
- const incomingLocals = { session: { userId: 'u-1' } } as Record<string, unknown>;
427
- const result = await renderer.testPrepareRenderOptions({
428
- file: '/app/pages/dynamic-page.ts',
429
- params: {},
430
- query: {},
431
- locals: incomingLocals,
432
- });
433
-
434
- expect(result.locals).toBe(incomingLocals);
435
- expect(result.pageLocals).toBe(incomingLocals);
436
- });
437
-
438
- it('should include an ownership plan for page, layout, and html template roots', async () => {
439
- const renderer = new TestIntegrationRenderer({
440
- appConfig: {
441
- ...AppConfig,
442
- integrations: [
443
- {
444
- name: 'foreign-renderer',
445
- } as unknown as EcoPagesAppConfig['integrations'][number],
446
- ],
447
- } as EcoPagesAppConfig,
448
- assetProcessingService: AssetService,
449
- runtimeOrigin: 'http://localhost:3000',
450
- });
451
-
452
- const ForeignComponent = (() => '<aside>Foreign</aside>') as EcoComponent<Record<string, unknown>>;
453
- ForeignComponent.config = {
454
- integration: 'foreign-renderer',
455
- __eco: {
456
- id: 'foreign-component',
457
- file: '/app/components/foreign-component.tsx',
458
- integration: 'foreign-renderer',
459
- },
460
- };
461
-
462
- const Layout = (() => '<main>Layout</main>') as EcoComponent<Record<string, unknown>>;
463
- Layout.config = {
464
- __eco: {
465
- id: 'layout-component',
466
- file: '/app/layouts/default.tsx',
467
- integration: 'test-renderer',
468
- },
469
- dependencies: {
470
- components: [ForeignComponent],
471
- },
472
- };
473
-
474
- const PageIdx = (() => 'Page Content') as EcoPageComponent<any>;
475
- PageIdx.config = {
476
- layout: Layout,
477
- __eco: {
478
- id: 'page-component',
479
- file: '/app/pages/index.tsx',
480
- integration: 'test-renderer',
481
- },
482
- };
483
-
484
- const HtmlTemplate = (() => 'HTML Template') as EcoComponent<HtmlTemplateProps>;
485
- HtmlTemplate.config = {
486
- __eco: {
487
- id: 'html-template',
488
- file: '/app/index.ghtml.ts',
489
- integration: 'test-renderer',
490
- },
491
- };
492
-
493
- renderer.PageModule = {
494
- default: PageIdx,
495
- };
496
- renderer.HtmlTemplate = HtmlTemplate;
497
-
498
- const result = await renderer.testPrepareRenderOptions({
499
- file: '/app/pages/index.tsx',
500
- params: {},
501
- query: {},
502
- });
503
-
504
- expect(result.ownershipPlan).toEqual(
505
- expect.objectContaining({
506
- foreignEdgeCount: 1,
507
- hasValidationErrors: false,
508
- rendererNames: expect.arrayContaining(['test-renderer', 'foreign-renderer']),
509
- root: expect.objectContaining({
510
- source: 'route',
511
- children: expect.arrayContaining([
512
- expect.objectContaining({ source: 'html-template' }),
513
- expect.objectContaining({ source: 'layout' }),
514
- expect.objectContaining({ source: 'page' }),
515
- ]),
516
- }),
517
- }),
518
- );
519
- });
520
-
521
- it('should record validation errors for unknown foreign integration owners', async () => {
522
- const renderer = new TestIntegrationRenderer({
523
- appConfig: AppConfig,
524
- assetProcessingService: AssetService,
525
- runtimeOrigin: 'http://localhost:3000',
526
- });
527
-
528
- const UnknownForeignComponent = (() => '<aside>Foreign</aside>') as EcoComponent<Record<string, unknown>>;
529
- UnknownForeignComponent.config = {
530
- integration: 'missing-renderer',
531
- __eco: {
532
- id: 'missing-foreign-component',
533
- file: '/app/components/missing-foreign-component.tsx',
534
- integration: 'missing-renderer',
535
- },
536
- };
537
-
538
- const PageIdx = (() => 'Page Content') as EcoPageComponent<any>;
539
- PageIdx.config = {
540
- __eco: {
541
- id: 'page-component',
542
- file: '/app/pages/index.tsx',
543
- integration: 'test-renderer',
544
- },
545
- dependencies: {
546
- components: [UnknownForeignComponent],
547
- },
548
- };
549
-
550
- const HtmlTemplate = (() => 'HTML Template') as EcoComponent<HtmlTemplateProps>;
551
- HtmlTemplate.config = {
552
- __eco: {
553
- id: 'html-template',
554
- file: '/app/index.ghtml.ts',
555
- integration: 'test-renderer',
556
- },
557
- };
558
-
559
- renderer.PageModule = {
560
- default: PageIdx,
561
- };
562
- renderer.HtmlTemplate = HtmlTemplate;
563
-
564
- const result = await renderer.testPrepareRenderOptions({
565
- file: '/app/pages/index.tsx',
566
- params: {},
567
- query: {},
568
- });
569
-
570
- expect(result.ownershipPlan?.hasValidationErrors).toBe(true);
571
- expect(result.ownershipPlan?.validationErrors).toEqual(
572
- expect.arrayContaining([
573
- expect.objectContaining({
574
- code: 'UNKNOWN_INTEGRATION_OWNER',
575
- componentId: 'missing-foreign-component',
576
- integrationName: 'missing-renderer',
577
- }),
578
- ]),
579
- );
580
- });
581
-
582
- it('should expose a compatibility foreign-subtree payload contract', async () => {
583
- const renderer = new TestIntegrationRenderer({
584
- appConfig: AppConfig,
585
- assetProcessingService: AssetService,
586
- runtimeOrigin: 'http://localhost:3000',
587
- });
588
-
589
- renderer.MockComponentRenderResult = {
590
- html: '<main data-root="true">Hello</main>',
591
- canAttachAttributes: true,
592
- rootTag: 'main',
593
- integrationName: 'test-renderer',
594
- rootAttributes: { 'data-eco-component-id': 'root-1' },
595
- assets: [
596
- {
597
- kind: 'script',
598
- inline: true,
599
- content: 'console.log("foreign-subtree")',
600
- position: 'body',
601
- },
602
- ],
603
- };
604
-
605
- const payload = await renderer.testRenderForeignSubtree({
606
- component: (() => '<main>Hello</main>') as EcoComponent<Record<string, unknown>>,
607
- props: {},
608
- });
609
-
610
- expect(payload).toEqual<ForeignSubtreeRenderPayload>({
611
- html: '<main data-root="true">Hello</main>',
612
- assets: [
613
- {
614
- kind: 'script',
615
- inline: true,
616
- content: 'console.log("foreign-subtree")',
617
- position: 'body',
618
- },
619
- ],
620
- rootTag: 'main',
621
- rootAttributes: { 'data-eco-component-id': 'root-1' },
622
- attachmentPolicy: { kind: 'first-element' },
623
- integrationName: 'test-renderer',
624
- });
625
- });
626
-
627
- it('should resolve foreign-owned boundaries in the owning renderer', () => {
628
- const renderer = new TestIntegrationRenderer({
629
- appConfig: AppConfig,
630
- assetProcessingService: AssetService,
631
- runtimeOrigin: 'http://localhost:3000',
632
- });
633
-
634
- const result = renderer.testShouldDelegateForeignChild({
635
- currentIntegration: 'ghtml',
636
- targetIntegration: 'react',
637
- });
638
-
639
- expect(result).toBe(true);
640
- });
641
-
642
- it('should keep same-integration boundaries in the current render pass', () => {
643
- const renderer = new TestIntegrationRenderer({
644
- appConfig: AppConfig,
645
- assetProcessingService: AssetService,
646
- runtimeOrigin: 'http://localhost:3000',
647
- });
648
-
649
- const result = renderer.testShouldDelegateForeignChild({
650
- currentIntegration: 'react',
651
- targetIntegration: 'react',
652
- });
653
-
654
- expect(result).toBe(false);
655
- });
656
-
657
- it('should delegate foreign component boundaries through the shared execution seam', async () => {
658
- const foreignRenderer = {
659
- renderComponentWithForeignChildren: vi.fn(async () => ({
660
- html: '<aside>Owned by foreign renderer</aside>',
661
- canAttachAttributes: true,
662
- rootTag: 'aside',
663
- integrationName: 'foreign-renderer',
664
- })),
665
- } as unknown as IntegrationRenderer;
666
-
667
- const initializeRenderer = vi.fn(() => foreignRenderer);
668
- const renderer = new TestIntegrationRenderer({
669
- appConfig: {
670
- ...AppConfig,
671
- integrations: [
672
- {
673
- name: 'foreign-renderer',
674
- initializeRenderer,
675
- } as unknown as EcoPagesAppConfig['integrations'][number],
676
- ],
677
- } as EcoPagesAppConfig,
678
- assetProcessingService: AssetService,
679
- runtimeOrigin: 'http://localhost:3000',
680
- });
681
-
682
- const ForeignComponent = (() => '<aside>Foreign</aside>') as EcoComponent<Record<string, unknown>>;
683
- ForeignComponent.config = {
684
- integration: 'foreign-renderer',
685
- __eco: {
686
- id: 'foreign-component',
687
- file: '/app/components/foreign-component.tsx',
688
- integration: 'foreign-renderer',
689
- },
690
- };
691
-
692
- const rendererCache = new Map<string, IntegrationRenderer<any>>();
693
- const result = await renderer.renderComponentWithForeignChildren({
694
- component: ForeignComponent,
695
- props: { label: 'foreign' },
696
- integrationContext: { rendererCache },
697
- });
698
-
699
- expect(result).toEqual(
700
- expect.objectContaining({
701
- html: '<aside>Owned by foreign renderer</aside>',
702
- integrationName: 'foreign-renderer',
703
- }),
704
- );
705
- expect(initializeRenderer).toHaveBeenCalledTimes(1);
706
- expect(foreignRenderer.renderComponentWithForeignChildren).toHaveBeenCalledTimes(1);
707
- });
708
-
709
- it('should preserve shared integration context fields when delegating to the owning renderer', async () => {
710
- const foreignRenderer = {
711
- renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) => ({
712
- html: `<aside>${String(
713
- (input.integrationContext as BaseIntegrationContext | undefined)?.componentInstanceId ?? 'missing',
714
- )}</aside>`,
715
- canAttachAttributes: true,
716
- rootTag: 'aside',
717
- integrationName: 'foreign-renderer',
718
- })),
719
- } as unknown as IntegrationRenderer;
720
- const initializeRenderer = vi.fn(() => foreignRenderer);
721
-
722
- const renderer = new TestIntegrationRenderer({
723
- appConfig: {
724
- ...AppConfig,
725
- integrations: [
726
- {
727
- name: 'foreign-renderer',
728
- initializeRenderer,
729
- } as unknown as EcoPagesAppConfig['integrations'][number],
730
- ],
731
- } as EcoPagesAppConfig,
732
- assetProcessingService: AssetService,
733
- runtimeOrigin: 'http://localhost:3000',
734
- });
735
-
736
- const ForeignComponent = (() => '<aside>Foreign</aside>') as EcoComponent<Record<string, unknown>>;
737
- ForeignComponent.config = {
738
- integration: 'foreign-renderer',
739
- __eco: {
740
- id: 'foreign-component',
741
- file: '/app/components/foreign-component.tsx',
742
- integration: 'foreign-renderer',
743
- },
744
- };
745
-
746
- await renderer.renderComponentWithForeignChildren({
747
- component: ForeignComponent,
748
- props: { label: 'foreign' },
749
- integrationContext: {
750
- componentInstanceId: 'host-1',
751
- } satisfies BaseIntegrationContext,
752
- });
753
-
754
- expect(foreignRenderer.renderComponentWithForeignChildren).toHaveBeenCalledWith(
755
- expect.objectContaining({
756
- integrationContext: expect.objectContaining({
757
- componentInstanceId: 'host-1',
758
- rendererCache: expect.any(Map),
759
- }),
760
- }),
761
- );
762
- });
763
-
764
- it('should fall back to local rendering when the resolved owner renderer is the current renderer', async () => {
765
- const renderer = new TestIntegrationRenderer({
766
- appConfig: {
767
- ...AppConfig,
768
- integrations: [
769
- {
770
- name: 'foreign-renderer',
771
- initializeRenderer: () => renderer,
772
- } as unknown as EcoPagesAppConfig['integrations'][number],
773
- ],
774
- } as EcoPagesAppConfig,
775
- assetProcessingService: AssetService,
776
- runtimeOrigin: 'http://localhost:3000',
777
- });
778
- renderer.MockComponentRenderResult = {
779
- html: '<aside>Local renderer</aside>',
780
- canAttachAttributes: true,
781
- rootTag: 'aside',
782
- integrationName: 'test-renderer',
783
- };
784
-
785
- const ForeignComponent = (() => '<aside>Foreign</aside>') as EcoComponent<Record<string, unknown>>;
786
- ForeignComponent.config = {
787
- integration: 'foreign-renderer',
788
- __eco: {
789
- id: 'foreign-component',
790
- file: '/app/components/foreign-component.tsx',
791
- integration: 'foreign-renderer',
792
- },
793
- };
794
-
795
- const rendererCache = new Map<string, IntegrationRenderer<any>>();
796
- const result = await renderer.renderComponentWithForeignChildren({
797
- component: ForeignComponent,
798
- props: { label: 'foreign' },
799
- integrationContext: { rendererCache },
800
- });
801
-
802
- expect(result.html).toBe('<aside>Local renderer</aside>');
803
- });
804
-
805
- it('should prefer processed lazy script srcUrl for _resolvedLazyTriggers', async () => {
806
- let capturedDeps: unknown[] = [];
807
- const LazySrcUrl = '/assets/_hmr/components/lit-counter/lit-counter.script.js';
808
- const Service = {
809
- processDependencies: vi.fn(async (deps: unknown[]) => {
810
- capturedDeps = deps;
811
- const lazyFileDep = (
812
- deps as Array<{
813
- kind?: string;
814
- source?: string;
815
- filepath?: string;
816
- attributes?: Record<string, string>;
817
- }>
818
- ).find((dep) => dep.kind === 'script' && dep.source === 'file' && Boolean(dep.filepath));
819
-
820
- return [
821
- {
822
- kind: 'script',
823
- filepath: lazyFileDep?.filepath,
824
- attributes: lazyFileDep?.attributes,
825
- srcUrl: LazySrcUrl,
826
- },
827
- ] as ProcessedAsset[];
828
- }),
829
- } as unknown as AssetProcessingService;
830
-
831
- const renderer = new TestIntegrationRenderer({
832
- appConfig: AppConfig,
833
- assetProcessingService: Service,
834
- runtimeOrigin: 'http://localhost:3000',
835
- });
836
-
837
- const component = ((_) => '<lit-counter></lit-counter>') as EcoComponent<Record<string, unknown>>;
838
- component.config = {
839
- __eco: {
840
- id: 'lit-counter',
841
- integration: 'lit',
842
- file: '/app/components/lit-counter/lit-counter.kita.tsx',
843
- },
844
- dependencies: {
845
- scripts: [
846
- {
847
- src: './lit-counter.script.ts',
848
- lazy: { 'on:interaction': 'click,mouseenter,focusin' },
849
- },
850
- ],
851
- },
852
- };
853
-
854
- await renderer.testProcessComponentDependencies([component]);
855
-
856
- expect(component.config._resolvedLazyScripts).toBeUndefined();
857
- expect(component.config._resolvedLazyTriggers).toHaveLength(1);
858
- expect(component.config._resolvedLazyTriggers?.[0]?.rules).toEqual([
859
- {
860
- 'on:interaction': {
861
- value: 'click,mouseenter,focusin',
862
- scripts: [LazySrcUrl],
863
- },
864
- },
865
- ]);
866
- expect(
867
- capturedDeps.some((dep) => {
868
- if (!dep || typeof dep !== 'object') return false;
869
- const candidate = dep as { source?: string; importPath?: string };
870
- return candidate.source === 'node-module' && candidate.importPath === '@ecopages/scripts-injector';
871
- }),
872
- ).toBe(false);
873
- });
874
-
875
- it('should fallback to static lazy script URL when processed srcUrl is unavailable', async () => {
876
- const Service = {
877
- processDependencies: vi.fn(async () => {
878
- return [
879
- {
880
- kind: 'script',
881
- filepath: '/app/components/lit-counter/lit-counter.script.ts',
882
- },
883
- ] as ProcessedAsset[];
884
- }),
885
- } as unknown as AssetProcessingService;
886
-
887
- const renderer = new TestIntegrationRenderer({
888
- appConfig: AppConfig,
889
- assetProcessingService: Service,
890
- runtimeOrigin: 'http://localhost:3000',
891
- });
892
-
893
- const component = ((_) => '<lit-counter></lit-counter>') as EcoComponent<Record<string, unknown>>;
894
- component.config = {
895
- __eco: {
896
- id: 'lit-counter',
897
- integration: 'lit',
898
- file: '/app/components/lit-counter/lit-counter.kita.tsx',
899
- },
900
- dependencies: {
901
- scripts: [
902
- {
903
- src: './lit-counter.script.ts',
904
- lazy: { 'on:interaction': 'click,mouseenter,focusin' },
905
- },
906
- ],
907
- },
908
- };
909
-
910
- await renderer.testProcessComponentDependencies([component]);
911
-
912
- expect(component.config._resolvedLazyScripts).toBeUndefined();
913
- expect(component.config._resolvedLazyTriggers).toHaveLength(1);
914
- expect(component.config._resolvedLazyTriggers?.[0]?.rules).toEqual([
915
- {
916
- 'on:interaction': {
917
- value: 'click,mouseenter,focusin',
918
- scripts: ['/assets/components/lit-counter/lit-counter.script.js'],
919
- },
920
- },
921
- ]);
922
- });
923
-
924
- describe('renderToResponse', () => {
925
- it('should render a view with default status 200', async () => {
926
- const renderer = new TestIntegrationRenderer({
927
- appConfig: AppConfig,
928
- assetProcessingService: AssetService,
929
- runtimeOrigin: 'http://localhost:3000',
930
- });
931
-
932
- const View = ((props: { title: string }) => `<h1>${props.title}</h1>`) as EcoComponent<{
933
- title: string;
934
- }>;
935
-
936
- const response = await renderer.renderToResponse(View, { title: 'Hello' }, {});
937
-
938
- expect(response.status).toBe(200);
939
- expect(response.headers.get('Content-Type')).toBe('text/html; charset=utf-8');
940
- const body = await response.text();
941
- expect(body).toContain('<h1>Hello</h1>');
942
- });
943
-
944
- it('should render a partial view without layout', async () => {
945
- const renderer = new TestIntegrationRenderer({
946
- appConfig: AppConfig,
947
- assetProcessingService: AssetService,
948
- runtimeOrigin: 'http://localhost:3000',
949
- });
950
-
951
- const View = ((props: { content: string }) => `<div>${props.content}</div>`) as EcoComponent<{
952
- content: string;
953
- }>;
954
-
955
- const response = await renderer.renderToResponse(View, { content: 'Partial' }, { partial: true });
956
-
957
- const body = await response.text();
958
- expect(body).toBe('<div>Partial</div>');
959
- expect(body).not.toContain('<!DOCTYPE html>');
960
- });
961
-
962
- it('should apply custom status code', async () => {
963
- const renderer = new TestIntegrationRenderer({
964
- appConfig: AppConfig,
965
- assetProcessingService: AssetService,
966
- runtimeOrigin: 'http://localhost:3000',
967
- });
968
-
969
- const View = (() => '<p>Not Found</p>') as EcoComponent<object>;
970
-
971
- const response = await renderer.renderToResponse(View, {}, { status: 404 });
972
-
973
- expect(response.status).toBe(404);
974
- });
975
-
976
- it('should apply custom headers', async () => {
977
- const renderer = new TestIntegrationRenderer({
978
- appConfig: AppConfig,
979
- assetProcessingService: AssetService,
980
- runtimeOrigin: 'http://localhost:3000',
981
- });
982
-
983
- const View = (() => '<p>Cached</p>') as EcoComponent<object>;
984
-
985
- const response = await renderer.renderToResponse(
986
- View,
987
- {},
988
- {
989
- headers: {
990
- 'Cache-Control': 'max-age=3600',
991
- 'X-Custom-Header': 'test-value',
992
- },
993
- },
994
- );
995
-
996
- expect(response.headers.get('Cache-Control')).toBe('max-age=3600');
997
- expect(response.headers.get('X-Custom-Header')).toBe('test-value');
998
- });
999
-
1000
- it('should render with layout when not partial', async () => {
1001
- const renderer = new TestIntegrationRenderer({
1002
- appConfig: AppConfig,
1003
- assetProcessingService: AssetService,
1004
- runtimeOrigin: 'http://localhost:3000',
1005
- });
1006
-
1007
- const Layout = ((props: { children: string }) =>
1008
- `<main class="layout">${props.children}</main>`) as EcoComponent<{ children: string }>;
1009
-
1010
- const View = ((props: { message: string }) => `<p>${props.message}</p>`) as EcoComponent<{
1011
- message: string;
1012
- }>;
1013
- View.config = { layout: Layout };
1014
-
1015
- const response = await renderer.renderToResponse(View, { message: 'With Layout' }, {});
1016
-
1017
- const body = await response.text();
1018
- expect(body).toContain('<main class="layout">');
1019
- expect(body).toContain('<p>With Layout</p>');
1020
- });
1021
- });
1022
-
1023
- describe('renderComponent', () => {
1024
- it('should render a component with structured output', async () => {
1025
- const renderer = new TestIntegrationRenderer({
1026
- appConfig: AppConfig,
1027
- assetProcessingService: AssetService,
1028
- runtimeOrigin: 'http://localhost:3000',
1029
- });
1030
-
1031
- const View = ((props: { title: string }) => `<h1>${props.title}</h1>`) as EcoComponent<{ title: string }>;
1032
-
1033
- const result = await renderer.renderComponent({
1034
- component: View,
1035
- props: { title: 'Hello Component' },
1036
- });
1037
-
1038
- expect(result.integrationName).toBe('test-renderer');
1039
- expect(result.canAttachAttributes).toBe(true);
1040
- expect(result.rootTag).toBe('h1');
1041
- expect(result.html).toContain('<h1>Hello Component</h1>');
1042
- });
1043
- });
1044
-
1045
- describe('execute component-level artifacts', () => {
1046
- it('should handle stream-like render bodies without re-consuming disturbed responses', async () => {
1047
- const renderer = new TestIntegrationRenderer({
1048
- appConfig: AppConfig,
1049
- assetProcessingService: AssetService,
1050
- runtimeOrigin: 'http://localhost:3000',
1051
- });
1052
-
1053
- renderer.RenderedBody = new Response('<html><body><main>Stream Body</main></body></html>').body as BodyInit;
1054
- renderer.PageModule = {
1055
- default: (() => '<main>Stream Body</main>') as unknown as EcoPageComponent<any>,
1056
- };
1057
- renderer.HtmlTemplate = (() =>
1058
- '<html><body><main>Stream Body</main></body></html>') as EcoComponent<HtmlTemplateProps>;
1059
-
1060
- const result = await renderer.execute({
1061
- file: '/app/pages/index.ts',
1062
- params: {},
1063
- query: {},
1064
- });
1065
-
1066
- const body = await new Response(result.body as BodyInit).text();
1067
- expect(body).toContain('<main>Stream Body</main>');
1068
- });
1069
-
1070
- it('should include renderComponent assets and apply root attributes', async () => {
1071
- const appConfig = {
1072
- ...AppConfig,
1073
- } as EcoPagesAppConfig;
1074
-
1075
- const renderer = new TestIntegrationRenderer({
1076
- appConfig,
1077
- assetProcessingService: AssetService,
1078
- runtimeOrigin: 'http://localhost:3000',
1079
- });
1080
-
1081
- renderer.RenderedBody = '<html><body><main>Test Page</main></body></html>';
1082
- renderer.MockComponentRenderResult = {
1083
- html: '<main>Test Page</main>',
1084
- canAttachAttributes: true,
1085
- rootTag: 'main',
1086
- integrationName: 'test-renderer',
1087
- rootAttributes: { 'data-eco-component-id': 'eco-page-root' },
1088
- assets: [
1089
- {
1090
- kind: 'script',
1091
- srcUrl: '/assets/island.js',
1092
- position: 'head',
1093
- } as ProcessedAsset,
1094
- ],
1095
- };
1096
-
1097
- renderer.PageModule = {
1098
- default: (() => '<main>Test Page</main>') as unknown as EcoPageComponent<any>,
1099
- };
1100
- renderer.HtmlTemplate = (() =>
1101
- '<html><body><main>Test Page</main></body></html>') as EcoComponent<HtmlTemplateProps>;
1102
-
1103
- const result = await renderer.execute({
1104
- file: '/app/pages/index.ts',
1105
- params: {},
1106
- query: {},
1107
- });
1108
-
1109
- const body = await new Response(result.body as BodyInit).text();
1110
- expect(body).toContain('<main data-eco-component-id="eco-page-root">Test Page</main>');
1111
-
1112
- const processedDeps = (renderer as any).htmlTransformer.getProcessedDependencies();
1113
- expect(processedDeps.some((dep: ProcessedAsset) => dep.srcUrl === '/assets/island.js')).toBe(true);
1114
- });
1115
-
1116
- it('should not force render nested dependency components without resolved props context', async () => {
1117
- const explicitRenderer = {
1118
- renderComponent: vi.fn(async () => ({
1119
- html: '<aside>Nested</aside>',
1120
- canAttachAttributes: true,
1121
- rootTag: 'aside',
1122
- integrationName: 'explicit-renderer',
1123
- })),
1124
- renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
1125
- explicitRenderer.renderComponent(input),
1126
- ),
1127
- } as unknown as IntegrationRenderer;
1128
-
1129
- const appConfig = {
1130
- ...AppConfig,
1131
- integrations: [
1132
- {
1133
- name: 'explicit-renderer',
1134
- initializeRenderer: () => explicitRenderer,
1135
- },
1136
- ],
1137
- } as unknown as EcoPagesAppConfig;
1138
-
1139
- const renderer = new TestIntegrationRenderer({
1140
- appConfig,
1141
- assetProcessingService: AssetService,
1142
- runtimeOrigin: 'http://localhost:3000',
1143
- });
1144
-
1145
- renderer.RenderedBody = '<html><body><main>Test Page</main></body></html>';
1146
- renderer.MockComponentRenderResult = {
1147
- html: '<main>Test Page</main>',
1148
- canAttachAttributes: true,
1149
- rootTag: 'main',
1150
- integrationName: 'test-renderer',
1151
- rootAttributes: { 'data-eco-component-id': 'eco-page-root' },
1152
- };
1153
-
1154
- const NestedComponent = (() => '<aside>Nested</aside>') as EcoComponent<Record<string, unknown>>;
1155
- NestedComponent.config = {
1156
- integration: 'explicit-renderer',
1157
- __eco: {
1158
- id: 'nested-component',
1159
- file: '/app/components/nested-component.ts',
1160
- integration: 'test-renderer',
1161
- },
1162
- };
1163
-
1164
- const Page = (() => '<main>Test Page</main>') as unknown as EcoPageComponent<any>;
1165
- Page.config = {
1166
- dependencies: {
1167
- components: [NestedComponent],
1168
- },
1169
- };
1170
-
1171
- renderer.PageModule = {
1172
- default: Page,
1173
- };
1174
- renderer.HtmlTemplate = (() =>
1175
- '<html><body><main>Test Page</main></body></html>') as EcoComponent<HtmlTemplateProps>;
1176
-
1177
- const result = await renderer.execute({
1178
- file: '/app/pages/index.ts',
1179
- params: {},
1180
- query: {},
1181
- });
1182
-
1183
- expect((explicitRenderer.renderComponent as any).mock.calls).toHaveLength(0);
1184
-
1185
- const body = await new Response(result.body as BodyInit).text();
1186
- expect(body).toContain('<main data-eco-component-id="eco-page-root">Test Page</main>');
1187
-
1188
- const processedDeps = (renderer as any).htmlTransformer.getProcessedDependencies();
1189
- expect(processedDeps.some((dep: ProcessedAsset) => dep.srcUrl === '/assets/nested-explicit.js')).toBe(
1190
- false,
1191
- );
1192
- });
1193
-
1194
- it('should include global integration dependencies for referenced component integrations once', async () => {
1195
- const explicitIntegrationDependency = {
1196
- kind: 'script',
1197
- srcUrl: '/assets/react-runtime.js',
1198
- position: 'head',
1199
- } as ProcessedAsset;
1200
-
1201
- const appConfig = {
1202
- ...AppConfig,
1203
- integrations: [
1204
- {
1205
- name: 'react',
1206
- initializeRenderer: () => renderer as unknown as IntegrationRenderer,
1207
- getResolvedIntegrationDependencies: () => [explicitIntegrationDependency],
1208
- },
1209
- ],
1210
- } as unknown as EcoPagesAppConfig;
1211
-
1212
- const renderer = new TestIntegrationRenderer({
1213
- appConfig,
1214
- assetProcessingService: AssetService,
1215
- runtimeOrigin: 'http://localhost:3000',
1216
- });
1217
-
1218
- renderer.RenderedBody = '<html><body><main>Test Page</main></body></html>';
1219
- renderer.MockComponentRenderResult = {
1220
- html: '<main>Test Page</main>',
1221
- canAttachAttributes: true,
1222
- rootTag: 'main',
1223
- integrationName: 'test-renderer',
1224
- };
1225
-
1226
- const ReactNested = (() => '<div>React Nested</div>') as EcoComponent<Record<string, unknown>>;
1227
- ReactNested.config = {
1228
- integration: 'react',
1229
- __eco: {
1230
- id: 'react-nested',
1231
- file: '/app/components/react-nested.react.tsx',
1232
- integration: 'react',
1233
- },
1234
- };
1235
-
1236
- const Page = (() => '<main>Test Page</main>') as unknown as EcoPageComponent<any>;
1237
- Page.config = {
1238
- dependencies: {
1239
- components: [ReactNested],
1240
- },
1241
- };
1242
-
1243
- renderer.PageModule = {
1244
- default: Page,
1245
- };
1246
- renderer.HtmlTemplate = (() =>
1247
- '<html><body><main>Test Page</main></body></html>') as EcoComponent<HtmlTemplateProps>;
1248
-
1249
- await renderer.execute({
1250
- file: '/app/pages/index.ts',
1251
- params: {},
1252
- query: {},
1253
- });
1254
-
1255
- const processedDeps = (renderer as any).htmlTransformer.getProcessedDependencies();
1256
- expect(
1257
- processedDeps.filter((dep: ProcessedAsset) => dep.srcUrl === '/assets/react-runtime.js'),
1258
- ).toHaveLength(1);
1259
- });
1260
-
1261
- it('should fail route execution when unresolved eco-marker artifact HTML is returned', async () => {
1262
- const explicitRenderer = {
1263
- renderComponent: vi.fn(async () => ({
1264
- html: '<aside>Nested Render</aside>',
1265
- canAttachAttributes: true,
1266
- rootTag: 'aside',
1267
- integrationName: 'explicit-renderer',
1268
- rootAttributes: { 'data-eco-component-id': 'nested-1' },
1269
- assets: [
1270
- {
1271
- kind: 'script',
1272
- srcUrl: '/assets/nested-render.js',
1273
- position: 'head',
1274
- } as ProcessedAsset,
1275
- ],
1276
- })),
1277
- renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
1278
- explicitRenderer.renderComponent(input),
1279
- ),
1280
- } as unknown as IntegrationRenderer;
1281
-
1282
- const appConfig = {
1283
- ...AppConfig,
1284
- integrations: [
1285
- {
1286
- name: 'explicit-renderer',
1287
- initializeRenderer: () => explicitRenderer,
1288
- },
1289
- ],
1290
- } as unknown as EcoPagesAppConfig;
1291
-
1292
- const renderer = new TestIntegrationRenderer({
1293
- appConfig,
1294
- assetProcessingService: AssetService,
1295
- runtimeOrigin: 'http://localhost:3000',
1296
- });
1297
-
1298
- renderer.RenderedBody =
1299
- '<html><body><main>Before<eco-marker data-eco-node-id="n_1" data-eco-component-ref="nested-component" data-eco-props-ref="props-1"></eco-marker>After</main></body></html>';
1300
- renderer.MockComponentRenderResult = {
1301
- html: '<main>Test Page</main>',
1302
- canAttachAttributes: true,
1303
- rootTag: 'main',
1304
- integrationName: 'test-renderer',
1305
- };
1306
-
1307
- const NestedComponent = (() => '<aside>Nested</aside>') as EcoComponent<Record<string, unknown>>;
1308
- NestedComponent.config = {
1309
- integration: 'explicit-renderer',
1310
- __eco: {
1311
- id: 'nested-component',
1312
- file: '/app/components/nested-component.ts',
1313
- integration: 'explicit-renderer',
1314
- },
1315
- };
1316
-
1317
- const Page = (() => '<main>Test Page</main>') as unknown as EcoPageComponent<any>;
1318
- Page.config = {
1319
- dependencies: {
1320
- components: [NestedComponent],
1321
- },
1322
- };
1323
-
1324
- renderer.PageModule = {
1325
- default: Page,
1326
- } as unknown as EcoPageFile;
1327
- renderer.RenderBodyFactory = () => renderer.RenderedBody;
1328
- renderer.HtmlTemplate = (() =>
1329
- '<html><body><main>Test Page</main></body></html>') as EcoComponent<HtmlTemplateProps>;
1330
-
1331
- await expect(
1332
- renderer.execute({
1333
- file: '/app/pages/index.ts',
1334
- params: {},
1335
- query: {},
1336
- }),
1337
- ).rejects.toThrow('Full-route unresolved-marker fallback has been removed');
1338
- expect((explicitRenderer.renderComponent as any).mock.calls).toHaveLength(0);
1339
- });
1340
-
1341
- it('should fail route execution when unresolved eco-marker artifact HTML remains inside surrounding shell html', async () => {
1342
- const explicitRenderer = {
1343
- renderComponent: vi.fn(async () => ({
1344
- html: '<aside data-explicit-shell="nested"><span>Nested Render</span></aside>',
1345
- canAttachAttributes: true,
1346
- rootTag: 'aside',
1347
- integrationName: 'explicit-renderer',
1348
- rootAttributes: { 'data-eco-component-id': 'nested-contract-root' },
1349
- assets: [
1350
- {
1351
- kind: 'script',
1352
- srcUrl: '/assets/explicit-contract.js',
1353
- position: 'head',
1354
- } as ProcessedAsset,
1355
- ],
1356
- })),
1357
- renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
1358
- explicitRenderer.renderComponent(input),
1359
- ),
1360
- } as unknown as IntegrationRenderer;
1361
-
1362
- const appConfig = {
1363
- ...AppConfig,
1364
- integrations: [
1365
- {
1366
- name: 'explicit-renderer',
1367
- initializeRenderer: () => explicitRenderer,
1368
- },
1369
- ],
1370
- } as unknown as EcoPagesAppConfig;
1371
-
1372
- const renderer = new TestIntegrationRenderer({
1373
- appConfig,
1374
- assetProcessingService: AssetService,
1375
- runtimeOrigin: 'http://localhost:3000',
1376
- });
1377
-
1378
- renderer.RenderedBody =
1379
- '<html><body><main><section data-shell="outer">Before<eco-marker data-eco-node-id="n_1" data-eco-component-ref="nested-component" data-eco-props-ref="props-1"></eco-marker>After</section></main></body></html>';
1380
- renderer.MockComponentRenderResult = {
1381
- html: '<main>Test Page</main>',
1382
- canAttachAttributes: true,
1383
- rootTag: 'main',
1384
- integrationName: 'test-renderer',
1385
- };
1386
-
1387
- const NestedComponent = (() => '<aside>Nested</aside>') as EcoComponent<Record<string, unknown>>;
1388
- NestedComponent.config = {
1389
- integration: 'explicit-renderer',
1390
- __eco: {
1391
- id: 'nested-component',
1392
- file: '/app/components/nested-component.ts',
1393
- integration: 'explicit-renderer',
1394
- },
1395
- };
1396
-
1397
- const Page = (() => '<main>Test Page</main>') as unknown as EcoPageComponent<any>;
1398
- Page.config = {
1399
- dependencies: {
1400
- components: [NestedComponent],
1401
- },
1402
- };
1403
-
1404
- renderer.PageModule = {
1405
- default: Page,
1406
- } as unknown as EcoPageFile;
1407
- renderer.RenderBodyFactory = () => renderer.RenderedBody;
1408
- renderer.HtmlTemplate = (() =>
1409
- '<html><body><main>Test Page</main></body></html>') as EcoComponent<HtmlTemplateProps>;
1410
-
1411
- await expect(
1412
- renderer.execute({
1413
- file: '/app/pages/index.ts',
1414
- params: {},
1415
- query: {},
1416
- }),
1417
- ).rejects.toThrow('Full-route unresolved-marker fallback has been removed');
1418
- expect((explicitRenderer.renderComponent as any).mock.calls).toHaveLength(0);
1419
- });
1420
-
1421
- it('should fail route execution for deep multi-level unresolved eco-marker artifacts', async () => {
1422
- const renderOrder: string[] = [];
1423
- const explicitRenderer = {
1424
- renderComponent: vi.fn(async (input: ComponentRenderInput) => {
1425
- const componentId = input.component.config?.__eco?.id;
1426
- renderOrder.push(componentId as string);
1427
-
1428
- if (componentId === 'leaf-component') {
1429
- return {
1430
- html: '<span>Leaf Render</span>',
1431
- canAttachAttributes: true,
1432
- rootTag: 'span',
1433
- integrationName: 'explicit-renderer',
1434
- };
1435
- }
1436
-
1437
- if (componentId === 'parent-component') {
1438
- return {
1439
- html: `<section>${input.children ?? ''}</section>`,
1440
- canAttachAttributes: true,
1441
- rootTag: 'section',
1442
- integrationName: 'explicit-renderer',
1443
- };
1444
- }
1445
-
1446
- return {
1447
- html: `<article>${input.children ?? ''}</article>`,
1448
- canAttachAttributes: true,
1449
- rootTag: 'article',
1450
- integrationName: 'explicit-renderer',
1451
- rootAttributes: { 'data-eco-component-id': 'root-node' },
1452
- };
1453
- }),
1454
- renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
1455
- explicitRenderer.renderComponent(input),
1456
- ),
1457
- } as unknown as IntegrationRenderer;
1458
-
1459
- const appConfig = {
1460
- ...AppConfig,
1461
- integrations: [
1462
- {
1463
- name: 'explicit-renderer',
1464
- initializeRenderer: () => explicitRenderer,
1465
- },
1466
- ],
1467
- } as unknown as EcoPagesAppConfig;
1468
-
1469
- const renderer = new TestIntegrationRenderer({
1470
- appConfig,
1471
- assetProcessingService: AssetService,
1472
- runtimeOrigin: 'http://localhost:3000',
1473
- });
1474
-
1475
- const _parentMarker = createUnresolvedMarkerArtifact('n_2', 'parent-component', 'props-parent');
1476
- const _leafMarker = createUnresolvedMarkerArtifact('n_3', 'leaf-component', 'props-leaf');
1477
-
1478
- renderer.RenderedBody = `<html><body><main>${createUnresolvedMarkerArtifact('n_1', 'root-component', 'props-root')}</main></body></html>`;
1479
- renderer.MockComponentRenderResult = {
1480
- html: '<main>Test Page</main>',
1481
- canAttachAttributes: true,
1482
- rootTag: 'main',
1483
- integrationName: 'test-renderer',
1484
- };
1485
-
1486
- const RootComponent = (() => '<article>Root</article>') as EcoComponent<Record<string, unknown>>;
1487
- RootComponent.config = {
1488
- integration: 'explicit-renderer',
1489
- __eco: {
1490
- id: 'root-component',
1491
- file: '/app/components/root-component.ts',
1492
- integration: 'explicit-renderer',
1493
- },
1494
- };
1495
-
1496
- const ParentComponent = (() => '<section>Parent</section>') as EcoComponent<Record<string, unknown>>;
1497
- ParentComponent.config = {
1498
- integration: 'explicit-renderer',
1499
- __eco: {
1500
- id: 'parent-component',
1501
- file: '/app/components/parent-component.ts',
1502
- integration: 'explicit-renderer',
1503
- },
1504
- };
1505
-
1506
- const LeafComponent = (() => '<span>Leaf</span>') as EcoComponent<Record<string, unknown>>;
1507
- LeafComponent.config = {
1508
- integration: 'explicit-renderer',
1509
- __eco: {
1510
- id: 'leaf-component',
1511
- file: '/app/components/leaf-component.ts',
1512
- integration: 'explicit-renderer',
1513
- },
1514
- };
1515
-
1516
- const Page = (() => '<main>Test Page</main>') as unknown as EcoPageComponent<any>;
1517
- Page.config = {
1518
- dependencies: {
1519
- components: [RootComponent, ParentComponent, LeafComponent],
1520
- },
1521
- };
1522
-
1523
- renderer.PageModule = {
1524
- default: Page,
1525
- } as unknown as EcoPageFile;
1526
- renderer.RenderBodyFactory = () => renderer.RenderedBody;
1527
- renderer.HtmlTemplate = (() =>
1528
- '<html><body><main>Test Page</main></body></html>') as EcoComponent<HtmlTemplateProps>;
1529
-
1530
- await expect(
1531
- renderer.execute({
1532
- file: '/app/pages/index.ts',
1533
- params: {},
1534
- query: {},
1535
- }),
1536
- ).rejects.toThrow('Full-route unresolved-marker fallback has been removed');
1537
- expect(renderOrder).toEqual([]);
1538
- });
1539
-
1540
- it('should fail route execution when props reference is missing', async () => {
1541
- const explicitRenderer = {
1542
- renderComponent: vi.fn(async () => ({
1543
- html: '<aside>Nested Render</aside>',
1544
- canAttachAttributes: true,
1545
- rootTag: 'aside',
1546
- integrationName: 'explicit-renderer',
1547
- })),
1548
- renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
1549
- explicitRenderer.renderComponent(input),
1550
- ),
1551
- } as unknown as IntegrationRenderer;
1552
-
1553
- const appConfig = {
1554
- ...AppConfig,
1555
- integrations: [
1556
- {
1557
- name: 'explicit-renderer',
1558
- initializeRenderer: () => explicitRenderer,
1559
- },
1560
- ],
1561
- } as unknown as EcoPagesAppConfig;
1562
-
1563
- const renderer = new TestIntegrationRenderer({
1564
- appConfig,
1565
- assetProcessingService: AssetService,
1566
- runtimeOrigin: 'http://localhost:3000',
1567
- });
1568
-
1569
- renderer.RenderedBody =
1570
- '<html><body><main><eco-marker data-eco-node-id="n_1" data-eco-component-ref="nested-component" data-eco-props-ref="props-missing"></eco-marker></main></body></html>';
1571
- renderer.MockComponentRenderResult = {
1572
- html: '<main>Test Page</main>',
1573
- canAttachAttributes: true,
1574
- rootTag: 'main',
1575
- integrationName: 'test-renderer',
1576
- };
1577
-
1578
- const NestedComponent = (() => '<aside>Nested</aside>') as EcoComponent<Record<string, unknown>>;
1579
- NestedComponent.config = {
1580
- integration: 'explicit-renderer',
1581
- __eco: {
1582
- id: 'nested-component',
1583
- file: '/app/components/nested-component.ts',
1584
- integration: 'explicit-renderer',
1585
- },
1586
- };
1587
-
1588
- const Page = (() => '<main>Test Page</main>') as unknown as EcoPageComponent<any>;
1589
- Page.config = {
1590
- dependencies: {
1591
- components: [NestedComponent],
1592
- },
1593
- };
1594
-
1595
- renderer.PageModule = {
1596
- default: Page,
1597
- } as unknown as EcoPageFile;
1598
- renderer.HtmlTemplate = (() =>
1599
- '<html><body><main>Test Page</main></body></html>') as EcoComponent<HtmlTemplateProps>;
1600
-
1601
- await expect(
1602
- renderer.execute({
1603
- file: '/app/pages/index.ts',
1604
- params: {},
1605
- query: {},
1606
- }),
1607
- ).rejects.toThrow('Full-route unresolved-marker fallback has been removed');
1608
- });
1609
-
1610
- it('should not recursively resolve unresolved eco-marker artifacts that were only passed through resolved child html', async () => {
1611
- const renderer = new TestIntegrationRenderer({
1612
- appConfig: AppConfig,
1613
- assetProcessingService: AssetService,
1614
- runtimeOrigin: 'http://localhost:3000',
1615
- });
1616
-
1617
- renderer.MockComponentRenderResult = {
1618
- html: '<section><eco-marker data-eco-node-id="n_2" data-eco-component-ref="nested-component" data-eco-props-ref="p_2"></eco-marker></section>',
1619
- canAttachAttributes: true,
1620
- rootTag: 'section',
1621
- integrationName: 'test-renderer',
1622
- };
1623
-
1624
- const Component = (() => '<section />') as EcoComponent<Record<string, unknown>>;
1625
- Component.config = {
1626
- integration: 'test-renderer',
1627
- __eco: {
1628
- id: 'component',
1629
- file: '/app/components/component.ts',
1630
- integration: 'test-renderer',
1631
- },
1632
- };
1633
-
1634
- await expect(
1635
- renderer.renderComponentWithForeignChildren({
1636
- component: Component,
1637
- props: {},
1638
- children: '<aside>already resolved child html</aside>',
1639
- }),
1640
- ).resolves.toEqual(
1641
- expect.objectContaining({
1642
- html: '<section><eco-marker data-eco-node-id="n_2" data-eco-component-ref="nested-component" data-eco-props-ref="p_2"></eco-marker></section>',
1643
- }),
1644
- );
1645
- });
1646
-
1647
- it('fails fast when a renderer without a foreign-child runtime crosses into a foreign owner', async () => {
1648
- const foreignRenderer = {
1649
- renderComponent: vi.fn(async () => ({
1650
- html: '<span>resolved nested marker</span>',
1651
- canAttachAttributes: true,
1652
- rootTag: 'span',
1653
- integrationName: 'foreign-renderer',
1654
- })),
1655
- renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
1656
- foreignRenderer.renderComponent(input),
1657
- ),
1658
- } as unknown as IntegrationRenderer;
1659
-
1660
- const appConfig = {
1661
- ...AppConfig,
1662
- integrations: [
1663
- {
1664
- name: 'foreign-renderer',
1665
- initializeRenderer: () => foreignRenderer,
1666
- },
1667
- ],
1668
- } as unknown as EcoPagesAppConfig;
1669
-
1670
- const renderer = new TestIntegrationRenderer({
1671
- appConfig,
1672
- assetProcessingService: AssetService,
1673
- runtimeOrigin: 'http://localhost:3000',
1674
- });
1675
- renderer.UseFailFastForeignChildRuntime = true;
1676
-
1677
- const ForeignComponent = eco.component<{}, string>({
1678
- integration: 'foreign-renderer',
1679
- render: () => '<span>foreign</span>',
1680
- });
1681
-
1682
- const ShellComponent = eco.component<{ children?: string }, string>({
1683
- integration: 'test-renderer',
1684
- dependencies: {
1685
- components: [ForeignComponent],
1686
- },
1687
- render: ({ children }) => `<section>${children ?? ''}${ForeignComponent({})}</section>`,
1688
- });
1689
-
1690
- const passedThroughMarker = createUnresolvedMarkerArtifact(
1691
- 'n_passed',
1692
- 'passed-through-component',
1693
- 'p_passed',
1694
- );
1695
-
1696
- await expect(
1697
- renderer.renderComponentWithForeignChildren({
1698
- component: ShellComponent,
1699
- props: { children: passedThroughMarker },
1700
- children: passedThroughMarker,
1701
- }),
1702
- ).rejects.toThrow('without a renderer-owned foreign-child runtime');
1703
- expect(foreignRenderer.renderComponent).toHaveBeenCalledTimes(0);
1704
- });
1705
-
1706
- it('fails route execution when deep mixed-integration eco-marker artifacts are returned at the route level', async () => {
1707
- const renderOrder: string[] = [];
1708
- const explicitRenderer = {
1709
- renderComponent: vi.fn(async (input: ComponentRenderInput) => {
1710
- const componentId = input.component.config?.__eco?.id as string | undefined;
1711
- if (componentId) {
1712
- renderOrder.push(componentId);
1713
- }
1714
-
1715
- if (componentId === 'leaf-component') {
1716
- return {
1717
- html: '<span data-leaf="true">Leaf Render</span>',
1718
- canAttachAttributes: true,
1719
- rootTag: 'span',
1720
- integrationName: 'explicit-renderer',
1721
- };
1722
- }
1723
-
1724
- if (componentId === 'parent-component') {
1725
- return {
1726
- html: `<section data-parent="true">${input.children ?? ''}</section>`,
1727
- canAttachAttributes: true,
1728
- rootTag: 'section',
1729
- integrationName: 'explicit-renderer',
1730
- };
1731
- }
1732
-
1733
- return {
1734
- html: `<article data-root="true">${input.children ?? ''}</article>`,
1735
- canAttachAttributes: true,
1736
- rootTag: 'article',
1737
- integrationName: 'explicit-renderer',
1738
- };
1739
- }),
1740
- renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
1741
- explicitRenderer.renderComponent(input),
1742
- ),
1743
- } as unknown as IntegrationRenderer;
1744
-
1745
- const appConfig = {
1746
- ...AppConfig,
1747
- integrations: [
1748
- {
1749
- name: 'explicit-renderer',
1750
- initializeRenderer: () => explicitRenderer,
1751
- },
1752
- ],
1753
- } as unknown as EcoPagesAppConfig;
1754
-
1755
- const renderer = new TestIntegrationRenderer({
1756
- appConfig,
1757
- assetProcessingService: AssetService,
1758
- runtimeOrigin: 'http://localhost:3000',
1759
- });
1760
-
1761
- const _parentMarker = createUnresolvedMarkerArtifact('n_2', 'parent-component', 'props-parent');
1762
- const _leafMarker = createUnresolvedMarkerArtifact('n_3', 'leaf-component', 'props-leaf');
1763
-
1764
- renderer.RenderedBody = `<html><body><main><div data-shell="deep">${createUnresolvedMarkerArtifact('n_1', 'root-component', 'props-root')}</div></main></body></html>`;
1765
- renderer.MockComponentRenderResult = {
1766
- html: '<main>Test Page</main>',
1767
- canAttachAttributes: true,
1768
- rootTag: 'main',
1769
- integrationName: 'test-renderer',
1770
- };
1771
-
1772
- const RootComponent = (() => '<article>Root</article>') as EcoComponent<Record<string, unknown>>;
1773
- RootComponent.config = {
1774
- integration: 'explicit-renderer',
1775
- __eco: {
1776
- id: 'root-component',
1777
- file: '/app/components/root-component.ts',
1778
- integration: 'explicit-renderer',
1779
- },
1780
- };
1781
-
1782
- const ParentComponent = (() => '<section>Parent</section>') as EcoComponent<Record<string, unknown>>;
1783
- ParentComponent.config = {
1784
- integration: 'explicit-renderer',
1785
- __eco: {
1786
- id: 'parent-component',
1787
- file: '/app/components/parent-component.ts',
1788
- integration: 'explicit-renderer',
1789
- },
1790
- };
1791
-
1792
- const LeafComponent = (() => '<span>Leaf</span>') as EcoComponent<Record<string, unknown>>;
1793
- LeafComponent.config = {
1794
- integration: 'explicit-renderer',
1795
- __eco: {
1796
- id: 'leaf-component',
1797
- file: '/app/components/leaf-component.ts',
1798
- integration: 'explicit-renderer',
1799
- },
1800
- };
1801
-
1802
- const Page = (() => '<main>Test Page</main>') as unknown as EcoPageComponent<any>;
1803
- Page.config = {
1804
- dependencies: {
1805
- components: [RootComponent, ParentComponent, LeafComponent],
1806
- },
1807
- };
1808
-
1809
- renderer.PageModule = {
1810
- default: Page,
1811
- } as unknown as EcoPageFile;
1812
- renderer.RenderBodyFactory = () => renderer.RenderedBody;
1813
- renderer.HtmlTemplate = (() =>
1814
- '<html><body><main>Test Page</main></body></html>') as EcoComponent<HtmlTemplateProps>;
1815
-
1816
- await expect(
1817
- renderer.execute({
1818
- file: '/app/pages/index.ts',
1819
- params: {},
1820
- query: {},
1821
- }),
1822
- ).rejects.toThrow('Full-route unresolved-marker fallback has been removed');
1823
- expect(renderOrder).toEqual([]);
1824
- });
1825
-
1826
- it('renders same-integration leaf components under their own integration context', async () => {
1827
- const renderer = new TestIntegrationRenderer({
1828
- appConfig: AppConfig,
1829
- assetProcessingService: AssetService,
1830
- runtimeOrigin: 'http://localhost:3000',
1831
- });
1832
-
1833
- const LeafComponent = eco.component<{}, string>({
1834
- integration: 'test-renderer',
1835
- render: () => '<section>Leaf Render</section>',
1836
- });
1837
-
1838
- const result = await runWithComponentRenderContext(
1839
- {
1840
- currentIntegration: 'foreign-renderer',
1841
- },
1842
- async () =>
1843
- renderer.renderComponentWithForeignChildren({
1844
- component: LeafComponent,
1845
- props: {},
1846
- }),
1847
- );
1848
-
1849
- expect(result.value.html).toBe('<section>Leaf Render</section>');
1850
- expect(result.value.html).not.toContain('<eco-marker');
1851
- expect(renderer.ForeignChildRuntimeCreationCount).toBe(0);
1852
- });
1853
-
1854
- it('uses inline partial rendering when no foreign children are present', async () => {
1855
- const renderer = new TestIntegrationRenderer({
1856
- appConfig: AppConfig,
1857
- assetProcessingService: AssetService,
1858
- runtimeOrigin: 'http://localhost:3000',
1859
- });
1860
- const View = (() => '<section>Inline</section>') as EcoComponent<Record<string, unknown>>;
1861
- View.config = {
1862
- integration: 'test-renderer',
1863
- __eco: {
1864
- id: 'inline-view',
1865
- file: '/app/components/inline-view.ts',
1866
- integration: 'test-renderer',
1867
- },
1868
- };
1869
-
1870
- let inlineRenderCount = 0;
1871
- const response = await renderer.testRenderPartialViewResponse({
1872
- view: View,
1873
- props: {},
1874
- renderInline: async () => {
1875
- inlineRenderCount += 1;
1876
- return '<section>Inline</section>';
1877
- },
1878
- });
1879
-
1880
- expect(await response.text()).toBe('<section>Inline</section>');
1881
- expect(inlineRenderCount).toBe(1);
1882
- expect(renderer.ForeignChildRenderCount).toBe(0);
1883
- });
1884
-
1885
- it('falls back to foreign-subtree partial rendering when compatibility is needed', async () => {
1886
- const renderer = new TestIntegrationRenderer({
1887
- appConfig: AppConfig,
1888
- assetProcessingService: AssetService,
1889
- runtimeOrigin: 'http://localhost:3000',
1890
- });
1891
- const ForeignChild = (() => '<span>Foreign</span>') as EcoComponent<Record<string, unknown>>;
1892
- ForeignChild.config = {
1893
- integration: 'react',
1894
- __eco: {
1895
- id: 'foreign-child',
1896
- file: '/app/components/foreign-child.tsx',
1897
- integration: 'react',
1898
- },
1899
- };
1900
-
1901
- const View = (() => '<section>Foreign Subtree</section>') as EcoComponent<Record<string, unknown>>;
1902
- View.config = {
1903
- integration: 'test-renderer',
1904
- __eco: {
1905
- id: 'foreign-subtree-view',
1906
- file: '/app/components/foreign-subtree-view.ts',
1907
- integration: 'test-renderer',
1908
- },
1909
- dependencies: { components: [ForeignChild] },
1910
- };
1911
- renderer.MockComponentRenderResult = {
1912
- html: '<section>Foreign Subtree</section>',
1913
- canAttachAttributes: true,
1914
- rootTag: 'section',
1915
- integrationName: 'test-renderer',
1916
- };
1917
-
1918
- let inlineRenderCount = 0;
1919
- const response = await renderer.testRenderPartialViewResponse({
1920
- view: View,
1921
- props: {},
1922
- renderInline: async () => {
1923
- inlineRenderCount += 1;
1924
- return '<section>Inline</section>';
1925
- },
1926
- });
1927
-
1928
- expect(await response.text()).toBe('<section>Foreign Subtree</section>');
1929
- expect(inlineRenderCount).toBe(0);
1930
- expect(renderer.ForeignChildRenderCount).toBe(1);
1931
- });
1932
-
1933
- it('reuses one foreign renderer instance across shared view shell composition', async () => {
1934
- const foreignRenderer = {
1935
- renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) => {
1936
- const componentId = input.component.config?.__eco?.id;
1937
-
1938
- if (componentId === 'foreign-html-template') {
1939
- return {
1940
- html: `<html><body>${String(input.children ?? '')}</body></html>`,
1941
- canAttachAttributes: true,
1942
- rootTag: 'html',
1943
- integrationName: 'foreign-renderer',
1944
- };
1945
- }
1946
-
1947
- if (componentId === 'foreign-layout') {
1948
- return {
1949
- html: `<main>${String(input.children ?? '')}</main>`,
1950
- canAttachAttributes: true,
1951
- rootTag: 'main',
1952
- integrationName: 'foreign-renderer',
1953
- };
1954
- }
1955
-
1956
- return {
1957
- html: '<section>Foreign View</section>',
1958
- canAttachAttributes: true,
1959
- rootTag: 'section',
1960
- integrationName: 'foreign-renderer',
1961
- };
1962
- }),
1963
- } as unknown as IntegrationRenderer;
1964
-
1965
- const initializeRenderer = vi.fn(() => foreignRenderer);
1966
- const appConfig = {
1967
- ...AppConfig,
1968
- integrations: [
1969
- {
1970
- name: 'foreign-renderer',
1971
- initializeRenderer,
1972
- },
1973
- ],
1974
- } as unknown as EcoPagesAppConfig;
1975
-
1976
- const renderer = new TestIntegrationRenderer({
1977
- appConfig,
1978
- assetProcessingService: AssetService,
1979
- runtimeOrigin: 'http://localhost:3000',
1980
- });
1981
-
1982
- const View = (() => '<section>Foreign View</section>') as EcoComponent<Record<string, unknown>>;
1983
- View.config = {
1984
- integration: 'foreign-renderer',
1985
- __eco: {
1986
- id: 'foreign-view',
1987
- file: '/app/components/foreign-view.ts',
1988
- integration: 'foreign-renderer',
1989
- },
1990
- };
1991
-
1992
- const Layout = (() => '<main />') as EcoComponent<Record<string, unknown>>;
1993
- Layout.config = {
1994
- integration: 'foreign-renderer',
1995
- __eco: {
1996
- id: 'foreign-layout',
1997
- file: '/app/components/foreign-layout.ts',
1998
- integration: 'foreign-renderer',
1999
- },
2000
- };
2001
-
2002
- renderer.HtmlTemplate = (() => '<html><body></body></html>') as EcoComponent<HtmlTemplateProps>;
2003
- renderer.HtmlTemplate.config = {
2004
- integration: 'foreign-renderer',
2005
- __eco: {
2006
- id: 'foreign-html-template',
2007
- file: '/app/components/foreign-html-template.ts',
2008
- integration: 'foreign-renderer',
2009
- },
2010
- };
2011
-
2012
- const response = await renderer.testRenderViewWithDocumentShell({
2013
- view: View,
2014
- props: {},
2015
- layout: Layout,
2016
- });
2017
-
2018
- expect(await response.text()).toContain('<main><section>Foreign View</section></main>');
2019
- expect(initializeRenderer).toHaveBeenCalledTimes(1);
2020
- expect(foreignRenderer.renderComponentWithForeignChildren).toHaveBeenCalledTimes(3);
2021
- });
2022
-
2023
- it('should skip foreign-child wrapping for pure same-integration component trees', () => {
2024
- const renderer = new TestIntegrationRenderer({
2025
- appConfig: AppConfig,
2026
- assetProcessingService: AssetService,
2027
- runtimeOrigin: 'http://localhost:3000',
2028
- });
2029
-
2030
- const Child = (() => '<span>Child</span>') as EcoComponent<Record<string, unknown>>;
2031
- Child.config = {
2032
- integration: 'test-renderer',
2033
- __eco: {
2034
- id: 'child-component',
2035
- file: '/app/components/child-component.ts',
2036
- integration: 'test-renderer',
2037
- },
2038
- };
2039
-
2040
- const Root = (() => '<section>Root</section>') as EcoComponent<Record<string, unknown>>;
2041
- Root.config = {
2042
- integration: 'test-renderer',
2043
- __eco: {
2044
- id: 'root-component',
2045
- file: '/app/components/root-component.ts',
2046
- integration: 'test-renderer',
2047
- },
2048
- dependencies: { components: [Child] },
2049
- };
2050
-
2051
- expect(renderer.testHasForeignChildDescendants(Root)).toBe(false);
2052
- });
2053
-
2054
- it('should detect nested cross-integration component trees', () => {
2055
- const renderer = new TestIntegrationRenderer({
2056
- appConfig: AppConfig,
2057
- assetProcessingService: AssetService,
2058
- runtimeOrigin: 'http://localhost:3000',
2059
- });
2060
-
2061
- const ForeignChild = (() => '<span>Child</span>') as EcoComponent<Record<string, unknown>>;
2062
- ForeignChild.config = {
2063
- integration: 'react',
2064
- __eco: {
2065
- id: 'foreign-child-component',
2066
- file: '/app/components/foreign-child-component.tsx',
2067
- integration: 'react',
2068
- },
2069
- };
2070
-
2071
- const Root = (() => '<section>Root</section>') as EcoComponent<Record<string, unknown>>;
2072
- Root.config = {
2073
- integration: 'test-renderer',
2074
- __eco: {
2075
- id: 'root-component',
2076
- file: '/app/components/root-component.ts',
2077
- integration: 'test-renderer',
2078
- },
2079
- dependencies: { components: [ForeignChild] },
2080
- };
2081
-
2082
- expect(renderer.testHasForeignChildDescendants(Root)).toBe(true);
2083
- });
2084
- });
2085
- });