@jasonshimmy/vite-plugin-cer-app 0.1.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 (299) hide show
  1. package/.github/copilot-instructions.md +130 -0
  2. package/.github/workflows/publish.yml +206 -0
  3. package/.nvmrc +1 -0
  4. package/CHANGELOG.md +10 -0
  5. package/IMPLEMENTATION_PLAN.md +391 -0
  6. package/README.md +231 -0
  7. package/VITE_PLUGIN_FRAMEWORK_PLAN.md +594 -0
  8. package/commits.txt +3 -0
  9. package/dist/__tests__/plugin/path-utils.test.d.ts +2 -0
  10. package/dist/__tests__/plugin/path-utils.test.d.ts.map +1 -0
  11. package/dist/__tests__/plugin/path-utils.test.js +305 -0
  12. package/dist/__tests__/plugin/path-utils.test.js.map +1 -0
  13. package/dist/__tests__/plugin/scanner.test.d.ts +2 -0
  14. package/dist/__tests__/plugin/scanner.test.d.ts.map +1 -0
  15. package/dist/__tests__/plugin/scanner.test.js +143 -0
  16. package/dist/__tests__/plugin/scanner.test.js.map +1 -0
  17. package/dist/__tests__/plugin/transforms/auto-import.test.d.ts +2 -0
  18. package/dist/__tests__/plugin/transforms/auto-import.test.d.ts.map +1 -0
  19. package/dist/__tests__/plugin/transforms/auto-import.test.js +151 -0
  20. package/dist/__tests__/plugin/transforms/auto-import.test.js.map +1 -0
  21. package/dist/__tests__/plugin/transforms/head-inject.test.d.ts +2 -0
  22. package/dist/__tests__/plugin/transforms/head-inject.test.d.ts.map +1 -0
  23. package/dist/__tests__/plugin/transforms/head-inject.test.js +151 -0
  24. package/dist/__tests__/plugin/transforms/head-inject.test.js.map +1 -0
  25. package/dist/__tests__/plugin/virtual/components.test.d.ts +2 -0
  26. package/dist/__tests__/plugin/virtual/components.test.d.ts.map +1 -0
  27. package/dist/__tests__/plugin/virtual/components.test.js +47 -0
  28. package/dist/__tests__/plugin/virtual/components.test.js.map +1 -0
  29. package/dist/__tests__/plugin/virtual/composables.test.d.ts +2 -0
  30. package/dist/__tests__/plugin/virtual/composables.test.d.ts.map +1 -0
  31. package/dist/__tests__/plugin/virtual/composables.test.js +48 -0
  32. package/dist/__tests__/plugin/virtual/composables.test.js.map +1 -0
  33. package/dist/__tests__/plugin/virtual/layouts.test.d.ts +2 -0
  34. package/dist/__tests__/plugin/virtual/layouts.test.d.ts.map +1 -0
  35. package/dist/__tests__/plugin/virtual/layouts.test.js +59 -0
  36. package/dist/__tests__/plugin/virtual/layouts.test.js.map +1 -0
  37. package/dist/__tests__/plugin/virtual/middleware.test.d.ts +2 -0
  38. package/dist/__tests__/plugin/virtual/middleware.test.d.ts.map +1 -0
  39. package/dist/__tests__/plugin/virtual/middleware.test.js +58 -0
  40. package/dist/__tests__/plugin/virtual/middleware.test.js.map +1 -0
  41. package/dist/__tests__/plugin/virtual/plugins.test.d.ts +2 -0
  42. package/dist/__tests__/plugin/virtual/plugins.test.d.ts.map +1 -0
  43. package/dist/__tests__/plugin/virtual/plugins.test.js +73 -0
  44. package/dist/__tests__/plugin/virtual/plugins.test.js.map +1 -0
  45. package/dist/__tests__/plugin/virtual/routes.test.d.ts +2 -0
  46. package/dist/__tests__/plugin/virtual/routes.test.d.ts.map +1 -0
  47. package/dist/__tests__/plugin/virtual/routes.test.js +167 -0
  48. package/dist/__tests__/plugin/virtual/routes.test.js.map +1 -0
  49. package/dist/__tests__/plugin/virtual/server-api.test.d.ts +2 -0
  50. package/dist/__tests__/plugin/virtual/server-api.test.d.ts.map +1 -0
  51. package/dist/__tests__/plugin/virtual/server-api.test.js +72 -0
  52. package/dist/__tests__/plugin/virtual/server-api.test.js.map +1 -0
  53. package/dist/__tests__/runtime/use-head.test.d.ts +2 -0
  54. package/dist/__tests__/runtime/use-head.test.d.ts.map +1 -0
  55. package/dist/__tests__/runtime/use-head.test.js +202 -0
  56. package/dist/__tests__/runtime/use-head.test.js.map +1 -0
  57. package/dist/__tests__/runtime/use-page-data.test.d.ts +2 -0
  58. package/dist/__tests__/runtime/use-page-data.test.d.ts.map +1 -0
  59. package/dist/__tests__/runtime/use-page-data.test.js +41 -0
  60. package/dist/__tests__/runtime/use-page-data.test.js.map +1 -0
  61. package/dist/cli/commands/build.d.ts +3 -0
  62. package/dist/cli/commands/build.d.ts.map +1 -0
  63. package/dist/cli/commands/build.js +103 -0
  64. package/dist/cli/commands/build.js.map +1 -0
  65. package/dist/cli/commands/dev.d.ts +3 -0
  66. package/dist/cli/commands/dev.d.ts.map +1 -0
  67. package/dist/cli/commands/dev.js +92 -0
  68. package/dist/cli/commands/dev.js.map +1 -0
  69. package/dist/cli/commands/generate.d.ts +7 -0
  70. package/dist/cli/commands/generate.d.ts.map +1 -0
  71. package/dist/cli/commands/generate.js +72 -0
  72. package/dist/cli/commands/generate.js.map +1 -0
  73. package/dist/cli/commands/preview.d.ts +3 -0
  74. package/dist/cli/commands/preview.d.ts.map +1 -0
  75. package/dist/cli/commands/preview.js +191 -0
  76. package/dist/cli/commands/preview.js.map +1 -0
  77. package/dist/cli/create/index.d.ts +3 -0
  78. package/dist/cli/create/index.d.ts.map +1 -0
  79. package/dist/cli/create/index.js +184 -0
  80. package/dist/cli/create/index.js.map +1 -0
  81. package/dist/cli/index.d.ts +3 -0
  82. package/dist/cli/index.d.ts.map +1 -0
  83. package/dist/cli/index.js +17 -0
  84. package/dist/cli/index.js.map +1 -0
  85. package/dist/index.d.ts +9 -0
  86. package/dist/index.d.ts.map +1 -0
  87. package/dist/index.js +4 -0
  88. package/dist/index.js.map +1 -0
  89. package/dist/plugin/build-ssg.d.ts +12 -0
  90. package/dist/plugin/build-ssg.d.ts.map +1 -0
  91. package/dist/plugin/build-ssg.js +212 -0
  92. package/dist/plugin/build-ssg.js.map +1 -0
  93. package/dist/plugin/build-ssr.d.ts +10 -0
  94. package/dist/plugin/build-ssr.d.ts.map +1 -0
  95. package/dist/plugin/build-ssr.js +139 -0
  96. package/dist/plugin/build-ssr.js.map +1 -0
  97. package/dist/plugin/dev-server.d.ts +46 -0
  98. package/dist/plugin/dev-server.d.ts.map +1 -0
  99. package/dist/plugin/dev-server.js +194 -0
  100. package/dist/plugin/dev-server.js.map +1 -0
  101. package/dist/plugin/dts-generator.d.ts +27 -0
  102. package/dist/plugin/dts-generator.d.ts.map +1 -0
  103. package/dist/plugin/dts-generator.js +180 -0
  104. package/dist/plugin/dts-generator.js.map +1 -0
  105. package/dist/plugin/index.d.ts +13 -0
  106. package/dist/plugin/index.d.ts.map +1 -0
  107. package/dist/plugin/index.js +298 -0
  108. package/dist/plugin/index.js.map +1 -0
  109. package/dist/plugin/path-utils.d.ts +57 -0
  110. package/dist/plugin/path-utils.d.ts.map +1 -0
  111. package/dist/plugin/path-utils.js +160 -0
  112. package/dist/plugin/path-utils.js.map +1 -0
  113. package/dist/plugin/scanner.d.ts +17 -0
  114. package/dist/plugin/scanner.d.ts.map +1 -0
  115. package/dist/plugin/scanner.js +54 -0
  116. package/dist/plugin/scanner.js.map +1 -0
  117. package/dist/plugin/transforms/auto-import.d.ts +14 -0
  118. package/dist/plugin/transforms/auto-import.d.ts.map +1 -0
  119. package/dist/plugin/transforms/auto-import.js +154 -0
  120. package/dist/plugin/transforms/auto-import.js.map +1 -0
  121. package/dist/plugin/transforms/head-inject.d.ts +29 -0
  122. package/dist/plugin/transforms/head-inject.d.ts.map +1 -0
  123. package/dist/plugin/transforms/head-inject.js +127 -0
  124. package/dist/plugin/transforms/head-inject.js.map +1 -0
  125. package/dist/plugin/virtual/components.d.ts +6 -0
  126. package/dist/plugin/virtual/components.d.ts.map +1 -0
  127. package/dist/plugin/virtual/components.js +22 -0
  128. package/dist/plugin/virtual/components.js.map +1 -0
  129. package/dist/plugin/virtual/composables.d.ts +6 -0
  130. package/dist/plugin/virtual/composables.d.ts.map +1 -0
  131. package/dist/plugin/virtual/composables.js +22 -0
  132. package/dist/plugin/virtual/composables.js.map +1 -0
  133. package/dist/plugin/virtual/error.d.ts +13 -0
  134. package/dist/plugin/virtual/error.d.ts.map +1 -0
  135. package/dist/plugin/virtual/error.js +32 -0
  136. package/dist/plugin/virtual/error.js.map +1 -0
  137. package/dist/plugin/virtual/layouts.d.ts +6 -0
  138. package/dist/plugin/virtual/layouts.d.ts.map +1 -0
  139. package/dist/plugin/virtual/layouts.js +33 -0
  140. package/dist/plugin/virtual/layouts.js.map +1 -0
  141. package/dist/plugin/virtual/loading.d.ts +11 -0
  142. package/dist/plugin/virtual/loading.d.ts.map +1 -0
  143. package/dist/plugin/virtual/loading.js +30 -0
  144. package/dist/plugin/virtual/loading.js.map +1 -0
  145. package/dist/plugin/virtual/middleware.d.ts +6 -0
  146. package/dist/plugin/virtual/middleware.d.ts.map +1 -0
  147. package/dist/plugin/virtual/middleware.js +36 -0
  148. package/dist/plugin/virtual/middleware.js.map +1 -0
  149. package/dist/plugin/virtual/plugins.d.ts +6 -0
  150. package/dist/plugin/virtual/plugins.d.ts.map +1 -0
  151. package/dist/plugin/virtual/plugins.js +30 -0
  152. package/dist/plugin/virtual/plugins.js.map +1 -0
  153. package/dist/plugin/virtual/routes.d.ts +16 -0
  154. package/dist/plugin/virtual/routes.d.ts.map +1 -0
  155. package/dist/plugin/virtual/routes.js +131 -0
  156. package/dist/plugin/virtual/routes.js.map +1 -0
  157. package/dist/plugin/virtual/server-api.d.ts +6 -0
  158. package/dist/plugin/virtual/server-api.d.ts.map +1 -0
  159. package/dist/plugin/virtual/server-api.js +41 -0
  160. package/dist/plugin/virtual/server-api.js.map +1 -0
  161. package/dist/plugin/virtual/server-middleware.d.ts +6 -0
  162. package/dist/plugin/virtual/server-middleware.d.ts.map +1 -0
  163. package/dist/plugin/virtual/server-middleware.js +36 -0
  164. package/dist/plugin/virtual/server-middleware.js.map +1 -0
  165. package/dist/runtime/app-template.d.ts +10 -0
  166. package/dist/runtime/app-template.d.ts.map +1 -0
  167. package/dist/runtime/app-template.js +143 -0
  168. package/dist/runtime/app-template.js.map +1 -0
  169. package/dist/runtime/composables/index.d.ts +4 -0
  170. package/dist/runtime/composables/index.d.ts.map +1 -0
  171. package/dist/runtime/composables/index.js +3 -0
  172. package/dist/runtime/composables/index.js.map +1 -0
  173. package/dist/runtime/composables/use-head.d.ts +30 -0
  174. package/dist/runtime/composables/use-head.d.ts.map +1 -0
  175. package/dist/runtime/composables/use-head.js +182 -0
  176. package/dist/runtime/composables/use-head.js.map +1 -0
  177. package/dist/runtime/composables/use-page-data.d.ts +32 -0
  178. package/dist/runtime/composables/use-page-data.d.ts.map +1 -0
  179. package/dist/runtime/composables/use-page-data.js +41 -0
  180. package/dist/runtime/composables/use-page-data.js.map +1 -0
  181. package/dist/runtime/entry-client-template.d.ts +8 -0
  182. package/dist/runtime/entry-client-template.d.ts.map +1 -0
  183. package/dist/runtime/entry-client-template.js +18 -0
  184. package/dist/runtime/entry-client-template.js.map +1 -0
  185. package/dist/runtime/entry-server-template.d.ts +9 -0
  186. package/dist/runtime/entry-server-template.d.ts.map +1 -0
  187. package/dist/runtime/entry-server-template.js +72 -0
  188. package/dist/runtime/entry-server-template.js.map +1 -0
  189. package/dist/types/api.d.ts +16 -0
  190. package/dist/types/api.d.ts.map +1 -0
  191. package/dist/types/api.js +2 -0
  192. package/dist/types/api.js.map +1 -0
  193. package/dist/types/config.d.ts +32 -0
  194. package/dist/types/config.d.ts.map +1 -0
  195. package/dist/types/config.js +4 -0
  196. package/dist/types/config.js.map +1 -0
  197. package/dist/types/index.d.ts +7 -0
  198. package/dist/types/index.d.ts.map +1 -0
  199. package/dist/types/index.js +2 -0
  200. package/dist/types/index.js.map +1 -0
  201. package/dist/types/middleware.d.ts +6 -0
  202. package/dist/types/middleware.d.ts.map +1 -0
  203. package/dist/types/middleware.js +2 -0
  204. package/dist/types/middleware.js.map +1 -0
  205. package/dist/types/page.d.ts +21 -0
  206. package/dist/types/page.d.ts.map +1 -0
  207. package/dist/types/page.js +2 -0
  208. package/dist/types/page.js.map +1 -0
  209. package/dist/types/plugin.d.ts +12 -0
  210. package/dist/types/plugin.d.ts.map +1 -0
  211. package/dist/types/plugin.js +2 -0
  212. package/dist/types/plugin.js.map +1 -0
  213. package/docs/cli.md +233 -0
  214. package/docs/components.md +114 -0
  215. package/docs/composables.md +99 -0
  216. package/docs/configuration.md +270 -0
  217. package/docs/data-loading.md +165 -0
  218. package/docs/getting-started.md +235 -0
  219. package/docs/head-management.md +206 -0
  220. package/docs/layouts.md +112 -0
  221. package/docs/middleware.md +140 -0
  222. package/docs/plugins.md +138 -0
  223. package/docs/rendering-modes.md +251 -0
  224. package/docs/routing.md +180 -0
  225. package/docs/server-api.md +172 -0
  226. package/docs/testing.md +462 -0
  227. package/package.json +75 -0
  228. package/src/__tests__/plugin/path-utils.test.ts +399 -0
  229. package/src/__tests__/plugin/scanner.test.ts +172 -0
  230. package/src/__tests__/plugin/transforms/auto-import.test.ts +229 -0
  231. package/src/__tests__/plugin/transforms/head-inject.test.ts +178 -0
  232. package/src/__tests__/plugin/virtual/components.test.ts +56 -0
  233. package/src/__tests__/plugin/virtual/composables.test.ts +57 -0
  234. package/src/__tests__/plugin/virtual/layouts.test.ts +70 -0
  235. package/src/__tests__/plugin/virtual/middleware.test.ts +68 -0
  236. package/src/__tests__/plugin/virtual/plugins.test.ts +84 -0
  237. package/src/__tests__/plugin/virtual/routes.test.ts +202 -0
  238. package/src/__tests__/plugin/virtual/server-api.test.ts +85 -0
  239. package/src/__tests__/runtime/use-head.test.ts +243 -0
  240. package/src/__tests__/runtime/use-page-data.test.ts +45 -0
  241. package/src/cli/commands/build.ts +114 -0
  242. package/src/cli/commands/dev.ts +101 -0
  243. package/src/cli/commands/generate.ts +81 -0
  244. package/src/cli/commands/preview.ts +218 -0
  245. package/src/cli/create/index.ts +250 -0
  246. package/src/cli/create/templates/spa/app/app.ts.tpl +74 -0
  247. package/src/cli/create/templates/spa/app/layouts/default.ts.tpl +15 -0
  248. package/src/cli/create/templates/spa/app/pages/index.ts.tpl +8 -0
  249. package/src/cli/create/templates/spa/cer.config.ts.tpl +6 -0
  250. package/src/cli/create/templates/spa/index.html.tpl +12 -0
  251. package/src/cli/create/templates/spa/package.json.tpl +18 -0
  252. package/src/cli/create/templates/ssg/app/app.ts.tpl +74 -0
  253. package/src/cli/create/templates/ssg/app/layouts/default.ts.tpl +15 -0
  254. package/src/cli/create/templates/ssg/app/pages/index.ts.tpl +17 -0
  255. package/src/cli/create/templates/ssg/cer.config.ts.tpl +13 -0
  256. package/src/cli/create/templates/ssg/index.html.tpl +12 -0
  257. package/src/cli/create/templates/ssg/package.json.tpl +19 -0
  258. package/src/cli/create/templates/ssr/app/app.ts.tpl +74 -0
  259. package/src/cli/create/templates/ssr/app/layouts/default.ts.tpl +15 -0
  260. package/src/cli/create/templates/ssr/app/pages/index.ts.tpl +8 -0
  261. package/src/cli/create/templates/ssr/cer.config.ts.tpl +10 -0
  262. package/src/cli/create/templates/ssr/index.html.tpl +12 -0
  263. package/src/cli/create/templates/ssr/package.json.tpl +18 -0
  264. package/src/cli/index.ts +20 -0
  265. package/src/index.ts +13 -0
  266. package/src/plugin/build-ssg.ts +259 -0
  267. package/src/plugin/build-ssr.ts +147 -0
  268. package/src/plugin/dev-server.ts +266 -0
  269. package/src/plugin/dts-generator.ts +214 -0
  270. package/src/plugin/index.ts +330 -0
  271. package/src/plugin/path-utils.ts +186 -0
  272. package/src/plugin/scanner.ts +65 -0
  273. package/src/plugin/transforms/auto-import.ts +190 -0
  274. package/src/plugin/transforms/head-inject.ts +161 -0
  275. package/src/plugin/virtual/components.ts +28 -0
  276. package/src/plugin/virtual/composables.ts +28 -0
  277. package/src/plugin/virtual/error.ts +34 -0
  278. package/src/plugin/virtual/layouts.ts +41 -0
  279. package/src/plugin/virtual/loading.ts +33 -0
  280. package/src/plugin/virtual/middleware.ts +45 -0
  281. package/src/plugin/virtual/plugins.ts +36 -0
  282. package/src/plugin/virtual/routes.ts +147 -0
  283. package/src/plugin/virtual/server-api.ts +52 -0
  284. package/src/plugin/virtual/server-middleware.ts +44 -0
  285. package/src/runtime/app-template.ts +142 -0
  286. package/src/runtime/composables/index.ts +3 -0
  287. package/src/runtime/composables/use-head.ts +204 -0
  288. package/src/runtime/composables/use-page-data.ts +39 -0
  289. package/src/runtime/entry-client-template.ts +17 -0
  290. package/src/runtime/entry-server-template.ts +71 -0
  291. package/src/types/api.ts +19 -0
  292. package/src/types/config.ts +39 -0
  293. package/src/types/index.ts +6 -0
  294. package/src/types/middleware.ts +16 -0
  295. package/src/types/page.ts +29 -0
  296. package/src/types/plugin.ts +13 -0
  297. package/tsconfig.build.json +10 -0
  298. package/tsconfig.json +19 -0
  299. package/vitest.config.ts +29 -0
