@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.
- package/CHANGELOG.md +8 -0
- package/README.md +2 -0
- package/commits.txt +1 -1
- package/dist/cli/create/index.js +7 -3
- package/dist/cli/create/index.js.map +1 -1
- package/dist/cli/create/templates/spa/.gitignore.tpl +25 -0
- package/dist/cli/create/templates/spa/index.html.tpl +1 -1
- package/dist/cli/create/templates/ssg/.gitignore.tpl +25 -0
- package/dist/cli/create/templates/ssg/index.html.tpl +1 -1
- package/dist/cli/create/templates/ssr/.gitignore.tpl +25 -0
- package/dist/cli/create/templates/ssr/cer.config.ts.tpl +0 -1
- package/dist/cli/create/templates/ssr/index.html.tpl +1 -1
- package/dist/plugin/build-ssr.d.ts.map +1 -1
- package/dist/plugin/build-ssr.js +18 -0
- package/dist/plugin/build-ssr.js.map +1 -1
- package/dist/plugin/dev-server.d.ts +0 -1
- package/dist/plugin/dev-server.d.ts.map +1 -1
- package/dist/plugin/dts-generator.js +1 -1
- package/dist/plugin/dts-generator.js.map +1 -1
- package/dist/plugin/generated-dir.d.ts +5 -11
- package/dist/plugin/generated-dir.d.ts.map +1 -1
- package/dist/plugin/generated-dir.js +43 -31
- package/dist/plugin/generated-dir.js.map +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +9 -1
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/transforms/auto-import.js +2 -2
- package/dist/plugin/transforms/auto-import.js.map +1 -1
- package/dist/runtime/app-template.d.ts +5 -4
- package/dist/runtime/app-template.d.ts.map +1 -1
- package/dist/runtime/app-template.js +6 -5
- package/dist/runtime/app-template.js.map +1 -1
- package/dist/runtime/composables/index.d.ts +1 -0
- package/dist/runtime/composables/index.d.ts.map +1 -1
- package/dist/runtime/composables/index.js +1 -0
- package/dist/runtime/composables/index.js.map +1 -1
- package/dist/runtime/composables/use-inject.d.ts +29 -0
- package/dist/runtime/composables/use-inject.d.ts.map +1 -0
- package/dist/runtime/composables/use-inject.js +48 -0
- package/dist/runtime/composables/use-inject.js.map +1 -0
- package/dist/runtime/entry-server-template.d.ts +1 -1
- package/dist/runtime/entry-server-template.d.ts.map +1 -1
- package/dist/runtime/entry-server-template.js +20 -0
- package/dist/runtime/entry-server-template.js.map +1 -1
- package/dist/types/config.d.ts +0 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js.map +1 -1
- package/docs/cli.md +1 -1
- package/docs/composables.md +37 -0
- package/docs/configuration.md +2 -11
- package/docs/getting-started.md +2 -100
- package/docs/plugins.md +23 -15
- package/docs/rendering-modes.md +3 -4
- package/docs/testing.md +3 -3
- package/e2e/kitchen-sink/app/pages/(auth)/protected.ts +1 -5
- package/e2e/kitchen-sink/cer-auto-imports.d.ts +1 -0
- package/package.json +1 -1
- package/src/__tests__/plugin/build-ssr.test.ts +10 -0
- package/src/__tests__/plugin/cer-app-plugin.test.ts +15 -0
- package/src/__tests__/plugin/dev-server.test.ts +1 -1
- package/src/__tests__/plugin/dts-generator.test.ts +5 -0
- package/src/__tests__/plugin/entry-server-template.test.ts +24 -0
- package/src/__tests__/plugin/generated-dir.test.ts +8 -39
- package/src/__tests__/plugin/resolve-config.test.ts +0 -5
- package/src/__tests__/plugin/transforms/auto-import.test.ts +7 -0
- package/src/__tests__/runtime/use-inject-client.test.ts +67 -0
- package/src/__tests__/runtime/use-inject.test.ts +66 -0
- package/src/__tests__/types/config.test.ts +1 -1
- package/src/cli/create/index.ts +12 -8
- package/src/cli/create/templates/spa/.gitignore.tpl +25 -0
- package/src/cli/create/templates/spa/index.html.tpl +1 -1
- package/src/cli/create/templates/ssg/.gitignore.tpl +25 -0
- package/src/cli/create/templates/ssg/index.html.tpl +1 -1
- package/src/cli/create/templates/ssr/.gitignore.tpl +25 -0
- package/src/cli/create/templates/ssr/cer.config.ts.tpl +0 -1
- package/src/cli/create/templates/ssr/index.html.tpl +1 -1
- package/src/plugin/build-ssr.ts +18 -0
- package/src/plugin/dev-server.ts +1 -1
- package/src/plugin/dts-generator.ts +1 -1
- package/src/plugin/generated-dir.ts +44 -31
- package/src/plugin/index.ts +9 -1
- package/src/plugin/transforms/auto-import.ts +2 -2
- package/src/runtime/app-template.ts +6 -5
- package/src/runtime/composables/index.ts +1 -0
- package/src/runtime/composables/use-inject.ts +49 -0
- package/src/runtime/entry-server-template.ts +20 -0
- package/src/types/config.ts +0 -1
- package/dist/cli/create/templates/spa/app/app.ts.tpl +0 -93
- package/dist/cli/create/templates/ssg/app/app.ts.tpl +0 -97
- package/dist/cli/create/templates/ssr/app/app.ts.tpl +0 -97
- package/src/cli/create/templates/spa/app/app.ts.tpl +0 -93
- package/src/cli/create/templates/ssg/app/app.ts.tpl +0 -97
- 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
|
|
39
|
-
*
|
|
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(
|
|
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="
|
|
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
|
|
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
|
|
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(
|
|
71
|
-
appendFileSync(gitignorePath, `\n# CER App generated directory\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,
|
|
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`
|
|
81
|
-
* - `.cer/index.html`
|
|
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
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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(
|
|
112
|
+
writeFileSync(join(dir, 'index.html'), generateDefaultHtml(), 'utf-8')
|
|
100
113
|
|
|
101
114
|
ensureGitignore(config.root)
|
|
102
115
|
}
|
package/src/plugin/index.ts
CHANGED
|
@@ -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
|
|
2
|
+
* Template string for `.cer/app.ts` — the framework client entry point.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
//
|
|
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'
|
|
@@ -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 (<, >, & …) 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)
|
package/src/types/config.ts
CHANGED
|
@@ -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 }
|