@jasonshimmy/vite-plugin-cer-app 0.1.6 → 0.3.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 (95) hide show
  1. package/.github/workflows/publish.yml +56 -5
  2. package/CHANGELOG.md +8 -0
  3. package/README.md +2 -0
  4. package/commits.txt +1 -1
  5. package/dist/cli/commands/build.d.ts.map +1 -1
  6. package/dist/cli/commands/build.js +19 -5
  7. package/dist/cli/commands/build.js.map +1 -1
  8. package/dist/cli/commands/dev.js +1 -1
  9. package/dist/cli/commands/dev.js.map +1 -1
  10. package/dist/cli/commands/preview.d.ts.map +1 -1
  11. package/dist/cli/commands/preview.js +0 -1
  12. package/dist/cli/commands/preview.js.map +1 -1
  13. package/dist/cli/create/index.js +7 -3
  14. package/dist/cli/create/index.js.map +1 -1
  15. package/dist/cli/create/templates/spa/.gitignore.tpl +25 -0
  16. package/dist/cli/create/templates/spa/index.html.tpl +1 -1
  17. package/dist/cli/create/templates/ssg/.gitignore.tpl +25 -0
  18. package/dist/cli/create/templates/ssg/index.html.tpl +1 -1
  19. package/dist/cli/create/templates/ssr/.gitignore.tpl +25 -0
  20. package/dist/cli/create/templates/ssr/cer.config.ts.tpl +0 -1
  21. package/dist/cli/create/templates/ssr/index.html.tpl +1 -1
  22. package/dist/plugin/build-ssg.d.ts.map +1 -1
  23. package/dist/plugin/build-ssg.js.map +1 -1
  24. package/dist/plugin/build-ssr.d.ts +10 -0
  25. package/dist/plugin/build-ssr.d.ts.map +1 -1
  26. package/dist/plugin/build-ssr.js +21 -8
  27. package/dist/plugin/build-ssr.js.map +1 -1
  28. package/dist/plugin/dev-server.d.ts +0 -1
  29. package/dist/plugin/dev-server.d.ts.map +1 -1
  30. package/dist/plugin/dev-server.js +0 -2
  31. package/dist/plugin/dev-server.js.map +1 -1
  32. package/dist/plugin/dts-generator.d.ts +4 -4
  33. package/dist/plugin/dts-generator.d.ts.map +1 -1
  34. package/dist/plugin/dts-generator.js +39 -19
  35. package/dist/plugin/dts-generator.js.map +1 -1
  36. package/dist/plugin/generated-dir.d.ts +28 -0
  37. package/dist/plugin/generated-dir.d.ts.map +1 -0
  38. package/dist/plugin/generated-dir.js +106 -0
  39. package/dist/plugin/generated-dir.js.map +1 -0
  40. package/dist/plugin/index.d.ts.map +1 -1
  41. package/dist/plugin/index.js +27 -1
  42. package/dist/plugin/index.js.map +1 -1
  43. package/dist/plugin/path-utils.js.map +1 -1
  44. package/dist/plugin/virtual/loading.d.ts.map +1 -1
  45. package/dist/plugin/virtual/loading.js.map +1 -1
  46. package/dist/runtime/app-template.d.ts +9 -0
  47. package/dist/runtime/app-template.d.ts.map +1 -0
  48. package/dist/runtime/app-template.js +159 -0
  49. package/dist/runtime/app-template.js.map +1 -0
  50. package/dist/types/config.d.ts +0 -1
  51. package/dist/types/config.d.ts.map +1 -1
  52. package/dist/types/config.js.map +1 -1
  53. package/docs/cli.md +1 -1
  54. package/docs/configuration.md +2 -11
  55. package/docs/getting-started.md +2 -100
  56. package/docs/rendering-modes.md +4 -5
  57. package/docs/routing.md +1 -1
  58. package/e2e/kitchen-sink/tsconfig.json +3 -0
  59. package/eslint.config.ts +22 -0
  60. package/package.json +6 -1
  61. package/src/__tests__/plugin/build-ssr.test.ts +24 -10
  62. package/src/__tests__/plugin/cer-app-plugin.test.ts +35 -0
  63. package/src/__tests__/plugin/dev-server.test.ts +1 -1
  64. package/src/__tests__/plugin/dts-generator.test.ts +15 -6
  65. package/src/__tests__/plugin/generated-dir.test.ts +137 -0
  66. package/src/__tests__/plugin/resolve-config.test.ts +0 -5
  67. package/src/__tests__/types/config.test.ts +1 -1
  68. package/src/cli/commands/build.ts +19 -5
  69. package/src/cli/commands/dev.ts +2 -2
  70. package/src/cli/commands/preview.ts +7 -5
  71. package/src/cli/create/index.ts +12 -8
  72. package/src/cli/create/templates/spa/.gitignore.tpl +25 -0
  73. package/src/cli/create/templates/spa/index.html.tpl +1 -1
  74. package/src/cli/create/templates/ssg/.gitignore.tpl +25 -0
  75. package/src/cli/create/templates/ssg/index.html.tpl +1 -1
  76. package/src/cli/create/templates/ssr/.gitignore.tpl +25 -0
  77. package/src/cli/create/templates/ssr/cer.config.ts.tpl +0 -1
  78. package/src/cli/create/templates/ssr/index.html.tpl +1 -1
  79. package/src/plugin/build-ssg.ts +2 -2
  80. package/src/plugin/build-ssr.ts +22 -8
  81. package/src/plugin/dev-server.ts +5 -4
  82. package/src/plugin/dts-generator.ts +43 -19
  83. package/src/plugin/generated-dir.ts +115 -0
  84. package/src/plugin/index.ts +32 -2
  85. package/src/plugin/path-utils.ts +1 -1
  86. package/src/plugin/virtual/loading.ts +0 -1
  87. package/{e2e/kitchen-sink/app/app.ts → src/runtime/app-template.ts} +24 -7
  88. package/src/types/config.ts +0 -1
  89. package/dist/cli/create/templates/spa/app/app.ts.tpl +0 -93
  90. package/dist/cli/create/templates/ssg/app/app.ts.tpl +0 -97
  91. package/dist/cli/create/templates/ssr/app/app.ts.tpl +0 -97
  92. package/e2e/kitchen-sink/index.html +0 -12
  93. package/src/cli/create/templates/spa/app/app.ts.tpl +0 -93
  94. package/src/cli/create/templates/ssg/app/app.ts.tpl +0 -97
  95. package/src/cli/create/templates/ssr/app/app.ts.tpl +0 -97
