@jasonshimmy/vite-plugin-cer-app 0.23.2 → 0.25.0

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 (404) hide show
  1. package/dist/cli/adapters/netlify.js +4 -2
  2. package/dist/cli/adapters/netlify.js.map +1 -1
  3. package/dist/cli/checks/lighthouse.d.ts +26 -0
  4. package/dist/cli/checks/lighthouse.d.ts.map +1 -0
  5. package/dist/cli/checks/lighthouse.js +121 -0
  6. package/dist/cli/checks/lighthouse.js.map +1 -0
  7. package/dist/cli/checks/links.d.ts +12 -0
  8. package/dist/cli/checks/links.d.ts.map +1 -0
  9. package/dist/cli/checks/links.js +89 -0
  10. package/dist/cli/checks/links.js.map +1 -0
  11. package/dist/cli/checks/performance.d.ts +17 -0
  12. package/dist/cli/checks/performance.d.ts.map +1 -0
  13. package/dist/cli/checks/performance.js +61 -0
  14. package/dist/cli/checks/performance.js.map +1 -0
  15. package/dist/cli/commands/check.d.ts +3 -0
  16. package/dist/cli/commands/check.d.ts.map +1 -0
  17. package/dist/cli/commands/check.js +85 -0
  18. package/dist/cli/commands/check.js.map +1 -0
  19. package/dist/cli/commands/preview.d.ts.map +1 -1
  20. package/dist/cli/commands/preview.js +4 -2
  21. package/dist/cli/commands/preview.js.map +1 -1
  22. package/dist/cli/create/index.d.ts +11 -1
  23. package/dist/cli/create/index.d.ts.map +1 -1
  24. package/dist/cli/create/index.js +135 -65
  25. package/dist/cli/create/index.js.map +1 -1
  26. package/dist/cli/create/templates/spa/package.json.tpl +5 -3
  27. package/dist/cli/create/templates/ssg/cer.config.ts.tpl +2 -0
  28. package/dist/cli/create/templates/ssg/package.json.tpl +5 -3
  29. package/dist/cli/create/templates/ssr/package.json.tpl +5 -3
  30. package/dist/cli/index.js +2 -0
  31. package/dist/cli/index.js.map +1 -1
  32. package/dist/index.d.ts +4 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/plugin/build-ssg.d.ts +22 -6
  35. package/dist/plugin/build-ssg.d.ts.map +1 -1
  36. package/dist/plugin/build-ssg.js +226 -17
  37. package/dist/plugin/build-ssg.js.map +1 -1
  38. package/dist/plugin/content/index.d.ts +1 -1
  39. package/dist/plugin/content/index.d.ts.map +1 -1
  40. package/dist/plugin/content/index.js +8 -8
  41. package/dist/plugin/content/index.js.map +1 -1
  42. package/dist/plugin/content/parser.d.ts +3 -3
  43. package/dist/plugin/content/parser.d.ts.map +1 -1
  44. package/dist/plugin/content/parser.js +103 -24
  45. package/dist/plugin/content/parser.js.map +1 -1
  46. package/dist/plugin/dev-server.d.ts +4 -1
  47. package/dist/plugin/dev-server.d.ts.map +1 -1
  48. package/dist/plugin/dev-server.js.map +1 -1
  49. package/dist/plugin/dts-generator.d.ts.map +1 -1
  50. package/dist/plugin/dts-generator.js +50 -8
  51. package/dist/plugin/dts-generator.js.map +1 -1
  52. package/dist/plugin/generated-dir.d.ts +2 -0
  53. package/dist/plugin/generated-dir.d.ts.map +1 -1
  54. package/dist/plugin/generated-dir.js +17 -6
  55. package/dist/plugin/generated-dir.js.map +1 -1
  56. package/dist/plugin/html-post-process.d.ts +7 -0
  57. package/dist/plugin/html-post-process.d.ts.map +1 -1
  58. package/dist/plugin/html-post-process.js +26 -0
  59. package/dist/plugin/html-post-process.js.map +1 -1
  60. package/dist/plugin/index.d.ts.map +1 -1
  61. package/dist/plugin/index.js +60 -13
  62. package/dist/plugin/index.js.map +1 -1
  63. package/dist/plugin/transforms/auto-import.d.ts.map +1 -1
  64. package/dist/plugin/transforms/auto-import.js +7 -0
  65. package/dist/plugin/transforms/auto-import.js.map +1 -1
  66. package/dist/plugin/transforms/prune-runtime-css.d.ts +8 -0
  67. package/dist/plugin/transforms/prune-runtime-css.d.ts.map +1 -0
  68. package/dist/plugin/transforms/prune-runtime-css.js +40 -0
  69. package/dist/plugin/transforms/prune-runtime-css.js.map +1 -0
  70. package/dist/runtime/app-template.d.ts +1 -1
  71. package/dist/runtime/app-template.d.ts.map +1 -1
  72. package/dist/runtime/app-template.js +206 -36
  73. package/dist/runtime/app-template.js.map +1 -1
  74. package/dist/runtime/composables/index.d.ts +4 -0
  75. package/dist/runtime/composables/index.d.ts.map +1 -1
  76. package/dist/runtime/composables/index.js +2 -0
  77. package/dist/runtime/composables/index.js.map +1 -1
  78. package/dist/runtime/composables/use-active-headings.d.ts +24 -0
  79. package/dist/runtime/composables/use-active-headings.d.ts.map +1 -0
  80. package/dist/runtime/composables/use-active-headings.js +112 -0
  81. package/dist/runtime/composables/use-active-headings.js.map +1 -0
  82. package/dist/runtime/composables/use-content-page.d.ts +45 -0
  83. package/dist/runtime/composables/use-content-page.d.ts.map +1 -0
  84. package/dist/runtime/composables/use-content-page.js +181 -0
  85. package/dist/runtime/composables/use-content-page.js.map +1 -0
  86. package/dist/runtime/composables/use-content-search.d.ts.map +1 -1
  87. package/dist/runtime/composables/use-content-search.js +1 -5
  88. package/dist/runtime/composables/use-content-search.js.map +1 -1
  89. package/dist/runtime/composables/use-fetch.d.ts +2 -1
  90. package/dist/runtime/composables/use-fetch.d.ts.map +1 -1
  91. package/dist/runtime/composables/use-fetch.js +112 -43
  92. package/dist/runtime/composables/use-fetch.js.map +1 -1
  93. package/dist/runtime/composables/use-route.d.ts.map +1 -1
  94. package/dist/runtime/composables/use-route.js +39 -11
  95. package/dist/runtime/composables/use-route.js.map +1 -1
  96. package/dist/runtime/content/client.js +2 -2
  97. package/dist/runtime/content/client.js.map +1 -1
  98. package/dist/runtime/entry-server-template.d.ts +16 -1
  99. package/dist/runtime/entry-server-template.d.ts.map +1 -1
  100. package/dist/runtime/entry-server-template.js +120 -13
  101. package/dist/runtime/entry-server-template.js.map +1 -1
  102. package/dist/types/config.d.ts +45 -4
  103. package/dist/types/config.d.ts.map +1 -1
  104. package/dist/types/config.js.map +1 -1
  105. package/dist/types/content.d.ts +18 -1
  106. package/dist/types/content.d.ts.map +1 -1
  107. package/dist/types/middleware.d.ts +2 -2
  108. package/dist/types/middleware.d.ts.map +1 -1
  109. package/package.json +22 -19
  110. package/.github/copilot-instructions.md +0 -169
  111. package/.github/workflows/publish.yml +0 -257
  112. package/.nvmrc +0 -1
  113. package/CHANGELOG.md +0 -236
  114. package/CLAUDE.md +0 -113
  115. package/commits.txt +0 -1
  116. package/cypress.config.ts +0 -16
  117. package/docs/authentication.md +0 -285
  118. package/docs/cli.md +0 -324
  119. package/docs/components.md +0 -237
  120. package/docs/composables.md +0 -907
  121. package/docs/configuration.md +0 -696
  122. package/docs/content.md +0 -570
  123. package/docs/data-loading.md +0 -336
  124. package/docs/getting-started.md +0 -237
  125. package/docs/head-management.md +0 -212
  126. package/docs/i18n.md +0 -159
  127. package/docs/layouts.md +0 -224
  128. package/docs/middleware.md +0 -234
  129. package/docs/plugins.md +0 -170
  130. package/docs/rendering-modes.md +0 -377
  131. package/docs/routing.md +0 -485
  132. package/docs/server-api.md +0 -253
  133. package/docs/testing.md +0 -455
  134. package/docs/use-fetch.md +0 -183
  135. package/e2e/cypress/e2e/api.cy.ts +0 -123
  136. package/e2e/cypress/e2e/async-component.cy.ts +0 -27
  137. package/e2e/cypress/e2e/component-splitting.cy.ts +0 -50
  138. package/e2e/cypress/e2e/content.cy.ts +0 -425
  139. package/e2e/cypress/e2e/cookie.cy.ts +0 -85
  140. package/e2e/cypress/e2e/data.cy.ts +0 -158
  141. package/e2e/cypress/e2e/error-boundary.cy.ts +0 -88
  142. package/e2e/cypress/e2e/fouc.cy.ts +0 -254
  143. package/e2e/cypress/e2e/group-meta.cy.ts +0 -36
  144. package/e2e/cypress/e2e/head.cy.ts +0 -89
  145. package/e2e/cypress/e2e/i18n.cy.ts +0 -79
  146. package/e2e/cypress/e2e/interactive.cy.ts +0 -137
  147. package/e2e/cypress/e2e/isr-nested-runtime.cy.ts +0 -102
  148. package/e2e/cypress/e2e/jit-css-custom-colors.cy.ts +0 -43
  149. package/e2e/cypress/e2e/middleware-chain.cy.ts +0 -89
  150. package/e2e/cypress/e2e/middleware-status.cy.ts +0 -30
  151. package/e2e/cypress/e2e/middleware.cy.ts +0 -45
  152. package/e2e/cypress/e2e/navigate.cy.ts +0 -39
  153. package/e2e/cypress/e2e/observability.cy.ts +0 -77
  154. package/e2e/cypress/e2e/per-route-error.cy.ts +0 -42
  155. package/e2e/cypress/e2e/per-route-render.cy.ts +0 -70
  156. package/e2e/cypress/e2e/preview-hardening.cy.ts +0 -88
  157. package/e2e/cypress/e2e/routes.cy.ts +0 -128
  158. package/e2e/cypress/e2e/seo-meta.cy.ts +0 -108
  159. package/e2e/cypress/e2e/server-middleware.cy.ts +0 -46
  160. package/e2e/cypress/e2e/session-rotation.cy.ts +0 -25
  161. package/e2e/cypress/e2e/session.cy.ts +0 -73
  162. package/e2e/cypress/e2e/ssr-render-error.cy.ts +0 -68
  163. package/e2e/cypress/e2e/synthetic-404.cy.ts +0 -45
  164. package/e2e/cypress/e2e/use-page-data.cy.ts +0 -122
  165. package/e2e/cypress/e2e/use-state.cy.ts +0 -138
  166. package/e2e/cypress/support/commands.ts +0 -120
  167. package/e2e/cypress/support/e2e.ts +0 -10
  168. package/e2e/cypress/tsconfig.json +0 -14
  169. package/e2e/kitchen-sink/app/components/ks-badge.ts +0 -8
  170. package/e2e/kitchen-sink/app/composables/useKsCounter.ts +0 -9
  171. package/e2e/kitchen-sink/app/error.ts +0 -18
  172. package/e2e/kitchen-sink/app/layouts/admin.ts +0 -13
  173. package/e2e/kitchen-sink/app/layouts/default.ts +0 -25
  174. package/e2e/kitchen-sink/app/layouts/group.ts +0 -9
  175. package/e2e/kitchen-sink/app/layouts/minimal.ts +0 -7
  176. package/e2e/kitchen-sink/app/loading.ts +0 -9
  177. package/e2e/kitchen-sink/app/middleware/auth.ts +0 -9
  178. package/e2e/kitchen-sink/app/middleware/custom-log.ts +0 -4
  179. package/e2e/kitchen-sink/app/middleware/group-auth.ts +0 -7
  180. package/e2e/kitchen-sink/app/middleware/logger.ts +0 -14
  181. package/e2e/kitchen-sink/app/pages/(auth)/login.ts +0 -13
  182. package/e2e/kitchen-sink/app/pages/(auth)/protected.ts +0 -16
  183. package/e2e/kitchen-sink/app/pages/404.ts +0 -9
  184. package/e2e/kitchen-sink/app/pages/about.ts +0 -17
  185. package/e2e/kitchen-sink/app/pages/admin/_layout.ts +0 -1
  186. package/e2e/kitchen-sink/app/pages/admin/dashboard.ts +0 -11
  187. package/e2e/kitchen-sink/app/pages/async-component-error-test.ts +0 -18
  188. package/e2e/kitchen-sink/app/pages/async-component-test.ts +0 -18
  189. package/e2e/kitchen-sink/app/pages/blog/[slug].ts +0 -59
  190. package/e2e/kitchen-sink/app/pages/blog/index.ts +0 -51
  191. package/e2e/kitchen-sink/app/pages/content-blog.ts +0 -37
  192. package/e2e/kitchen-sink/app/pages/content-doc.ts +0 -42
  193. package/e2e/kitchen-sink/app/pages/content-fallback.ts +0 -36
  194. package/e2e/kitchen-sink/app/pages/content-guides.ts +0 -35
  195. package/e2e/kitchen-sink/app/pages/content-index.ts +0 -39
  196. package/e2e/kitchen-sink/app/pages/content-search.ts +0 -36
  197. package/e2e/kitchen-sink/app/pages/cookie-test.ts +0 -22
  198. package/e2e/kitchen-sink/app/pages/counter.ts +0 -17
  199. package/e2e/kitchen-sink/app/pages/group-auth-blocked.ts +0 -9
  200. package/e2e/kitchen-sink/app/pages/group-meta-test/_layout.ts +0 -6
  201. package/e2e/kitchen-sink/app/pages/group-meta-test/custom-mw.ts +0 -14
  202. package/e2e/kitchen-sink/app/pages/group-meta-test/page.ts +0 -9
  203. package/e2e/kitchen-sink/app/pages/group-meta-test/protected.ts +0 -8
  204. package/e2e/kitchen-sink/app/pages/head.ts +0 -20
  205. package/e2e/kitchen-sink/app/pages/i18n-test.ts +0 -14
  206. package/e2e/kitchen-sink/app/pages/index.ts +0 -27
  207. package/e2e/kitchen-sink/app/pages/isr-test.ts +0 -17
  208. package/e2e/kitchen-sink/app/pages/items/[id].ts +0 -20
  209. package/e2e/kitchen-sink/app/pages/jit-css-custom-colors-test.ts +0 -14
  210. package/e2e/kitchen-sink/app/pages/loader-error-test.ts +0 -13
  211. package/e2e/kitchen-sink/app/pages/loader-props-test.ts +0 -19
  212. package/e2e/kitchen-sink/app/pages/loader-response-error-test.ts +0 -13
  213. package/e2e/kitchen-sink/app/pages/middleware-chain-test.ts +0 -12
  214. package/e2e/kitchen-sink/app/pages/middleware-status-test.ts +0 -9
  215. package/e2e/kitchen-sink/app/pages/navigate-test.ts +0 -16
  216. package/e2e/kitchen-sink/app/pages/observability-test.ts +0 -25
  217. package/e2e/kitchen-sink/app/pages/per-route-error-test.error.ts +0 -13
  218. package/e2e/kitchen-sink/app/pages/per-route-error-test.ts +0 -15
  219. package/e2e/kitchen-sink/app/pages/plugin-dsd-test.ts +0 -10
  220. package/e2e/kitchen-sink/app/pages/render-error-test.ts +0 -12
  221. package/e2e/kitchen-sink/app/pages/render-server-test.ts +0 -12
  222. package/e2e/kitchen-sink/app/pages/render-spa-test.ts +0 -12
  223. package/e2e/kitchen-sink/app/pages/route-info.ts +0 -14
  224. package/e2e/kitchen-sink/app/pages/seo-test.ts +0 -23
  225. package/e2e/kitchen-sink/app/pages/session-rotation-test.ts +0 -25
  226. package/e2e/kitchen-sink/app/pages/use-state-test.ts +0 -37
  227. package/e2e/kitchen-sink/app/plugins/01.setup.ts +0 -7
  228. package/e2e/kitchen-sink/app/plugins/02.ks-plugin-components.ts +0 -27
  229. package/e2e/kitchen-sink/cer-auto-imports.d.ts +0 -51
  230. package/e2e/kitchen-sink/cer-env.d.ts +0 -35
  231. package/e2e/kitchen-sink/cer-tsconfig.json +0 -30
  232. package/e2e/kitchen-sink/cer.config.ts +0 -43
  233. package/e2e/kitchen-sink/content/01.guides/01.index.md +0 -8
  234. package/e2e/kitchen-sink/content/01.guides/02.intro.md +0 -8
  235. package/e2e/kitchen-sink/content/01.guides/10.advanced.md +0 -8
  236. package/e2e/kitchen-sink/content/blog/2026-04-01-hello.md +0 -26
  237. package/e2e/kitchen-sink/content/blog/2026-04-02-draft.md +0 -10
  238. package/e2e/kitchen-sink/content/blog/index.md +0 -8
  239. package/e2e/kitchen-sink/content/blog/no-frontmatter.md +0 -7
  240. package/e2e/kitchen-sink/content/docs/getting-started.md +0 -48
  241. package/e2e/kitchen-sink/content/index.md +0 -16
  242. package/e2e/kitchen-sink/server/api/echo.ts +0 -12
  243. package/e2e/kitchen-sink/server/api/health.ts +0 -3
  244. package/e2e/kitchen-sink/server/api/posts/[slug].ts +0 -11
  245. package/e2e/kitchen-sink/server/api/posts/index.ts +0 -5
  246. package/e2e/kitchen-sink/server/api/session.ts +0 -23
  247. package/e2e/kitchen-sink/server/data/posts.ts +0 -21
  248. package/e2e/kitchen-sink/server/middleware/01-headers.ts +0 -6
  249. package/e2e/kitchen-sink/server/middleware/02-status-test.ts +0 -10
  250. package/e2e/kitchen-sink/tsconfig.json +0 -3
  251. package/e2e/scripts/clean.mjs +0 -14
  252. package/eslint.config.ts +0 -22
  253. package/src/__tests__/cli/adapters/cloudflare-worker.integration.test.ts +0 -132
  254. package/src/__tests__/cli/adapters/cloudflare.test.ts +0 -332
  255. package/src/__tests__/cli/adapters/netlify-bridge.integration.test.ts +0 -139
  256. package/src/__tests__/cli/adapters/netlify.test.ts +0 -286
  257. package/src/__tests__/cli/adapters/vercel-launcher.integration.test.ts +0 -129
  258. package/src/__tests__/cli/adapters/vercel.test.ts +0 -263
  259. package/src/__tests__/cli/preview-hardening.test.ts +0 -265
  260. package/src/__tests__/cli/preview-isr.test.ts +0 -320
  261. package/src/__tests__/index.test.ts +0 -21
  262. package/src/__tests__/plugin/app-template.test.ts +0 -166
  263. package/src/__tests__/plugin/build-ssg-render.test.ts +0 -155
  264. package/src/__tests__/plugin/build-ssg.test.ts +0 -598
  265. package/src/__tests__/plugin/build-ssr.test.ts +0 -192
  266. package/src/__tests__/plugin/cer-app-plugin.test.ts +0 -552
  267. package/src/__tests__/plugin/content/emitter.test.ts +0 -117
  268. package/src/__tests__/plugin/content/loader.test.ts +0 -154
  269. package/src/__tests__/plugin/content/parser.test.ts +0 -381
  270. package/src/__tests__/plugin/content/path-utils.test.ts +0 -69
  271. package/src/__tests__/plugin/content/search.test.ts +0 -119
  272. package/src/__tests__/plugin/dev-server.test.ts +0 -813
  273. package/src/__tests__/plugin/dts-generator.test.ts +0 -394
  274. package/src/__tests__/plugin/entry-server-template.test.ts +0 -385
  275. package/src/__tests__/plugin/generated-dir.test.ts +0 -144
  276. package/src/__tests__/plugin/html-post-process.test.ts +0 -146
  277. package/src/__tests__/plugin/path-utils.test.ts +0 -411
  278. package/src/__tests__/plugin/resolve-config.test.ts +0 -233
  279. package/src/__tests__/plugin/scanner.test.ts +0 -186
  280. package/src/__tests__/plugin/transforms/auto-import.test.ts +0 -510
  281. package/src/__tests__/plugin/transforms/head-inject.test.ts +0 -178
  282. package/src/__tests__/plugin/virtual/composables.test.ts +0 -57
  283. package/src/__tests__/plugin/virtual/content-components.test.ts +0 -66
  284. package/src/__tests__/plugin/virtual/error.test.ts +0 -71
  285. package/src/__tests__/plugin/virtual/layouts.test.ts +0 -70
  286. package/src/__tests__/plugin/virtual/loading.test.ts +0 -72
  287. package/src/__tests__/plugin/virtual/middleware.test.ts +0 -83
  288. package/src/__tests__/plugin/virtual/plugins.test.ts +0 -129
  289. package/src/__tests__/plugin/virtual/routes.test.ts +0 -884
  290. package/src/__tests__/plugin/virtual/server-api.test.ts +0 -164
  291. package/src/__tests__/plugin/virtual/server-middleware.test.ts +0 -102
  292. package/src/__tests__/runtime/app-template.test.ts +0 -208
  293. package/src/__tests__/runtime/define-middleware.test.ts +0 -64
  294. package/src/__tests__/runtime/define-server-middleware.test.ts +0 -54
  295. package/src/__tests__/runtime/entry-client-template.test.ts +0 -61
  296. package/src/__tests__/runtime/isr-handler.test.ts +0 -338
  297. package/src/__tests__/runtime/oauth/handler.test.ts +0 -358
  298. package/src/__tests__/runtime/oauth/providers.test.ts +0 -47
  299. package/src/__tests__/runtime/oauth-index.test.ts +0 -43
  300. package/src/__tests__/runtime/use-auth.test.ts +0 -169
  301. package/src/__tests__/runtime/use-content-search-composable.test.ts +0 -405
  302. package/src/__tests__/runtime/use-content-search.test.ts +0 -163
  303. package/src/__tests__/runtime/use-content.test.ts +0 -226
  304. package/src/__tests__/runtime/use-cookie.test.ts +0 -277
  305. package/src/__tests__/runtime/use-fetch-component.test.ts +0 -214
  306. package/src/__tests__/runtime/use-fetch.test.ts +0 -377
  307. package/src/__tests__/runtime/use-head.test.ts +0 -311
  308. package/src/__tests__/runtime/use-inject-client.test.ts +0 -67
  309. package/src/__tests__/runtime/use-inject.test.ts +0 -66
  310. package/src/__tests__/runtime/use-locale.test.ts +0 -152
  311. package/src/__tests__/runtime/use-navigate.test.ts +0 -121
  312. package/src/__tests__/runtime/use-page-data.test.ts +0 -298
  313. package/src/__tests__/runtime/use-route.test.ts +0 -156
  314. package/src/__tests__/runtime/use-runtime-config.test.ts +0 -182
  315. package/src/__tests__/runtime/use-seo-meta.test.ts +0 -109
  316. package/src/__tests__/runtime/use-session.test.ts +0 -320
  317. package/src/__tests__/runtime/use-state.test.ts +0 -340
  318. package/src/__tests__/types/config.test.ts +0 -18
  319. package/src/cli/adapters/cloudflare.ts +0 -335
  320. package/src/cli/adapters/netlify.ts +0 -326
  321. package/src/cli/adapters/vercel.ts +0 -311
  322. package/src/cli/commands/adapt.ts +0 -84
  323. package/src/cli/commands/build.ts +0 -165
  324. package/src/cli/commands/dev.ts +0 -125
  325. package/src/cli/commands/generate.ts +0 -91
  326. package/src/cli/commands/preview-isr.ts +0 -172
  327. package/src/cli/commands/preview.ts +0 -523
  328. package/src/cli/create/index.ts +0 -275
  329. package/src/cli/create/templates/shared/.gitignore.tpl +0 -25
  330. package/src/cli/create/templates/shared/app/layouts/default.ts.tpl +0 -15
  331. package/src/cli/create/templates/shared/app/pages/index.ts.tpl +0 -8
  332. package/src/cli/create/templates/shared/index.html.tpl +0 -12
  333. package/src/cli/create/templates/shared/tsconfig.json.tpl +0 -6
  334. package/src/cli/create/templates/spa/cer.config.ts.tpl +0 -6
  335. package/src/cli/create/templates/spa/package.json.tpl +0 -18
  336. package/src/cli/create/templates/ssg/cer.config.ts.tpl +0 -10
  337. package/src/cli/create/templates/ssg/package.json.tpl +0 -19
  338. package/src/cli/create/templates/ssr/cer.config.ts.tpl +0 -6
  339. package/src/cli/create/templates/ssr/package.json.tpl +0 -18
  340. package/src/cli/index.ts +0 -22
  341. package/src/index.ts +0 -20
  342. package/src/plugin/build-ssg.ts +0 -403
  343. package/src/plugin/build-ssr.ts +0 -134
  344. package/src/plugin/content/emitter.ts +0 -50
  345. package/src/plugin/content/index.ts +0 -233
  346. package/src/plugin/content/parser.ts +0 -259
  347. package/src/plugin/content/path-utils.ts +0 -55
  348. package/src/plugin/content/scanner.ts +0 -26
  349. package/src/plugin/content/search.ts +0 -28
  350. package/src/plugin/dev-server.ts +0 -379
  351. package/src/plugin/dts-generator.ts +0 -273
  352. package/src/plugin/generated-dir.ts +0 -121
  353. package/src/plugin/html-post-process.ts +0 -96
  354. package/src/plugin/index.ts +0 -584
  355. package/src/plugin/path-utils.ts +0 -186
  356. package/src/plugin/scanner.ts +0 -65
  357. package/src/plugin/transforms/auto-import.ts +0 -206
  358. package/src/plugin/transforms/head-inject.ts +0 -161
  359. package/src/plugin/virtual/composables.ts +0 -28
  360. package/src/plugin/virtual/content-components.ts +0 -83
  361. package/src/plugin/virtual/error.ts +0 -34
  362. package/src/plugin/virtual/layouts.ts +0 -41
  363. package/src/plugin/virtual/loading.ts +0 -32
  364. package/src/plugin/virtual/middleware.ts +0 -45
  365. package/src/plugin/virtual/plugins.ts +0 -46
  366. package/src/plugin/virtual/routes.ts +0 -514
  367. package/src/plugin/virtual/server-api.ts +0 -85
  368. package/src/plugin/virtual/server-middleware.ts +0 -44
  369. package/src/runtime/app-template.ts +0 -377
  370. package/src/runtime/composables/define-middleware.ts +0 -17
  371. package/src/runtime/composables/define-server-middleware.ts +0 -18
  372. package/src/runtime/composables/index.ts +0 -27
  373. package/src/runtime/composables/use-auth.ts +0 -125
  374. package/src/runtime/composables/use-content-search.ts +0 -205
  375. package/src/runtime/composables/use-content.ts +0 -146
  376. package/src/runtime/composables/use-cookie.ts +0 -135
  377. package/src/runtime/composables/use-fetch.ts +0 -438
  378. package/src/runtime/composables/use-head.ts +0 -234
  379. package/src/runtime/composables/use-inject.ts +0 -49
  380. package/src/runtime/composables/use-locale.ts +0 -109
  381. package/src/runtime/composables/use-navigate.ts +0 -58
  382. package/src/runtime/composables/use-page-data.ts +0 -93
  383. package/src/runtime/composables/use-route.ts +0 -71
  384. package/src/runtime/composables/use-runtime-config.ts +0 -123
  385. package/src/runtime/composables/use-seo-meta.ts +0 -80
  386. package/src/runtime/composables/use-session.ts +0 -186
  387. package/src/runtime/composables/use-state.ts +0 -92
  388. package/src/runtime/content/client.ts +0 -168
  389. package/src/runtime/entry-client-template.ts +0 -39
  390. package/src/runtime/entry-server-template.ts +0 -493
  391. package/src/runtime/isr-handler.ts +0 -184
  392. package/src/runtime/oauth/handler.ts +0 -407
  393. package/src/runtime/oauth/index.ts +0 -49
  394. package/src/runtime/oauth/providers.ts +0 -38
  395. package/src/types/api.ts +0 -40
  396. package/src/types/config.ts +0 -379
  397. package/src/types/content.ts +0 -66
  398. package/src/types/index.ts +0 -7
  399. package/src/types/middleware.ts +0 -63
  400. package/src/types/page.ts +0 -117
  401. package/src/types/plugin.ts +0 -34
  402. package/tsconfig.build.json +0 -10
  403. package/tsconfig.json +0 -19
  404. package/vitest.config.ts +0 -33
