@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,907 +0,0 @@
1
- # Composables
2
-
3
- Composables are reusable reactive logic functions. Files in `app/composables/` are automatically re-exported as a barrel — you can import from `virtual:cer-composables` anywhere in your app.
4
-
5
- ---
6
-
7
- ## Creating a composable
8
-
9
- ```ts
10
- // app/composables/useTheme.ts
11
- import { ref, computed } from '@jasonshimmy/custom-elements-runtime'
12
-
13
- export function useTheme() {
14
- const theme = ref<'light' | 'dark'>('light')
15
-
16
- const isDark = computed(() => theme.value === 'dark')
17
-
18
- function toggle() {
19
- theme.value = theme.value === 'light' ? 'dark' : 'light'
20
- document.documentElement.setAttribute('data-theme', theme.value)
21
- }
22
-
23
- return { theme, isDark, toggle }
24
- }
25
- ```
26
-
27
- ---
28
-
29
- ## Using a composable in a page
30
-
31
- When `autoImports.composables` is `true` (the default), composables are **automatically imported** in page, layout, and component files — you don't need to write the import statement at all:
32
-
33
- ```ts
34
- // app/pages/index.ts
35
- // No import needed — useTheme is auto-imported from virtual:cer-composables
36
- component('page-index', () => {
37
- const { isDark, toggle } = useTheme()
38
-
39
- return html`
40
- <button @click="${toggle}">
41
- Switch to ${isDark.value ? 'light' : 'dark'} mode
42
- </button>
43
- `
44
- })
45
- ```
46
-
47
- If you need to import explicitly (e.g. in a file outside `app/pages/`, `app/layouts/`, or `app/components/`), import from `virtual:cer-composables`:
48
-
49
- ```ts
50
- import { useTheme } from 'virtual:cer-composables'
51
- ```
52
-
53
- ---
54
-
55
- ## Virtual module
56
-
57
- `virtual:cer-composables` is a generated barrel that re-exports everything from all files in `app/composables/`:
58
-
59
- ```ts
60
- // virtual:cer-composables (auto-generated)
61
- export * from "/project/app/composables/useTheme.ts"
62
- export * from "/project/app/composables/useAuth.ts"
63
- ```
64
-
65
- If `app/composables/` does not exist or is empty, the module exports nothing (no error).
66
-
67
- ---
68
-
69
- ## File naming
70
-
71
- Any `.ts` file in `app/composables/` (including subdirectories) is included. No naming convention is enforced, but `use` prefix is conventional:
72
-
73
- ```
74
- app/composables/
75
- useTheme.ts
76
- useAuth.ts
77
- data/
78
- usePosts.ts
79
- ```
80
-
81
- ---
82
-
83
- ## Side-effect safety
84
-
85
- Composables must be side-effect-free at import time. The barrel module is imported during app initialization, before components render. Any code that runs immediately at module scope will execute at that point.
86
-
87
- **Safe:**
88
-
89
- ```ts
90
- // app/composables/useCounter.ts
91
- export function useCounter() {
92
- const count = ref(0)
93
- return { count }
94
- }
95
- ```
96
-
97
- **Avoid:**
98
-
99
- ```ts
100
- // app/composables/useSession.ts
101
- // ⚠️ This runs at import time, before the DOM is ready
102
- const session = await fetchSession()
103
- export function useSession() { return session }
104
- ```
105
-
106
- Use `useOnConnected` or lazy initialization inside the function body for side effects.
107
-
108
- ---
109
-
110
- ## Built-in framework composables
111
-
112
- These composables are provided by the framework and auto-imported alongside the runtime. They do **not** live in `app/composables/` — they are injected from `@jasonshimmy/vite-plugin-cer-app/composables`.
113
-
114
- ### Auto-import scope
115
-
116
- Built-in composables are auto-imported in the following directories:
117
-
118
- | Directory | Auto-imported |
119
- |---|---|
120
- | `app/pages/` | ✅ |
121
- | `app/layouts/` | ✅ |
122
- | `app/components/` | ✅ |
123
- | `app/middleware/` | ✅ |
124
- | `app/composables/` | ✅ |
125
- | `server/middleware/` | ✅ |
126
- | `server/api/` | ❌ — import explicitly |
127
-
128
- Files in `server/api/` are not in the auto-import scope. To use composables there, import them explicitly:
129
-
130
- ```ts
131
- import { useSession, useCookie } from '@jasonshimmy/vite-plugin-cer-app/composables'
132
- ```
133
-
134
- See [server-api.md](./server-api.md) for details and usage examples.
135
-
136
- ### `useFetch<T>(url, options?)`
137
-
138
- Isomorphic data-fetching composable:
139
-
140
- - **Inside a `component()` render function** — returns reactive `data`, `pending`, and `error` refs that re-render the component automatically when the request settles.
141
- - **Inside a `loader` or other async context** — returns a thenable result you can `await` to block SSR rendering until data is ready.
142
-
143
- ```ts
144
- component('page-posts', () => {
145
- const { data: posts, pending, error } = useFetch<Post[]>('/api/posts')
146
-
147
- return html`
148
- ${when(pending.value, () => html`<p>Loading…</p>`)}
149
- ${when(!!error.value, () => html`<p>Error: ${error.value!.message}</p>`)}
150
- <ul>${each(posts.value ?? [], p => html`<li>${p.title}</li>`)}</ul>
151
- `
152
- })
153
- ```
154
-
155
- See [use-fetch.md](./use-fetch.md) for full documentation including options, lazy fetching, POST requests, and TypeScript types.
156
-
157
- ---
158
-
159
- ### `useAuth(sessionKey?)`
160
-
161
- Returns the authenticated user and helpers for login/logout. Works isomorphically — on the server it reads the auth session from the per-request context; on the client it reads from the value injected into the HTML at render time.
162
-
163
- ```ts
164
- component('page-nav', () => {
165
- const { user, loggedIn, login, logout } = useAuth()
166
-
167
- return html`
168
- ${match()
169
- .when(loggedIn, () => html`<span>${user?.name}</span><button @click="${logout}">Log out</button>`)
170
- .otherwise(() => html`<button @click="${() => login('github')}">Log in</button>`)
171
- .done()}
172
- `
173
- })
174
- ```
175
-
176
- Requires `auth` configuration in `cer.config.ts`. See [authentication.md](./authentication.md) for setup, OAuth providers, middleware guards, and TypeScript types.
177
-
178
- ---
179
-
180
- ### `useHead(input)`
181
-
182
- Sets document head tags (`<title>`, `<meta>`, `<link>`, etc.). Works in SPA, SSR, and SSG modes. See [head-management.md](./head-management.md).
183
-
184
- ### `usePageData<T>()`
185
-
186
- Returns the serialized loader data for the current page, hydrated from `window.__CER_DATA__` on the client or from the per-request `AsyncLocalStorage` context during SSR/SSG. See [data-loading.md](./data-loading.md).
187
-
188
- ### `useInject<T>(key, defaultValue?): T | undefined`
189
-
190
- Reads a value provided by a plugin via `app.provide(key, value)`. Works consistently in all rendering modes:
191
-
192
- - **SPA / client** — resolves via `inject()` from the component context tree.
193
- - **SSR / SSG** — reads from `globalThis.__cerPluginProvides`, populated by the server entry before the first render.
194
-
195
- ```ts
196
- // app/pages/dashboard.ts
197
- component('page-dashboard', () => {
198
- const store = useInject<Store>('store')
199
- return html`<p>Count: ${store?.state.count ?? 0}</p>`
200
- })
201
- ```
202
-
203
- If you need it outside auto-imported directories, import explicitly:
204
-
205
- ```ts
206
- import { useInject } from '@jasonshimmy/vite-plugin-cer-app/composables'
207
- ```
208
-
209
- > **Note:** Prefer `useInject` over the raw `inject()` primitive whenever reading plugin-provided values. Raw `inject()` works in SPA mode but returns `undefined` in SSR and SSG because the server renders components without `<cer-layout-view>`'s provide context.
210
-
211
- ### `useRuntimeConfig()`
212
-
213
- Returns the runtime configuration set in `cer.config.ts`. Returns `{ public, private? }`:
214
- - `public` — available everywhere (server and client)
215
- - `private` — server-only secrets resolved from `process.env` at startup; `undefined` on the client
216
-
217
- ```ts
218
- // cer.config.ts
219
- export default defineConfig({
220
- runtimeConfig: {
221
- public: {
222
- apiBase: process.env.VITE_API_BASE ?? '/api',
223
- },
224
- private: {
225
- dbUrl: '', // resolved from process.env.DB_URL at server startup
226
- },
227
- },
228
- })
229
- ```
230
-
231
- ```ts
232
- // app/pages/index.ts — public config, works on client and server
233
- component('page-index', () => {
234
- const { public: cfg } = useRuntimeConfig()
235
- return html`<p>API base: ${cfg.apiBase}</p>`
236
- })
237
- ```
238
-
239
- ```ts
240
- // app/pages/data.ts — private config, server-only (loader)
241
- export const loader = async () => {
242
- const { private: priv } = useRuntimeConfig()
243
- const rows = await db.query(priv?.dbUrl)
244
- return { rows }
245
- }
246
- ```
247
-
248
- **Only use `runtimeConfig.public` for values safe to expose to the browser.** Use `runtimeConfig.private` for secrets — they are never sent to the client.
249
-
250
- Keys declared in `runtimeConfig.private` with an empty-string default are treated as **required** secrets. If the corresponding environment variable is not set at server startup, a warning is logged:
251
-
252
- ```
253
- [cer-app] runtimeConfig.private: "dbUrl" is an empty string — set DB_URL in the environment to provide a value.
254
- ```
255
-
256
- If the default is a non-empty string it is used as a genuine fallback — no warning is emitted.
257
-
258
- If you need it outside auto-imported directories:
259
-
260
- ```ts
261
- import { useRuntimeConfig } from '@jasonshimmy/vite-plugin-cer-app/composables'
262
- ```
263
-
264
- ---
265
-
266
- ### `useSeoMeta(input)`
267
-
268
- Thin wrapper over `useHead()` for the most common SEO tags — Open Graph, Twitter Card, meta description, and canonical URL. Works in SPA, SSR, and SSG modes.
269
-
270
- ```ts
271
- // app/pages/about.ts
272
- component('page-about', () => {
273
- useSeoMeta({
274
- title: 'About Us',
275
- description: 'Learn more about our team.',
276
- ogTitle: 'About Us — My Site',
277
- ogDescription: 'Learn more about our team.',
278
- ogImage: 'https://example.com/og/about.png',
279
- ogUrl: 'https://example.com/about',
280
- ogType: 'website',
281
- ogSiteName: 'My Site',
282
- twitterCard: 'summary_large_image',
283
- twitterSite: '@mysite',
284
- canonical: 'https://example.com/about',
285
- })
286
- return html`<h1>About Us</h1>`
287
- })
288
- ```
289
-
290
- Only properties you set are emitted — passing `undefined` (or omitting a property entirely) skips that tag.
291
-
292
- #### `SeoMetaInput` fields
293
-
294
- | Field | Tag emitted |
295
- |---|---|
296
- | `title` | `<title>` |
297
- | `description` | `<meta name="description">` |
298
- | `ogTitle` | `<meta property="og:title">` |
299
- | `ogDescription` | `<meta property="og:description">` |
300
- | `ogImage` | `<meta property="og:image">` |
301
- | `ogUrl` | `<meta property="og:url">` |
302
- | `ogType` | `<meta property="og:type">` |
303
- | `ogSiteName` | `<meta property="og:site_name">` |
304
- | `twitterCard` | `<meta name="twitter:card">` |
305
- | `twitterTitle` | `<meta name="twitter:title">` |
306
- | `twitterDescription` | `<meta name="twitter:description">` |
307
- | `twitterImage` | `<meta name="twitter:image">` |
308
- | `twitterSite` | `<meta name="twitter:site">` |
309
- | `canonical` | `<link rel="canonical">` |
310
-
311
- If you need it outside auto-imported directories:
312
-
313
- ```ts
314
- import { useSeoMeta } from '@jasonshimmy/vite-plugin-cer-app/composables'
315
- ```
316
-
317
- TypeScript types:
318
-
319
- ```ts
320
- import type { SeoMetaInput } from '@jasonshimmy/vite-plugin-cer-app/types'
321
- ```
322
-
323
- ---
324
-
325
- ### `useCookie(name, options?)`
326
-
327
- Isomorphic cookie composable. Reads and writes cookies transparently on both server and client:
328
-
329
- - **Server (SSR/SSG):** reads `req.headers.cookie`; writes `Set-Cookie` response headers via `res.setHeader`.
330
- - **Client:** reads and writes `document.cookie`.
331
-
332
- ```ts
333
- // app/pages/profile.ts
334
- component('page-profile', () => {
335
- const session = useCookie('session')
336
-
337
- // Read
338
- console.log(session.value) // 'abc123' | undefined
339
-
340
- // Write
341
- session.set('abc123', { httpOnly: true, sameSite: 'Strict' })
342
-
343
- // Remove
344
- session.remove()
345
-
346
- return html`<p>Session: ${session.value ?? 'none'}</p>`
347
- })
348
- ```
349
-
350
- #### `CookieRef`
351
-
352
- | Member | Type | Description |
353
- |---|---|---|
354
- | `value` | `string \| undefined` | Current cookie value (read at call time) |
355
- | `set(value, options?)` | `void` | Write the cookie |
356
- | `remove(options?)` | `void` | Delete the cookie (sets `Max-Age=0`) |
357
-
358
- #### `CookieOptions`
359
-
360
- | Option | Type | Description |
361
- |---|---|---|
362
- | `path` | `string` | Cookie path (defaults to `/` when setting/removing) |
363
- | `domain` | `string` | Cookie domain |
364
- | `maxAge` | `number` | Max age in seconds |
365
- | `expires` | `Date` | Expiry date |
366
- | `httpOnly` | `boolean` | Set `HttpOnly` flag |
367
- | `secure` | `boolean` | Set `Secure` flag |
368
- | `sameSite` | `'Strict' \| 'Lax' \| 'None'` | `SameSite` attribute |
369
-
370
- Default options can be passed as the second argument to `useCookie` — they are merged with options passed to `set()`/`remove()`:
371
-
372
- ```ts
373
- const auth = useCookie('auth', { httpOnly: true, secure: true, sameSite: 'Strict' })
374
- auth.set('tok') // inherits httpOnly, secure, sameSite automatically
375
- ```
376
-
377
- If you need it outside auto-imported directories:
378
-
379
- ```ts
380
- import { useCookie } from '@jasonshimmy/vite-plugin-cer-app/composables'
381
- ```
382
-
383
- TypeScript types:
384
-
385
- ```ts
386
- import type { CookieOptions, CookieRef } from '@jasonshimmy/vite-plugin-cer-app/types'
387
- ```
388
-
389
- ---
390
-
391
- ### `defineMiddleware(fn)`
392
-
393
- Identity helper that gives TypeScript the correct `MiddlewareFn` type. Auto-imported — no import needed in `app/middleware/` files.
394
-
395
- ```ts
396
- // app/middleware/auth.ts
397
- export default defineMiddleware(async (to, from) => {
398
- const { loggedIn } = useAuth()
399
- return loggedIn ? true : '/login'
400
- })
401
- ```
402
-
403
- ---
404
-
405
- ### `defineServerMiddleware(fn)`
406
-
407
- Identity helper for server-side middleware. Files in `server/middleware/` export a default `defineServerMiddleware()` function. They run in **alphabetical filename order** on every SSR and API request, before routing — in all environments (dev server, Vercel, Netlify, Cloudflare).
408
-
409
- ```ts
410
- // server/middleware/01-cors.ts
411
- export default defineServerMiddleware((req, res, next) => {
412
- res.setHeader('Access-Control-Allow-Origin', '*')
413
- next()
414
- })
415
-
416
- // server/middleware/02-auth.ts
417
- export default defineServerMiddleware(async (req, res, next) => {
418
- const session = useSession<{ userId: string }>()
419
- const data = await session.get()
420
- if (!data?.userId) { res.statusCode = 401; res.end('Unauthorized'); return }
421
- ;(req as any).user = data
422
- next()
423
- })
424
- ```
425
-
426
- Call `next()` to continue to the next middleware or request handler. Write the response without calling `next()` to short-circuit the chain. Calling `next(err)` with an error sends a `500` response.
427
-
428
- If you need it outside auto-imported directories:
429
-
430
- ```ts
431
- import { defineServerMiddleware } from '@jasonshimmy/vite-plugin-cer-app/composables'
432
- ```
433
-
434
- ---
435
-
436
- ### `useSession<T>(options?)`
437
-
438
- HMAC-SHA-256 signed cookie session. Stores JSON-serialisable session data in a single `httpOnly` cookie, signed with a secret key. Works isomorphically: on the server it reads/writes HTTP headers; on the client it reads the cookie from `document.cookie`.
439
-
440
- **Setup:** declare the signing key in `runtimeConfig.private`:
441
-
442
- ```ts
443
- // cer.config.ts
444
- export default defineConfig({
445
- runtimeConfig: {
446
- private: { sessionSecret: '' }, // resolved from SESSION_SECRET env var at startup
447
- },
448
- })
449
- ```
450
-
451
- **Secret rotation:** `sessionSecret` accepts a `string | string[]`. When an array is provided, the framework signs new sessions with the **first** secret and verifies incoming tokens against **all** secrets in order. This lets you rotate keys without immediately invalidating all existing sessions:
452
-
453
- ```ts
454
- // cer.config.ts
455
- export default defineConfig({
456
- runtimeConfig: {
457
- private: {
458
- sessionSecret: [
459
- process.env.SESSION_SECRET_NEW!, // active key — signs all new sessions
460
- process.env.SESSION_SECRET_OLD!, // accepted during rotation window
461
- ],
462
- },
463
- },
464
- })
465
- ```
466
-
467
- Rotation workflow:
468
- 1. Generate a new secret and add it as the **first** element; keep the old secret as the second.
469
- 2. Deploy. New sessions are signed with the new key; existing sessions signed with the old key are still accepted.
470
- 3. After all old sessions have expired (or after your desired rotation window), remove the second element.
471
-
472
- A token is rejected only when it fails verification against every secret in the array.
473
-
474
- **Usage:**
475
-
476
- ```ts
477
- // server/middleware/auth.ts — validate session on every request
478
- export default defineServerMiddleware(async (req, res, next) => {
479
- const session = useSession<{ userId: string; role: string }>()
480
- const data = await session.get()
481
- if (!data?.userId) { res.statusCode = 401; res.end(); return }
482
- next()
483
- })
484
-
485
- // app/pages/login.ts — create session after verifying credentials
486
- export const loader = async ({ req }) => {
487
- // ... verify credentials
488
- const session = useSession<{ userId: string }>()
489
- await session.set({ userId: user.id })
490
- return { ok: true }
491
- }
492
-
493
- // app/pages/logout.ts
494
- export const loader = async () => {
495
- await useSession().clear()
496
- return { ok: true }
497
- }
498
- ```
499
-
500
- | Method | Returns | Description |
501
- |---|---|---|
502
- | `get()` | `Promise<T \| null>` | Reads and verifies the session cookie. Returns data or `null` if absent/invalid/tampered. |
503
- | `set(data)` | `Promise<void>` | Signs `data` and writes the session cookie. Replaces any existing session. |
504
- | `clear()` | `Promise<void>` | Clears the session cookie by setting `maxAge = 0`. |
505
-
506
- #### `SessionOptions`
507
-
508
- | Option | Type | Default | Description |
509
- |---|---|---|---|
510
- | `name` | `string` | `'session'` | Cookie name |
511
- | `maxAge` | `number` | `604800` (7 days) | Cookie max-age in seconds |
512
-
513
- If you need it outside auto-imported directories:
514
-
515
- ```ts
516
- import { useSession } from '@jasonshimmy/vite-plugin-cer-app/composables'
517
- import type { SessionOptions, SessionComposable } from '@jasonshimmy/vite-plugin-cer-app/composables'
518
- ```
519
-
520
-
521
- See [Middleware](./middleware.md) for full documentation.
522
-
523
- ---
524
-
525
- ### `useRoute()`
526
-
527
- Returns the current route's `path`, `params`, `query`, and `meta` — works isomorphically in all rendering modes.
528
-
529
- - **Server (SSR/SSG)** — reads from the per-request `AsyncLocalStorage` context populated before the page renders.
530
- - **Client** — reads from the router instance exposed by the framework.
531
-
532
- ```ts
533
- // app/layouts/default.ts — display page title from route meta
534
- component('layout-default', () => {
535
- const route = useRoute()
536
-
537
- return html`
538
- <header>
539
- <h1>${route.meta?.title ?? 'My App'}</h1>
540
- </header>
541
- <main><slot></slot></main>
542
- `
543
- })
544
- ```
545
-
546
- ```ts
547
- // app/pages/post.ts — use dynamic route params
548
- component('page-post', () => {
549
- const { params } = useRoute()
550
- const { data: post } = useFetch(`/api/posts/${params.id}`)
551
- return html`<h1>${post.value?.title}</h1>`
552
- })
553
- ```
554
-
555
- #### `RouteInfo`
556
-
557
- | Field | Type | Description |
558
- |---|---|---|
559
- | `path` | `string` | Current URL path, e.g. `'/posts/42'` |
560
- | `params` | `Record<string, string>` | Dynamic route params, e.g. `{ id: '42' }` |
561
- | `query` | `Record<string, string>` | Parsed query string, e.g. `{ page: '2' }` |
562
- | `meta` | `Record<string, unknown> \| null` | The raw `meta` object exported by the matched page file. Custom fields like `title`, `render`, `hydrate`, etc., are accessible here. |
563
-
564
- If you need it outside auto-imported directories:
565
-
566
- ```ts
567
- import { useRoute } from '@jasonshimmy/vite-plugin-cer-app/composables'
568
- import type { RouteInfo } from '@jasonshimmy/vite-plugin-cer-app/composables'
569
- ```
570
-
571
- ---
572
-
573
- ### `useState<T>(key, init?)`
574
-
575
- Globally-keyed reactive state shared across layouts, pages, and components. Any two components that call `useState` with the same key get the **same reactive ref** — mutating `.value` in one component automatically re-renders all components that read it.
576
-
577
- Works isomorphically:
578
- - **SSR/SSG** — state is scoped per-request via `AsyncLocalStorage`. Set initial values inside a page `loader` (which runs before rendering) so the layout can read them synchronously during the server render pass. After rendering, all state values are serialized into `window.__CER_STATE_INIT__` and hydrated on the client.
579
- - **Client** — state lives in a singleton `Map` on `globalThis`. On first use, the Map is pre-populated from `window.__CER_STATE_INIT__` (the SSR snapshot) so there is no flash to default values after hydration. Mutations propagate reactively to all components sharing the key.
580
-
581
- **Key contract:** the `init` value (or factory) is only evaluated when the key does not yet exist. Subsequent calls with the same key return the existing ref; `init` is ignored.
582
-
583
- > **SSR → client:** State set in a page `loader` is serialized from server to client via `window.__CER_STATE_INIT__`. On first `useState()` call, the client Map is pre-populated from this snapshot — the layout renders with the correct value immediately after hydration, with no flash to default values.
584
- >
585
- > State set in a component render function IS included in the snapshot (render functions execute during SSR before state is serialized), but the layout's initial HTML will still show its fallback value — the layout has already rendered its HTML by the time the page component executes. The reactive system updates the layout after client hydration.
586
- >
587
- > State set in `useOnConnected` is **not** in the snapshot — `useOnConnected` never fires during SSR since there is no DOM. **Always set page-specific state in the `loader`** — it runs before rendering begins, so both the initial SSR HTML and the hydration snapshot contain the correct value.
588
-
589
- #### Page-to-layout communication
590
-
591
- The primary use case is passing reactive metadata (title, breadcrumbs, etc.) from a page to its layout:
592
-
593
- ```ts
594
- // app/pages/about.ts
595
- // Set in the loader so it's available during SSR rendering (layout renders before page)
596
- export const loader = async () => {
597
- useState<string>('pageTitle').value = 'About Us'
598
- return {}
599
- }
600
-
601
- component('page-about', () => {
602
- const title = useState<string>('pageTitle')
603
- return html`<h1>${title.value}</h1>`
604
- })
605
- ```
606
-
607
- ```ts
608
- // app/layouts/default.ts
609
- component('layout-default', () => {
610
- // 'My App' is the fallback shown before any page sets a title
611
- const pageTitle = useState('pageTitle', 'My App')
612
-
613
- return html`
614
- <header><h1>${pageTitle.value}</h1></header>
615
- <slot></slot>
616
- `
617
- })
618
- ```
619
-
620
- On **SSR/SSG**: the loader runs first → sets the state → layout reads it synchronously → initial HTML already contains the correct title.
621
- On **client**: the page sets the state → the layout's reactive dependency fires → layout re-renders.
622
-
623
- #### Shared counter across components
624
-
625
- ```ts
626
- // app/composables/useSharedCount.ts
627
- export function useSharedCount() {
628
- const count = useState('sharedCount', 0)
629
- const increment = () => { count.value++ }
630
- return { count, increment }
631
- }
632
- ```
633
-
634
- ```ts
635
- // app/components/counter-a.ts
636
- component('counter-a', () => {
637
- const { count, increment } = useSharedCount()
638
- return html`<button @click="${increment}">Count: ${count.value}</button>`
639
- })
640
-
641
- // app/components/counter-b.ts — same key, same ref, same value
642
- component('counter-b', () => {
643
- const { count } = useSharedCount()
644
- return html`<p>Count seen by B: ${count.value}</p>`
645
- })
646
- ```
647
-
648
- Clicking the button in `counter-a` updates `counter-b` automatically.
649
-
650
- #### SSR rendering order
651
-
652
- On SSR, layouts render **before** pages (outer-to-inner vnode tree). If state is set inside a page's `component()` render function it will be too late for the layout to read it during SSR. **Always set page-specific state in the `loader`** so it is written before rendering begins.
653
-
654
- #### Hard refresh behavior
655
-
656
- `useState` is **in-memory only**. A hard browser refresh wipes `globalThis.__CER_STATE__` and starts fresh — exactly like the first page load. Whether values are available immediately after a refresh depends on where they are set:
657
-
658
- | Where value is set | Available to layout on SSR? | Available after hard refresh? |
659
- |---|---|---|
660
- | Page `loader` | ✅ | ✅ (loader re-runs) |
661
- | `init` param of `useState` | ✅ (fallback default) | ✅ (init re-evaluated) |
662
- | Component render function | ❌ (layout renders first) | ✅ (brief flash, then reactive update) |
663
- | `useOnConnected` | ❌ (never fires during SSR) | ✅ (brief flash, then reactive update) |
664
-
665
- **Values are not persisted across hard refreshes.** If a user changes a theme from `'light'` to `'dark'` and then hard-refreshes, they get `'light'` (the init) again.
666
-
667
- To persist state across refreshes, seed the init from a persistent store using the **factory form** — it is only evaluated once per session (when the key does not yet exist in the Map):
668
-
669
- ```ts
670
- export function useTheme() {
671
- const theme = useState<string>('theme', () =>
672
- (typeof localStorage !== 'undefined' ? localStorage.getItem('theme') : null) ?? 'light'
673
- )
674
-
675
- function setTheme(value: string) {
676
- theme.value = value
677
- if (typeof localStorage !== 'undefined') {
678
- localStorage.setItem('theme', value)
679
- }
680
- }
681
-
682
- return { theme, setTheme }
683
- }
684
- ```
685
-
686
- #### TypeScript
687
-
688
- ```ts
689
- const pageTitle = useState<string>('pageTitle', 'My App')
690
- // pageTitle.value is typed as string
691
- ```
692
-
693
- If you need it outside auto-imported directories:
694
-
695
- ```ts
696
- import { useState } from '@jasonshimmy/vite-plugin-cer-app/composables'
697
- ```
698
-
699
- ---
700
-
701
- ### `useLocale()`
702
-
703
- Returns locale information for the current request and helpers to build locale-aware URLs. Works isomorphically — on the server it reads the URL path via `AsyncLocalStorage`; on the client it reads `window.location.pathname`.
704
-
705
- **Requires `i18n` configuration in `cer.config.ts`.** If `i18n` is not configured, `locale` and `defaultLocale` both return `'en'` and `locales` returns `['en']`.
706
-
707
- ```ts
708
- const {
709
- locale, // 'fr' — current locale code
710
- locales, // ['en', 'fr'] — all configured locales
711
- defaultLocale, // 'en' — the default locale
712
- switchLocalePath, // function — build a URL in another locale
713
- } = useLocale()
714
- ```
715
-
716
- #### `switchLocalePath(targetLocale, path?)`
717
-
718
- Returns the equivalent path in the target locale. When `path` is omitted the current pathname is used.
719
-
720
- ```ts
721
- // On /fr/about
722
- switchLocalePath('en') // → '/about'
723
- switchLocalePath('en', '/fr/about') // → '/about'
724
- switchLocalePath('de', '/fr/about') // → '/de/about'
725
-
726
- // On /about (default locale)
727
- switchLocalePath('fr') // → '/fr/about'
728
- ```
729
-
730
- #### Locale switcher example
731
-
732
- ```ts
733
- // app/components/locale-switcher.ts
734
- component('locale-switcher', () => {
735
- const { locale, locales, switchLocalePath } = useLocale()
736
-
737
- return html`
738
- <nav>
739
- ${each(locales, (l) => html`
740
- <a
741
- :href="${switchLocalePath(l)}"
742
- :aria-current="${l === locale ? 'true' : 'false'}"
743
- >${l.toUpperCase()}</a>
744
- `)}
745
- </nav>
746
- `
747
- })
748
- ```
749
-
750
- #### `LocaleComposable`
751
-
752
- | Field | Type | Description |
753
- |---|---|---|
754
- | `locale` | `string` | Current locale code detected from the URL |
755
- | `locales` | `string[]` | All configured locale codes |
756
- | `defaultLocale` | `string` | The configured default locale |
757
- | `switchLocalePath` | `(target: string, path?: string) => string` | Returns the path rewritten for `target` locale |
758
-
759
- If you need it outside auto-imported directories:
760
-
761
- ```ts
762
- import { useLocale } from '@jasonshimmy/vite-plugin-cer-app/composables'
763
- import type { LocaleComposable } from '@jasonshimmy/vite-plugin-cer-app/types'
764
- ```
765
-
766
- See [i18n.md](./i18n.md) for full documentation including strategies, SSR/SSG behavior, and edge cases.
767
-
768
- ---
769
-
770
- ### `navigateTo(path): Promise<void>`
771
-
772
- Programmatic navigation — works isomorphically:
773
-
774
- - **Server context** (inside a loader or middleware): sends a `302` redirect immediately via the request's response object.
775
- - **Client context**: delegates to the framework router so the full navigation pipeline (middleware, loaders, loading indicator) runs normally.
776
-
777
- ```ts
778
- // app/middleware/require-auth.ts
779
- export default defineMiddleware(() => {
780
- const { loggedIn } = useAuth()
781
- if (!loggedIn) return navigateTo('/login')
782
- })
783
- ```
784
-
785
- ```ts
786
- // app/pages/dashboard.ts — navigate programmatically on a button click
787
- component('page-dashboard', () => {
788
- return html`
789
- <button @click="${() => navigateTo('/settings')}">
790
- Open Settings
791
- </button>
792
- `
793
- })
794
- ```
795
-
796
- If you need it outside auto-imported directories:
797
-
798
- ```ts
799
- import { navigateTo } from '@jasonshimmy/vite-plugin-cer-app/composables'
800
- ```
801
-
802
- ---
803
-
804
- ### `queryContent(path?)`
805
-
806
- Queries content items from the file-based content layer. Returns a `QueryBuilder` that can be filtered, sorted, paginated, and terminated with `.find()`, `.first()`, or `.count()`.
807
-
808
- Requires `content: {}` in `cer.config.ts`. See [content.md](./content.md) for full configuration, type reference, and rendering-mode behavior.
809
-
810
- ```ts
811
- // All items
812
- const all = await queryContent().find()
813
-
814
- // Blog posts only (path prefix)
815
- const posts = await queryContent('/blog').sortBy('date', 'desc').find()
816
-
817
- // Single full document (includes body + TOC)
818
- const doc = await queryContent('/docs/getting-started').first()
819
-
820
- // Count
821
- const total = await queryContent().count()
822
- ```
823
-
824
- **QueryBuilder methods:**
825
-
826
- | Method | Returns | Description |
827
- |---|---|---|
828
- | `.where(predicate)` | `QueryBuilder` | Predicate function — `(doc: ContentMeta) => boolean`. |
829
- | `.sortBy(field, order?)` | `QueryBuilder` | Sort ascending (`'asc'`) or descending (`'desc'`). |
830
- | `.limit(n)` | `QueryBuilder` | Return at most `n` items. |
831
- | `.skip(n)` | `QueryBuilder` | Skip the first `n` items (pagination). |
832
- | `.find()` | `Promise<ContentMeta[]>` | Execute, return lean metadata array. |
833
- | `.first()` | `Promise<ContentItem \| null>` | Execute, return first full document with body and TOC. |
834
- | `.count()` | `Promise<number>` | Execute, return count only. |
835
-
836
- **Usage with a page loader:**
837
-
838
- ```ts
839
- component('page-blog', () => {
840
- const ssrData = usePageData<{ posts: ContentMeta[] }>()
841
- const posts = ref<ContentMeta[]>(ssrData?.posts ?? [])
842
-
843
- useOnConnected(async () => {
844
- if (ssrData) return // hydrated from loader
845
- posts.value = await queryContent('/blog').find()
846
- })
847
-
848
- return html`
849
- <ul>
850
- ${each(posts.value, p => html`<li><a :href="${p._path}">${p.title}</a></li>`)}
851
- </ul>
852
- `
853
- })
854
-
855
- export const loader = async () => {
856
- const posts = await queryContent('/blog').find()
857
- return { posts }
858
- }
859
- ```
860
-
861
- If you need it outside auto-imported directories:
862
-
863
- ```ts
864
- import { queryContent } from '@jasonshimmy/vite-plugin-cer-app/composables'
865
- ```
866
-
867
- ---
868
-
869
- ### `useContentSearch()`
870
-
871
- Reactive full-text search over the content layer. Loads the MiniSearch index lazily on first use. Returns `query`, `results`, and `loading` refs that update reactively as the user types.
872
-
873
- Requires `content: {}` in `cer.config.ts`. See [content.md](./content.md) for full documentation.
874
-
875
- ```ts
876
- component('page-search', () => {
877
- const { query, results, loading } = useContentSearch()
878
-
879
- return html`
880
- <input type="search" :model="${query}" placeholder="Search…" />
881
- ${loading.value ? html`<p>Searching…</p>` : ''}
882
- <ul>
883
- ${each(results.value, r => html`
884
- <li><a :href="${r._path}">${r.title}</a></li>
885
- `)}
886
- </ul>
887
- `
888
- })
889
- ```
890
-
891
- **Return value:**
892
-
893
- ```ts
894
- interface UseContentSearchReturn {
895
- query: Ref<string> // bind with :model
896
- results: Ref<ContentSearchResult[]> // reactive search results
897
- loading: Ref<boolean> // true while debounce is pending or search is in flight
898
- }
899
- ```
900
-
901
- Search is debounced (200 ms) so the index is not queried on every keystroke. `loading` is set to `true` as soon as the user starts typing and returns to `false` once results arrive or the query is cleared. An empty query clears results immediately and cancels any in-flight search. MiniSearch is loaded once and cached for the lifetime of the page. Searched fields are `title` and `description`.
902
-
903
- If you need it outside auto-imported directories:
904
-
905
- ```ts
906
- import { useContentSearch } from '@jasonshimmy/vite-plugin-cer-app/composables'
907
- ```