@jasonshimmy/vite-plugin-cer-app 0.1.0 → 0.1.2
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/IMPLEMENTATION_PLAN.md +1 -1
- package/README.md +7 -7
- package/VITE_PLUGIN_FRAMEWORK_PLAN.md +12 -12
- package/commits.txt +1 -3
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +2 -0
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/preview.d.ts.map +1 -1
- package/dist/cli/commands/preview.js +21 -2
- package/dist/cli/commands/preview.js.map +1 -1
- package/dist/cli/create/index.js +2 -2
- package/dist/cli/create/index.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/plugin/build-ssg.d.ts.map +1 -1
- package/dist/plugin/build-ssg.js +10 -21
- package/dist/plugin/build-ssg.js.map +1 -1
- package/dist/plugin/build-ssr.d.ts.map +1 -1
- package/dist/plugin/build-ssr.js +151 -28
- package/dist/plugin/build-ssr.js.map +1 -1
- package/dist/plugin/dts-generator.js +4 -4
- package/dist/plugin/dts-generator.js.map +1 -1
- package/dist/plugin/index.js +2 -2
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/transforms/auto-import.js +3 -3
- package/dist/plugin/transforms/auto-import.js.map +1 -1
- package/dist/plugin/virtual/components.js +3 -3
- package/dist/plugin/virtual/components.js.map +1 -1
- package/dist/plugin/virtual/composables.js +3 -3
- package/dist/plugin/virtual/composables.js.map +1 -1
- package/dist/plugin/virtual/error.js +2 -2
- package/dist/plugin/virtual/error.js.map +1 -1
- package/dist/plugin/virtual/layouts.js +3 -3
- package/dist/plugin/virtual/layouts.js.map +1 -1
- package/dist/plugin/virtual/loading.js +2 -2
- package/dist/plugin/virtual/loading.js.map +1 -1
- package/dist/plugin/virtual/middleware.js +3 -3
- package/dist/plugin/virtual/middleware.js.map +1 -1
- package/dist/plugin/virtual/plugins.js +3 -3
- package/dist/plugin/virtual/plugins.js.map +1 -1
- package/dist/plugin/virtual/routes.d.ts.map +1 -1
- package/dist/plugin/virtual/routes.js +14 -4
- package/dist/plugin/virtual/routes.js.map +1 -1
- package/dist/plugin/virtual/server-api.js +3 -3
- package/dist/plugin/virtual/server-api.js.map +1 -1
- package/dist/plugin/virtual/server-middleware.js +3 -3
- package/dist/plugin/virtual/server-middleware.js.map +1 -1
- package/dist/runtime/app-template.d.ts +1 -1
- package/dist/runtime/app-template.d.ts.map +1 -1
- package/dist/runtime/app-template.js +6 -0
- package/dist/runtime/app-template.js.map +1 -1
- package/dist/runtime/composables/use-page-data.d.ts +15 -6
- package/dist/runtime/composables/use-page-data.d.ts.map +1 -1
- package/dist/runtime/composables/use-page-data.js +30 -9
- package/dist/runtime/composables/use-page-data.js.map +1 -1
- 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 +138 -17
- package/dist/runtime/entry-server-template.js.map +1 -1
- package/docs/cli.md +2 -2
- package/docs/configuration.md +4 -4
- package/docs/data-loading.md +8 -7
- package/docs/getting-started.md +5 -5
- package/docs/head-management.md +3 -3
- package/docs/middleware.md +2 -2
- package/docs/plugins.md +1 -1
- package/docs/rendering-modes.md +1 -1
- package/docs/routing.md +1 -1
- package/docs/server-api.md +10 -1
- package/docs/testing.md +4 -4
- package/package.json +1 -1
- package/src/__tests__/index.test.ts +21 -0
- package/src/__tests__/plugin/build-ssg.test.ts +265 -0
- package/src/__tests__/plugin/build-ssr.test.ts +180 -0
- package/src/__tests__/plugin/cer-app-plugin.test.ts +409 -0
- package/src/__tests__/plugin/dts-generator.test.ts +246 -0
- package/src/__tests__/plugin/resolve-config.test.ts +158 -0
- package/src/__tests__/plugin/transforms/auto-import.test.ts +1 -1
- package/src/__tests__/plugin/virtual/components.test.ts +1 -1
- package/src/__tests__/plugin/virtual/composables.test.ts +1 -1
- package/src/__tests__/plugin/virtual/error.test.ts +71 -0
- package/src/__tests__/plugin/virtual/layouts.test.ts +1 -1
- package/src/__tests__/plugin/virtual/loading.test.ts +72 -0
- package/src/__tests__/plugin/virtual/middleware.test.ts +1 -1
- package/src/__tests__/plugin/virtual/plugins.test.ts +1 -1
- package/src/__tests__/plugin/virtual/routes.test.ts +1 -1
- package/src/__tests__/plugin/virtual/server-api.test.ts +1 -1
- package/src/__tests__/plugin/virtual/server-middleware.test.ts +102 -0
- package/src/__tests__/runtime/use-page-data.test.ts +81 -5
- package/src/__tests__/types/config.test.ts +23 -0
- package/src/cli/commands/generate.ts +2 -0
- package/src/cli/commands/preview.ts +21 -2
- package/src/cli/create/index.ts +2 -2
- package/src/cli/create/templates/spa/cer.config.ts.tpl +1 -1
- package/src/cli/create/templates/spa/package.json.tpl +1 -1
- package/src/cli/create/templates/ssg/cer.config.ts.tpl +1 -1
- package/src/cli/create/templates/ssg/package.json.tpl +1 -1
- package/src/cli/create/templates/ssr/cer.config.ts.tpl +1 -1
- package/src/cli/create/templates/ssr/package.json.tpl +1 -1
- package/src/cli/index.ts +1 -1
- package/src/plugin/build-ssg.ts +9 -22
- package/src/plugin/build-ssr.ts +150 -28
- package/src/plugin/dts-generator.ts +4 -4
- package/src/plugin/index.ts +2 -2
- package/src/plugin/transforms/auto-import.ts +3 -3
- package/src/plugin/virtual/components.ts +3 -3
- package/src/plugin/virtual/composables.ts +3 -3
- package/src/plugin/virtual/error.ts +2 -2
- package/src/plugin/virtual/layouts.ts +3 -3
- package/src/plugin/virtual/loading.ts +2 -2
- package/src/plugin/virtual/middleware.ts +3 -3
- package/src/plugin/virtual/plugins.ts +3 -3
- package/src/plugin/virtual/routes.ts +15 -4
- package/src/plugin/virtual/server-api.ts +3 -3
- package/src/plugin/virtual/server-middleware.ts +3 -3
- package/src/runtime/app-template.ts +6 -0
- package/src/runtime/composables/use-page-data.ts +31 -9
- package/src/runtime/entry-server-template.ts +138 -17
- package/tsconfig.build.json +1 -1
- package/dist/__tests__/plugin/path-utils.test.d.ts +0 -2
- package/dist/__tests__/plugin/path-utils.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/path-utils.test.js +0 -305
- package/dist/__tests__/plugin/path-utils.test.js.map +0 -1
- package/dist/__tests__/plugin/scanner.test.d.ts +0 -2
- package/dist/__tests__/plugin/scanner.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/scanner.test.js +0 -143
- package/dist/__tests__/plugin/scanner.test.js.map +0 -1
- package/dist/__tests__/plugin/transforms/auto-import.test.d.ts +0 -2
- package/dist/__tests__/plugin/transforms/auto-import.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/transforms/auto-import.test.js +0 -151
- package/dist/__tests__/plugin/transforms/auto-import.test.js.map +0 -1
- package/dist/__tests__/plugin/transforms/head-inject.test.d.ts +0 -2
- package/dist/__tests__/plugin/transforms/head-inject.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/transforms/head-inject.test.js +0 -151
- package/dist/__tests__/plugin/transforms/head-inject.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/components.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/components.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/components.test.js +0 -47
- package/dist/__tests__/plugin/virtual/components.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/composables.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/composables.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/composables.test.js +0 -48
- package/dist/__tests__/plugin/virtual/composables.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/layouts.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/layouts.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/layouts.test.js +0 -59
- package/dist/__tests__/plugin/virtual/layouts.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/middleware.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/middleware.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/middleware.test.js +0 -58
- package/dist/__tests__/plugin/virtual/middleware.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/plugins.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/plugins.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/plugins.test.js +0 -73
- package/dist/__tests__/plugin/virtual/plugins.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/routes.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/routes.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/routes.test.js +0 -167
- package/dist/__tests__/plugin/virtual/routes.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/server-api.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/server-api.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/server-api.test.js +0 -72
- package/dist/__tests__/plugin/virtual/server-api.test.js.map +0 -1
- package/dist/__tests__/runtime/use-head.test.d.ts +0 -2
- package/dist/__tests__/runtime/use-head.test.d.ts.map +0 -1
- package/dist/__tests__/runtime/use-head.test.js +0 -202
- package/dist/__tests__/runtime/use-head.test.js.map +0 -1
- package/dist/__tests__/runtime/use-page-data.test.d.ts +0 -2
- package/dist/__tests__/runtime/use-page-data.test.d.ts.map +0 -1
- package/dist/__tests__/runtime/use-page-data.test.js +0 -41
- package/dist/__tests__/runtime/use-page-data.test.js.map +0 -1
package/src/plugin/build-ssr.ts
CHANGED
|
@@ -3,66 +3,189 @@ import { join, resolve } from 'pathe'
|
|
|
3
3
|
import { existsSync } from 'node:fs'
|
|
4
4
|
import type { ResolvedCerConfig } from './dev-server.js'
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Resolves the client build entry point for an SSR/SSG build.
|
|
8
|
+
*
|
|
9
|
+
* Priority order:
|
|
10
|
+
* 1. `index.html` at the project root — preferred because Vite writes a
|
|
11
|
+
* processed `index.html` to `dist/client/` with correct asset references,
|
|
12
|
+
* which `renderPath` then uses as the shell template for SSG pages.
|
|
13
|
+
* 2. `app/entry-client.ts` — fallback for projects that handle HTML injection
|
|
14
|
+
* themselves (e.g. a custom Express server).
|
|
15
|
+
* 3. `app/app.ts` — last resort (same bundle, no DSD hydration preamble).
|
|
16
|
+
*/
|
|
17
|
+
function resolveClientEntry(config: ResolvedCerConfig): string {
|
|
18
|
+
const indexHtml = resolve(config.root, 'index.html')
|
|
19
|
+
if (existsSync(indexHtml)) return indexHtml
|
|
20
|
+
const entryClient = resolve(config.srcDir, 'entry-client.ts')
|
|
21
|
+
if (existsSync(entryClient)) return entryClient
|
|
22
|
+
return resolve(config.srcDir, 'app.ts')
|
|
23
|
+
}
|
|
24
|
+
|
|
6
25
|
/**
|
|
7
26
|
* The server entry template that wires all virtual modules together and
|
|
8
27
|
* exports a request handler for Node.js (Express-compatible).
|
|
9
28
|
*/
|
|
10
|
-
function generateServerEntryCode(
|
|
11
|
-
return `// AUTO-GENERATED server entry by vite-plugin-cer-app
|
|
29
|
+
function generateServerEntryCode(): string {
|
|
30
|
+
return `// AUTO-GENERATED server entry by @jasonshimmy/vite-plugin-cer-app
|
|
31
|
+
import { readFileSync, existsSync } from 'node:fs'
|
|
32
|
+
import { dirname, join } from 'node:path'
|
|
33
|
+
import { fileURLToPath } from 'node:url'
|
|
12
34
|
import 'virtual:cer-components'
|
|
13
35
|
import routes from 'virtual:cer-routes'
|
|
14
36
|
import layouts from 'virtual:cer-layouts'
|
|
15
37
|
import plugins from 'virtual:cer-plugins'
|
|
16
38
|
import apiRoutes from 'virtual:cer-server-api'
|
|
17
|
-
import {
|
|
39
|
+
import { registerBuiltinComponents } from '@jasonshimmy/custom-elements-runtime'
|
|
40
|
+
import { registerEntityMap, renderToStringWithJITCSSDSD, DSD_POLYFILL_SCRIPT } from '@jasonshimmy/custom-elements-runtime/ssr'
|
|
41
|
+
import entitiesJson from '@jasonshimmy/custom-elements-runtime/entities.json'
|
|
18
42
|
import { initRouter } from '@jasonshimmy/custom-elements-runtime/router'
|
|
19
|
-
import { createStreamingSSRHandler } from '@jasonshimmy/custom-elements-runtime/ssr-middleware'
|
|
20
43
|
|
|
21
44
|
registerBuiltinComponents()
|
|
22
45
|
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
46
|
+
// Pre-load the full HTML entity map so named entities like — decode
|
|
47
|
+
// correctly during SSR. Without this the bundled runtime falls back to a
|
|
48
|
+
// minimal set (<, >, & …) and re-escapes everything else.
|
|
49
|
+
registerEntityMap(entitiesJson)
|
|
50
|
+
|
|
51
|
+
// Load the Vite-built client index.html (dist/client/index.html) so every SSR
|
|
52
|
+
// response includes the client-side scripts needed for hydration and routing.
|
|
53
|
+
// The server bundle lives at dist/server/server.js, so ../client resolves correctly.
|
|
54
|
+
const _clientTemplatePath = join(dirname(fileURLToPath(import.meta.url)), '../client/index.html')
|
|
55
|
+
const _clientTemplate = existsSync(_clientTemplatePath)
|
|
56
|
+
? readFileSync(_clientTemplatePath, 'utf-8')
|
|
57
|
+
: null
|
|
58
|
+
|
|
59
|
+
// Merge the SSR rendered body with the Vite client shell so the final page
|
|
60
|
+
// contains both pre-rendered DSD content and the client bundle scripts.
|
|
61
|
+
function _mergeWithClientTemplate(ssrHtml, clientTemplate) {
|
|
62
|
+
const headTag = '<head>', headCloseTag = '</head>'
|
|
63
|
+
const bodyTag = '<body>', bodyCloseTag = '</body>'
|
|
64
|
+
const headStart = ssrHtml.indexOf(headTag)
|
|
65
|
+
const headEnd = ssrHtml.indexOf(headCloseTag)
|
|
66
|
+
const bodyStart = ssrHtml.indexOf(bodyTag)
|
|
67
|
+
const bodyEnd = ssrHtml.lastIndexOf(bodyCloseTag)
|
|
68
|
+
const ssrHead = headStart >= 0 && headEnd > headStart
|
|
69
|
+
? ssrHtml.slice(headStart + headTag.length, headEnd).trim() : ''
|
|
70
|
+
const ssrBody = bodyStart >= 0 && bodyEnd > bodyStart
|
|
71
|
+
? ssrHtml.slice(bodyStart + bodyTag.length, bodyEnd).trim() : ssrHtml
|
|
72
|
+
// Hoist <style> elements from the SSR body into the document <head> so
|
|
73
|
+
// JIT CSS rules are applied before the layout paints.
|
|
74
|
+
const headParts = ssrHead ? [ssrHead] : []
|
|
75
|
+
let ssrBodyContent = ssrBody
|
|
76
|
+
let pos = 0
|
|
77
|
+
while (pos < ssrBodyContent.length) {
|
|
78
|
+
const styleOpen = ssrBodyContent.indexOf('<style', pos)
|
|
79
|
+
if (styleOpen < 0) break
|
|
80
|
+
const styleClose = ssrBodyContent.indexOf('</style>', styleOpen)
|
|
81
|
+
if (styleClose < 0) break
|
|
82
|
+
headParts.push(ssrBodyContent.slice(styleOpen, styleClose + 8))
|
|
83
|
+
ssrBodyContent = ssrBodyContent.slice(0, styleOpen) + ssrBodyContent.slice(styleClose + 8)
|
|
84
|
+
pos = styleOpen
|
|
85
|
+
}
|
|
86
|
+
ssrBodyContent = ssrBodyContent.trim()
|
|
87
|
+
// Inject the pre-rendered layout+page as light DOM of the app mount element
|
|
88
|
+
// so it is visible before JS boots, then the client router takes over.
|
|
89
|
+
let merged = clientTemplate
|
|
90
|
+
if (merged.includes('<cer-layout-view></cer-layout-view>')) {
|
|
91
|
+
merged = merged.replace('<cer-layout-view></cer-layout-view>',
|
|
92
|
+
'<cer-layout-view>' + ssrBodyContent + '</cer-layout-view>')
|
|
93
|
+
} else if (merged.includes('<div id="app"></div>')) {
|
|
94
|
+
merged = merged.replace('<div id="app"></div>',
|
|
95
|
+
'<div id="app">' + ssrBodyContent + '</div>')
|
|
96
|
+
}
|
|
97
|
+
const headAdditions = headParts.filter(Boolean).join('\\n')
|
|
98
|
+
if (headAdditions) merged = merged.replace('</head>', headAdditions + '\\n</head>')
|
|
99
|
+
return merged
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Per-request async setup: initialize a fresh router, resolve the matched
|
|
103
|
+
// route and layout, pre-load the page module, and call the data loader.
|
|
104
|
+
// Returns the vnode tree, router, head additions, and the raw loader data.
|
|
27
105
|
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
106
|
+
// loaderData is returned (not set on globalThis) so the handler can assign it
|
|
107
|
+
// synchronously right before renderToStringWithJITCSS — guaranteeing that
|
|
108
|
+
// concurrent renders (SSG concurrency > 1) never race on a shared global.
|
|
109
|
+
const _prepareRequest = async (req) => {
|
|
31
110
|
const router = initRouter({ routes, initialUrl: req.url ?? '/' })
|
|
32
111
|
const current = router.getCurrent()
|
|
33
112
|
const { route, params } = router.matchRoute(current.path)
|
|
34
113
|
const layoutName = route?.meta?.layout ?? 'default'
|
|
35
114
|
const layoutTag = layouts[layoutName]
|
|
36
|
-
const inner = html\`<router-view></router-view>\`
|
|
37
|
-
const vnode = layoutTag
|
|
38
|
-
? { tag: layoutTag, props: {}, children: [inner] }
|
|
39
|
-
: inner
|
|
40
115
|
|
|
41
|
-
//
|
|
116
|
+
// Pre-load the page module so we can embed the component tag directly.
|
|
117
|
+
// This avoids the async router-view (which injects content via script tags
|
|
118
|
+
// and breaks Declarative Shadow DOM on initial parse).
|
|
119
|
+
let pageVnode = { tag: 'div', props: {}, children: [] }
|
|
42
120
|
let head
|
|
121
|
+
let loaderData = null
|
|
43
122
|
if (route?.load) {
|
|
44
123
|
try {
|
|
45
124
|
const mod = await route.load()
|
|
125
|
+
const pageTag = mod.default
|
|
126
|
+
if (pageTag) {
|
|
127
|
+
pageVnode = { tag: pageTag, props: { attrs: { ...params } }, children: [] }
|
|
128
|
+
}
|
|
46
129
|
if (typeof mod.loader === 'function') {
|
|
47
130
|
const query = current.query ?? {}
|
|
48
131
|
const data = await mod.loader({ params, query, req })
|
|
49
132
|
if (data !== undefined && data !== null) {
|
|
133
|
+
loaderData = data
|
|
50
134
|
head = \`<script>window.__CER_DATA__ = \${JSON.stringify(data)}</script>\`
|
|
51
135
|
}
|
|
52
136
|
}
|
|
53
137
|
} catch {
|
|
54
|
-
//
|
|
138
|
+
// Non-fatal: loader errors fall back to an empty page; client will refetch.
|
|
55
139
|
}
|
|
56
140
|
}
|
|
57
141
|
|
|
58
|
-
|
|
142
|
+
const vnode = layoutTag
|
|
143
|
+
? { tag: layoutTag, props: {}, children: [pageVnode] }
|
|
144
|
+
: pageVnode
|
|
145
|
+
|
|
146
|
+
return { vnode, router, head, loaderData }
|
|
59
147
|
}
|
|
60
148
|
|
|
61
|
-
export const handler =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
149
|
+
export const handler = async (req, res) => {
|
|
150
|
+
const { vnode, router, head, loaderData } = await _prepareRequest(req)
|
|
151
|
+
|
|
152
|
+
// Set loader data on globalThis synchronously before the render so
|
|
153
|
+
// usePageData() can read it. Because renderToStringWithJITCSSDSD is entirely
|
|
154
|
+
// synchronous and JavaScript is single-threaded, no concurrent request can
|
|
155
|
+
// overwrite __CER_DATA__ between this assignment and the read inside setup().
|
|
156
|
+
if (loaderData !== null) {
|
|
157
|
+
;(globalThis).__CER_DATA__ = loaderData
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// dsdPolyfill: false — we inject the polyfill manually after merging so it
|
|
161
|
+
// lands at the end of <body>, not inside <cer-layout-view> light DOM where
|
|
162
|
+
// scripts may not execute.
|
|
163
|
+
const { htmlWithStyles } = renderToStringWithJITCSSDSD(vnode, {
|
|
164
|
+
dsdPolyfill: false,
|
|
165
|
+
router,
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
// Clear immediately after the synchronous render so the value never leaks
|
|
169
|
+
// to the next request on this same server process.
|
|
170
|
+
delete (globalThis).__CER_DATA__
|
|
171
|
+
|
|
172
|
+
// Wrap the rendered body in a full HTML document and inject the head additions
|
|
173
|
+
// (loader data script, useHead() tags, JIT styles). No polyfill in body yet.
|
|
174
|
+
const ssrHtml = \`<!DOCTYPE html><html><head>\${head ?? ''}</head><body>\${htmlWithStyles}</body></html>\`
|
|
175
|
+
|
|
176
|
+
let finalHtml = _clientTemplate
|
|
177
|
+
? _mergeWithClientTemplate(ssrHtml, _clientTemplate)
|
|
178
|
+
: ssrHtml
|
|
179
|
+
|
|
180
|
+
// Inject DSD polyfill at end of <body>, outside <cer-layout-view>, so the
|
|
181
|
+
// browser runs it after parsing the declarative shadow roots.
|
|
182
|
+
finalHtml = finalHtml.includes('</body>')
|
|
183
|
+
? finalHtml.replace('</body>', DSD_POLYFILL_SCRIPT + '</body>')
|
|
184
|
+
: finalHtml + DSD_POLYFILL_SCRIPT
|
|
185
|
+
|
|
186
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
|
187
|
+
res.end(finalHtml)
|
|
188
|
+
}
|
|
66
189
|
|
|
67
190
|
export { apiRoutes, plugins, layouts }
|
|
68
191
|
export default handler
|
|
@@ -82,10 +205,9 @@ export async function buildSSR(
|
|
|
82
205
|
const clientOutDir = join(config.root, 'dist/client')
|
|
83
206
|
const serverOutDir = join(config.root, 'dist/server')
|
|
84
207
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
: resolve(config.srcDir, 'app.ts')
|
|
208
|
+
// Resolve the client entry — index.html is preferred so Vite writes a
|
|
209
|
+
// processed index.html to dist/client/ for use as the SSG shell template.
|
|
210
|
+
const clientEntry = resolveClientEntry(config)
|
|
89
211
|
|
|
90
212
|
// Build the client bundle
|
|
91
213
|
console.log('[cer-app] Building client bundle...')
|
|
@@ -103,7 +225,7 @@ export async function buildSSR(
|
|
|
103
225
|
})
|
|
104
226
|
|
|
105
227
|
// Generate server entry source inline via a virtual plugin
|
|
106
|
-
const serverEntryCode = generateServerEntryCode(
|
|
228
|
+
const serverEntryCode = generateServerEntryCode()
|
|
107
229
|
const VIRTUAL_SERVER_ENTRY = 'virtual:cer-server-entry'
|
|
108
230
|
const RESOLVED_SERVER_ENTRY = '\0virtual:cer-server-entry'
|
|
109
231
|
|
|
@@ -94,7 +94,7 @@ export async function generateAutoImportDts(
|
|
|
94
94
|
const exports = composableExports ?? await scanComposableExports(composablesDir)
|
|
95
95
|
|
|
96
96
|
const lines: string[] = [
|
|
97
|
-
'// AUTO-GENERATED by vite-plugin-cer-app',
|
|
97
|
+
'// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app',
|
|
98
98
|
'// Do not edit — regenerated automatically on dev server start and build.',
|
|
99
99
|
'',
|
|
100
100
|
'export {}',
|
|
@@ -115,7 +115,7 @@ export async function generateAutoImportDts(
|
|
|
115
115
|
lines.push('')
|
|
116
116
|
|
|
117
117
|
for (const name of FRAMEWORK_GLOBALS) {
|
|
118
|
-
lines.push(` const ${name}: typeof import('vite-plugin-cer-app/composables')['${name}']`)
|
|
118
|
+
lines.push(` const ${name}: typeof import('@jasonshimmy/vite-plugin-cer-app/composables')['${name}']`)
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
if (exports.size > 0) {
|
|
@@ -151,7 +151,7 @@ export async function generateVirtualModuleDts(
|
|
|
151
151
|
const exports = composableExports ?? await scanComposableExports(composablesDir)
|
|
152
152
|
|
|
153
153
|
const lines: string[] = [
|
|
154
|
-
'// AUTO-GENERATED by vite-plugin-cer-app',
|
|
154
|
+
'// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app',
|
|
155
155
|
'// Do not edit — regenerated automatically on dev server start and build.',
|
|
156
156
|
'',
|
|
157
157
|
`declare module 'virtual:cer-routes' {`,
|
|
@@ -164,7 +164,7 @@ export async function generateVirtualModuleDts(
|
|
|
164
164
|
`declare module 'virtual:cer-components' {}`,
|
|
165
165
|
'',
|
|
166
166
|
`declare module 'virtual:cer-plugins' {`,
|
|
167
|
-
` import type { AppPlugin } from 'vite-plugin-cer-app/types'`,
|
|
167
|
+
` import type { AppPlugin } from '@jasonshimmy/vite-plugin-cer-app/types'`,
|
|
168
168
|
` const plugins: AppPlugin[]`,
|
|
169
169
|
` export default plugins`,
|
|
170
170
|
`}`,
|
package/src/plugin/index.ts
CHANGED
|
@@ -133,7 +133,7 @@ function generateAppConfigModule(config: ResolvedCerConfig): string {
|
|
|
133
133
|
ssr: config.ssr,
|
|
134
134
|
ssg: config.ssg,
|
|
135
135
|
}
|
|
136
|
-
return `// AUTO-GENERATED by vite-plugin-cer-app\nexport const appConfig = ${JSON.stringify(exportedConfig, null, 2)}\nexport default appConfig\n`
|
|
136
|
+
return `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\nexport const appConfig = ${JSON.stringify(exportedConfig, null, 2)}\nexport default appConfig\n`
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
/**
|
|
@@ -183,7 +183,7 @@ export function cerApp(userConfig: CerAppConfig = {}): Plugin[] {
|
|
|
183
183
|
const moduleCache = new Map<string, string>()
|
|
184
184
|
|
|
185
185
|
const cerAppPlugin: Plugin = {
|
|
186
|
-
name: 'vite-plugin-cer-app',
|
|
186
|
+
name: '@jasonshimmy/vite-plugin-cer-app',
|
|
187
187
|
|
|
188
188
|
config(viteConfig) {
|
|
189
189
|
const root = viteConfig.root ? resolve(viteConfig.root) : process.cwd()
|
|
@@ -11,7 +11,7 @@ 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 'vite-plugin-cer-app/composables';`
|
|
14
|
+
const FRAMEWORK_IMPORTS = `import { useHead, usePageData } from '@jasonshimmy/vite-plugin-cer-app/composables';`
|
|
15
15
|
|
|
16
16
|
const FRAMEWORK_IDENTIFIERS = ['useHead', 'usePageData']
|
|
17
17
|
|
|
@@ -160,8 +160,8 @@ function buildComposableImport(code: string, composableExports?: Map<string, str
|
|
|
160
160
|
* Returns true if injection is needed (not already imported).
|
|
161
161
|
*/
|
|
162
162
|
function isFrameworkImportNeeded(code: string): boolean {
|
|
163
|
-
if (code.includes("from 'vite-plugin-cer-app/composables'") ||
|
|
164
|
-
code.includes('from "vite-plugin-cer-app/composables"')) {
|
|
163
|
+
if (code.includes("from '@jasonshimmy/vite-plugin-cer-app/composables'") ||
|
|
164
|
+
code.includes('from "@jasonshimmy/vite-plugin-cer-app/composables"')) {
|
|
165
165
|
return false
|
|
166
166
|
}
|
|
167
167
|
|
|
@@ -7,16 +7,16 @@ import { scanDirectory } from '../scanner.js'
|
|
|
7
7
|
*/
|
|
8
8
|
export async function generateComponentsCode(componentsDir: string): Promise<string> {
|
|
9
9
|
if (!existsSync(componentsDir)) {
|
|
10
|
-
return `// AUTO-GENERATED by vite-plugin-cer-app\n// No components directory found\n`
|
|
10
|
+
return `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\n// No components directory found\n`
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const files = await scanDirectory('**/*.ts', componentsDir)
|
|
14
14
|
|
|
15
15
|
if (files.length === 0) {
|
|
16
|
-
return `// AUTO-GENERATED by vite-plugin-cer-app\n// No components found\n`
|
|
16
|
+
return `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\n// No components found\n`
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const lines: string[] = ['// AUTO-GENERATED by vite-plugin-cer-app', '']
|
|
19
|
+
const lines: string[] = ['// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app', '']
|
|
20
20
|
|
|
21
21
|
for (const file of files) {
|
|
22
22
|
lines.push(`import ${JSON.stringify(file)}`)
|
|
@@ -7,16 +7,16 @@ import { scanDirectory } from '../scanner.js'
|
|
|
7
7
|
*/
|
|
8
8
|
export async function generateComposablesCode(composablesDir: string): Promise<string> {
|
|
9
9
|
if (!existsSync(composablesDir)) {
|
|
10
|
-
return `// AUTO-GENERATED by vite-plugin-cer-app\n// No composables directory found\n`
|
|
10
|
+
return `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\n// No composables directory found\n`
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const files = await scanDirectory('**/*.ts', composablesDir)
|
|
14
14
|
|
|
15
15
|
if (files.length === 0) {
|
|
16
|
-
return `// AUTO-GENERATED by vite-plugin-cer-app\n// No composables found\n`
|
|
16
|
+
return `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\n// No composables found\n`
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const lines: string[] = ['// AUTO-GENERATED by vite-plugin-cer-app', '']
|
|
19
|
+
const lines: string[] = ['// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app', '']
|
|
20
20
|
|
|
21
21
|
for (const file of files) {
|
|
22
22
|
lines.push(`export * from ${JSON.stringify(file)}`)
|
|
@@ -17,7 +17,7 @@ export async function generateErrorCode(srcDir: string): Promise<string> {
|
|
|
17
17
|
|
|
18
18
|
if (!existsSync(errorFile)) {
|
|
19
19
|
return [
|
|
20
|
-
'// AUTO-GENERATED by vite-plugin-cer-app',
|
|
20
|
+
'// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app',
|
|
21
21
|
'export const hasError = false',
|
|
22
22
|
'export const errorTag = null',
|
|
23
23
|
'',
|
|
@@ -25,7 +25,7 @@ export async function generateErrorCode(srcDir: string): Promise<string> {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
return [
|
|
28
|
-
'// AUTO-GENERATED by vite-plugin-cer-app',
|
|
28
|
+
'// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app',
|
|
29
29
|
`import ${JSON.stringify(errorFile)}`,
|
|
30
30
|
'export const hasError = true',
|
|
31
31
|
"export const errorTag = 'page-error'",
|
|
@@ -8,16 +8,16 @@ import { fileToLayoutName, fileToLayoutTagName } from '../path-utils.js'
|
|
|
8
8
|
*/
|
|
9
9
|
export async function generateLayoutsCode(layoutsDir: string): Promise<string> {
|
|
10
10
|
if (!existsSync(layoutsDir)) {
|
|
11
|
-
return `// AUTO-GENERATED by vite-plugin-cer-app\nexport const layouts = {}\nexport default layouts\n`
|
|
11
|
+
return `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\nexport const layouts = {}\nexport default layouts\n`
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const files = await scanDirectory('**/*.ts', layoutsDir)
|
|
15
15
|
|
|
16
16
|
if (files.length === 0) {
|
|
17
|
-
return `// AUTO-GENERATED by vite-plugin-cer-app\nexport const layouts = {}\nexport default layouts\n`
|
|
17
|
+
return `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\nexport const layouts = {}\nexport default layouts\n`
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const lines: string[] = ['// AUTO-GENERATED by vite-plugin-cer-app', '']
|
|
20
|
+
const lines: string[] = ['// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app', '']
|
|
21
21
|
|
|
22
22
|
// Side-effect imports to register the layout custom elements
|
|
23
23
|
for (const file of files) {
|
|
@@ -16,7 +16,7 @@ export async function generateLoadingCode(srcDir: string): Promise<string> {
|
|
|
16
16
|
|
|
17
17
|
if (!existsSync(loadingFile)) {
|
|
18
18
|
return [
|
|
19
|
-
'// AUTO-GENERATED by vite-plugin-cer-app',
|
|
19
|
+
'// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app',
|
|
20
20
|
'export const hasLoading = false',
|
|
21
21
|
'export const loadingTag = null',
|
|
22
22
|
'',
|
|
@@ -24,7 +24,7 @@ export async function generateLoadingCode(srcDir: string): Promise<string> {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
return [
|
|
27
|
-
'// AUTO-GENERATED by vite-plugin-cer-app',
|
|
27
|
+
'// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app',
|
|
28
28
|
`import ${JSON.stringify(loadingFile)}`,
|
|
29
29
|
'export const hasLoading = true',
|
|
30
30
|
"export const loadingTag = 'page-loading'",
|
|
@@ -8,16 +8,16 @@ import { scanDirectory } from '../scanner.js'
|
|
|
8
8
|
*/
|
|
9
9
|
export async function generateMiddlewareCode(middlewareDir: string): Promise<string> {
|
|
10
10
|
if (!existsSync(middlewareDir)) {
|
|
11
|
-
return `// AUTO-GENERATED by vite-plugin-cer-app\nexport const middleware = {}\nexport default middleware\n`
|
|
11
|
+
return `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\nexport const middleware = {}\nexport default middleware\n`
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const files = await scanDirectory('**/*.ts', middlewareDir)
|
|
15
15
|
|
|
16
16
|
if (files.length === 0) {
|
|
17
|
-
return `// AUTO-GENERATED by vite-plugin-cer-app\nexport const middleware = {}\nexport default middleware\n`
|
|
17
|
+
return `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\nexport const middleware = {}\nexport default middleware\n`
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const lines: string[] = ['// AUTO-GENERATED by vite-plugin-cer-app', '']
|
|
20
|
+
const lines: string[] = ['// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app', '']
|
|
21
21
|
|
|
22
22
|
const entries: Array<{ alias: string; name: string }> = []
|
|
23
23
|
|
|
@@ -8,17 +8,17 @@ import { sortPluginFiles } from '../path-utils.js'
|
|
|
8
8
|
*/
|
|
9
9
|
export async function generatePluginsCode(pluginsDir: string): Promise<string> {
|
|
10
10
|
if (!existsSync(pluginsDir)) {
|
|
11
|
-
return `// AUTO-GENERATED by vite-plugin-cer-app\nexport const plugins = []\nexport default plugins\n`
|
|
11
|
+
return `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\nexport const plugins = []\nexport default plugins\n`
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const files = await scanDirectory('**/*.ts', pluginsDir)
|
|
15
15
|
|
|
16
16
|
if (files.length === 0) {
|
|
17
|
-
return `// AUTO-GENERATED by vite-plugin-cer-app\nexport const plugins = []\nexport default plugins\n`
|
|
17
|
+
return `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\nexport const plugins = []\nexport default plugins\n`
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const sorted = sortPluginFiles(files)
|
|
21
|
-
const lines: string[] = ['// AUTO-GENERATED by vite-plugin-cer-app', '']
|
|
21
|
+
const lines: string[] = ['// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app', '']
|
|
22
22
|
|
|
23
23
|
const aliases: string[] = []
|
|
24
24
|
sorted.forEach((file, i) => {
|
|
@@ -51,16 +51,16 @@ function extractLayout(source: string): string | null {
|
|
|
51
51
|
*/
|
|
52
52
|
export async function generateRoutesCode(pagesDir: string): Promise<string> {
|
|
53
53
|
if (!existsSync(pagesDir)) {
|
|
54
|
-
return `// AUTO-GENERATED by vite-plugin-cer-app\nconst routes = []\nexport default routes\n`
|
|
54
|
+
return `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\nconst routes = []\nexport default routes\n`
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
const files = await scanDirectory('**/*.ts', pagesDir)
|
|
58
58
|
|
|
59
59
|
if (files.length === 0) {
|
|
60
|
-
return `// AUTO-GENERATED by vite-plugin-cer-app\nconst routes = []\nexport default routes\n`
|
|
60
|
+
return `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\nconst routes = []\nexport default routes\n`
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
const
|
|
63
|
+
const rawEntries = files.map((f) => {
|
|
64
64
|
const entry = buildRouteEntry(f, pagesDir)
|
|
65
65
|
// 404.ts convention: treat as catch-all not-found route
|
|
66
66
|
if (basename(f) === '404.ts') {
|
|
@@ -68,6 +68,17 @@ export async function generateRoutesCode(pagesDir: string): Promise<string> {
|
|
|
68
68
|
}
|
|
69
69
|
return entry
|
|
70
70
|
})
|
|
71
|
+
|
|
72
|
+
// Deduplicate by routePath — keep the first occurrence after sorting to
|
|
73
|
+
// avoid "Duplicate route path detected" warnings (e.g. when both 404.ts
|
|
74
|
+
// and [...all].ts resolve to the same /:all* catch-all route).
|
|
75
|
+
const seen = new Set<string>()
|
|
76
|
+
const entries = rawEntries.filter((e) => {
|
|
77
|
+
if (seen.has(e.routePath)) return false
|
|
78
|
+
seen.add(e.routePath)
|
|
79
|
+
return true
|
|
80
|
+
})
|
|
81
|
+
|
|
71
82
|
const sorted = sortRoutes(entries)
|
|
72
83
|
|
|
73
84
|
// Read each file's source once to extract static metadata (middleware + layout)
|
|
@@ -83,7 +94,7 @@ export async function generateRoutesCode(pagesDir: string): Promise<string> {
|
|
|
83
94
|
}),
|
|
84
95
|
)
|
|
85
96
|
|
|
86
|
-
const lines: string[] = ['// AUTO-GENERATED by vite-plugin-cer-app', '']
|
|
97
|
+
const lines: string[] = ['// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app', '']
|
|
87
98
|
|
|
88
99
|
// Build routes array with lazy load() functions for code splitting.
|
|
89
100
|
const routeItems = sorted.map((entry, i) => {
|
|
@@ -8,16 +8,16 @@ import { fileToRoutePath } from '../path-utils.js'
|
|
|
8
8
|
*/
|
|
9
9
|
export async function generateServerApiCode(serverApiDir: string): Promise<string> {
|
|
10
10
|
if (!existsSync(serverApiDir)) {
|
|
11
|
-
return `// AUTO-GENERATED by vite-plugin-cer-app\nexport const apiRoutes = []\nexport default apiRoutes\n`
|
|
11
|
+
return `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\nexport const apiRoutes = []\nexport default apiRoutes\n`
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const files = await scanDirectory('**/*.ts', serverApiDir)
|
|
15
15
|
|
|
16
16
|
if (files.length === 0) {
|
|
17
|
-
return `// AUTO-GENERATED by vite-plugin-cer-app\nexport const apiRoutes = []\nexport default apiRoutes\n`
|
|
17
|
+
return `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\nexport const apiRoutes = []\nexport default apiRoutes\n`
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const lines: string[] = ['// AUTO-GENERATED by vite-plugin-cer-app', '']
|
|
20
|
+
const lines: string[] = ['// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app', '']
|
|
21
21
|
|
|
22
22
|
const entries: Array<{ alias: string; apiPath: string }> = []
|
|
23
23
|
|
|
@@ -8,19 +8,19 @@ import { scanDirectory } from '../scanner.js'
|
|
|
8
8
|
*/
|
|
9
9
|
export async function generateServerMiddlewareCode(serverMiddlewareDir: string): Promise<string> {
|
|
10
10
|
if (!existsSync(serverMiddlewareDir)) {
|
|
11
|
-
return `// AUTO-GENERATED by vite-plugin-cer-app\nexport const serverMiddleware = []\nexport default serverMiddleware\n`
|
|
11
|
+
return `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\nexport const serverMiddleware = []\nexport default serverMiddleware\n`
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const files = await scanDirectory('**/*.ts', serverMiddlewareDir)
|
|
15
15
|
|
|
16
16
|
if (files.length === 0) {
|
|
17
|
-
return `// AUTO-GENERATED by vite-plugin-cer-app\nexport const serverMiddleware = []\nexport default serverMiddleware\n`
|
|
17
|
+
return `// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\nexport const serverMiddleware = []\nexport default serverMiddleware\n`
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// Sort alphabetically so middleware runs in a deterministic order
|
|
21
21
|
const sorted = [...files].sort()
|
|
22
22
|
|
|
23
|
-
const lines: string[] = ['// AUTO-GENERATED by vite-plugin-cer-app', '']
|
|
23
|
+
const lines: string[] = ['// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app', '']
|
|
24
24
|
|
|
25
25
|
const entries: Array<{ alias: string; name: string }> = []
|
|
26
26
|
|
|
@@ -135,6 +135,12 @@ for (const plugin of plugins) {
|
|
|
135
135
|
|
|
136
136
|
if (typeof window !== 'undefined') {
|
|
137
137
|
await router.replace(window.location.pathname + window.location.search + window.location.hash)
|
|
138
|
+
// Clear SSR loader data after the initial navigation so that subsequent
|
|
139
|
+
// client-side navigations don't accidentally reuse it. We wait until after
|
|
140
|
+
// router.replace() so both the pre-rendered element (upgraded during
|
|
141
|
+
// component registration) and the new element from router.replace() can
|
|
142
|
+
// both call usePageData() and receive the hydration data.
|
|
143
|
+
delete (globalThis as any).__CER_DATA__
|
|
138
144
|
createDOMJITCSS().mount()
|
|
139
145
|
}
|
|
140
146
|
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* usePageData — reads SSR-injected loader data for the current page.
|
|
3
3
|
*
|
|
4
|
-
* During SSR/SSG the server calls the matched route's `loader()` function
|
|
5
|
-
*
|
|
6
|
-
* HTML `<head>`. On client hydration this composable reads that payload so
|
|
7
|
-
* the page component can skip the initial data fetch.
|
|
4
|
+
* During SSR/SSG the server calls the matched route's `loader()` function
|
|
5
|
+
* and makes the result available in two ways:
|
|
8
6
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* 1. **Server render pass** — the data is stored in a per-request
|
|
8
|
+
* `AsyncLocalStorage` context (set via `_cerDataStore.enterWith(data)`
|
|
9
|
+
* in the entry-server template). `usePageData()` reads this store so the
|
|
10
|
+
* component renders with real data in the initial SSR/SSG HTML.
|
|
11
|
+
*
|
|
12
|
+
* 2. **Client hydration** — the server also serializes the data as
|
|
13
|
+
* `window.__CER_DATA__` in the page `<head>`. The client entry captures
|
|
14
|
+
* this into `globalThis.__CER_DATA__` before the app boots. On first
|
|
15
|
+
* component instantiation `usePageData()` returns that value so the client
|
|
16
|
+
* starts with the correct state without an extra network round-trip.
|
|
17
|
+
*
|
|
18
|
+
* The client-side value is cleared after the first read so subsequent
|
|
19
|
+
* client-side navigations don't accidentally reuse stale SSR data.
|
|
11
20
|
*
|
|
12
21
|
* @returns The serialized loader result, or `null` if no SSR data is present.
|
|
13
22
|
*
|
|
@@ -29,11 +38,24 @@
|
|
|
29
38
|
* ```
|
|
30
39
|
*/
|
|
31
40
|
export function usePageData<T = unknown>(): T | null {
|
|
32
|
-
// Works in both SSR (globalThis) and browser (window) contexts.
|
|
33
41
|
const g = globalThis as Record<string, unknown>
|
|
42
|
+
|
|
43
|
+
// Server-side: read from the per-request AsyncLocalStorage context.
|
|
44
|
+
// __CER_DATA_STORE__ is set by the entry-server template and is only present
|
|
45
|
+
// in Node.js, so this branch is tree-shaken out of the client bundle.
|
|
46
|
+
const store = g['__CER_DATA_STORE__'] as { getStore(): unknown } | undefined
|
|
47
|
+
if (store) {
|
|
48
|
+
const ssrData = store.getStore() as T | null | undefined
|
|
49
|
+
if (ssrData !== undefined && ssrData !== null) return ssrData
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Client-side: read from window.__CER_DATA__ captured by the client entry.
|
|
53
|
+
// Do NOT clear here — the data is cleared by app-template.ts after the
|
|
54
|
+
// initial router.replace() completes. This ensures both the pre-rendered
|
|
55
|
+
// element (upgraded during component registration) and the new element
|
|
56
|
+
// created by router.replace() can both access the SSR data without a
|
|
57
|
+
// race where the first read consumes it before the second can use it.
|
|
34
58
|
const data = g['__CER_DATA__'] as T | undefined
|
|
35
59
|
if (data === undefined || data === null) return null
|
|
36
|
-
// Clear so that subsequent client navigations don't reuse stale SSR data.
|
|
37
|
-
delete g['__CER_DATA__']
|
|
38
60
|
return data
|
|
39
61
|
}
|