@jasonshimmy/vite-plugin-cer-app 0.2.0 → 0.4.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 (93) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +2 -0
  3. package/commits.txt +1 -1
  4. package/dist/cli/create/index.js +7 -3
  5. package/dist/cli/create/index.js.map +1 -1
  6. package/dist/cli/create/templates/spa/.gitignore.tpl +25 -0
  7. package/dist/cli/create/templates/spa/index.html.tpl +1 -1
  8. package/dist/cli/create/templates/ssg/.gitignore.tpl +25 -0
  9. package/dist/cli/create/templates/ssg/index.html.tpl +1 -1
  10. package/dist/cli/create/templates/ssr/.gitignore.tpl +25 -0
  11. package/dist/cli/create/templates/ssr/cer.config.ts.tpl +0 -1
  12. package/dist/cli/create/templates/ssr/index.html.tpl +1 -1
  13. package/dist/plugin/build-ssr.d.ts.map +1 -1
  14. package/dist/plugin/build-ssr.js +18 -0
  15. package/dist/plugin/build-ssr.js.map +1 -1
  16. package/dist/plugin/dev-server.d.ts +0 -1
  17. package/dist/plugin/dev-server.d.ts.map +1 -1
  18. package/dist/plugin/dts-generator.js +1 -1
  19. package/dist/plugin/dts-generator.js.map +1 -1
  20. package/dist/plugin/generated-dir.d.ts +5 -11
  21. package/dist/plugin/generated-dir.d.ts.map +1 -1
  22. package/dist/plugin/generated-dir.js +43 -31
  23. package/dist/plugin/generated-dir.js.map +1 -1
  24. package/dist/plugin/index.d.ts.map +1 -1
  25. package/dist/plugin/index.js +9 -1
  26. package/dist/plugin/index.js.map +1 -1
  27. package/dist/plugin/transforms/auto-import.js +2 -2
  28. package/dist/plugin/transforms/auto-import.js.map +1 -1
  29. package/dist/runtime/app-template.d.ts +5 -4
  30. package/dist/runtime/app-template.d.ts.map +1 -1
  31. package/dist/runtime/app-template.js +6 -5
  32. package/dist/runtime/app-template.js.map +1 -1
  33. package/dist/runtime/composables/index.d.ts +1 -0
  34. package/dist/runtime/composables/index.d.ts.map +1 -1
  35. package/dist/runtime/composables/index.js +1 -0
  36. package/dist/runtime/composables/index.js.map +1 -1
  37. package/dist/runtime/composables/use-inject.d.ts +29 -0
  38. package/dist/runtime/composables/use-inject.d.ts.map +1 -0
  39. package/dist/runtime/composables/use-inject.js +48 -0
  40. package/dist/runtime/composables/use-inject.js.map +1 -0
  41. package/dist/runtime/entry-server-template.d.ts +1 -1
  42. package/dist/runtime/entry-server-template.d.ts.map +1 -1
  43. package/dist/runtime/entry-server-template.js +20 -0
  44. package/dist/runtime/entry-server-template.js.map +1 -1
  45. package/dist/types/config.d.ts +0 -1
  46. package/dist/types/config.d.ts.map +1 -1
  47. package/dist/types/config.js.map +1 -1
  48. package/docs/cli.md +1 -1
  49. package/docs/composables.md +37 -0
  50. package/docs/configuration.md +2 -11
  51. package/docs/getting-started.md +2 -100
  52. package/docs/plugins.md +23 -15
  53. package/docs/rendering-modes.md +3 -4
  54. package/docs/testing.md +3 -3
  55. package/e2e/kitchen-sink/app/pages/(auth)/protected.ts +1 -5
  56. package/e2e/kitchen-sink/cer-auto-imports.d.ts +1 -0
  57. package/package.json +1 -1
  58. package/src/__tests__/plugin/build-ssr.test.ts +10 -0
  59. package/src/__tests__/plugin/cer-app-plugin.test.ts +15 -0
  60. package/src/__tests__/plugin/dev-server.test.ts +1 -1
  61. package/src/__tests__/plugin/dts-generator.test.ts +5 -0
  62. package/src/__tests__/plugin/entry-server-template.test.ts +24 -0
  63. package/src/__tests__/plugin/generated-dir.test.ts +8 -39
  64. package/src/__tests__/plugin/resolve-config.test.ts +0 -5
  65. package/src/__tests__/plugin/transforms/auto-import.test.ts +7 -0
  66. package/src/__tests__/runtime/use-inject-client.test.ts +67 -0
  67. package/src/__tests__/runtime/use-inject.test.ts +66 -0
  68. package/src/__tests__/types/config.test.ts +1 -1
  69. package/src/cli/create/index.ts +12 -8
  70. package/src/cli/create/templates/spa/.gitignore.tpl +25 -0
  71. package/src/cli/create/templates/spa/index.html.tpl +1 -1
  72. package/src/cli/create/templates/ssg/.gitignore.tpl +25 -0
  73. package/src/cli/create/templates/ssg/index.html.tpl +1 -1
  74. package/src/cli/create/templates/ssr/.gitignore.tpl +25 -0
  75. package/src/cli/create/templates/ssr/cer.config.ts.tpl +0 -1
  76. package/src/cli/create/templates/ssr/index.html.tpl +1 -1
  77. package/src/plugin/build-ssr.ts +18 -0
  78. package/src/plugin/dev-server.ts +1 -1
  79. package/src/plugin/dts-generator.ts +1 -1
  80. package/src/plugin/generated-dir.ts +44 -31
  81. package/src/plugin/index.ts +9 -1
  82. package/src/plugin/transforms/auto-import.ts +2 -2
  83. package/src/runtime/app-template.ts +6 -5
  84. package/src/runtime/composables/index.ts +1 -0
  85. package/src/runtime/composables/use-inject.ts +49 -0
  86. package/src/runtime/entry-server-template.ts +20 -0
  87. package/src/types/config.ts +0 -1
  88. package/dist/cli/create/templates/spa/app/app.ts.tpl +0 -93
  89. package/dist/cli/create/templates/ssg/app/app.ts.tpl +0 -97
  90. package/dist/cli/create/templates/ssr/app/app.ts.tpl +0 -97
  91. package/src/cli/create/templates/spa/app/app.ts.tpl +0 -93
  92. package/src/cli/create/templates/ssg/app/app.ts.tpl +0 -97
  93. package/src/cli/create/templates/ssr/app/app.ts.tpl +0 -97
