@ecopages/core 0.2.0-alpha.1

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 (342) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/LICENSE +21 -0
  3. package/README.md +32 -0
  4. package/package.json +279 -0
  5. package/src/adapters/abstract/application-adapter.d.ts +168 -0
  6. package/src/adapters/abstract/application-adapter.js +109 -0
  7. package/src/adapters/abstract/application-adapter.ts +337 -0
  8. package/src/adapters/abstract/router-adapter.d.ts +26 -0
  9. package/src/adapters/abstract/router-adapter.js +5 -0
  10. package/src/adapters/abstract/router-adapter.ts +30 -0
  11. package/src/adapters/abstract/server-adapter.d.ts +69 -0
  12. package/src/adapters/abstract/server-adapter.js +15 -0
  13. package/src/adapters/abstract/server-adapter.ts +79 -0
  14. package/src/adapters/bun/client-bridge.d.ts +34 -0
  15. package/src/adapters/bun/client-bridge.js +48 -0
  16. package/src/adapters/bun/client-bridge.ts +62 -0
  17. package/src/adapters/bun/create-app.d.ts +60 -0
  18. package/src/adapters/bun/create-app.js +117 -0
  19. package/src/adapters/bun/create-app.ts +189 -0
  20. package/src/adapters/bun/define-api-handler.d.ts +61 -0
  21. package/src/adapters/bun/define-api-handler.js +15 -0
  22. package/src/adapters/bun/define-api-handler.ts +114 -0
  23. package/src/adapters/bun/hmr-manager.d.ts +84 -0
  24. package/src/adapters/bun/hmr-manager.js +227 -0
  25. package/src/adapters/bun/hmr-manager.ts +281 -0
  26. package/src/adapters/bun/index.d.ts +3 -0
  27. package/src/adapters/bun/index.js +8 -0
  28. package/src/adapters/bun/index.ts +3 -0
  29. package/src/adapters/bun/server-adapter.d.ts +155 -0
  30. package/src/adapters/bun/server-adapter.js +368 -0
  31. package/src/adapters/bun/server-adapter.ts +492 -0
  32. package/src/adapters/bun/server-lifecycle.d.ts +52 -0
  33. package/src/adapters/bun/server-lifecycle.js +120 -0
  34. package/src/adapters/bun/server-lifecycle.ts +154 -0
  35. package/src/adapters/index.d.ts +6 -0
  36. package/src/adapters/index.js +14 -0
  37. package/src/adapters/index.ts +6 -0
  38. package/src/adapters/node/create-app.d.ts +21 -0
  39. package/src/adapters/node/create-app.js +143 -0
  40. package/src/adapters/node/create-app.ts +179 -0
  41. package/src/adapters/node/index.d.ts +4 -0
  42. package/src/adapters/node/index.js +8 -0
  43. package/src/adapters/node/index.ts +9 -0
  44. package/src/adapters/node/node-client-bridge.d.ts +26 -0
  45. package/src/adapters/node/node-client-bridge.js +66 -0
  46. package/src/adapters/node/node-client-bridge.ts +79 -0
  47. package/src/adapters/node/node-hmr-manager.d.ts +62 -0
  48. package/src/adapters/node/node-hmr-manager.js +221 -0
  49. package/src/adapters/node/node-hmr-manager.ts +271 -0
  50. package/src/adapters/node/server-adapter.d.ts +190 -0
  51. package/src/adapters/node/server-adapter.js +420 -0
  52. package/src/adapters/node/server-adapter.ts +561 -0
  53. package/src/adapters/node/static-content-server.d.ts +24 -0
  54. package/src/adapters/node/static-content-server.js +166 -0
  55. package/src/adapters/node/static-content-server.ts +203 -0
  56. package/src/adapters/shared/api-response.d.ts +52 -0
  57. package/src/adapters/shared/api-response.js +96 -0
  58. package/src/adapters/shared/api-response.ts +104 -0
  59. package/src/adapters/shared/application-adapter.d.ts +18 -0
  60. package/src/adapters/shared/application-adapter.js +90 -0
  61. package/src/adapters/shared/application-adapter.ts +199 -0
  62. package/src/adapters/shared/explicit-static-route-matcher.d.ts +38 -0
  63. package/src/adapters/shared/explicit-static-route-matcher.js +100 -0
  64. package/src/adapters/shared/explicit-static-route-matcher.ts +134 -0
  65. package/src/adapters/shared/file-route-middleware-pipeline.d.ts +65 -0
  66. package/src/adapters/shared/file-route-middleware-pipeline.js +98 -0
  67. package/src/adapters/shared/file-route-middleware-pipeline.ts +123 -0
  68. package/src/adapters/shared/fs-server-response-factory.d.ts +19 -0
  69. package/src/adapters/shared/fs-server-response-factory.js +97 -0
  70. package/src/adapters/shared/fs-server-response-factory.ts +118 -0
  71. package/src/adapters/shared/fs-server-response-matcher.d.ts +71 -0
  72. package/src/adapters/shared/fs-server-response-matcher.js +155 -0
  73. package/src/adapters/shared/fs-server-response-matcher.ts +198 -0
  74. package/src/adapters/shared/render-context.d.ts +14 -0
  75. package/src/adapters/shared/render-context.js +69 -0
  76. package/src/adapters/shared/render-context.ts +105 -0
  77. package/src/adapters/shared/server-adapter.d.ts +87 -0
  78. package/src/adapters/shared/server-adapter.js +353 -0
  79. package/src/adapters/shared/server-adapter.ts +442 -0
  80. package/src/adapters/shared/server-route-handler.d.ts +89 -0
  81. package/src/adapters/shared/server-route-handler.js +120 -0
  82. package/src/adapters/shared/server-route-handler.ts +166 -0
  83. package/src/adapters/shared/server-static-builder.d.ts +38 -0
  84. package/src/adapters/shared/server-static-builder.js +46 -0
  85. package/src/adapters/shared/server-static-builder.ts +82 -0
  86. package/src/build/build-adapter.d.ts +74 -0
  87. package/src/build/build-adapter.js +54 -0
  88. package/src/build/build-adapter.ts +132 -0
  89. package/src/build/build-types.d.ts +57 -0
  90. package/src/build/build-types.js +0 -0
  91. package/src/build/build-types.ts +83 -0
  92. package/src/build/esbuild-build-adapter.d.ts +69 -0
  93. package/src/build/esbuild-build-adapter.js +390 -0
  94. package/src/build/esbuild-build-adapter.ts +510 -0
  95. package/src/config/config-builder.d.ts +227 -0
  96. package/src/config/config-builder.js +392 -0
  97. package/src/config/config-builder.ts +474 -0
  98. package/src/constants.d.ts +32 -0
  99. package/src/constants.js +21 -0
  100. package/src/constants.ts +39 -0
  101. package/src/create-app.d.ts +17 -0
  102. package/src/create-app.js +66 -0
  103. package/src/create-app.ts +87 -0
  104. package/src/declarations.d.ts +26 -0
  105. package/src/define-api-handler.d.ts +25 -0
  106. package/src/define-api-handler.js +15 -0
  107. package/src/define-api-handler.ts +66 -0
  108. package/src/dev/sc-server.d.ts +30 -0
  109. package/src/dev/sc-server.js +111 -0
  110. package/src/dev/sc-server.ts +143 -0
  111. package/src/eco/README.md +636 -0
  112. package/src/eco/component-render-context.d.ts +105 -0
  113. package/src/eco/component-render-context.js +77 -0
  114. package/src/eco/component-render-context.ts +202 -0
  115. package/src/eco/eco.d.ts +9 -0
  116. package/src/eco/eco.js +110 -0
  117. package/src/eco/eco.ts +221 -0
  118. package/src/eco/eco.types.d.ts +170 -0
  119. package/src/eco/eco.types.js +0 -0
  120. package/src/eco/eco.types.ts +202 -0
  121. package/src/eco/eco.utils.d.ts +40 -0
  122. package/src/eco/eco.utils.js +40 -0
  123. package/src/eco/eco.utils.ts +89 -0
  124. package/src/eco/global-injector-map.d.ts +16 -0
  125. package/src/eco/global-injector-map.js +80 -0
  126. package/src/eco/global-injector-map.ts +112 -0
  127. package/src/eco/lazy-injector-map.d.ts +8 -0
  128. package/src/eco/lazy-injector-map.js +70 -0
  129. package/src/eco/lazy-injector-map.ts +120 -0
  130. package/src/eco/module-dependencies.d.ts +18 -0
  131. package/src/eco/module-dependencies.js +49 -0
  132. package/src/eco/module-dependencies.ts +75 -0
  133. package/src/env.d.ts +20 -0
  134. package/src/errors/http-error.d.ts +31 -0
  135. package/src/errors/http-error.js +50 -0
  136. package/src/errors/http-error.ts +72 -0
  137. package/src/errors/index.d.ts +2 -0
  138. package/src/errors/index.js +4 -0
  139. package/src/errors/index.ts +2 -0
  140. package/src/errors/locals-access-error.d.ts +4 -0
  141. package/src/errors/locals-access-error.js +9 -0
  142. package/src/errors/locals-access-error.ts +7 -0
  143. package/src/global/app-logger.d.ts +2 -0
  144. package/src/global/app-logger.js +6 -0
  145. package/src/global/app-logger.ts +4 -0
  146. 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
  147. package/src/hmr/client/__screenshots__/hmr-runtime.test.browser.ts/HMR-Runtime-HMR-Server-Integration-should-load-fixture-app-page-1.png +0 -0
  148. package/src/hmr/client/__screenshots__/hmr-runtime.test.browser.ts/HMR-Runtime-WebSocket-Connection-should-connect-to-correct-HMR-endpoint-1.png +0 -0
  149. package/src/hmr/client/hmr-runtime.d.ts +10 -0
  150. package/src/hmr/client/hmr-runtime.js +86 -0
  151. package/src/hmr/client/hmr-runtime.ts +121 -0
  152. package/src/hmr/hmr-strategy.d.ts +159 -0
  153. package/src/hmr/hmr-strategy.js +29 -0
  154. package/src/hmr/hmr-strategy.ts +172 -0
  155. package/src/hmr/hmr.test.e2e.d.ts +1 -0
  156. package/src/hmr/hmr.test.e2e.js +50 -0
  157. package/src/hmr/hmr.test.e2e.ts +75 -0
  158. package/src/hmr/strategies/default-hmr-strategy.d.ts +43 -0
  159. package/src/hmr/strategies/default-hmr-strategy.js +34 -0
  160. package/src/hmr/strategies/default-hmr-strategy.ts +60 -0
  161. package/src/hmr/strategies/js-hmr-strategy.d.ts +136 -0
  162. package/src/hmr/strategies/js-hmr-strategy.js +179 -0
  163. package/src/hmr/strategies/js-hmr-strategy.ts +308 -0
  164. package/src/index.browser.d.ts +3 -0
  165. package/src/index.browser.js +4 -0
  166. package/src/index.browser.ts +3 -0
  167. package/src/index.d.ts +5 -0
  168. package/src/index.js +10 -0
  169. package/src/index.ts +5 -0
  170. package/src/integrations/ghtml/ghtml-renderer.d.ts +15 -0
  171. package/src/integrations/ghtml/ghtml-renderer.js +60 -0
  172. package/src/integrations/ghtml/ghtml-renderer.ts +93 -0
  173. package/src/integrations/ghtml/ghtml.plugin.d.ts +20 -0
  174. package/src/integrations/ghtml/ghtml.plugin.js +21 -0
  175. package/src/integrations/ghtml/ghtml.plugin.ts +32 -0
  176. package/src/internal-types.d.ts +200 -0
  177. package/src/internal-types.js +0 -0
  178. package/src/internal-types.ts +212 -0
  179. package/src/plugins/alias-resolver-plugin.d.ts +2 -0
  180. package/src/plugins/alias-resolver-plugin.js +39 -0
  181. package/src/plugins/alias-resolver-plugin.ts +45 -0
  182. package/src/plugins/eco-component-meta-plugin.d.ts +95 -0
  183. package/src/plugins/eco-component-meta-plugin.js +157 -0
  184. package/src/plugins/eco-component-meta-plugin.ts +474 -0
  185. package/src/plugins/integration-plugin.d.ts +102 -0
  186. package/src/plugins/integration-plugin.js +100 -0
  187. package/src/plugins/integration-plugin.ts +184 -0
  188. package/src/plugins/processor.d.ts +82 -0
  189. package/src/plugins/processor.js +122 -0
  190. package/src/plugins/processor.ts +220 -0
  191. package/src/public-types.d.ts +1094 -0
  192. package/src/public-types.js +0 -0
  193. package/src/public-types.ts +1255 -0
  194. package/src/route-renderer/GRAPH.md +387 -0
  195. package/src/route-renderer/README.md +135 -0
  196. package/src/route-renderer/component-graph-executor.d.ts +32 -0
  197. package/src/route-renderer/component-graph-executor.js +31 -0
  198. package/src/route-renderer/component-graph-executor.ts +84 -0
  199. package/src/route-renderer/component-graph.d.ts +42 -0
  200. package/src/route-renderer/component-graph.js +72 -0
  201. package/src/route-renderer/component-graph.ts +159 -0
  202. package/src/route-renderer/component-marker.d.ts +52 -0
  203. package/src/route-renderer/component-marker.js +46 -0
  204. package/src/route-renderer/component-marker.ts +117 -0
  205. package/src/route-renderer/dependency-resolver.d.ts +24 -0
  206. package/src/route-renderer/dependency-resolver.js +428 -0
  207. package/src/route-renderer/dependency-resolver.ts +596 -0
  208. package/src/route-renderer/html-post-processing.service.d.ts +40 -0
  209. package/src/route-renderer/html-post-processing.service.js +86 -0
  210. package/src/route-renderer/html-post-processing.service.ts +103 -0
  211. package/src/route-renderer/integration-renderer.d.ts +339 -0
  212. package/src/route-renderer/integration-renderer.js +526 -0
  213. package/src/route-renderer/integration-renderer.ts +696 -0
  214. package/src/route-renderer/marker-graph-resolver.d.ts +76 -0
  215. package/src/route-renderer/marker-graph-resolver.js +93 -0
  216. package/src/route-renderer/marker-graph-resolver.ts +153 -0
  217. package/src/route-renderer/page-module-loader.d.ts +61 -0
  218. package/src/route-renderer/page-module-loader.js +102 -0
  219. package/src/route-renderer/page-module-loader.ts +153 -0
  220. package/src/route-renderer/render-execution.service.d.ts +69 -0
  221. package/src/route-renderer/render-execution.service.js +91 -0
  222. package/src/route-renderer/render-execution.service.ts +158 -0
  223. package/src/route-renderer/render-preparation.service.d.ts +112 -0
  224. package/src/route-renderer/render-preparation.service.js +243 -0
  225. package/src/route-renderer/render-preparation.service.ts +358 -0
  226. package/src/route-renderer/route-renderer.d.ts +26 -0
  227. package/src/route-renderer/route-renderer.js +68 -0
  228. package/src/route-renderer/route-renderer.ts +80 -0
  229. package/src/router/fs-router-scanner.d.ts +41 -0
  230. package/src/router/fs-router-scanner.js +155 -0
  231. package/src/router/fs-router-scanner.ts +217 -0
  232. package/src/router/fs-router.d.ts +26 -0
  233. package/src/router/fs-router.js +100 -0
  234. package/src/router/fs-router.ts +122 -0
  235. package/src/services/asset-processing-service/asset-processing.service.d.ts +41 -0
  236. package/src/services/asset-processing-service/asset-processing.service.js +250 -0
  237. package/src/services/asset-processing-service/asset-processing.service.ts +306 -0
  238. package/src/services/asset-processing-service/asset.factory.d.ts +17 -0
  239. package/src/services/asset-processing-service/asset.factory.js +82 -0
  240. package/src/services/asset-processing-service/asset.factory.ts +105 -0
  241. package/src/services/asset-processing-service/assets.types.d.ts +88 -0
  242. package/src/services/asset-processing-service/assets.types.js +0 -0
  243. package/src/services/asset-processing-service/assets.types.ts +112 -0
  244. package/src/services/asset-processing-service/index.d.ts +3 -0
  245. package/src/services/asset-processing-service/index.js +3 -0
  246. package/src/services/asset-processing-service/index.ts +3 -0
  247. package/src/services/asset-processing-service/processor.interface.d.ts +22 -0
  248. package/src/services/asset-processing-service/processor.interface.js +6 -0
  249. package/src/services/asset-processing-service/processor.interface.ts +27 -0
  250. package/src/services/asset-processing-service/processor.registry.d.ts +8 -0
  251. package/src/services/asset-processing-service/processor.registry.js +15 -0
  252. package/src/services/asset-processing-service/processor.registry.ts +18 -0
  253. package/src/services/asset-processing-service/processors/base/base-processor.d.ts +24 -0
  254. package/src/services/asset-processing-service/processors/base/base-processor.js +59 -0
  255. package/src/services/asset-processing-service/processors/base/base-processor.ts +76 -0
  256. package/src/services/asset-processing-service/processors/base/base-script-processor.d.ts +16 -0
  257. package/src/services/asset-processing-service/processors/base/base-script-processor.js +80 -0
  258. package/src/services/asset-processing-service/processors/base/base-script-processor.ts +105 -0
  259. package/src/services/asset-processing-service/processors/index.d.ts +5 -0
  260. package/src/services/asset-processing-service/processors/index.js +5 -0
  261. package/src/services/asset-processing-service/processors/index.ts +5 -0
  262. package/src/services/asset-processing-service/processors/script/content-script.processor.d.ts +5 -0
  263. package/src/services/asset-processing-service/processors/script/content-script.processor.js +57 -0
  264. package/src/services/asset-processing-service/processors/script/content-script.processor.ts +66 -0
  265. package/src/services/asset-processing-service/processors/script/file-script.processor.d.ts +8 -0
  266. package/src/services/asset-processing-service/processors/script/file-script.processor.js +76 -0
  267. package/src/services/asset-processing-service/processors/script/file-script.processor.ts +88 -0
  268. package/src/services/asset-processing-service/processors/script/node-module-script.processor.d.ts +7 -0
  269. package/src/services/asset-processing-service/processors/script/node-module-script.processor.js +74 -0
  270. package/src/services/asset-processing-service/processors/script/node-module-script.processor.ts +84 -0
  271. package/src/services/asset-processing-service/processors/stylesheet/content-stylesheet.processor.d.ts +5 -0
  272. package/src/services/asset-processing-service/processors/stylesheet/content-stylesheet.processor.js +25 -0
  273. package/src/services/asset-processing-service/processors/stylesheet/content-stylesheet.processor.ts +27 -0
  274. package/src/services/asset-processing-service/processors/stylesheet/file-stylesheet.processor.d.ts +9 -0
  275. package/src/services/asset-processing-service/processors/stylesheet/file-stylesheet.processor.js +63 -0
  276. package/src/services/asset-processing-service/processors/stylesheet/file-stylesheet.processor.ts +77 -0
  277. package/src/services/cache/cache.types.d.ts +107 -0
  278. package/src/services/cache/cache.types.js +0 -0
  279. package/src/services/cache/cache.types.ts +126 -0
  280. package/src/services/cache/index.d.ts +7 -0
  281. package/src/services/cache/index.js +7 -0
  282. package/src/services/cache/index.ts +18 -0
  283. package/src/services/cache/memory-cache-store.d.ts +42 -0
  284. package/src/services/cache/memory-cache-store.js +98 -0
  285. package/src/services/cache/memory-cache-store.ts +130 -0
  286. package/src/services/cache/page-cache-service.d.ts +70 -0
  287. package/src/services/cache/page-cache-service.js +152 -0
  288. package/src/services/cache/page-cache-service.ts +202 -0
  289. package/src/services/html-transformer.service.d.ts +50 -0
  290. package/src/services/html-transformer.service.js +163 -0
  291. package/src/services/html-transformer.service.ts +217 -0
  292. package/src/services/page-module-import.service.d.ts +37 -0
  293. package/src/services/page-module-import.service.js +88 -0
  294. package/src/services/page-module-import.service.ts +129 -0
  295. package/src/services/page-request-cache-coordinator.service.d.ts +75 -0
  296. package/src/services/page-request-cache-coordinator.service.js +107 -0
  297. package/src/services/page-request-cache-coordinator.service.ts +128 -0
  298. package/src/services/schema-validation-service.d.ts +122 -0
  299. package/src/services/schema-validation-service.js +101 -0
  300. package/src/services/schema-validation-service.ts +204 -0
  301. package/src/services/validation/standard-schema.types.d.ts +65 -0
  302. package/src/services/validation/standard-schema.types.js +0 -0
  303. package/src/services/validation/standard-schema.types.ts +68 -0
  304. package/src/static-site-generator/static-site-generator.d.ts +57 -0
  305. package/src/static-site-generator/static-site-generator.js +272 -0
  306. package/src/static-site-generator/static-site-generator.ts +359 -0
  307. package/src/utils/css.d.ts +1 -0
  308. package/src/utils/css.js +7 -0
  309. package/src/utils/css.ts +5 -0
  310. package/src/utils/deep-merge.d.ts +14 -0
  311. package/src/utils/deep-merge.js +32 -0
  312. package/src/utils/deep-merge.ts +47 -0
  313. package/src/utils/hash.d.ts +1 -0
  314. package/src/utils/hash.js +7 -0
  315. package/src/utils/hash.ts +5 -0
  316. package/src/utils/html.d.ts +1 -0
  317. package/src/utils/html.js +4 -0
  318. package/src/utils/html.ts +1 -0
  319. package/src/utils/invariant.d.ts +5 -0
  320. package/src/utils/invariant.js +11 -0
  321. package/src/utils/invariant.ts +15 -0
  322. package/src/utils/locals-utils.d.ts +15 -0
  323. package/src/utils/locals-utils.js +24 -0
  324. package/src/utils/locals-utils.ts +37 -0
  325. package/src/utils/parse-cli-args.d.ts +24 -0
  326. package/src/utils/parse-cli-args.js +47 -0
  327. package/src/utils/parse-cli-args.ts +83 -0
  328. package/src/utils/path-utils.module.d.ts +5 -0
  329. package/src/utils/path-utils.module.js +14 -0
  330. package/src/utils/path-utils.module.ts +14 -0
  331. package/src/utils/runtime.d.ts +11 -0
  332. package/src/utils/runtime.js +40 -0
  333. package/src/utils/runtime.ts +44 -0
  334. package/src/utils/server-utils.module.d.ts +19 -0
  335. package/src/utils/server-utils.module.js +56 -0
  336. package/src/utils/server-utils.module.ts +67 -0
  337. package/src/watchers/project-watcher.d.ts +120 -0
  338. package/src/watchers/project-watcher.js +238 -0
  339. package/src/watchers/project-watcher.test-helpers.d.ts +4 -0
  340. package/src/watchers/project-watcher.test-helpers.js +51 -0
  341. package/src/watchers/project-watcher.test-helpers.ts +40 -0
  342. package/src/watchers/project-watcher.ts +306 -0