@@ -1,493 +0,0 @@
1
- /**
2
- * Template string for `entry-server.ts`.
3
- *
4
- * This is the Node.js SSR entry point used for both development and production
5
- * builds. It imports all virtual modules, wires up the routing, and exports a
6
- * request handler compatible with Express/Fastify/Node http.
7
- *
8
- * Key features:
9
- * - AsyncLocalStorage for race-condition-free concurrent renders (SSG concurrency > 1)
10
- * - Declarative Shadow DOM via renderToStreamWithJITCSSDSD (always on, streamed)
11
- * - useHead() support via beginHeadCollection / endHeadCollection
12
- * - DSD polyfill injected at end of <body> after client-template merge
13
- */
14
- export const ENTRY_SERVER_TEMPLATE = `// Server-side entry — AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app
15
- import { readFileSync, existsSync } from 'node:fs'
16
- import { dirname, join } from 'node:path'
17
- import { fileURLToPath } from 'node:url'
18
- import { AsyncLocalStorage } from 'node:async_hooks'
19
- import 'virtual:cer-content-components'
20
- import routes from 'virtual:cer-routes'
21
- import layouts from 'virtual:cer-layouts'
22
- import plugins from 'virtual:cer-plugins'
23
- import apiRoutes from 'virtual:cer-server-api'
24
- import serverMiddleware from 'virtual:cer-server-middleware'
25
- import { runtimeConfig, _runtimePrivateDefaults, _authSessionKey, _hooks } from 'virtual:cer-app-config'
26
- import { registerBuiltinComponents, setDevMode } from '@jasonshimmy/custom-elements-runtime'
27
- import { registerEntityMap, renderToStreamWithJITCSSDSD, DSD_POLYFILL_SCRIPT } from '@jasonshimmy/custom-elements-runtime/ssr'
28
- import entitiesJson from '@jasonshimmy/custom-elements-runtime/entities.json'
29
- import { initRouter } from '@jasonshimmy/custom-elements-runtime/router'
30
- import { beginHeadCollection, endHeadCollection, serializeHeadTags, initRuntimeConfig, resolvePrivateConfig, useSession } from '@jasonshimmy/vite-plugin-cer-app/composables'
31
- import { errorTag } from 'virtual:cer-error'
32
- import { createIsrHandler } from '@jasonshimmy/vite-plugin-cer-app/isr'
33
-
34
- const _cerProcess = (globalThis).process
35
- const _cerNodeEnv = _cerProcess?.env?.NODE_ENV ?? _cerProcess?.env?.MODE
36
- const _cerRuntimeDev =
37
- typeof _cerNodeEnv === 'string'
38
- ? _cerNodeEnv !== 'production'
39
- : typeof import.meta.env?.DEV === 'boolean'
40
- ? import.meta.env.DEV
41
- : typeof import.meta.env?.PROD === 'boolean'
42
- ? !import.meta.env.PROD
43
- : typeof import.meta.env?.MODE === 'string'
44
- ? import.meta.env.MODE !== 'production'
45
- : false
46
- ;(globalThis).__CE_RUNTIME_DEV__ = _cerRuntimeDev
47
-
48
- registerBuiltinComponents()
49
- setDevMode(_cerRuntimeDev)
50
-
51
- // Resolve private config from environment variables at server startup.
52
- // Each key declared in runtimeConfig.private is looked up in process.env,
53
- // first as-is, then as ALL_CAPS. The declared default is used as fallback.
54
- initRuntimeConfig({ ...runtimeConfig, private: resolvePrivateConfig(_runtimePrivateDefaults ?? {}) })
55
-
56
- // Pre-load the full HTML entity map so named entities like &mdash; decode
57
- // correctly during SSR. Without this the bundled runtime falls back to a
58
- // minimal set (&lt;, &gt;, &amp; …) and re-escapes everything else.
59
- registerEntityMap(entitiesJson)
60
-
61
- // Run plugins once at server startup so their provide() values are available
62
- // to useInject() during every SSR/SSG render pass. Stored on globalThis so all
63
- // dynamically-imported page chunks share the same reference.
64
- const _pluginProvides = new Map()
65
- ;(globalThis).__cerPluginProvides = _pluginProvides
66
- const _pluginsReady = (async () => {
67
- const _bootstrapRouter = initRouter({ routes })
68
- for (const plugin of plugins) {
69
- if (plugin && typeof plugin.setup === 'function') {
70
- await plugin.setup({
71
- router: _bootstrapRouter,
72
- provide: (key, value) => _pluginProvides.set(key, value),
73
- config: {},
74
- })
75
- }
76
- }
77
- })()
78
-
79
- // Async-local storage for request-scoped SSR loader data.
80
- // Using AsyncLocalStorage ensures concurrent SSR renders (e.g. SSG with
81
- // concurrency > 1) never see each other's data — each request's async chain
82
- // carries its own store value, so usePageData() is always race-condition-free.
83
- const _cerDataStore = new AsyncLocalStorage()
84
- // Expose the store so the usePageData() composable can read it server-side.
85
- ;(globalThis).__CER_DATA_STORE__ = _cerDataStore
86
-
87
- // Async-local storage for request-scoped req/res access.
88
- // Allows isomorphic composables (e.g. useCookie) to read/write HTTP headers
89
- // without prop-drilling the request context through the component tree.
90
- const _cerReqStore = new AsyncLocalStorage()
91
- ;(globalThis).__CER_REQ_STORE__ = _cerReqStore
92
-
93
- // Async-local storage for the authenticated user resolved before each render.
94
- // useAuth() reads this store server-side to return the current user synchronously.
95
- const _cerAuthStore = new AsyncLocalStorage()
96
- ;(globalThis).__CER_AUTH_STORE__ = _cerAuthStore
97
-
98
- // Async-local storage for per-request useFetch() data.
99
- // useFetch() writes fetched results here; the handler serialises the map into
100
- // window.__CER_FETCH_DATA__ for client-side hydration.
101
- const _cerFetchStore = new AsyncLocalStorage()
102
- ;(globalThis).__CER_FETCH_STORE__ = _cerFetchStore
103
-
104
- // Async-local storage for the current route info (path, params, query, meta).
105
- // useRoute() reads this on the server so layouts and components can access
106
- // route metadata without prop-drilling.
107
- const _cerRouteStore = new AsyncLocalStorage()
108
- ;(globalThis).__CER_ROUTE_STORE__ = _cerRouteStore
109
-
110
- // Async-local storage for per-request useState() reactive state.
111
- // Each request gets a fresh Map so concurrent SSR/SSG renders never share
112
- // reactive state set by different pages or loaders.
113
- const _cerStateStore = new AsyncLocalStorage()
114
- ;(globalThis).__CER_STATE_STORE__ = _cerStateStore
115
-
116
- // Runs fn inside the per-request AsyncLocalStorage context so that isomorphic
117
- // composables (useCookie, useSession, etc.) can access req/res without prop-drilling.
118
- // Call this for every API handler invocation — not just SSR renders.
119
- export function runWithRequestContext(req, res, fn) {
120
- return _cerReqStore.run({ req, res }, fn)
121
- }
122
-
123
- // Runs the server/middleware/ chain for a request.
124
- // Returns false if a middleware short-circuited the response; true to continue.
125
- // Exported so Netlify / Vercel / Cloudflare bridges can also call it for API routes.
126
- // P1-2: Middleware may throw { status: 401 } (or any numeric .status) to produce
127
- // a non-500 response — the status is extracted and forwarded to res.statusCode.
128
- export async function runServerMiddleware(req, res) {
129
- for (const { handler: mw } of (serverMiddleware ?? [])) {
130
- if (typeof mw !== 'function') continue
131
- let calledNext = false
132
- try {
133
- await new Promise((resolve, reject) => {
134
- Promise.resolve(mw(req, res, (err) => {
135
- if (err) reject(err)
136
- else { calledNext = true; resolve() }
137
- })).catch(reject)
138
- })
139
- } catch (err) {
140
- if (_hooks?.onError) {
141
- try { await _hooks.onError(err, { type: 'middleware', path: new URL(req.url ?? '/', 'http://x').pathname, req }) } catch { /* hooks must not crash the handler */ }
142
- }
143
- if (!res.writableEnded) {
144
- const statusCode = (typeof err === 'object' && err !== null && 'status' in err && typeof err.status === 'number')
145
- ? (isNaN(err.status) ? 500 : err.status)
146
- : 500
147
- res.statusCode = statusCode
148
- res.end('Internal Server Error')
149
- }
150
- return false
151
- }
152
- if (res.writableEnded || !calledNext) return false
153
- }
154
- return true
155
- }
156
-
157
- // Load the Vite-built client index.html (dist/client/index.html) so every SSR
158
- // response includes the client-side scripts needed for hydration and routing.
159
- // The server bundle lives at dist/server/server.js, so ../client resolves correctly.
160
- //
161
- // Cloudflare Workers (and other runtimes without node:fs) can inject the
162
- // template before this module loads by setting globalThis.__CER_CLIENT_TEMPLATE__.
163
- // The Cloudflare adapter inlines dist/client/index.html as a string constant in
164
- // _worker.js and sets the global before dynamically importing the server bundle.
165
- let _clientTemplate = (globalThis).__CER_CLIENT_TEMPLATE__ ?? null
166
- if (!_clientTemplate) {
167
- try {
168
- const _clientTemplatePath = join(dirname(fileURLToPath(import.meta.url)), '../client/index.html')
169
- _clientTemplate = existsSync(_clientTemplatePath)
170
- ? readFileSync(_clientTemplatePath, 'utf-8')
171
- : null
172
- } catch {
173
- // node:fs not available in this runtime — Cloudflare adapter must set
174
- // globalThis.__CER_CLIENT_TEMPLATE__ before importing this bundle.
175
- }
176
- }
177
-
178
- // Merge the SSR rendered body with the Vite client shell so the final page
179
- // contains both pre-rendered DSD content and the client bundle scripts.
180
- function _mergeWithClientTemplate(ssrHtml, clientTemplate) {
181
- const headTag = '<head>', headCloseTag = '</head>'
182
- const bodyTag = '<body>', bodyCloseTag = '</body>'
183
- const headStart = ssrHtml.indexOf(headTag)
184
- const headEnd = ssrHtml.indexOf(headCloseTag)
185
- const bodyStart = ssrHtml.indexOf(bodyTag)
186
- const bodyEnd = ssrHtml.lastIndexOf(bodyCloseTag)
187
- const ssrHead = headStart >= 0 && headEnd > headStart
188
- ? ssrHtml.slice(headStart + headTag.length, headEnd).trim() : ''
189
- const ssrBody = bodyStart >= 0 && bodyEnd > bodyStart
190
- ? ssrHtml.slice(bodyStart + bodyTag.length, bodyEnd).trim() : ssrHtml
191
- // Hoist only top-level <style id=...> elements (cer-ssr-jit, cer-ssr-global)
192
- // from the SSR body into the document <head>. Plain <style> blocks without
193
- // an id attribute belong to shadow DOM templates and must stay in place —
194
- // hoisting them to <head> breaks shadow DOM style encapsulation (document
195
- // styles do not pierce shadow roots), which is the root cause of FOUC.
196
- const headParts = ssrHead ? [ssrHead] : []
197
- let ssrBodyContent = ssrBody
198
- let pos = 0
199
- while (pos < ssrBodyContent.length) {
200
- const styleOpen = ssrBodyContent.indexOf('<style id=', pos)
201
- if (styleOpen < 0) break
202
- const styleClose = ssrBodyContent.indexOf('</style>', styleOpen)
203
- if (styleClose < 0) break
204
- headParts.push(ssrBodyContent.slice(styleOpen, styleClose + 8))
205
- ssrBodyContent = ssrBodyContent.slice(0, styleOpen) + ssrBodyContent.slice(styleClose + 8)
206
- pos = styleOpen
207
- }
208
- ssrBodyContent = ssrBodyContent.trim()
209
- // Inject the pre-rendered layout+page as light DOM of the app mount element
210
- // so it is visible before JS boots, then the client router takes over.
211
- let merged = clientTemplate
212
- if (merged.includes('<cer-layout-view></cer-layout-view>')) {
213
- merged = merged.replace('<cer-layout-view></cer-layout-view>',
214
- '<cer-layout-view>' + ssrBodyContent + '</cer-layout-view>')
215
- } else if (merged.includes('<div id="app"></div>')) {
216
- merged = merged.replace('<div id="app"></div>',
217
- '<div id="app">' + ssrBodyContent + '</div>')
218
- }
219
- const headAdditions = headParts.filter(Boolean).join('\\n')
220
- if (headAdditions) {
221
- // If SSR provides a <title>, replace the client template's <title> so the
222
- // SSR title wins (client template title is the fallback default).
223
- if (headAdditions.includes('<title>')) {
224
- merged = merged.replace(/<title>[^<]*<\\/title>/, '')
225
- }
226
- merged = merged.replace('</head>', headAdditions + '\\n</head>')
227
- }
228
- return merged
229
- }
230
-
231
- // Per-request async setup: initialize a fresh router, resolve the matched
232
- // route and layout, pre-load the page module, and call the data loader.
233
- // Loader data is returned so the handler can scope it to _cerDataStore.run()
234
- // during rendering. (AsyncLocalStorage.enterWith() inside an awaited child
235
- // function does not propagate back to the parent continuation, so run() is
236
- // the only reliable approach.)
237
- const _prepareRequest = async (req) => {
238
- await _pluginsReady
239
- const router = initRouter({ routes, initialUrl: req.url ?? '/' })
240
- const current = router.getCurrent()
241
- const { route, params } = router.matchRoute(current.path)
242
-
243
- // Store the current route info so useRoute() can read it synchronously
244
- // from any layout or component during this render pass.
245
- _cerRouteStore.enterWith({
246
- path: current.path,
247
- params,
248
- query: current.query ?? {},
249
- meta: route?.meta ?? null,
250
- })
251
-
252
- // Pre-load the page module so we can embed the component tag directly.
253
- // This avoids the async router-view (which injects content via script tags
254
- // and breaks Declarative Shadow DOM on initial parse).
255
- let pageVnode = { tag: 'div', props: {}, children: [] }
256
- let head
257
- // Loader data to pass to usePageData() during rendering. Declared here
258
- // (outside try/catch) so it's visible in all return paths.
259
- let loaderData = null
260
- if (route?.load) {
261
- try {
262
- const mod = await route.load()
263
- const pageTag = mod.default
264
- // P2-2: Route-level error tag (from co-located .error.ts or _error.ts).
265
- // Preferred over the global errorTag when rendering loader errors for this route.
266
- const routeErrorTag = mod.errorTag ?? null
267
-
268
- // P1-1: Synthetic 404 catch-all — no page component registered.
269
- if (!pageTag) {
270
- const notFoundErrorTag = routeErrorTag ?? errorTag
271
- const notFoundVnode = notFoundErrorTag
272
- ? { tag: notFoundErrorTag, props: { attrs: { error: 'Not Found', status: '404' } }, children: [] }
273
- : { tag: 'div', props: {}, children: [] }
274
- return { vnode: notFoundVnode, router, head: undefined, status: 404 }
275
- }
276
-
277
- // Run the loader before creating the page vnode so we can pass its
278
- // primitive return values as HTML attributes. useProps() in the page
279
- // component reads element attributes, so merging loader data here makes
280
- // both useProps() and usePageData() work in SSR / SSG.
281
- let loaderAttrs = {}
282
- if (typeof mod.loader === 'function') {
283
- const query = current.query ?? {}
284
- const data = await mod.loader({ params, query, req })
285
- if (data !== undefined && data !== null) {
286
- // Store loader data so the handler can pass it to _cerDataStore.run()
287
- // below. Using enterWith() here doesn't work because it only modifies
288
- // the async context *inside* this awaited function, not the outer
289
- // handler's continuation where renderToStreamWithJITCSSDSD runs.
290
- loaderData = data
291
- head = \`<script>window.__CER_DATA__ = \${JSON.stringify(data)}</script>\`
292
- // Expose primitive loader values as element attributes so useProps()
293
- // can read them. Complex objects are only accessible via usePageData().
294
- loaderAttrs = Object.fromEntries(
295
- Object.entries(data).filter(([, v]) => v !== null && v !== undefined && typeof v !== 'object' && typeof v !== 'function')
296
- )
297
- }
298
- }
299
-
300
- pageVnode = { tag: pageTag, props: { attrs: { ...params, ...loaderAttrs } }, children: [] }
301
- } catch (err) {
302
- // Loader threw — render the error page server-side if app/error.ts exists.
303
- const status = (err && typeof err === 'object' && 'status' in err && typeof err.status === 'number')
304
- ? err.status : 500
305
- const message = (err instanceof Error) ? err.message : String(err)
306
- if (_hooks?.onError) {
307
- try { await _hooks.onError(err, { type: 'loader', path: new URL(req.url ?? '/', 'http://x').pathname, req }) } catch { /* hooks must not crash the handler */ }
308
- }
309
- // P2-2: Prefer the route-level errorTag over the global one.
310
- // routeErrorTag is not in scope here; use route?.meta?.errorTag from the matched route.
311
- const effectiveErrorTag = route?.meta?.errorTag ?? errorTag
312
- if (!effectiveErrorTag) {
313
- console.error('[cer-app] Loader error (no app/error.ts defined):', err)
314
- }
315
- const errVnode = effectiveErrorTag
316
- ? { tag: effectiveErrorTag, props: { attrs: { error: message, status: String(status) } }, children: [] }
317
- : { tag: 'div', props: {}, children: [] }
318
- return { vnode: errVnode, router, head: undefined, status }
319
- }
320
- }
321
-
322
- // Resolve layout chain: nested layouts (meta.layoutChain) or single layout.
323
- const chain = route?.meta?.layoutChain
324
- ? route.meta.layoutChain
325
- : [route?.meta?.layout ?? 'default']
326
-
327
- // Wrap pageVnode in the layout chain from innermost to outermost.
328
- let vnode = pageVnode
329
- for (let i = chain.length - 1; i >= 0; i--) {
330
- const tag = layouts[chain[i]]
331
- if (tag) vnode = { tag, props: {}, children: [vnode] }
332
- }
333
-
334
- // Only framework-owned not-found routes should force a 404 status. A user
335
- // catch-all page ([...all].ts) may successfully resolve real content paths
336
- // and should stay 200 unless its loader explicitly throws a 404.
337
- const isNotFoundRoute = route?.meta?._cerNotFound === true
338
- return { vnode, router, head, status: isNotFoundRoute ? 404 : null, loaderData }
339
- }
340
-
341
- export const handler = async (req, res) => {
342
- const _requestPath = new URL(req.url ?? '/', 'http://x').pathname
343
- const _requestStart = Date.now()
344
- if (_hooks?.onRequest) {
345
- try { await _hooks.onRequest({ path: _requestPath, method: req.method ?? 'GET', req }) } catch { /* hooks must not crash the handler */ }
346
- }
347
- await _cerStateStore.run(new Map(), async () => {
348
- await _cerReqStore.run({ req, res }, async () => {
349
- await _cerDataStore.run(null, async () => {
350
- // Fresh per-request fetch map — populated by useFetch() calls inside loaders.
351
- const _fetchMap = new Map()
352
- await _cerFetchStore.run(_fetchMap, async () => {
353
- // Pre-resolve the authenticated user so useAuth() works synchronously during rendering.
354
- let _authUser = null
355
- if (_authSessionKey) {
356
- try { _authUser = await useSession({ name: _authSessionKey }).get() } catch { /* no session secret */ }
357
- }
358
- await _cerAuthStore.run(_authUser, async () => {
359
- const { vnode, router, head, status, loaderData } = await _prepareRequest(req)
360
- if (status != null) res.statusCode = status
361
-
362
- let _headCollectionOpen = false
363
- // Wrap the entire render pass in _cerDataStore.run(loaderData) so that
364
- // usePageData() inside component renderFn calls sees the correct store
365
- // value. AsyncLocalStorage.enterWith() inside _prepareRequest does NOT
366
- // propagate back to this outer async continuation — it only affects the
367
- // async chain inside _prepareRequest itself. Using run() here is the only
368
- // reliable way to scope the data store to the synchronous render pass.
369
- await _cerDataStore.run(loaderData ?? null, async () => {
370
- try {
371
- // Begin collecting useHead() calls made during the synchronous render pass.
372
- // IMPORTANT: the stream's start() function runs synchronously on construction,
373
- // so ALL useHead() calls happen before the stream object is returned. We must
374
- // call endHeadCollection() immediately — before any await — to avoid a race
375
- // window where a concurrent request (e.g. SSG concurrency > 1) resets the
376
- // shared globalThis collector while this handler is suspended at an await.
377
- _headCollectionOpen = true
378
- beginHeadCollection()
379
-
380
- // dsdPolyfill: false — we inject the polyfill manually after merging so it
381
- // lands at the end of <body>, not inside <cer-layout-view> light DOM where
382
- // scripts may not execute.
383
- // The first chunk from the stream is the full synchronous render. Subsequent
384
- // chunks are async component swap scripts streamed as they resolve.
385
- const stream = renderToStreamWithJITCSSDSD(vnode, { dsdPolyfill: false, router })
386
-
387
- // Collect head tags synchronously — all useHead() calls have already fired
388
- // inside the stream constructor's start() before it returned.
389
- const headTags = serializeHeadTags(endHeadCollection())
390
- _headCollectionOpen = false
391
-
392
- const reader = stream.getReader()
393
-
394
- // Read the first (synchronous) chunk — rejects if the sync render failed.
395
- const { value: firstChunk = '' } = await reader.read()
396
-
397
- // Serialise useFetch() results collected during loader execution.
398
- const _fetchObj = Object.fromEntries(_fetchMap)
399
- const _fetchScript = Object.keys(_fetchObj).length > 0
400
- ? \`<script>window.__CER_FETCH_DATA__ = \${JSON.stringify(_fetchObj)}</script>\`
401
- : ''
402
-
403
- // Serialise the auth user for client-side hydration via useAuth().
404
- const _authScript = _authUser
405
- ? \`<script>window.__CER_AUTH_USER__ = \${JSON.stringify(_authUser)}</script>\`
406
- : ''
407
-
408
- // Serialise useState() values for client-side hydration.
409
- // The state Map is populated by loader calls (in _prepareRequest) and by component
410
- // render functions (during renderToStreamWithJITCSSDSD above). Both run before this
411
- // point. Injected as window.__CER_STATE_INIT__ so the client useState() can
412
- // pre-populate its singleton Map on first use — no flash to default values.
413
- const _stateMap = _cerStateStore.getStore()
414
- let _stateScript = ''
415
- if (_stateMap && _stateMap.size > 0) {
416
- const _stateObj = {}
417
- for (const [k, v] of _stateMap) { _stateObj[k] = v.value }
418
- _stateScript = \`<script>window.__CER_STATE_INIT__ = \${JSON.stringify(_stateObj)}</script>\`
419
- }
420
-
421
- // Merge loader data script + useHead() tags + fetch/auth/state hydration scripts.
422
- const headContent = [head, headTags, _fetchScript, _authScript, _stateScript].filter(Boolean).join('\\n')
423
-
424
- // Wrap the rendered body in a full HTML document and inject the head additions
425
- // (loader data script, useHead() tags, JIT styles). No polyfill in body yet.
426
- const ssrHtml = \`<!DOCTYPE html><html><head>\${headContent}</head><body>\${firstChunk}</body></html>\`
427
-
428
- // In dev mode the module-level _clientTemplate is null (only the
429
- // production dist/client/index.html path is searched at init time).
430
- // The dev server sets (globalThis).__CER_CLIENT_TEMPLATE__ per-request
431
- // after running server.transformIndexHtml so the Vite client scripts
432
- // (/@vite/client, HMR) are included in every SSR response.
433
- const _resolvedClientTemplate = (globalThis).__CER_CLIENT_TEMPLATE__ ?? _clientTemplate
434
- const merged = _resolvedClientTemplate
435
- ? _mergeWithClientTemplate(ssrHtml, _resolvedClientTemplate)
436
- : ssrHtml
437
-
438
- // Split at </body> so async swap scripts and the DSD polyfill can be streamed
439
- // in before the document is closed.
440
- const bodyCloseIdx = merged.lastIndexOf('</body>')
441
- const beforeBodyClose = bodyCloseIdx >= 0 ? merged.slice(0, bodyCloseIdx) : merged
442
- const fromBodyClose = bodyCloseIdx >= 0 ? merged.slice(bodyCloseIdx) : ''
443
-
444
- res.setHeader('Content-Type', 'text/html; charset=utf-8')
445
- res.setHeader('Transfer-Encoding', 'chunked')
446
- res.write(beforeBodyClose)
447
-
448
- // Stream async component swap scripts through as-is.
449
- while (true) {
450
- const { value, done } = await reader.read()
451
- if (done) break
452
- res.write(value)
453
- }
454
-
455
- // Inject DSD polyfill immediately before </body>, then close the document.
456
- res.end(DSD_POLYFILL_SCRIPT + fromBodyClose)
457
- if (_hooks?.onResponse) {
458
- try { void _hooks.onResponse({ path: _requestPath, method: req.method ?? 'GET', statusCode: res.statusCode, duration: Date.now() - _requestStart, req }) } catch { /* ignore */ }
459
- }
460
- } catch (_renderErr) {
461
- if (_hooks?.onError) {
462
- try { await _hooks.onError(_renderErr, { type: 'render', path: _requestPath, req }) } catch { /* hooks must not crash the handler */ }
463
- }
464
- // Ensure the head collector is never left open on error.
465
- if (_headCollectionOpen) { try { endHeadCollection() } catch { /* ignore */ } }
466
- // If headers have not been flushed yet we can still send a proper 500 page.
467
- // If writing has already started we can only close the connection cleanly.
468
- if (!res.headersSent) {
469
- res.statusCode = 500
470
- res.setHeader('Content-Type', 'text/html; charset=utf-8')
471
- res.end('<!DOCTYPE html><html><head></head><body><h1>500 Internal Server Error</h1><p>An unexpected error occurred while rendering this page.</p></body></html>')
472
- } else {
473
- res.end()
474
- }
475
- if (_hooks?.onResponse) {
476
- try { void _hooks.onResponse({ path: _requestPath, method: req.method ?? 'GET', statusCode: res.statusCode, duration: Date.now() - _requestStart, req }) } catch { /* ignore */ }
477
- }
478
- }
479
- }) // _cerDataStore.run(loaderData)
480
- }) // _cerAuthStore.run
481
- }) // _cerFetchStore.run
482
- }) // _cerDataStore.run
483
- }) // _cerReqStore.run
484
- }) // _cerStateStore.run
485
- }
486
-
487
- // ISR-wrapped handler for production integrations (Express, Hono, Fastify).
488
- // Routes with meta.ssg.revalidate are served stale-while-revalidate.
489
- export const isrHandler = createIsrHandler(routes, handler)
490
-
491
- export { apiRoutes, plugins, layouts, routes, serverMiddleware }
492
- export default handler
493
- `
@@ -1,184 +0,0 @@
1
- /**
2
- * createIsrHandler — portable stale-while-revalidate ISR factory.
3
- *
4
- * Wraps any Express-compatible SSR handler with an in-memory ISR cache.
5
- * Routes that export `meta.ssg.revalidate` get cached for the declared TTL.
6
- *
7
- * Usage (Express):
8
- * import { createIsrHandler } from '@jasonshimmy/vite-plugin-cer-app/isr'
9
- * import { handler, routes } from './dist/server/server.js'
10
- * app.use(createIsrHandler(routes, handler))
11
- *
12
- * Usage (Hono):
13
- * import { createIsrHandler } from '@jasonshimmy/vite-plugin-cer-app/isr'
14
- * import { handler, routes } from './dist/server/server.js'
15
- * app.use('*', createIsrHandler(routes, handler))
16
- */
17
-
18
- import type { IncomingMessage, ServerResponse } from 'node:http'
19
-
20
- /** A single cached SSR response stored by `createIsrHandler`. Includes the full rendered HTML, response headers, status code, and revalidation metadata. */
21
- export interface IsrCacheEntry {
22
- html: string
23
- headers: Record<string, string>
24
- statusCode: number
25
- builtAt: number
26
- revalidate: number
27
- }
28
-
29
- /** The SSR request handler signature produced by the server entry bundle (exported as `handler`). Compatible with Express, Hono, and any Node.js HTTP server. */
30
- export type SsrHandlerFn = (req: IncomingMessage, res: ServerResponse) => unknown
31
-
32
- // ─── Internal helpers ─────────────────────────────────────────────────────────
33
-
34
- function _matchPattern(pattern: string, urlPath: string): boolean {
35
- const norm = (s: string) => s.replace(/\/+$/, '') || '/'
36
- if (norm(pattern) === norm(urlPath)) return true
37
- const regexStr =
38
- '^' +
39
- norm(pattern)
40
- .replace(/[.+?^${}()|[\]\\]/g, '\\$&')
41
- .replace(/:[^/]+\*/g, '.*')
42
- .replace(/:[^/]+/g, '[^/]+') +
43
- '$'
44
- return new RegExp(regexStr).test(norm(urlPath))
45
- }
46
-
47
- function _findRevalidate(
48
- routes: Array<{ path: string; meta?: Record<string, unknown> }>,
49
- urlPath: string,
50
- ): number | null {
51
- for (const route of routes) {
52
- if (_matchPattern(route.path, urlPath)) {
53
- const ssg = route.meta?.ssg as Record<string, unknown> | undefined
54
- if (typeof ssg?.revalidate === 'number') return ssg.revalidate
55
- return null
56
- }
57
- }
58
- return null
59
- }
60
-
61
- async function _renderForCache(
62
- urlPath: string,
63
- handler: SsrHandlerFn,
64
- revalidate: number,
65
- ): Promise<IsrCacheEntry | null> {
66
- return new Promise((resolve) => {
67
- const chunks: Buffer[] = []
68
- const capturedHeaders: Record<string, string | string[]> = {}
69
- let capturedStatus = 200
70
-
71
- const fakeRes = {
72
- get statusCode() { return capturedStatus },
73
- set statusCode(v: number) { capturedStatus = v },
74
- setHeader(name: string, value: string | string[]) {
75
- capturedHeaders[name.toLowerCase()] = value
76
- },
77
- write(chunk: string | Buffer) {
78
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, 'utf-8'))
79
- },
80
- end(body?: string | Buffer) {
81
- if (body !== undefined) {
82
- chunks.push(Buffer.isBuffer(body) ? body : Buffer.from(String(body), 'utf-8'))
83
- }
84
- resolve({
85
- html: Buffer.concat(chunks).toString('utf-8'),
86
- headers: Object.fromEntries(
87
- Object.entries(capturedHeaders).map(([k, v]) => [k, Array.isArray(v) ? v.join(', ') : v]),
88
- ),
89
- statusCode: capturedStatus,
90
- builtAt: Date.now(),
91
- revalidate,
92
- })
93
- },
94
- } as unknown as ServerResponse
95
-
96
- const fakeReq = {
97
- url: urlPath,
98
- method: 'GET',
99
- headers: { accept: 'text/html' },
100
- } as IncomingMessage
101
-
102
- try {
103
- const result = handler(fakeReq, fakeRes)
104
- if (result && typeof (result as Promise<void>).catch === 'function') {
105
- ;(result as Promise<void>).catch(() => resolve(null))
106
- }
107
- } catch {
108
- resolve(null)
109
- }
110
- })
111
- }
112
-
113
- function _serveFromCache(entry: IsrCacheEntry, res: ServerResponse, status: 'HIT' | 'STALE'): void {
114
- res.statusCode = entry.statusCode
115
- for (const [name, value] of Object.entries(entry.headers)) {
116
- res.setHeader(name, value)
117
- }
118
- res.setHeader('X-Cache', status)
119
- res.end(entry.html)
120
- }
121
-
122
- // ─── Public API ───────────────────────────────────────────────────────────────
123
-
124
- /**
125
- * Wraps an SSR handler with stale-while-revalidate ISR caching.
126
- *
127
- * Routes that declare `meta.ssg.revalidate` in the `routes` array are cached
128
- * in memory. After the TTL expires the stale response is served immediately
129
- * while a fresh render runs in the background (stale-while-revalidate).
130
- *
131
- * Routes without a `revalidate` value are passed through to the handler directly.
132
- */
133
- export function createIsrHandler(
134
- routes: Array<{ path: string; meta?: Record<string, unknown> }>,
135
- handler: SsrHandlerFn,
136
- ): SsrHandlerFn {
137
- const cache = new Map<string, IsrCacheEntry>()
138
- // True lock: stores the in-flight revalidation Promise per URL path.
139
- // A path present in this map means a background render is already in progress.
140
- const _inFlight = new Map<string, Promise<void>>()
141
-
142
- return async (req: IncomingMessage, res: ServerResponse): Promise<unknown> => {
143
- const urlPath = (req.url ?? '/').split('?')[0]
144
- const revalidate = _findRevalidate(routes, urlPath)
145
-
146
- if (revalidate === null) {
147
- return handler(req, res)
148
- }
149
-
150
- const cached = cache.get(urlPath)
151
- const now = Date.now()
152
-
153
- if (cached) {
154
- const ageSeconds = (now - cached.builtAt) / 1000
155
- if (ageSeconds < cached.revalidate) {
156
- _serveFromCache(cached, res, 'HIT')
157
- return
158
- }
159
- // Stale — serve immediately, then revalidate in the background if no
160
- // revalidation is already in flight for this path.
161
- _serveFromCache(cached, res, 'STALE')
162
- if (!_inFlight.has(urlPath)) {
163
- const promise = _renderForCache(urlPath, handler, revalidate).then((entry) => {
164
- if (entry) cache.set(urlPath, entry)
165
- }).catch(() => {
166
- // Background render failed — next request will try again.
167
- }).finally(() => {
168
- _inFlight.delete(urlPath)
169
- })
170
- _inFlight.set(urlPath, promise)
171
- }
172
- return
173
- }
174
-
175
- // Cache miss — render, cache, then serve.
176
- const entry = await _renderForCache(urlPath, handler, revalidate)
177
- if (entry) {
178
- cache.set(urlPath, entry)
179
- _serveFromCache(entry, res, 'HIT')
180
- } else {
181
- await handler(req, res)
182
- }
183
- }
184
- }