@ecopages/core 0.2.0-alpha.24 → 0.2.0-alpha.26

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