@@ -0,0 +1,115 @@
1
+ import { writeFileSync, existsSync, mkdirSync, readFileSync, appendFileSync } from 'node:fs'
2
+ import { join } from 'pathe'
3
+ import type { ResolvedCerConfig } from './dev-server.js'
4
+ import { APP_ENTRY_TEMPLATE } from '../runtime/app-template.js'
5
+
6
+ /** The name of the generated directory relative to the project root. */
7
+ export const GENERATED_DIR_NAME = '.cer'
8
+
9
+ /**
10
+ * Returns the absolute path to the .cer/ generated directory.
11
+ */
12
+ export function getGeneratedDir(root: string): string {
13
+ return join(root, GENERATED_DIR_NAME)
14
+ }
15
+
16
+ /**
17
+ * Returns the HTML entry path to use for builds.
18
+ * Prefers the consumer's root-level `index.html` when it exists;
19
+ * falls back to `.cer/index.html`.
20
+ */
21
+ export function resolveHtmlEntry(config: ResolvedCerConfig): string {
22
+ const userHtml = join(config.root, 'index.html')
23
+ if (existsSync(userHtml)) return userHtml
24
+ return join(getGeneratedDir(config.root), 'index.html')
25
+ }
26
+
27
+ /**
28
+ * Generates the content for the default `.cer/index.html`.
29
+ * Always points to the generated `/.cer/app.ts` entry.
30
+ */
31
+ export function generateDefaultHtml(): string {
32
+ return `<!DOCTYPE html>
33
+ <html lang="en">
34
+ <head>
35
+ <meta charset="UTF-8" />
36
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
37
+ <title>CER App</title>
38
+ </head>
39
+ <body>
40
+ <cer-layout-view></cer-layout-view>
41
+ <script type="module" src="/.cer/app.ts"></script>
42
+ </body>
43
+ </html>
44
+ `
45
+ }
46
+
47
+ const GITIGNORE_DEFAULTS = `# Dependencies
48
+ node_modules/
49
+
50
+ # Build output
51
+ dist/
52
+
53
+ # CER App generated directory
54
+ .cer/
55
+
56
+ # Environment variables
57
+ .env.local
58
+ .env.*.local
59
+
60
+ # Editor
61
+ .vscode/
62
+ .idea/
63
+ *.suo
64
+ *.sw?
65
+
66
+ # OS
67
+ .DS_Store
68
+ Thumbs.db
69
+
70
+ # Logs
71
+ *.log
72
+ `
73
+
74
+ /**
75
+ * Ensures `.cer/`, `node_modules/`, `dist/`, and other common entries are
76
+ * listed in the project's `.gitignore`. Creates `.gitignore` if it does not exist.
77
+ */
78
+ function ensureGitignore(root: string): void {
79
+ const gitignorePath = join(root, '.gitignore')
80
+ const cerEntry = `${GENERATED_DIR_NAME}/`
81
+
82
+ if (existsSync(gitignorePath)) {
83
+ const content = readFileSync(gitignorePath, 'utf-8')
84
+ if (!content.includes(cerEntry) && !content.includes(`${GENERATED_DIR_NAME}\n`)) {
85
+ appendFileSync(gitignorePath, `\n# CER App generated directory\n${cerEntry}\n`)
86
+ }
87
+ } else {
88
+ writeFileSync(gitignorePath, GITIGNORE_DEFAULTS)
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Writes all generated files to `.cer/`:
94
+ * - `.cer/app.ts` — framework entry (always regenerated)
95
+ * - `.cer/index.html` — default HTML shell (always regenerated)
96
+ * - `.cer/tsconfig.json` — written by dts-generator via writeTsconfigPaths
97
+ *
98
+ * Also ensures `.cer/` is listed in `.gitignore`.
99
+ */
100
+ export function writeGeneratedDir(config: ResolvedCerConfig): void {
101
+ const dir = getGeneratedDir(config.root)
102
+ if (!existsSync(dir)) {
103
+ mkdirSync(dir, { recursive: true })
104
+ }
105
+
106
+ // Always write the generated app.ts — this is the framework entry point and
107
+ // is never user-owned. Regenerating it on every dev/build ensures consumers
108
+ // automatically get the latest bootstrap code on plugin update (Nuxt-style).
109
+ writeFileSync(join(dir, 'app.ts'), APP_ENTRY_TEMPLATE, 'utf-8')
110
+
111
+ // Always write the default index.html so builds and the dev server can use it.
112
+ writeFileSync(join(dir, 'index.html'), generateDefaultHtml(), 'utf-8')
113
+
114
+ ensureGitignore(config.root)
115
+ }
@@ -1,11 +1,13 @@
1
1
  import { resolve, join } from 'pathe'
2
- import type { Plugin, ViteDevServer, ModuleNode } from 'vite'
2
+ import { existsSync, readFileSync } from 'node:fs'
3
+ import type { Plugin, ViteDevServer } from 'vite'
3
4
  import type { CerAppConfig } from '../types/config.js'
4
5
  import type { ResolvedCerConfig } from './dev-server.js'
5
6
  import { cerPlugin } from '@jasonshimmy/custom-elements-runtime/vite-plugin'
6
7
  import { autoImportTransform } from './transforms/auto-import.js'
7
8
  import { scanComposableExports, writeAutoImportDts, writeTsconfigPaths } from './dts-generator.js'
8
9
  import { configureCerDevServer } from './dev-server.js'
10
+ import { writeGeneratedDir, getGeneratedDir } from './generated-dir.js'
9
11
  import { generateRoutesCode } from './virtual/routes.js'
10
12
  import { generateLayoutsCode } from './virtual/layouts.js'
11
13
  import { generateComponentsCode } from './virtual/components.js'
@@ -60,7 +62,6 @@ export function resolveConfig(userConfig: CerAppConfig, root: string = process.c
60
62
  port: userConfig.port ?? 3000,
61
63
  ssr: {
62
64
  dsd: userConfig.ssr?.dsd ?? true,
63
- streaming: userConfig.ssr?.streaming ?? false,
64
65
  },
65
66
  ssg: {
66
67
  routes: userConfig.ssg?.routes ?? 'auto',
@@ -256,11 +257,38 @@ export function cerApp(userConfig: CerAppConfig = {}): Plugin[] {
256
257
  // config might not be set yet; resolve with cwd
257
258
  config = resolveConfig(userConfig, process.cwd())
258
259
  }
260
+
261
+ // Write .cer/ generated files (app.ts fallback, index.html, .gitignore)
262
+ writeGeneratedDir(config)
263
+
259
264
  // Scan composables and write .d.ts + tsconfig paths on dev server start
260
265
  composableExports = await scanComposableExports(config.composablesDir)
261
266
  await writeAutoImportDts(config.root, config.composablesDir, composableExports)
262
267
  writeTsconfigPaths(config.root, config.srcDir)
263
268
 
269
+ // Serve a generated index.html for HTML requests when the consumer has
270
+ // not provided one. This runs BEFORE configureCerDevServer so that the
271
+ // Vite HTML pipeline (HMR injection, module preprocessing) is applied.
272
+ const userHtml = resolve(config.root, 'index.html')
273
+ if (!existsSync(userHtml)) {
274
+ const cerHtmlPath = join(getGeneratedDir(config.root), 'index.html')
275
+ server.middlewares.use(async (req, res, next) => {
276
+ const url = (req as { url?: string }).url ?? '/'
277
+ const isHtmlRequest =
278
+ url === '/' ||
279
+ url === '/index.html' ||
280
+ (!url.includes('.') && !url.startsWith('/api/'))
281
+ if (isHtmlRequest && existsSync(cerHtmlPath)) {
282
+ const rawHtml = readFileSync(cerHtmlPath, 'utf-8')
283
+ const transformed = await server.transformIndexHtml(url, rawHtml)
284
+ res.setHeader('Content-Type', 'text/html; charset=utf-8')
285
+ res.end(transformed)
286
+ return
287
+ }
288
+ next()
289
+ })
290
+ }
291
+
264
292
  // Watch app/ and server/ directories for file changes
265
293
  const watchDirs = [
266
294
  config.pagesDir,
@@ -302,6 +330,8 @@ export function cerApp(userConfig: CerAppConfig = {}): Plugin[] {
302
330
  if (!config) {
303
331
  config = resolveConfig(userConfig, process.cwd())
304
332
  }
333
+ // Write .cer/ generated files before the build begins
334
+ writeGeneratedDir(config)
305
335
  // Scan composables and generate type declarations + tsconfig paths
306
336
  composableExports = await scanComposableExports(config.composablesDir)
307
337
  await writeAutoImportDts(config.root, config.composablesDir, composableExports)
@@ -1,4 +1,4 @@
1
- import { basename, dirname, join, relative } from 'pathe'
1
+ import { basename, relative } from 'pathe'
2
2
 
3
3
  export interface RouteEntry {
4
4
  filePath: string
@@ -1,6 +1,5 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import { join } from 'pathe'
3
- import { scanDirectory } from '../scanner.js'
4
3
 
5
4
  /**
6
5
  * Generates the virtual:cer-loading module code.
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Template string for `.cer/app.ts` — the framework client entry point.
3
+ *
4
+ * Always written to `.cer/app.ts` on every dev/build so consumers
5
+ * automatically receive the latest bootstrap code on plugin update.
6
+ * This file is gitignored and should never be edited directly.
7
+ */
8
+ export const APP_ENTRY_TEMPLATE = `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app — do not edit.
9
+ // Regenerated automatically on every dev server start and build.
10
+
1
11
  import '@jasonshimmy/custom-elements-runtime/css'
2
12
  import 'virtual:cer-jit-css'
3
13
  import 'virtual:cer-components'
@@ -27,10 +37,11 @@ const router = initRouter({ routes })
27
37
  const isNavigating = ref(false)
28
38
  const currentError = ref(null)
29
39
 
30
- ;(globalThis as any).resetError = () => {
40
+ const resetError = (): void => {
31
41
  currentError.value = null
32
- router.replace(router.getCurrent().path)
42
+ void router.replace(router.getCurrent().path)
33
43
  }
44
+ ;(globalThis as Record<string, unknown>).resetError = resetError
34
45
 
35
46
  const _push = router.push.bind(router)
36
47
  const _replace = router.replace.bind(router)
@@ -68,8 +79,8 @@ router.replace = async (path) => {
68
79
  // synchronously, calling the render function immediately.
69
80
  const _pluginProvides = new Map<string, unknown>()
70
81
  // Expose plugin provides globally so page components can read them synchronously
71
- // regardless of render order (inject/provide has timing issues in SSG mode).
72
- ;(globalThis as any).__cerPluginProvides = _pluginProvides
82
+ // regardless of render order.
83
+ ;(globalThis as Record<string, unknown>).__cerPluginProvides = _pluginProvides
73
84
 
74
85
  // ─── <cer-layout-view> ───────────────────────────────────────────────────────
75
86
 
@@ -100,7 +111,8 @@ component('cer-layout-view', () => {
100
111
  }
101
112
 
102
113
  const matched = router.matchRoute(current.value.path)
103
- const layoutName = (matched?.route as any)?.meta?.layout ?? 'default'
114
+ const routeMeta = matched?.route?.meta as { layout?: string } | undefined
115
+ const layoutName = routeMeta?.layout ?? 'default'
104
116
  const layoutTag = (layouts as Record<string, string>)[layoutName]
105
117
  const routerView = { tag: 'router-view', props: {}, children: [] }
106
118
 
@@ -110,7 +122,11 @@ component('cer-layout-view', () => {
110
122
 
111
123
  for (const plugin of plugins) {
112
124
  if (plugin && typeof plugin.setup === 'function') {
113
- await plugin.setup({ router, provide: (key: string, value: unknown) => { _pluginProvides.set(key, value) }, config: {} })
125
+ await plugin.setup({
126
+ router,
127
+ provide: (key: string, value: unknown) => { _pluginProvides.set(key, value) },
128
+ config: {},
129
+ })
114
130
  }
115
131
  }
116
132
 
@@ -135,7 +151,8 @@ if (typeof window !== 'undefined') {
135
151
  await _replace(window.location.pathname + window.location.search + window.location.hash)
136
152
  // Clear SSR loader data after initial navigation so subsequent client-side
137
153
  // navigations don't accidentally reuse stale server data.
138
- delete (globalThis as any).__CER_DATA__
154
+ delete (globalThis as Record<string, unknown>).__CER_DATA__
139
155
  }
140
156
 
141
157
  export { router }
158
+ `
@@ -13,7 +13,6 @@ export interface JitCssConfig {
13
13
 
14
14
  export interface SsrConfig {
15
15
  dsd?: boolean
16
- streaming?: boolean
17
16
  }
18
17
 
19
18
  export interface AutoImportsConfig {
@@ -1,93 +0,0 @@
1
- import '@jasonshimmy/custom-elements-runtime/css'
2
- import 'virtual:cer-jit-css'
3
- import 'virtual:cer-components'
4
- import routes from 'virtual:cer-routes'
5
- import layouts from 'virtual:cer-layouts'
6
- import plugins from 'virtual:cer-plugins'
7
- import { hasLoading, loadingTag } from 'virtual:cer-loading'
8
- import { hasError, errorTag } from 'virtual:cer-error'
9
- import {
10
- component,
11
- ref,
12
- provide,
13
- useOnConnected,
14
- useOnDisconnected,
15
- registerBuiltinComponents,
16
- } from '@jasonshimmy/custom-elements-runtime'
17
- import { initRouter } from '@jasonshimmy/custom-elements-runtime/router'
18
- import { enableJITCSS } from '@jasonshimmy/custom-elements-runtime/jit-css'
19
-
20
- registerBuiltinComponents()
21
- enableJITCSS()
22
-
23
- const router = initRouter({ routes })
24
-
25
- const isNavigating = ref(false)
26
- const currentError = ref(null)
27
- ;(globalThis as any).resetError = () => {
28
- currentError.value = null
29
- router.replace(router.getCurrent().path)
30
- }
31
-
32
- const _push = router.push.bind(router)
33
- const _replace = router.replace.bind(router)
34
- router.push = async (path) => {
35
- isNavigating.value = true
36
- currentError.value = null
37
- try { await _push(path) } catch (err) { currentError.value = err instanceof Error ? err.message : String(err) } finally { isNavigating.value = false }
38
- }
39
- router.replace = async (path) => {
40
- isNavigating.value = true
41
- currentError.value = null
42
- try { await _replace(path) } catch (err) { currentError.value = err instanceof Error ? err.message : String(err) } finally { isNavigating.value = false }
43
- }
44
-
45
- const _pluginProvides = new Map<string, unknown>()
46
- ;(globalThis as any).__cerPluginProvides = _pluginProvides
47
-
48
- component('cer-layout-view', () => {
49
- for (const [key, value] of _pluginProvides) {
50
- provide(key, value)
51
- }
52
-
53
- const current = ref(router.getCurrent())
54
- let unsub: (() => void) | undefined
55
- useOnConnected(() => { unsub = router.subscribe((s: typeof current.value) => { current.value = s }) })
56
- useOnDisconnected(() => { unsub?.(); unsub = undefined })
57
-
58
- if (currentError.value !== null) {
59
- if (hasError && errorTag) return { tag: errorTag, props: { attrs: { error: String(currentError.value) } }, children: [] }
60
- return { tag: 'div', props: { attrs: { style: 'padding:2rem;font-family:monospace' } }, children: String(currentError.value) }
61
- }
62
- if (isNavigating.value && hasLoading && loadingTag) return { tag: loadingTag, props: {}, children: [] }
63
-
64
- const matched = router.matchRoute(current.value.path)
65
- const layoutName = (matched?.route as any)?.meta?.layout ?? 'default'
66
- const layoutTag = (layouts as Record<string, string>)[layoutName]
67
- const routerView = { tag: 'router-view', props: {}, children: [] }
68
- return layoutTag ? { tag: layoutTag, props: {}, children: [routerView] } : routerView
69
- })
70
-
71
- for (const plugin of plugins) {
72
- if (plugin && typeof plugin.setup === 'function') {
73
- await plugin.setup({ router, provide: (key: string, value: unknown) => { _pluginProvides.set(key, value) }, config: {} })
74
- }
75
- }
76
-
77
- // Pre-load the current page's route chunk AFTER plugins run so that
78
- // cer-layout-view's first render (which calls provide()) completes before
79
- // page components are defined. This ensures inject() in child components
80
- // can find values stored by provide().
81
- if (typeof window !== 'undefined') {
82
- const _initMatch = router.matchRoute(window.location.pathname)
83
- if (_initMatch?.route?.load) {
84
- try { await _initMatch.route.load() } catch { /* non-fatal */ }
85
- }
86
- }
87
-
88
- if (typeof window !== 'undefined') {
89
- // Use the original (unwrapped) replace so isNavigating stays false on first paint.
90
- await _replace(window.location.pathname + window.location.search + window.location.hash)
91
- }
92
-
93
- export { router }
@@ -1,97 +0,0 @@
1
- import '@jasonshimmy/custom-elements-runtime/css'
2
- import 'virtual:cer-jit-css'
3
- import 'virtual:cer-components'
4
- import routes from 'virtual:cer-routes'
5
- import layouts from 'virtual:cer-layouts'
6
- import plugins from 'virtual:cer-plugins'
7
- import { hasLoading, loadingTag } from 'virtual:cer-loading'
8
- import { hasError, errorTag } from 'virtual:cer-error'
9
- import {
10
- component,
11
- ref,
12
- provide,
13
- useOnConnected,
14
- useOnDisconnected,
15
- registerBuiltinComponents,
16
- } from '@jasonshimmy/custom-elements-runtime'
17
- import { initRouter } from '@jasonshimmy/custom-elements-runtime/router'
18
- import { enableJITCSS } from '@jasonshimmy/custom-elements-runtime/jit-css'
19
-
20
- registerBuiltinComponents()
21
- enableJITCSS()
22
-
23
- const router = initRouter({ routes })
24
-
25
- const isNavigating = ref(false)
26
- const currentError = ref(null)
27
- ;(globalThis as any).resetError = () => {
28
- currentError.value = null
29
- router.replace(router.getCurrent().path)
30
- }
31
-
32
- const _push = router.push.bind(router)
33
- const _replace = router.replace.bind(router)
34
- router.push = async (path) => {
35
- isNavigating.value = true
36
- currentError.value = null
37
- try { await _push(path) } catch (err) { currentError.value = err instanceof Error ? err.message : String(err) } finally { isNavigating.value = false }
38
- }
39
- router.replace = async (path) => {
40
- isNavigating.value = true
41
- currentError.value = null
42
- try { await _replace(path) } catch (err) { currentError.value = err instanceof Error ? err.message : String(err) } finally { isNavigating.value = false }
43
- }
44
-
45
- const _pluginProvides = new Map<string, unknown>()
46
- ;(globalThis as any).__cerPluginProvides = _pluginProvides
47
-
48
- component('cer-layout-view', () => {
49
- for (const [key, value] of _pluginProvides) {
50
- provide(key, value)
51
- }
52
-
53
- const current = ref(router.getCurrent())
54
- let unsub: (() => void) | undefined
55
- useOnConnected(() => { unsub = router.subscribe((s: typeof current.value) => { current.value = s }) })
56
- useOnDisconnected(() => { unsub?.(); unsub = undefined })
57
-
58
- if (currentError.value !== null) {
59
- if (hasError && errorTag) return { tag: errorTag, props: { attrs: { error: String(currentError.value) } }, children: [] }
60
- return { tag: 'div', props: { attrs: { style: 'padding:2rem;font-family:monospace' } }, children: String(currentError.value) }
61
- }
62
- if (isNavigating.value && hasLoading && loadingTag) return { tag: loadingTag, props: {}, children: [] }
63
-
64
- const matched = router.matchRoute(current.value.path)
65
- const layoutName = (matched?.route as any)?.meta?.layout ?? 'default'
66
- const layoutTag = (layouts as Record<string, string>)[layoutName]
67
- const routerView = { tag: 'router-view', props: {}, children: [] }
68
- return layoutTag ? { tag: layoutTag, props: {}, children: [routerView] } : routerView
69
- })
70
-
71
- for (const plugin of plugins) {
72
- if (plugin && typeof plugin.setup === 'function') {
73
- await plugin.setup({ router, provide: (key: string, value: unknown) => { _pluginProvides.set(key, value) }, config: {} })
74
- }
75
- }
76
-
77
- // Pre-load the current page's route chunk AFTER plugins run so that
78
- // cer-layout-view's first render (which calls provide()) completes before
79
- // page components are defined. This ensures inject() in child components
80
- // can find values stored by provide().
81
- if (typeof window !== 'undefined') {
82
- const _initMatch = router.matchRoute(window.location.pathname)
83
- if (_initMatch?.route?.load) {
84
- try { await _initMatch.route.load() } catch { /* non-fatal */ }
85
- }
86
- }
87
-
88
- if (typeof window !== 'undefined') {
89
- // Use the original (unwrapped) replace so isNavigating stays false on first
90
- // paint — the loading component must not flash over pre-rendered SSG content.
91
- await _replace(window.location.pathname + window.location.search + window.location.hash)
92
- // Clear SSR hydration data after initial navigation so subsequent navigations
93
- // don't accidentally reuse it.
94
- delete (globalThis as any).__CER_DATA__
95
- }
96
-
97
- export { router }
@@ -1,97 +0,0 @@
1
- import '@jasonshimmy/custom-elements-runtime/css'
2
- import 'virtual:cer-jit-css'
3
- import 'virtual:cer-components'
4
- import routes from 'virtual:cer-routes'
5
- import layouts from 'virtual:cer-layouts'
6
- import plugins from 'virtual:cer-plugins'
7
- import { hasLoading, loadingTag } from 'virtual:cer-loading'
8
- import { hasError, errorTag } from 'virtual:cer-error'
9
- import {
10
- component,
11
- ref,
12
- provide,
13
- useOnConnected,
14
- useOnDisconnected,
15
- registerBuiltinComponents,
16
- } from '@jasonshimmy/custom-elements-runtime'
17
- import { initRouter } from '@jasonshimmy/custom-elements-runtime/router'
18
- import { enableJITCSS } from '@jasonshimmy/custom-elements-runtime/jit-css'
19
-
20
- registerBuiltinComponents()
21
- enableJITCSS()
22
-
23
- const router = initRouter({ routes })
24
-
25
- const isNavigating = ref(false)
26
- const currentError = ref(null)
27
- ;(globalThis as any).resetError = () => {
28
- currentError.value = null
29
- router.replace(router.getCurrent().path)
30
- }
31
-
32
- const _push = router.push.bind(router)
33
- const _replace = router.replace.bind(router)
34
- router.push = async (path) => {
35
- isNavigating.value = true
36
- currentError.value = null
37
- try { await _push(path) } catch (err) { currentError.value = err instanceof Error ? err.message : String(err) } finally { isNavigating.value = false }
38
- }
39
- router.replace = async (path) => {
40
- isNavigating.value = true
41
- currentError.value = null
42
- try { await _replace(path) } catch (err) { currentError.value = err instanceof Error ? err.message : String(err) } finally { isNavigating.value = false }
43
- }
44
-
45
- const _pluginProvides = new Map<string, unknown>()
46
- ;(globalThis as any).__cerPluginProvides = _pluginProvides
47
-
48
- component('cer-layout-view', () => {
49
- for (const [key, value] of _pluginProvides) {
50
- provide(key, value)
51
- }
52
-
53
- const current = ref(router.getCurrent())
54
- let unsub: (() => void) | undefined
55
- useOnConnected(() => { unsub = router.subscribe((s: typeof current.value) => { current.value = s }) })
56
- useOnDisconnected(() => { unsub?.(); unsub = undefined })
57
-
58
- if (currentError.value !== null) {
59
- if (hasError && errorTag) return { tag: errorTag, props: { attrs: { error: String(currentError.value) } }, children: [] }
60
- return { tag: 'div', props: { attrs: { style: 'padding:2rem;font-family:monospace' } }, children: String(currentError.value) }
61
- }
62
- if (isNavigating.value && hasLoading && loadingTag) return { tag: loadingTag, props: {}, children: [] }
63
-
64
- const matched = router.matchRoute(current.value.path)
65
- const layoutName = (matched?.route as any)?.meta?.layout ?? 'default'
66
- const layoutTag = (layouts as Record<string, string>)[layoutName]
67
- const routerView = { tag: 'router-view', props: {}, children: [] }
68
- return layoutTag ? { tag: layoutTag, props: {}, children: [routerView] } : routerView
69
- })
70
-
71
- for (const plugin of plugins) {
72
- if (plugin && typeof plugin.setup === 'function') {
73
- await plugin.setup({ router, provide: (key: string, value: unknown) => { _pluginProvides.set(key, value) }, config: {} })
74
- }
75
- }
76
-
77
- // Pre-load the current page's route chunk AFTER plugins run so that
78
- // cer-layout-view's first render (which calls provide()) completes before
79
- // page components are defined. This ensures inject() in child components
80
- // can find values stored by provide().
81
- if (typeof window !== 'undefined') {
82
- const _initMatch = router.matchRoute(window.location.pathname)
83
- if (_initMatch?.route?.load) {
84
- try { await _initMatch.route.load() } catch { /* non-fatal */ }
85
- }
86
- }
87
-
88
- if (typeof window !== 'undefined') {
89
- // Use the original (unwrapped) replace so isNavigating stays false on first
90
- // paint — the loading component must not flash over pre-rendered SSR content.
91
- await _replace(window.location.pathname + window.location.search + window.location.hash)
92
- // Clear SSR hydration data after initial navigation so subsequent navigations
93
- // don't accidentally reuse it.
94
- delete (globalThis as any).__CER_DATA__
95
- }
96
-
97
- export { router }
@@ -1,12 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Kitchen Sink</title>
7
- </head>
8
- <body>
9
- <cer-layout-view></cer-layout-view>
10
- <script type="module" src="/app/app.ts"></script>
11
- </body>
12
- </html>
@@ -1,93 +0,0 @@
1
- import '@jasonshimmy/custom-elements-runtime/css'
2
- import 'virtual:cer-jit-css'
3
- import 'virtual:cer-components'
4
- import routes from 'virtual:cer-routes'
5
- import layouts from 'virtual:cer-layouts'
6
- import plugins from 'virtual:cer-plugins'
7
- import { hasLoading, loadingTag } from 'virtual:cer-loading'
8
- import { hasError, errorTag } from 'virtual:cer-error'
9
- import {
10
- component,
11
- ref,
12
- provide,
13
- useOnConnected,
14
- useOnDisconnected,
15
- registerBuiltinComponents,
16
- } from '@jasonshimmy/custom-elements-runtime'
17
- import { initRouter } from '@jasonshimmy/custom-elements-runtime/router'
18
- import { enableJITCSS } from '@jasonshimmy/custom-elements-runtime/jit-css'
19
-
20
- registerBuiltinComponents()
21
- enableJITCSS()
22
-
23
- const router = initRouter({ routes })
24
-
25
- const isNavigating = ref(false)
26
- const currentError = ref(null)
27
- ;(globalThis as any).resetError = () => {
28
- currentError.value = null
29
- router.replace(router.getCurrent().path)
30
- }
31
-
32
- const _push = router.push.bind(router)
33
- const _replace = router.replace.bind(router)
34
- router.push = async (path) => {
35
- isNavigating.value = true
36
- currentError.value = null
37
- try { await _push(path) } catch (err) { currentError.value = err instanceof Error ? err.message : String(err) } finally { isNavigating.value = false }
38
- }
39
- router.replace = async (path) => {
40
- isNavigating.value = true
41
- currentError.value = null
42
- try { await _replace(path) } catch (err) { currentError.value = err instanceof Error ? err.message : String(err) } finally { isNavigating.value = false }
43
- }
44
-
45
- const _pluginProvides = new Map<string, unknown>()
46
- ;(globalThis as any).__cerPluginProvides = _pluginProvides
47
-
48
- component('cer-layout-view', () => {
49
- for (const [key, value] of _pluginProvides) {
50
- provide(key, value)
51
- }
52
-
53
- const current = ref(router.getCurrent())
54
- let unsub: (() => void) | undefined
55
- useOnConnected(() => { unsub = router.subscribe((s: typeof current.value) => { current.value = s }) })
56
- useOnDisconnected(() => { unsub?.(); unsub = undefined })
57
-
58
- if (currentError.value !== null) {
59
- if (hasError && errorTag) return { tag: errorTag, props: { attrs: { error: String(currentError.value) } }, children: [] }
60
- return { tag: 'div', props: { attrs: { style: 'padding:2rem;font-family:monospace' } }, children: String(currentError.value) }
61
- }
62
- if (isNavigating.value && hasLoading && loadingTag) return { tag: loadingTag, props: {}, children: [] }
63
-
64
- const matched = router.matchRoute(current.value.path)
65
- const layoutName = (matched?.route as any)?.meta?.layout ?? 'default'
66
- const layoutTag = (layouts as Record<string, string>)[layoutName]
67
- const routerView = { tag: 'router-view', props: {}, children: [] }
68
- return layoutTag ? { tag: layoutTag, props: {}, children: [routerView] } : routerView
69
- })
70
-
71
- for (const plugin of plugins) {
72
- if (plugin && typeof plugin.setup === 'function') {
73
- await plugin.setup({ router, provide: (key: string, value: unknown) => { _pluginProvides.set(key, value) }, config: {} })
74
- }
75
- }
76
-
77
- // Pre-load the current page's route chunk AFTER plugins run so that
78
- // cer-layout-view's first render (which calls provide()) completes before
79
- // page components are defined. This ensures inject() in child components
80
- // can find values stored by provide().
81
- if (typeof window !== 'undefined') {
82
- const _initMatch = router.matchRoute(window.location.pathname)
83
- if (_initMatch?.route?.load) {
84
- try { await _initMatch.route.load() } catch { /* non-fatal */ }
85
- }
86
- }
87
-
88
- if (typeof window !== 'undefined') {
89
- // Use the original (unwrapped) replace so isNavigating stays false on first paint.
90
- await _replace(window.location.pathname + window.location.search + window.location.hash)
91
- }
92
-
93
- export { router }