@@ -76,7 +76,7 @@ const RUNTIME_GLOBALS = [
76
76
 
77
77
  const DIRECTIVE_GLOBALS = ['when', 'each', 'match', 'anchorBlock']
78
78
 
79
- const FRAMEWORK_GLOBALS = ['useHead', 'usePageData']
79
+ const FRAMEWORK_GLOBALS = ['useHead', 'usePageData', 'useInject']
80
80
 
81
81
  /**
82
82
  * Scans a composables directory and returns a map of export name → file path.
@@ -13,16 +13,6 @@ export function getGeneratedDir(root: string): string {
13
13
  return join(root, GENERATED_DIR_NAME)
14
14
  }
15
15
 
16
- /**
17
- * Returns the app entry file path to use for builds and the dev server.
18
- * Prefers the consumer's `app/app.ts` when it exists; falls back to `.cer/app.ts`.
19
- */
20
- export function resolveAppEntry(config: ResolvedCerConfig): string {
21
- const userEntry = join(config.srcDir, 'app.ts')
22
- if (existsSync(userEntry)) return userEntry
23
- return join(getGeneratedDir(config.root), 'app.ts')
24
- }
25
-
26
16
  /**
27
17
  * Returns the HTML entry path to use for builds.
28
18
  * Prefers the consumer's root-level `index.html` when it exists;
@@ -35,13 +25,10 @@ export function resolveHtmlEntry(config: ResolvedCerConfig): string {
35
25
  }
36
26
 
37
27
  /**
38
- * Generates the content for a default `index.html`.
39
- * The script src points to the consumer's `app/app.ts` if it exists,
40
- * otherwise to the generated `.cer/app.ts`.
28
+ * Generates the content for the default `.cer/index.html`.
29
+ * Always points to the generated `/.cer/app.ts` entry.
41
30
  */
42
- export function generateDefaultHtml(config: ResolvedCerConfig): string {
43
- const userEntry = join(config.srcDir, 'app.ts')
44
- const scriptSrc = existsSync(userEntry) ? '/app/app.ts' : '/.cer/app.ts'
31
+ export function generateDefaultHtml(): string {
45
32
  return `<!DOCTYPE html>
46
33
  <html lang="en">
47
34
  <head>
@@ -51,34 +38,61 @@ export function generateDefaultHtml(config: ResolvedCerConfig): string {
51
38
  </head>
52
39
  <body>
53
40
  <cer-layout-view></cer-layout-view>
54
- <script type="module" src="${scriptSrc}"></script>
41
+ <script type="module" src="/.cer/app.ts"></script>
55
42
  </body>
56
43
  </html>
57
44
  `
58
45
  }
59
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
+
60
74
  /**
61
- * Ensures `.cer/` is listed in the project's `.gitignore`.
62
- * Creates `.gitignore` if it does not exist.
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.
63
77
  */
64
78
  function ensureGitignore(root: string): void {
65
79
  const gitignorePath = join(root, '.gitignore')
66
- const entry = `${GENERATED_DIR_NAME}/`
80
+ const cerEntry = `${GENERATED_DIR_NAME}/`
67
81
 
68
82
  if (existsSync(gitignorePath)) {
69
83
  const content = readFileSync(gitignorePath, 'utf-8')
70
- if (!content.includes(entry) && !content.includes(`${GENERATED_DIR_NAME}\n`)) {
71
- appendFileSync(gitignorePath, `\n# CER App generated directory\n${entry}\n`)
84
+ if (!content.includes(cerEntry) && !content.includes(`${GENERATED_DIR_NAME}\n`)) {
85
+ appendFileSync(gitignorePath, `\n# CER App generated directory\n${cerEntry}\n`)
72
86
  }
73
87
  } else {
74
- writeFileSync(gitignorePath, `# CER App generated directory\n${entry}\n`)
88
+ writeFileSync(gitignorePath, GITIGNORE_DEFAULTS)
75
89
  }
76
90
  }
77
91
 
78
92
  /**
79
93
  * Writes all generated files to `.cer/`:
80
- * - `.cer/app.ts` default entry (only when `app/app.ts` does not exist)
81
- * - `.cer/index.html` — default HTML shell
94
+ * - `.cer/app.ts` framework entry (always regenerated)
95
+ * - `.cer/index.html` — default HTML shell (always regenerated)
82
96
  * - `.cer/tsconfig.json` — written by dts-generator via writeTsconfigPaths
83
97
  *
84
98
  * Also ensures `.cer/` is listed in `.gitignore`.
@@ -89,14 +103,13 @@ export function writeGeneratedDir(config: ResolvedCerConfig): void {
89
103
  mkdirSync(dir, { recursive: true })
90
104
  }
91
105
 
92
- // Write default app.ts only when the consumer has not provided one.
93
- const userEntry = join(config.srcDir, 'app.ts')
94
- if (!existsSync(userEntry)) {
95
- writeFileSync(join(dir, 'app.ts'), APP_ENTRY_TEMPLATE, 'utf-8')
96
- }
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')
97
110
 
98
111
  // Always write the default index.html so builds and the dev server can use it.
99
- writeFileSync(join(dir, 'index.html'), generateDefaultHtml(config), 'utf-8')
112
+ writeFileSync(join(dir, 'index.html'), generateDefaultHtml(), 'utf-8')
100
113
 
101
114
  ensureGitignore(config.root)
102
115
  }
@@ -8,6 +8,7 @@ import { autoImportTransform } from './transforms/auto-import.js'
8
8
  import { scanComposableExports, writeAutoImportDts, writeTsconfigPaths } from './dts-generator.js'
9
9
  import { configureCerDevServer } from './dev-server.js'
10
10
  import { writeGeneratedDir, getGeneratedDir } from './generated-dir.js'
11
+ import { APP_ENTRY_TEMPLATE } from '../runtime/app-template.js'
11
12
  import { generateRoutesCode } from './virtual/routes.js'
12
13
  import { generateLayoutsCode } from './virtual/layouts.js'
13
14
  import { generateComponentsCode } from './virtual/components.js'
@@ -40,6 +41,11 @@ const RESOLVED_IDS = Object.fromEntries(
40
41
  Object.entries(VIRTUAL_IDS).map(([k, v]) => [k, `\0${v}`]),
41
42
  ) as Record<keyof typeof VIRTUAL_IDS, string>
42
43
 
44
+ // The generated app entry is served as a virtual module so Vite doesn't need
45
+ // to find it on disk (Vite's fs security policy blocks paths starting with `/.`).
46
+ const APP_ENTRY_URL = '/.cer/app.ts'
47
+ const RESOLVED_APP_ENTRY = '\0cer-app-entry'
48
+
43
49
  /**
44
50
  * Fills in default values for all config fields and resolves absolute paths.
45
51
  */
@@ -62,7 +68,6 @@ export function resolveConfig(userConfig: CerAppConfig, root: string = process.c
62
68
  port: userConfig.port ?? 3000,
63
69
  ssr: {
64
70
  dsd: userConfig.ssr?.dsd ?? true,
65
- streaming: userConfig.ssr?.streaming ?? false,
66
71
  },
67
72
  ssg: {
68
73
  routes: userConfig.ssg?.routes ?? 'auto',
@@ -215,12 +220,15 @@ export function cerApp(userConfig: CerAppConfig = {}): Plugin[] {
215
220
  },
216
221
 
217
222
  resolveId(id: string) {
223
+ if (id === APP_ENTRY_URL) return RESOLVED_APP_ENTRY
218
224
  if ((Object.values(VIRTUAL_IDS) as string[]).includes(id)) {
219
225
  return `\0${id}`
220
226
  }
221
227
  },
222
228
 
223
229
  async load(id: string) {
230
+ if (id === RESOLVED_APP_ENTRY) return APP_ENTRY_TEMPLATE
231
+
224
232
  const allResolved = Object.values(RESOLVED_IDS) as string[]
225
233
  if (!allResolved.includes(id)) return null
226
234
 
@@ -11,9 +11,9 @@ const RUNTIME_IMPORTS = `import { component, html, css, ref, computed, watch, wa
11
11
 
12
12
  const DIRECTIVE_IMPORTS = `import { when, each, match, anchorBlock } from '@jasonshimmy/custom-elements-runtime/directives';`
13
13
 
14
- const FRAMEWORK_IMPORTS = `import { useHead, usePageData } from '@jasonshimmy/vite-plugin-cer-app/composables';`
14
+ const FRAMEWORK_IMPORTS = `import { useHead, usePageData, useInject } from '@jasonshimmy/vite-plugin-cer-app/composables';`
15
15
 
16
- const FRAMEWORK_IDENTIFIERS = ['useHead', 'usePageData']
16
+ const FRAMEWORK_IDENTIFIERS = ['useHead', 'usePageData', 'useInject']
17
17
 
18
18
  const RUNTIME_IDENTIFIERS = [
19
19
  'component',
@@ -1,11 +1,12 @@
1
1
  /**
2
- * Template string for the default `.cer/app.ts` client entry point.
2
+ * Template string for `.cer/app.ts` — the framework client entry point.
3
3
  *
4
- * Written to `.cer/app.ts` when the consumer does not provide `app/app.ts`.
5
- * Consumers can override by creating their own `app/app.ts`.
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.
6
7
  */
7
- export const APP_ENTRY_TEMPLATE = `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app
8
- // This is the default client entry point. Create app/app.ts to override it.
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.
9
10
 
10
11
  import '@jasonshimmy/custom-elements-runtime/css'
11
12
  import 'virtual:cer-jit-css'
@@ -1,3 +1,4 @@
1
1
  export { useHead, beginHeadCollection, endHeadCollection, serializeHeadTags } from './use-head.js'
2
2
  export type { HeadInput } from './use-head.js'
3
3
  export { usePageData } from './use-page-data.js'
4
+ export { useInject } from './use-inject.js'
@@ -0,0 +1,49 @@
1
+ import { inject } from '@jasonshimmy/custom-elements-runtime'
2
+
3
+ const _g = globalThis as Record<string, unknown>
4
+ const _PROVIDES_KEY = '__cerPluginProvides'
5
+
6
+ /**
7
+ * useInject — reads a value provided by a plugin via plugin.setup()'s provide().
8
+ *
9
+ * Works consistently across all rendering modes:
10
+ *
11
+ * - **SPA/Client**: Uses inject() from the component context tree (established
12
+ * by cer-layout-view calling provide() for each plugin-provided value).
13
+ *
14
+ * - **SSR/SSG**: Reads from globalThis.__cerPluginProvides, populated when the
15
+ * server entry runs plugin.setup() before rendering.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * // In a plugin (app/plugins/my-plugin.ts):
20
+ * export default {
21
+ * name: 'my-plugin',
22
+ * setup({ provide }) {
23
+ * provide('my-service', { greet: () => 'hello' })
24
+ * }
25
+ * }
26
+ *
27
+ * // In a component:
28
+ * component('my-page', () => {
29
+ * const service = useInject<{ greet(): string }>('my-service')
30
+ * })
31
+ * ```
32
+ */
33
+ export function useInject<T = unknown>(key: string, defaultValue?: T): T | undefined {
34
+ // Server-side (SSR/SSG): read from the global plugin provides map.
35
+ // __cerPluginProvides is populated by the server entry before the render pass.
36
+ if (typeof document === 'undefined') {
37
+ const pluginProvides = _g[_PROVIDES_KEY] as Map<PropertyKey, unknown> | undefined
38
+ const value = pluginProvides?.get(key)
39
+ return value !== undefined ? (value as T) : defaultValue
40
+ }
41
+
42
+ // Client-side: inject() walks the component context tree established by
43
+ // cer-layout-view's provide() calls. Falls back to __cerPluginProvides for
44
+ // reads before cer-layout-view mounts (e.g. during plugin-registered components).
45
+ const value = inject<T>(key)
46
+ if (value !== undefined) return value
47
+ const pluginProvides = _g[_PROVIDES_KEY] as Map<PropertyKey, unknown> | undefined
48
+ return (pluginProvides?.get(key) as T | undefined) ?? defaultValue
49
+ }
@@ -28,6 +28,25 @@ registerBuiltinComponents()
28
28
  // minimal set (&lt;, &gt;, &amp; …) and re-escapes everything else.
29
29
  registerEntityMap(entitiesJson)
30
30
 
31
+ // Run plugins once at server startup so their provide() values are available
32
+ // to useInject() during every SSR render pass. Stored on globalThis so all
33
+ // dynamically-imported page chunks share the same reference (same pattern as
34
+ // __CER_HEAD_COLLECTOR__ and __CER_DATA_STORE__).
35
+ const _pluginProvides = new Map()
36
+ ;(globalThis).__cerPluginProvides = _pluginProvides
37
+ const _pluginsReady = (async () => {
38
+ const _bootstrapRouter = initRouter({ routes })
39
+ for (const plugin of plugins) {
40
+ if (plugin && typeof plugin.setup === 'function') {
41
+ await plugin.setup({
42
+ router: _bootstrapRouter,
43
+ provide: (key, value) => _pluginProvides.set(key, value),
44
+ config: {},
45
+ })
46
+ }
47
+ }
48
+ })()
49
+
31
50
  // Async-local storage for request-scoped SSR loader data.
32
51
  // Using AsyncLocalStorage ensures concurrent SSR renders (e.g. SSG with
33
52
  // concurrency > 1) never see each other's data — each request's async chain
@@ -109,6 +128,7 @@ function _mergeWithClientTemplate(ssrHtml, clientTemplate) {
109
128
  * context so concurrent renders never share state.
110
129
  */
111
130
  const vnodeFactory = async (req) => {
131
+ await _pluginsReady
112
132
  const router = initRouter({ routes, initialUrl: req.url ?? '/' })
113
133
  const current = router.getCurrent()
114
134
  const { route, params } = router.matchRoute(current.path)
@@ -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 }