@@ -0,0 +1,330 @@
1
+ import { resolve, join } from 'pathe'
2
+ import type { Plugin, ViteDevServer, ModuleNode } from 'vite'
3
+ import type { CerAppConfig } from '../types/config.js'
4
+ import type { ResolvedCerConfig } from './dev-server.js'
5
+ import { cerPlugin } from '@jasonshimmy/custom-elements-runtime/vite-plugin'
6
+ import { autoImportTransform } from './transforms/auto-import.js'
7
+ import { scanComposableExports, writeAutoImportDts, writeTsconfigPaths } from './dts-generator.js'
8
+ import { configureCerDevServer } from './dev-server.js'
9
+ import { generateRoutesCode } from './virtual/routes.js'
10
+ import { generateLayoutsCode } from './virtual/layouts.js'
11
+ import { generateComponentsCode } from './virtual/components.js'
12
+ import { generateComposablesCode } from './virtual/composables.js'
13
+ import { generatePluginsCode } from './virtual/plugins.js'
14
+ import { generateMiddlewareCode } from './virtual/middleware.js'
15
+ import { generateServerApiCode } from './virtual/server-api.js'
16
+ import { generateServerMiddlewareCode } from './virtual/server-middleware.js'
17
+ import { generateLoadingCode } from './virtual/loading.js'
18
+ import { generateErrorCode } from './virtual/error.js'
19
+ import { createWatcher } from './scanner.js'
20
+
21
+ // Virtual module IDs (raw)
22
+ const VIRTUAL_IDS = {
23
+ routes: 'virtual:cer-routes',
24
+ layouts: 'virtual:cer-layouts',
25
+ components: 'virtual:cer-components',
26
+ composables: 'virtual:cer-composables',
27
+ plugins: 'virtual:cer-plugins',
28
+ middleware: 'virtual:cer-middleware',
29
+ serverApi: 'virtual:cer-server-api',
30
+ serverMiddleware: 'virtual:cer-server-middleware',
31
+ appConfig: 'virtual:cer-app-config',
32
+ loading: 'virtual:cer-loading',
33
+ error: 'virtual:cer-error',
34
+ } as const
35
+
36
+ // Resolved virtual module IDs (prefixed with \0)
37
+ const RESOLVED_IDS = Object.fromEntries(
38
+ Object.entries(VIRTUAL_IDS).map(([k, v]) => [k, `\0${v}`]),
39
+ ) as Record<keyof typeof VIRTUAL_IDS, string>
40
+
41
+ /**
42
+ * Fills in default values for all config fields and resolves absolute paths.
43
+ */
44
+ export function resolveConfig(userConfig: CerAppConfig, root: string = process.cwd()): ResolvedCerConfig {
45
+ const mode = userConfig.mode ?? 'spa'
46
+ const srcDir = resolve(root, userConfig.srcDir ?? 'app')
47
+
48
+ return {
49
+ mode,
50
+ srcDir,
51
+ root,
52
+ pagesDir: join(srcDir, 'pages'),
53
+ layoutsDir: join(srcDir, 'layouts'),
54
+ componentsDir: join(srcDir, 'components'),
55
+ composablesDir: join(srcDir, 'composables'),
56
+ pluginsDir: join(srcDir, 'plugins'),
57
+ middlewareDir: join(srcDir, 'middleware'),
58
+ serverApiDir: join(root, 'server/api'),
59
+ serverMiddlewareDir: join(root, 'server/middleware'),
60
+ port: userConfig.port ?? 3000,
61
+ ssr: {
62
+ dsd: userConfig.ssr?.dsd ?? true,
63
+ streaming: userConfig.ssr?.streaming ?? false,
64
+ },
65
+ ssg: {
66
+ routes: userConfig.ssg?.routes ?? 'auto',
67
+ concurrency: userConfig.ssg?.concurrency ?? 4,
68
+ fallback: userConfig.ssg?.fallback ?? false,
69
+ },
70
+ router: {
71
+ base: userConfig.router?.base,
72
+ scrollToFragment: userConfig.router?.scrollToFragment,
73
+ },
74
+ jitCss: {
75
+ content: userConfig.jitCss?.content ?? [
76
+ `${srcDir}/pages/**/*.ts`,
77
+ `${srcDir}/components/**/*.ts`,
78
+ `${srcDir}/layouts/**/*.ts`,
79
+ ],
80
+ extendedColors: userConfig.jitCss?.extendedColors ?? false,
81
+ },
82
+ autoImports: {
83
+ components: userConfig.autoImports?.components ?? true,
84
+ composables: userConfig.autoImports?.composables ?? true,
85
+ directives: userConfig.autoImports?.directives ?? true,
86
+ runtime: userConfig.autoImports?.runtime ?? true,
87
+ },
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Maps a resolved virtual ID to the appropriate generator function.
93
+ */
94
+ async function generateVirtualModule(
95
+ id: string,
96
+ config: ResolvedCerConfig,
97
+ ): Promise<string | null> {
98
+ switch (id) {
99
+ case RESOLVED_IDS.routes:
100
+ return generateRoutesCode(config.pagesDir)
101
+ case RESOLVED_IDS.layouts:
102
+ return generateLayoutsCode(config.layoutsDir)
103
+ case RESOLVED_IDS.components:
104
+ return generateComponentsCode(config.componentsDir)
105
+ case RESOLVED_IDS.composables:
106
+ return generateComposablesCode(config.composablesDir)
107
+ case RESOLVED_IDS.plugins:
108
+ return generatePluginsCode(config.pluginsDir)
109
+ case RESOLVED_IDS.middleware:
110
+ return generateMiddlewareCode(config.middlewareDir)
111
+ case RESOLVED_IDS.serverApi:
112
+ return generateServerApiCode(config.serverApiDir)
113
+ case RESOLVED_IDS.serverMiddleware:
114
+ return generateServerMiddlewareCode(config.serverMiddlewareDir)
115
+ case RESOLVED_IDS.appConfig:
116
+ return generateAppConfigModule(config)
117
+ case RESOLVED_IDS.loading:
118
+ return generateLoadingCode(config.srcDir)
119
+ case RESOLVED_IDS.error:
120
+ return generateErrorCode(config.srcDir)
121
+ default:
122
+ return null
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Generates a virtual module that exports the resolved app config.
128
+ */
129
+ function generateAppConfigModule(config: ResolvedCerConfig): string {
130
+ const exportedConfig = {
131
+ mode: config.mode,
132
+ router: config.router,
133
+ ssr: config.ssr,
134
+ ssg: config.ssg,
135
+ }
136
+ return `// AUTO-GENERATED by vite-plugin-cer-app\nexport const appConfig = ${JSON.stringify(exportedConfig, null, 2)}\nexport default appConfig\n`
137
+ }
138
+
139
+ /**
140
+ * Determines which virtual module IDs should be invalidated when a file changes
141
+ * in a given directory.
142
+ */
143
+ function getDirtyVirtualIds(filePath: string, config: ResolvedCerConfig): string[] {
144
+ const dirty: string[] = []
145
+
146
+ if (filePath.startsWith(config.pagesDir)) {
147
+ dirty.push(RESOLVED_IDS.routes)
148
+ }
149
+ if (filePath.startsWith(config.layoutsDir)) {
150
+ dirty.push(RESOLVED_IDS.layouts)
151
+ }
152
+ if (filePath.startsWith(config.componentsDir)) {
153
+ dirty.push(RESOLVED_IDS.components)
154
+ }
155
+ if (filePath.startsWith(config.composablesDir)) {
156
+ dirty.push(RESOLVED_IDS.composables)
157
+ }
158
+ if (filePath.startsWith(config.pluginsDir)) {
159
+ dirty.push(RESOLVED_IDS.plugins)
160
+ }
161
+ if (filePath.startsWith(config.middlewareDir)) {
162
+ dirty.push(RESOLVED_IDS.middleware)
163
+ }
164
+ if (filePath.startsWith(config.serverApiDir)) {
165
+ dirty.push(RESOLVED_IDS.serverApi)
166
+ }
167
+ if (filePath.startsWith(config.serverMiddlewareDir)) {
168
+ dirty.push(RESOLVED_IDS.serverMiddleware)
169
+ }
170
+
171
+ return dirty
172
+ }
173
+
174
+ /**
175
+ * The main cerApp() Vite plugin factory.
176
+ * Returns an array of plugins: the cer-app orchestrator + the runtime JIT CSS plugin(s).
177
+ */
178
+ export function cerApp(userConfig: CerAppConfig = {}): Plugin[] {
179
+ let config: ResolvedCerConfig
180
+ let composableExports = new Map<string, string>()
181
+
182
+ // Cache for generated virtual module code (invalidated on file changes)
183
+ const moduleCache = new Map<string, string>()
184
+
185
+ const cerAppPlugin: Plugin = {
186
+ name: 'vite-plugin-cer-app',
187
+
188
+ config(viteConfig) {
189
+ const root = viteConfig.root ? resolve(viteConfig.root) : process.cwd()
190
+ config = resolveConfig(userConfig, root)
191
+ return {
192
+ build: {
193
+ target: 'esnext',
194
+ rollupOptions: {
195
+ onwarn(warning, warn) {
196
+ // loader and meta are optional exports from page files — suppress noise
197
+ if (
198
+ warning.code === 'MISSING_EXPORT' &&
199
+ (warning.binding === 'loader' || warning.binding === 'meta')
200
+ ) {
201
+ return
202
+ }
203
+ warn(warning)
204
+ },
205
+ },
206
+ },
207
+ }
208
+ },
209
+
210
+ configResolved(resolvedConfig) {
211
+ // Re-resolve with the final root
212
+ config = resolveConfig(userConfig, resolvedConfig.root)
213
+ },
214
+
215
+ resolveId(id: string) {
216
+ if ((Object.values(VIRTUAL_IDS) as string[]).includes(id)) {
217
+ return `\0${id}`
218
+ }
219
+ },
220
+
221
+ async load(id: string) {
222
+ const allResolved = Object.values(RESOLVED_IDS) as string[]
223
+ if (!allResolved.includes(id)) return null
224
+
225
+ // Return from cache if available
226
+ if (moduleCache.has(id)) {
227
+ return moduleCache.get(id)!
228
+ }
229
+
230
+ // Generate and cache
231
+ const code = await generateVirtualModule(id, config)
232
+ if (code !== null) {
233
+ moduleCache.set(id, code)
234
+ return code
235
+ }
236
+
237
+ return null
238
+ },
239
+
240
+ transform(code: string, id: string) {
241
+ if (!config) return null
242
+ if (config.autoImports?.runtime === false) return null
243
+ // Skip virtual modules
244
+ if (id.startsWith('\0')) return null
245
+
246
+ const result = autoImportTransform(code, id, {
247
+ srcDir: config.srcDir,
248
+ composableExports: config.autoImports?.composables !== false ? composableExports : undefined,
249
+ })
250
+ if (result === null) return null
251
+ return { code: result, map: null }
252
+ },
253
+
254
+ async configureServer(server: ViteDevServer) {
255
+ if (!config) {
256
+ // config might not be set yet; resolve with cwd
257
+ config = resolveConfig(userConfig, process.cwd())
258
+ }
259
+ // Scan composables and write .d.ts + tsconfig paths on dev server start
260
+ composableExports = await scanComposableExports(config.composablesDir)
261
+ await writeAutoImportDts(config.root, config.composablesDir, composableExports)
262
+ writeTsconfigPaths(config.root, config.srcDir)
263
+
264
+ // Watch app/ and server/ directories for file changes
265
+ const watchDirs = [
266
+ config.pagesDir,
267
+ config.layoutsDir,
268
+ config.componentsDir,
269
+ config.composablesDir,
270
+ config.pluginsDir,
271
+ config.middlewareDir,
272
+ config.serverApiDir,
273
+ config.serverMiddlewareDir,
274
+ ]
275
+
276
+ createWatcher(server.watcher, watchDirs, async (event, file) => {
277
+ if (event === 'add' || event === 'unlink') {
278
+ // Re-scan composables and regenerate .d.ts if a composable changed
279
+ if (file.startsWith(config.composablesDir)) {
280
+ composableExports = await scanComposableExports(config.composablesDir)
281
+ await writeAutoImportDts(config.root, config.composablesDir, composableExports)
282
+ }
283
+ // Invalidate relevant virtual modules
284
+ const dirtyIds = getDirtyVirtualIds(file, config)
285
+ for (const resolvedId of dirtyIds) {
286
+ moduleCache.delete(resolvedId)
287
+ const mod = server.moduleGraph.getModuleById(resolvedId)
288
+ if (mod) {
289
+ server.moduleGraph.invalidateModule(mod)
290
+ }
291
+ }
292
+ // Trigger HMR
293
+ server.ws.send({ type: 'full-reload' })
294
+ }
295
+ })
296
+
297
+ // Register dev server middleware for API routes + SSR
298
+ configureCerDevServer(server, config)
299
+ },
300
+
301
+ async buildStart() {
302
+ if (!config) {
303
+ config = resolveConfig(userConfig, process.cwd())
304
+ }
305
+ // Scan composables and generate type declarations + tsconfig paths
306
+ composableExports = await scanComposableExports(config.composablesDir)
307
+ await writeAutoImportDts(config.root, config.composablesDir, composableExports)
308
+ writeTsconfigPaths(config.root, config.srcDir)
309
+ // Warm the virtual module cache
310
+ for (const resolvedId of Object.values(RESOLVED_IDS)) {
311
+ const code = await generateVirtualModule(resolvedId, config)
312
+ if (code !== null) {
313
+ moduleCache.set(resolvedId, code)
314
+ }
315
+ }
316
+ },
317
+ }
318
+
319
+ // Include cerPlugin from the runtime for JIT CSS support
320
+ const jitPlugins = cerPlugin({
321
+ content: userConfig.jitCss?.content ?? [
322
+ `${userConfig.srcDir ?? 'app'}/pages/**/*.ts`,
323
+ `${userConfig.srcDir ?? 'app'}/components/**/*.ts`,
324
+ `${userConfig.srcDir ?? 'app'}/layouts/**/*.ts`,
325
+ ],
326
+ ssr: userConfig.ssr?.dsd ? { dsd: true } : undefined,
327
+ })
328
+
329
+ return [cerAppPlugin, ...jitPlugins]
330
+ }
@@ -0,0 +1,186 @@
1
+ import { basename, dirname, join, relative } from 'pathe'
2
+
3
+ export interface RouteEntry {
4
+ filePath: string
5
+ routePath: string
6
+ tagName: string
7
+ isDynamic: boolean
8
+ isCatchAll: boolean
9
+ }
10
+
11
+ /**
12
+ * Converts a file path to a route path.
13
+ * e.g. app/pages/blog/[slug].ts -> /blog/:slug
14
+ */
15
+ export function fileToRoutePath(filePath: string, pagesRoot: string): string {
16
+ // Get path relative to pagesRoot, strip extension
17
+ let rel = relative(pagesRoot, filePath)
18
+ rel = rel.replace(/\.[jt]s$/, '')
19
+
20
+ // Split into segments
21
+ const segments = rel.split('/')
22
+
23
+ const transformed: string[] = []
24
+ for (const seg of segments) {
25
+ // Strip route groups: (groupName)
26
+ if (/^\(.*\)$/.test(seg)) {
27
+ continue
28
+ }
29
+
30
+ // index becomes empty string (handled by parent)
31
+ if (seg === 'index') {
32
+ continue
33
+ }
34
+
35
+ // [...rest] -> :rest* (named splat so the param is accessible in components)
36
+ const catchAllMatch = seg.match(/^\[\.\.\.(.+)\]$/)
37
+ if (catchAllMatch) {
38
+ transformed.push(`:${catchAllMatch[1]}*`)
39
+ continue
40
+ }
41
+
42
+ // [param] -> :param
43
+ const dynamicMatch = seg.match(/^\[(.+)\]$/)
44
+ if (dynamicMatch) {
45
+ transformed.push(`:${dynamicMatch[1]}`)
46
+ continue
47
+ }
48
+
49
+ transformed.push(seg)
50
+ }
51
+
52
+ const path = '/' + transformed.join('/')
53
+ // Normalize double slashes
54
+ return path.replace(/\/+/g, '/')
55
+ }
56
+
57
+ /**
58
+ * Converts a file path to a kebab-case custom element tag name.
59
+ * e.g. app/pages/blog/[slug].ts -> page-blog-slug
60
+ */
61
+ export function fileToTagName(filePath: string, pagesRoot: string): string {
62
+ let rel = relative(pagesRoot, filePath)
63
+ rel = rel.replace(/\.[jt]s$/, '')
64
+
65
+ // Strip route groups
66
+ const segments = rel.split('/').filter((seg) => !/^\(.*\)$/.test(seg))
67
+
68
+ // Mirror fileToRoutePath: strip 'index' segments unless it's the only segment
69
+ // e.g. blog/index.ts -> page-blog (not page-blog-index), index.ts -> page-index
70
+ const tagSegments = segments
71
+ .map((seg) => seg.replace(/\[/g, '').replace(/\]/g, '').replace(/\./g, ''))
72
+ .filter((seg, _, arr) => !(seg === 'index' && arr.length > 1))
73
+
74
+ // Prefix with 'page-'
75
+ const name = 'page-' + tagSegments.join('-')
76
+ // Collapse multiple dashes and convert to lowercase
77
+ return name
78
+ .toLowerCase()
79
+ .replace(/-+/g, '-')
80
+ .replace(/^-|-$/g, '')
81
+ }
82
+
83
+ /**
84
+ * Converts a layout file path to a kebab-case custom element tag name.
85
+ * e.g. app/layouts/default.ts -> layout-default
86
+ */
87
+ export function fileToLayoutTagName(filePath: string, layoutsRoot: string): string {
88
+ let rel = relative(layoutsRoot, filePath)
89
+ rel = rel.replace(/\.[jt]s$/, '')
90
+
91
+ const segments = rel.split('/')
92
+ const name = 'layout-' + segments.join('-')
93
+ return name
94
+ .toLowerCase()
95
+ .replace(/-+/g, '-')
96
+ .replace(/^-|-$/g, '')
97
+ }
98
+
99
+ /**
100
+ * Sorts routes: static first, then dynamic, then catch-all.
101
+ */
102
+ export function sortRoutes(routes: RouteEntry[]): RouteEntry[] {
103
+ return [...routes].sort((a, b) => {
104
+ // Catch-all always last
105
+ if (a.isCatchAll && !b.isCatchAll) return 1
106
+ if (!a.isCatchAll && b.isCatchAll) return -1
107
+
108
+ // Dynamic after static
109
+ if (a.isDynamic && !b.isDynamic) return 1
110
+ if (!a.isDynamic && b.isDynamic) return -1
111
+
112
+ // Alphabetical among same type
113
+ return a.routePath.localeCompare(b.routePath)
114
+ })
115
+ }
116
+
117
+ /**
118
+ * Determines if a route path contains dynamic segments.
119
+ */
120
+ export function isRouteDynamic(routePath: string): boolean {
121
+ return routePath.includes(':')
122
+ }
123
+
124
+ /**
125
+ * Determines if a route path is a catch-all.
126
+ */
127
+ export function isRouteCatchAll(routePath: string): boolean {
128
+ return routePath.includes('*')
129
+ }
130
+
131
+ /**
132
+ * Builds a full RouteEntry from a file path.
133
+ */
134
+ export function buildRouteEntry(filePath: string, pagesRoot: string): RouteEntry {
135
+ const routePath = fileToRoutePath(filePath, pagesRoot)
136
+ const tagName = fileToTagName(filePath, pagesRoot)
137
+ return {
138
+ filePath,
139
+ routePath,
140
+ tagName,
141
+ isDynamic: isRouteDynamic(routePath),
142
+ isCatchAll: isRouteCatchAll(routePath),
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Returns the layout name (key) from a layout file path.
148
+ * e.g. app/layouts/default.ts -> 'default'
149
+ */
150
+ export function fileToLayoutName(filePath: string, layoutsRoot: string): string {
151
+ let rel = relative(layoutsRoot, filePath)
152
+ rel = rel.replace(/\.[jt]s$/, '')
153
+ return rel.replace(/\//g, '-').toLowerCase()
154
+ }
155
+
156
+ /**
157
+ * Returns a safe JS identifier for a file path (for import aliases).
158
+ */
159
+ export function fileToImportAlias(filePath: string, prefix: string = '_m'): string {
160
+ const base = basename(filePath).replace(/\.[jt]s$/, '')
161
+ const safe = base
162
+ .replace(/[^a-zA-Z0-9_]/g, '_')
163
+ .replace(/^(\d)/, '_$1')
164
+ return `${prefix}_${safe}`
165
+ }
166
+
167
+ /**
168
+ * Returns the numeric sort prefix from a filename (e.g. "01.store.ts" -> 1).
169
+ * Returns Infinity if no numeric prefix.
170
+ */
171
+ export function extractPluginOrder(fileName: string): number {
172
+ const match = basename(fileName).match(/^(\d+)\./)
173
+ return match ? parseInt(match[1], 10) : Infinity
174
+ }
175
+
176
+ /**
177
+ * Sorts plugin files by leading numeric prefix then alphabetically.
178
+ */
179
+ export function sortPluginFiles(files: string[]): string[] {
180
+ return [...files].sort((a, b) => {
181
+ const aOrder = extractPluginOrder(a)
182
+ const bOrder = extractPluginOrder(b)
183
+ if (aOrder !== bOrder) return aOrder - bOrder
184
+ return a.localeCompare(b)
185
+ })
186
+ }
@@ -0,0 +1,65 @@
1
+ import fg from 'fast-glob'
2
+ import type { FSWatcher } from 'vite'
3
+
4
+ /**
5
+ * Scans a directory for files matching the given glob pattern.
6
+ * Returns absolute file paths sorted alphabetically.
7
+ */
8
+ export async function scanDirectory(pattern: string, cwd: string): Promise<string[]> {
9
+ const files = await fg(pattern, {
10
+ cwd,
11
+ absolute: true,
12
+ onlyFiles: true,
13
+ ignore: ['**/node_modules/**', '**/.git/**'],
14
+ })
15
+ return files.sort()
16
+ }
17
+
18
+ /**
19
+ * Creates a file watcher for the given directories using Vite's built-in watcher.
20
+ * Calls `onChange(event, absoluteFilePath)` on add/unlink events.
21
+ *
22
+ * @param watcher - The FSWatcher from Vite's dev server (server.watcher)
23
+ * @param dirs - Absolute directory paths to watch
24
+ * @param onChange - Callback invoked with event type and absolute file path
25
+ * @returns A cleanup function that removes the listeners
26
+ */
27
+ export function createWatcher(
28
+ watcher: FSWatcher,
29
+ dirs: string[],
30
+ onChange: (event: string, file: string) => void,
31
+ ): () => void {
32
+ // Add directories to Vite's watcher
33
+ for (const dir of dirs) {
34
+ watcher.add(dir)
35
+ }
36
+
37
+ const handleAdd = (file: string) => {
38
+ if (dirs.some((dir) => file.startsWith(dir))) {
39
+ onChange('add', file)
40
+ }
41
+ }
42
+
43
+ const handleUnlink = (file: string) => {
44
+ if (dirs.some((dir) => file.startsWith(dir))) {
45
+ onChange('unlink', file)
46
+ }
47
+ }
48
+
49
+ const handleChange = (file: string) => {
50
+ if (dirs.some((dir) => file.startsWith(dir))) {
51
+ onChange('change', file)
52
+ }
53
+ }
54
+
55
+ watcher.on('add', handleAdd)
56
+ watcher.on('unlink', handleUnlink)
57
+ watcher.on('change', handleChange)
58
+
59
+ // Return cleanup function
60
+ return () => {
61
+ watcher.off('add', handleAdd)
62
+ watcher.off('unlink', handleUnlink)
63
+ watcher.off('change', handleChange)
64
+ }
65
+ }