@@ -0,0 +1,636 @@
1
+ # `eco` Namespace
2
+
3
+ A unified API for defining components, pages, and page data in EcoPages.
4
+
5
+ ## Overview
6
+
7
+ The `eco` namespace provides a consistent, type-safe interface for:
8
+
9
+ 1. **`eco.component()`** - Factory for defining reusable components with dependencies and optional lazy-loading
10
+ 2. **`eco.page()`** - Factory for defining page components with optional inline `staticPaths`, `staticProps`, and `metadata`
11
+ 3. **`eco.metadata()`** - Type-safe wrapper for page metadata (legacy pattern)
12
+ 4. **`eco.staticPaths()`** - Type-safe wrapper for dynamic route generation (legacy pattern)
13
+ 5. **`eco.staticProps()`** - Type-safe wrapper for static data fetching (legacy pattern)
14
+
15
+ ## Component Patterns
16
+
17
+ EcoPages supports two approaches for creating components, each suited for different use cases:
18
+
19
+ ### Simple JSX Functions (Static Components)
20
+
21
+ For purely presentational components without client-side interactivity, use plain JSX functions. This pattern works best with utility-first CSS frameworks like Tailwind, where styles are applied via class names:
22
+
23
+ ```tsx
24
+ import type { PropsWithChildren } from '@kitajs/html';
25
+ import { cn } from 'your-library';
26
+
27
+ type CardProps = PropsWithChildren<{
28
+ class?: string;
29
+ }>;
30
+
31
+ export function Card({ children, class: className }: CardProps) {
32
+ return <div class={cn('p-6 rounded-2xl border border-white/10 bg-zinc-900/30', className)}>{children}</div>;
33
+ }
34
+ ```
35
+
36
+ **When to use:**
37
+
38
+ - Static/presentational UI (cards, alerts, badges, layout primitives)
39
+ - Projects using utility-first CSS (Tailwind, UnoCSS, etc.)
40
+ - Components that only render HTML without scripts or dedicated stylesheets
41
+ - Zero runtime cost - just functions returning markup
42
+
43
+ > **Note:** If your component requires a dedicated CSS file, use `eco.component()` instead to manage the stylesheet dependency.
44
+
45
+ ### Plain React Components (With Bun)
46
+
47
+ When using Bun, React components with hooks and Tailwind CSS work out of the box without `eco.component()`. Bun auto-imports dependencies, so you can write standard React components:
48
+
49
+ ```tsx
50
+ import { useEffect, useState } from 'react';
51
+
52
+ export function ThemeToggle() {
53
+ const [mounted, setMounted] = useState(false);
54
+ const [isDark, setIsDark] = useState(false);
55
+
56
+ useEffect(() => {
57
+ setMounted(true);
58
+ const theme = localStorage.getItem('theme');
59
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
60
+
61
+ if (theme === 'dark' || (!theme && prefersDark)) {
62
+ setIsDark(true);
63
+ document.documentElement.classList.add('dark');
64
+ } else {
65
+ setIsDark(false);
66
+ document.documentElement.classList.remove('dark');
67
+ }
68
+ }, []);
69
+
70
+ const toggleTheme = () => {
71
+ const newTheme = !isDark;
72
+ setIsDark(newTheme);
73
+
74
+ if (newTheme) {
75
+ document.documentElement.classList.add('dark');
76
+ localStorage.setItem('theme', 'dark');
77
+ } else {
78
+ document.documentElement.classList.remove('dark');
79
+ localStorage.setItem('theme', 'light');
80
+ }
81
+ };
82
+
83
+ if (!mounted) return null;
84
+
85
+ return (
86
+ <button
87
+ onClick={toggleTheme}
88
+ className="p-2 rounded-md hover:bg-surface-200 dark:hover:bg-surface-800 transition-colors"
89
+ aria-label="Toggle theme"
90
+ >
91
+ {isDark ? <SunIcon /> : <MoonIcon />}
92
+ </button>
93
+ );
94
+ }
95
+ ```
96
+
97
+ **When to use:**
98
+
99
+ - React components with hooks (`useState`, `useEffect`, etc.)
100
+ - Components styled with Tailwind CSS classes
101
+ - Interactive UI that doesn't require external scripts or dedicated stylesheets
102
+ - Bun handles all imports automatically
103
+
104
+ > **Note:** Use `eco.component()` only when you need to manage external stylesheets, scripts, or lazy loading. For React components relying solely on hooks and Tailwind, plain functions are simpler and sufficient.
105
+
106
+ ### `eco.component()` (Interactive Components)
107
+
108
+ For components that need dependencies, scripts, or lazy loading:
109
+
110
+ ```tsx
111
+ import { eco } from '@ecopages/core';
112
+
113
+ export const Counter = eco.component({
114
+ dependencies: {
115
+ stylesheets: ['./counter.css'],
116
+ scripts: [{ src: './counter.script.ts', lazy: { 'on:interaction': 'mouseenter,focusin' } }],
117
+ },
118
+ render: ({ count }) => <my-counter count={count} />,
119
+ });
120
+ ```
121
+
122
+ **When to use:**
123
+
124
+ - Components with client-side interactivity
125
+ - Components requiring scripts or stylesheets
126
+ - Lazy-loaded components with hydration strategies
127
+ - Web components or custom elements
128
+
129
+ ### Comparison
130
+
131
+ | Aspect | Simple JSX | Plain React (Bun) | `eco.component()` |
132
+ | -------------------- | ---------- | ----------------- | ----------------- |
133
+ | React hooks | No | Yes | Yes |
134
+ | Scripts/Stylesheets | No | No | Yes |
135
+ | Lazy loading | No | No | Yes |
136
+ | Hydration strategies | No | No | Yes |
137
+ | Runtime cost | Zero | Minimal | Minimal |
138
+ | Use case | Static UI | Interactive UI | Advanced UI |
139
+
140
+ All patterns can coexist in the same project. Use the right tool for the job.
141
+
142
+ ## React Support
143
+
144
+ EcoPages fully supports React components. You can use `eco.component()` with React to managing dependencies and hydration while maintaining type safety.
145
+
146
+ Since React components return `ReactNode` or `JSX.Element` (not `EcoPagesElement`), you need to specify the return type generic:
147
+
148
+ ```tsx
149
+ import { eco } from '@ecopages/core';
150
+ import type { ReactNode } from 'react';
151
+
152
+ type ButtonProps = {
153
+ label: string;
154
+ onClick?: () => void;
155
+ };
156
+
157
+ // Specify ReactNode as the second generic argument
158
+ export const Button = eco.component<ButtonProps, ReactNode>({
159
+ dependencies: {
160
+ stylesheets: ['./button.css'],
161
+ },
162
+ render: ({ label, onClick }) => (
163
+ <button className="eco-button" onClick={onClick}>
164
+ {label}
165
+ </button>
166
+ ),
167
+ });
168
+ ```
169
+
170
+ You can also use it for pages:
171
+
172
+ ```tsx
173
+ import { eco } from '@ecopages/core';
174
+ import type { JSX } from 'react';
175
+
176
+ export default eco.page<Props, JSX.Element>({
177
+ layout: BaseLayout,
178
+ render: () => <h1>Hello React</h1>,
179
+ });
180
+ ```
181
+
182
+ ## Two Patterns for Pages
183
+
184
+ ### Consolidated API (Recommended)
185
+
186
+ Define everything in one place:
187
+
188
+ ```tsx
189
+ import { eco } from '@ecopages/core';
190
+
191
+ export default eco.page<BlogPostProps>({
192
+ layout: BaseLayout,
193
+ staticPaths: async () => ({ paths: getAllSlugs() }),
194
+ staticProps: async ({ pathname }) => ({
195
+ props: { post: await getPost(pathname.params.slug) },
196
+ }),
197
+ metadata: ({ props: { post } }) => ({
198
+ title: post.title,
199
+ description: post.excerpt,
200
+ }),
201
+ render: ({ post }) => <article>{post.content}</article>,
202
+ });
203
+ ```
204
+
205
+ ### Separate Exports (Legacy)
206
+
207
+ The traditional pattern with named exports:
208
+
209
+ ```tsx
210
+ import { eco } from '@ecopages/core';
211
+
212
+ export const getStaticPaths = eco.staticPaths(async () => ({
213
+ paths: getAllSlugs(),
214
+ }));
215
+
216
+ export const getStaticProps = eco.staticProps(async ({ pathname }) => ({
217
+ props: { post: await getPost(pathname.params.slug) },
218
+ }));
219
+
220
+ export const getMetadata = eco.metadata<typeof getStaticProps>(({ props: { post } }) => ({
221
+ title: post.title,
222
+ description: post.excerpt,
223
+ }));
224
+
225
+ export default eco.page<typeof getStaticProps>({
226
+ render: ({ post }) => <article>{post.content}</article>,
227
+ });
228
+ ```
229
+
230
+ Both patterns work and can be mixed - the renderer checks for attached properties first, then falls back to named exports.
231
+
232
+ ## API Reference
233
+
234
+ ### `eco.component()`
235
+
236
+ Define a reusable component with dependencies.
237
+
238
+ ```tsx
239
+ import { eco } from '@ecopages/core';
240
+
241
+ export const BaseLayout = eco.component({
242
+ dependencies: {
243
+ stylesheets: ['./base-layout.css'],
244
+ scripts: ['./base-layout.script.ts'],
245
+ },
246
+ render: ({ children, class: className }) => (
247
+ <html>
248
+ <body class={className}>{children}</body>
249
+ </html>
250
+ ),
251
+ });
252
+ ```
253
+
254
+ **With lazy-loaded scripts:**
255
+
256
+ ```tsx
257
+ export const Counter = eco.component({
258
+ dependencies: {
259
+ stylesheets: ['./counter.css'], // loaded immediately
260
+ scripts: [
261
+ { src: './counter.script.ts', lazy: { 'on:interaction': 'mouseenter,focusin' } }, // loaded on trigger
262
+ ],
263
+ },
264
+ render: ({ count }) => <my-counter count={count}></my-counter>,
265
+ });
266
+ ```
267
+
268
+ #### Lazy Loading Options
269
+
270
+ Lazy loading is configured per dependency entry in `dependencies.scripts`:
271
+
272
+ ```tsx
273
+ // Load on idle
274
+ scripts: [{ src: './component.script.ts', lazy: { 'on:idle': true } }];
275
+
276
+ // Load on interaction
277
+ scripts: [{ src: './component.script.ts', lazy: { 'on:interaction': 'mouseenter,focusin' } }];
278
+
279
+ // Load on visibility
280
+ scripts: [{ src: './component.script.ts', lazy: { 'on:visible': true } }]; // or viewport margin like '100px'
281
+ ```
282
+
283
+ Each entry can define its own trigger, so mixed strategies in one component are supported.
284
+
285
+ At render time, lazy script groups are emitted into a single [`scripts-injector`](https://github.com/ecopages/scripts-injector)
286
+ wrapper using an internal `<script type="ecopages/injector-map">` payload.
287
+
288
+ ### `eco.page()`
289
+
290
+ Define a page component with optional inline static functions.
291
+
292
+ **Consolidated API (everything in one place):**
293
+
294
+ ```tsx
295
+ type BlogPostProps = { slug: string; title: string; text: string };
296
+
297
+ export default eco.page<BlogPostProps>({
298
+ layout: BaseLayout,
299
+
300
+ // Generate paths for dynamic routes
301
+ staticPaths: async () => ({
302
+ paths: posts.map((p) => ({ params: { slug: p.slug } })),
303
+ }),
304
+
305
+ // Fetch data at build time
306
+ staticProps: async ({ pathname }) => ({
307
+ props: {
308
+ slug: pathname.params.slug as string,
309
+ title: post.title,
310
+ text: post.text,
311
+ },
312
+ }),
313
+
314
+ // Generate page metadata
315
+ metadata: ({ props: { title, slug } }) => ({
316
+ title: `${title} | My Blog`,
317
+ description: `Read about ${slug}`,
318
+ }),
319
+
320
+ // Render the page
321
+ render: ({ title, text }) => (
322
+ <article>
323
+ <h1>{title}</h1>
324
+ <p>{text}</p>
325
+ </article>
326
+ ),
327
+ });
328
+ ```
329
+
330
+ **Simple page without data fetching:**
331
+
332
+ ```tsx
333
+ export default eco.page({
334
+ layout: BaseLayout,
335
+ metadata: () => ({
336
+ title: 'Home',
337
+ description: 'Welcome to EcoPages',
338
+ }),
339
+ render: () => <h1>Welcome</h1>,
340
+ });
341
+ ```
342
+
343
+ **With layout:**
344
+
345
+ ```tsx
346
+ export default eco.page({
347
+ layout: BaseLayout, // Wraps render output automatically
348
+ render: () => <h1>Content</h1>,
349
+ });
350
+ ```
351
+
352
+ ### `eco.metadata()`
353
+
354
+ Type-safe wrapper for page metadata (for separate exports pattern).
355
+
356
+ ```tsx
357
+ export const getMetadata = eco.metadata<typeof getStaticProps>(({ props: { post } }) => ({
358
+ title: post.title,
359
+ description: post.excerpt,
360
+ }));
361
+ ```
362
+
363
+ ### `eco.staticPaths()`
364
+
365
+ Type-safe wrapper for dynamic route generation (for separate exports pattern).
366
+
367
+ ```tsx
368
+ export const getStaticPaths = eco.staticPaths(async () => ({
369
+ paths: posts.map((p) => ({ params: { slug: p.slug } })),
370
+ }));
371
+ ```
372
+
373
+ ### `eco.staticProps()`
374
+
375
+ Type-safe wrapper for static data fetching (for separate exports pattern).
376
+
377
+ ```tsx
378
+ export const getStaticProps = eco.staticProps(async ({ pathname }) => ({
379
+ props: { post: await getPost(pathname.params.slug) },
380
+ }));
381
+ ```
382
+
383
+ ## Complete Examples
384
+
385
+ ### Dynamic Blog Post (Consolidated API)
386
+
387
+ ```tsx
388
+ // pages/blog/[slug].tsx
389
+ import { eco } from '@ecopages/core';
390
+ import { BaseLayout } from '@/layouts/base-layout';
391
+ import { getBlogPost, getAllBlogPostSlugs, getAuthor } from '@/mocks/data';
392
+
393
+ type BlogPostProps = {
394
+ slug: string;
395
+ title: string;
396
+ text: string;
397
+ authorId: string;
398
+ authorName: string;
399
+ };
400
+
401
+ export default eco.page<BlogPostProps>({
402
+ layout: BaseLayout,
403
+
404
+ staticPaths: async () => {
405
+ return { paths: getAllBlogPostSlugs() };
406
+ },
407
+
408
+ staticProps: async ({ pathname }) => {
409
+ const slug = pathname.params.slug as string;
410
+ const post = getBlogPost(slug);
411
+ if (!post) throw new Error(`Post not found: ${slug}`);
412
+ const author = getAuthor(post.authorId);
413
+ return {
414
+ props: {
415
+ slug,
416
+ title: post.title,
417
+ text: post.text,
418
+ authorId: post.authorId,
419
+ authorName: author?.name ?? 'Unknown',
420
+ },
421
+ };
422
+ },
423
+
424
+ metadata: ({ props: { title, slug } }) => ({
425
+ title: `${title} | My Blog`,
426
+ description: `Read the blog post: ${slug}`,
427
+ }),
428
+
429
+ render: ({ title, text, authorId, authorName }) => (
430
+ <article>
431
+ <h1>{title}</h1>
432
+ <p>
433
+ By <a href={`/blog/author/${authorId}`}>{authorName}</a>
434
+ </p>
435
+ <div>{text}</div>
436
+ </article>
437
+ ),
438
+ });
439
+ ```
440
+
441
+ ### Lazy-Loaded Component
442
+
443
+ ```tsx
444
+ // components/counter/counter.tsx
445
+ import { eco } from '@ecopages/core';
446
+
447
+ export const Counter = eco.component({
448
+ dependencies: {
449
+ stylesheets: ['./counter.css'],
450
+ scripts: [{ src: './counter.script.ts', lazy: { 'on:interaction': 'mouseenter,focusin' } }],
451
+ },
452
+ render: ({ count }) => <my-counter count={count} />,
453
+ });
454
+ ```
455
+
456
+ **HTML Output:**
457
+
458
+ ```html
459
+ <scripts-injector>
460
+ <script type="ecopages/injector-map">
461
+ {
462
+ "on:interaction": {
463
+ "value": "mouseenter,focusin",
464
+ "scripts": ["/_assets/components/counter/counter.script.js"]
465
+ }
466
+ }
467
+ </script>
468
+ <my-counter count="5">
469
+ <!-- SSR content -->
470
+ </my-counter>
471
+ </scripts-injector>
472
+ ```
473
+
474
+ ### Page with Lazy Component
475
+
476
+ ```tsx
477
+ // pages/index.tsx
478
+ import { eco } from '@ecopages/core';
479
+ import { BaseLayout } from '@/layouts/base-layout';
480
+ import { Counter } from '@/components/counter';
481
+
482
+ export default eco.page({
483
+ layout: BaseLayout,
484
+ dependencies: {
485
+ components: [Counter],
486
+ },
487
+ metadata: () => ({
488
+ title: 'Home',
489
+ description: 'Welcome to EcoPages',
490
+ }),
491
+ render: () => (
492
+ <>
493
+ <h1>Welcome</h1>
494
+ <Counter count={5} />
495
+ </>
496
+ ),
497
+ });
498
+ ```
499
+
500
+ ## Type Definitions
501
+
502
+ ```typescript
503
+ type LazyTrigger = { 'on:idle': true } | { 'on:interaction': string } | { 'on:visible': true | string };
504
+
505
+ type DependencyEntry = {
506
+ src?: string;
507
+ content?: string;
508
+ lazy?: LazyTrigger;
509
+ ssr?: boolean;
510
+ attributes?: Record<string, string>;
511
+ };
512
+
513
+ interface EcoComponentDependencies {
514
+ scripts?: Array<string | DependencyEntry>;
515
+ stylesheets?: Array<string | DependencyEntry>;
516
+ components?: EcoComponent[];
517
+ }
518
+
519
+ interface ComponentOptions<P, E = EcoPagesElement> {
520
+ componentDir?: string;
521
+ dependencies?: EcoComponentDependencies;
522
+ render: (props: P) => E;
523
+ }
524
+
525
+ interface PageOptions<T, E = EcoPagesElement> {
526
+ componentDir?: string;
527
+ dependencies?: EcoComponentDependencies;
528
+ layout?: EcoComponent<{ children: E }>;
529
+ staticPaths?: GetStaticPaths;
530
+ staticProps?: GetStaticProps<T>;
531
+ metadata?: GetMetadata<T>;
532
+ render: (props: PagePropsFor<T>) => E;
533
+ }
534
+
535
+ type EcoPageComponent<T> = EcoComponent<PagePropsFor<T>> & {
536
+ staticPaths?: GetStaticPaths;
537
+ staticProps?: GetStaticProps<T>;
538
+ metadata?: GetMetadata<T>;
539
+ };
540
+
541
+ type PagePropsFor<T> =
542
+ T extends GetStaticProps<infer P>
543
+ ? P & { params?: Record<string, string>; query?: Record<string, string> }
544
+ : T & { params?: Record<string, string>; query?: Record<string, string> };
545
+ ```
546
+
547
+ ## Benefits
548
+
549
+ ### 1. Discoverability
550
+
551
+ ```tsx
552
+ import { eco } from '@ecopages/core';
553
+
554
+ eco. // IDE shows: component, page, metadata, staticPaths, staticProps
555
+ ```
556
+
557
+ ### 2. Type Safety
558
+
559
+ Props flow through the system automatically:
560
+
561
+ ```tsx
562
+ export default eco.page<{ title: string }>({
563
+ staticProps: async () => ({ props: { title: 'Hello' } }),
564
+ render: ({ title }) => <h1>{title}</h1>, // ✓ TypeScript knows `title` is a string
565
+ });
566
+ ```
567
+
568
+ ### 3. Single Source of Truth (Consolidated API)
569
+
570
+ All page configuration in one place - no hunting for separate exports:
571
+
572
+ ```tsx
573
+ export default eco.page<Props>({
574
+ layout: BaseLayout,
575
+ staticPaths: async () => ...,
576
+ staticProps: async () => ...,
577
+ metadata: () => ...,
578
+ render: () => ...,
579
+ });
580
+ ```
581
+
582
+ ### 4. Backwards Compatible
583
+
584
+ Both patterns work side by side - choose what works best for your use case.
585
+
586
+ ## Design Decisions
587
+
588
+ ### Why consolidated API?
589
+
590
+ | Aspect | Separate Exports | Consolidated API |
591
+ | ------------------ | ---------------------- | ---------------- |
592
+ | Discoverability | Scan file for exports | All in one place |
593
+ | Type inference | Manual with `typeof` | Automatic flow |
594
+ | Colocated concerns | Spread across file | Single object |
595
+ | Flexibility | Mix with other exports | Self-contained |
596
+
597
+ ### Why both patterns?
598
+
599
+ - **Consolidated** is cleaner for new pages
600
+ - **Separate exports** provides flexibility and follows Next.js conventions
601
+ - Backwards compatibility with existing code
602
+
603
+ ## Implementation Notes
604
+
605
+ The renderer checks for attached properties first:
606
+
607
+ ```typescript
608
+ // Check for attached static functions (consolidated API) or named exports (legacy)
609
+ const getStaticProps = Page.staticProps ?? module.getStaticProps;
610
+ const getMetadata = Page.metadata ?? module.getMetadata;
611
+ ```
612
+
613
+ This ensures both patterns work seamlessly.
614
+
615
+ ## Migration
616
+
617
+ Pages can be migrated incrementally:
618
+
619
+ ```tsx
620
+ // Before (separate exports)
621
+ export const getStaticProps = ...
622
+ export const getMetadata = ...
623
+ export default Page;
624
+
625
+ // After (consolidated)
626
+ export default eco.page({
627
+ staticProps: ...,
628
+ metadata: ...,
629
+ render: ...,
630
+ });
631
+ ```
632
+
633
+ ## References
634
+
635
+ - [scripts-injector](https://github.com/ecopages/scripts-injector) - Custom element for lazy script loading
636
+ - [Islands Architecture](https://www.patterns.dev/posts/islands-architecture) - Pattern inspiration