@jasonshimmy/vite-plugin-cer-app 0.4.5 → 0.4.6
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 +4 -0
- package/commits.txt +1 -1
- package/dist/cli/create/index.js +22 -7
- package/dist/cli/create/index.js.map +1 -1
- package/dist/cli/create/templates/{ssr → shared}/app/pages/index.ts.tpl +1 -1
- package/dist/plugin/build-ssr.d.ts.map +1 -1
- package/dist/plugin/build-ssr.js +2 -205
- package/dist/plugin/build-ssr.js.map +1 -1
- package/dist/runtime/entry-server-template.d.ts +10 -4
- package/dist/runtime/entry-server-template.d.ts.map +1 -1
- package/dist/runtime/entry-server-template.js +54 -64
- package/dist/runtime/entry-server-template.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/plugin/build-ssr.test.ts +3 -120
- package/src/__tests__/plugin/entry-server-template.test.ts +106 -3
- package/src/cli/create/index.ts +21 -7
- package/src/cli/create/templates/{spa → shared}/app/pages/index.ts.tpl +1 -1
- package/src/plugin/build-ssr.ts +2 -205
- package/src/runtime/entry-server-template.ts +54 -64
- package/dist/cli/create/templates/spa/app/pages/index.ts.tpl +0 -8
- package/dist/cli/create/templates/ssg/app/pages/index.ts.tpl +0 -8
- package/dist/cli/create/templates/ssr/.gitignore.tpl +0 -25
- package/dist/cli/create/templates/ssr/app/layouts/default.ts.tpl +0 -15
- package/dist/cli/create/templates/ssr/index.html.tpl +0 -12
- package/dist/cli/create/templates/ssr/tsconfig.json.tpl +0 -3
- package/src/cli/create/templates/spa/.gitignore.tpl +0 -25
- package/src/cli/create/templates/spa/app/layouts/default.ts.tpl +0 -15
- package/src/cli/create/templates/spa/index.html.tpl +0 -12
- package/src/cli/create/templates/spa/tsconfig.json.tpl +0 -3
- package/src/cli/create/templates/ssg/.gitignore.tpl +0 -25
- package/src/cli/create/templates/ssg/app/layouts/default.ts.tpl +0 -15
- package/src/cli/create/templates/ssg/app/pages/index.ts.tpl +0 -8
- package/src/cli/create/templates/ssg/index.html.tpl +0 -12
- package/src/cli/create/templates/ssg/tsconfig.json.tpl +0 -3
- package/src/cli/create/templates/ssr/.gitignore.tpl +0 -25
- package/src/cli/create/templates/ssr/app/layouts/default.ts.tpl +0 -15
- package/src/cli/create/templates/ssr/app/pages/index.ts.tpl +0 -8
- package/src/cli/create/templates/ssr/index.html.tpl +0 -12
- package/src/cli/create/templates/ssr/tsconfig.json.tpl +0 -3
- /package/dist/cli/create/templates/{spa → shared}/.gitignore.tpl +0 -0
- /package/dist/cli/create/templates/{spa → shared}/app/layouts/default.ts.tpl +0 -0
- /package/dist/cli/create/templates/{spa → shared}/index.html.tpl +0 -0
- /package/dist/cli/create/templates/{spa → shared}/tsconfig.json.tpl +0 -0
- /package/{dist/cli/create/templates/ssg → src/cli/create/templates/shared}/.gitignore.tpl +0 -0
- /package/{dist/cli/create/templates/ssg → src/cli/create/templates/shared}/app/layouts/default.ts.tpl +0 -0
- /package/{dist/cli/create/templates/ssg → src/cli/create/templates/shared}/index.html.tpl +0 -0
- /package/{dist/cli/create/templates/ssg → src/cli/create/templates/shared}/tsconfig.json.tpl +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
|
+
## [v0.4.6] - 2026-03-21
|
|
5
|
+
|
|
6
|
+
- fix: merge extraneous templates (7a859bd)
|
|
7
|
+
|
|
4
8
|
## [v0.4.5] - 2026-03-21
|
|
5
9
|
|
|
6
10
|
- fix: remove the dsd config option since we always want it on anyway. (3698886)
|
package/commits.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
- fix:
|
|
1
|
+
- fix: merge extraneous templates (7a859bd)
|
package/dist/cli/create/index.js
CHANGED
|
@@ -92,10 +92,16 @@ async function writeTemplateFiles(files, targetDir, tokens) {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
/**
|
|
95
|
-
* Returns the path to the template directory
|
|
95
|
+
* Returns the path to the shared template directory (files common to all modes).
|
|
96
|
+
*/
|
|
97
|
+
function getSharedTemplateDir() {
|
|
98
|
+
return join(__dirname, 'templates', 'shared');
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Returns the path to the mode-specific template directory.
|
|
96
102
|
* Resolves relative to the compiled dist output.
|
|
97
103
|
*/
|
|
98
|
-
function
|
|
104
|
+
function getModeTemplateDir(mode) {
|
|
99
105
|
// When running from compiled dist/, templates are in create/templates/
|
|
100
106
|
// This file is at dist/cli/create/index.js, so templates are at dist/cli/create/templates/
|
|
101
107
|
return join(__dirname, 'templates', mode);
|
|
@@ -123,15 +129,24 @@ async function main() {
|
|
|
123
129
|
process.exit(0);
|
|
124
130
|
}
|
|
125
131
|
}
|
|
126
|
-
// Load template files
|
|
127
|
-
const
|
|
128
|
-
|
|
132
|
+
// Load template files: shared first, then mode-specific (mode overrides shared)
|
|
133
|
+
const sharedDir = getSharedTemplateDir();
|
|
134
|
+
const modeDir = getModeTemplateDir(mode);
|
|
135
|
+
if (!existsSync(sharedDir) && !existsSync(modeDir)) {
|
|
129
136
|
// Fallback: generate minimal template inline
|
|
130
|
-
console.warn(`[create-cer-app] Template directory not found at ${
|
|
137
|
+
console.warn(`[create-cer-app] Template directory not found at ${modeDir}, using inline template.`);
|
|
131
138
|
await generateInlineTemplate(targetDir, projectName, mode);
|
|
132
139
|
}
|
|
133
140
|
else {
|
|
134
|
-
const files =
|
|
141
|
+
const files = new Map();
|
|
142
|
+
if (existsSync(sharedDir)) {
|
|
143
|
+
for (const [k, v] of await readTemplateFiles(sharedDir))
|
|
144
|
+
files.set(k, v);
|
|
145
|
+
}
|
|
146
|
+
if (existsSync(modeDir)) {
|
|
147
|
+
for (const [k, v] of await readTemplateFiles(modeDir))
|
|
148
|
+
files.set(k, v);
|
|
149
|
+
}
|
|
135
150
|
await writeTemplateFiles(files, targetDir, { projectName });
|
|
136
151
|
}
|
|
137
152
|
console.log(`\nProject created! To get started:\n`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli/create/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli/create/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-ssr.d.ts","sourceRoot":"","sources":["../../src/plugin/build-ssr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;AAG7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"build-ssr.d.ts","sourceRoot":"","sources":["../../src/plugin/build-ssr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;AAG7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAIxD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAQpE;AAMD;;;;;GAKG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,iBAAiB,EACzB,cAAc,GAAE,UAAe,GAC9B,OAAO,CAAC,IAAI,CAAC,CA8Ef"}
|
package/dist/plugin/build-ssr.js
CHANGED
|
@@ -2,6 +2,7 @@ import { build } from 'vite';
|
|
|
2
2
|
import { join, resolve } from 'pathe';
|
|
3
3
|
import { existsSync, renameSync } from 'node:fs';
|
|
4
4
|
import { getGeneratedDir, writeGeneratedDir } from './generated-dir.js';
|
|
5
|
+
import { ENTRY_SERVER_TEMPLATE } from '../runtime/entry-server-template.js';
|
|
5
6
|
/**
|
|
6
7
|
* Resolves the client build entry point for an SSR/SSG build.
|
|
7
8
|
*
|
|
@@ -23,212 +24,8 @@ export function resolveClientEntry(config) {
|
|
|
23
24
|
return entryClient;
|
|
24
25
|
return resolve(config.srcDir, 'app.ts');
|
|
25
26
|
}
|
|
26
|
-
/**
|
|
27
|
-
* The server entry template that wires all virtual modules together and
|
|
28
|
-
* exports a request handler for Node.js (Express-compatible).
|
|
29
|
-
*/
|
|
30
27
|
function generateServerEntryCode() {
|
|
31
|
-
return
|
|
32
|
-
import { readFileSync, existsSync } from 'node:fs'
|
|
33
|
-
import { dirname, join } from 'node:path'
|
|
34
|
-
import { fileURLToPath } from 'node:url'
|
|
35
|
-
import 'virtual:cer-components'
|
|
36
|
-
import routes from 'virtual:cer-routes'
|
|
37
|
-
import layouts from 'virtual:cer-layouts'
|
|
38
|
-
import plugins from 'virtual:cer-plugins'
|
|
39
|
-
import apiRoutes from 'virtual:cer-server-api'
|
|
40
|
-
import { registerBuiltinComponents } from '@jasonshimmy/custom-elements-runtime'
|
|
41
|
-
import { registerEntityMap, renderToStringWithJITCSSDSD, DSD_POLYFILL_SCRIPT } from '@jasonshimmy/custom-elements-runtime/ssr'
|
|
42
|
-
import entitiesJson from '@jasonshimmy/custom-elements-runtime/entities.json'
|
|
43
|
-
import { initRouter } from '@jasonshimmy/custom-elements-runtime/router'
|
|
44
|
-
import { beginHeadCollection, endHeadCollection, serializeHeadTags } from '@jasonshimmy/vite-plugin-cer-app/composables'
|
|
45
|
-
|
|
46
|
-
registerBuiltinComponents()
|
|
47
|
-
|
|
48
|
-
// Pre-load the full HTML entity map so named entities like — decode
|
|
49
|
-
// correctly during SSR. Without this the bundled runtime falls back to a
|
|
50
|
-
// minimal set (<, >, & …) and re-escapes everything else.
|
|
51
|
-
registerEntityMap(entitiesJson)
|
|
52
|
-
|
|
53
|
-
// Run plugins once at server startup so their provide() values are available
|
|
54
|
-
// to useInject() during every SSR/SSG render pass.
|
|
55
|
-
const _pluginProvides = new Map()
|
|
56
|
-
;(globalThis).__cerPluginProvides = _pluginProvides
|
|
57
|
-
const _pluginsReady = (async () => {
|
|
58
|
-
const _bootstrapRouter = initRouter({ routes })
|
|
59
|
-
for (const plugin of plugins) {
|
|
60
|
-
if (plugin && typeof plugin.setup === 'function') {
|
|
61
|
-
await plugin.setup({
|
|
62
|
-
router: _bootstrapRouter,
|
|
63
|
-
provide: (key, value) => _pluginProvides.set(key, value),
|
|
64
|
-
config: {},
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
})()
|
|
69
|
-
|
|
70
|
-
// Load the Vite-built client index.html (dist/client/index.html) so every SSR
|
|
71
|
-
// response includes the client-side scripts needed for hydration and routing.
|
|
72
|
-
// The server bundle lives at dist/server/server.js, so ../client resolves correctly.
|
|
73
|
-
const _clientTemplatePath = join(dirname(fileURLToPath(import.meta.url)), '../client/index.html')
|
|
74
|
-
const _clientTemplate = existsSync(_clientTemplatePath)
|
|
75
|
-
? readFileSync(_clientTemplatePath, 'utf-8')
|
|
76
|
-
: null
|
|
77
|
-
|
|
78
|
-
// Merge the SSR rendered body with the Vite client shell so the final page
|
|
79
|
-
// contains both pre-rendered DSD content and the client bundle scripts.
|
|
80
|
-
function _mergeWithClientTemplate(ssrHtml, clientTemplate) {
|
|
81
|
-
const headTag = '<head>', headCloseTag = '</head>'
|
|
82
|
-
const bodyTag = '<body>', bodyCloseTag = '</body>'
|
|
83
|
-
const headStart = ssrHtml.indexOf(headTag)
|
|
84
|
-
const headEnd = ssrHtml.indexOf(headCloseTag)
|
|
85
|
-
const bodyStart = ssrHtml.indexOf(bodyTag)
|
|
86
|
-
const bodyEnd = ssrHtml.lastIndexOf(bodyCloseTag)
|
|
87
|
-
const ssrHead = headStart >= 0 && headEnd > headStart
|
|
88
|
-
? ssrHtml.slice(headStart + headTag.length, headEnd).trim() : ''
|
|
89
|
-
const ssrBody = bodyStart >= 0 && bodyEnd > bodyStart
|
|
90
|
-
? ssrHtml.slice(bodyStart + bodyTag.length, bodyEnd).trim() : ssrHtml
|
|
91
|
-
// Hoist only top-level <style id=...> elements (cer-ssr-jit, cer-ssr-global)
|
|
92
|
-
// from the SSR body into the document <head>. Plain <style> blocks without
|
|
93
|
-
// an id attribute belong to shadow DOM templates and must stay in place —
|
|
94
|
-
// hoisting them to <head> breaks shadow DOM style encapsulation (document
|
|
95
|
-
// styles do not pierce shadow roots), which is the root cause of FOUC.
|
|
96
|
-
const headParts = ssrHead ? [ssrHead] : []
|
|
97
|
-
let ssrBodyContent = ssrBody
|
|
98
|
-
let pos = 0
|
|
99
|
-
while (pos < ssrBodyContent.length) {
|
|
100
|
-
const styleOpen = ssrBodyContent.indexOf('<style id=', pos)
|
|
101
|
-
if (styleOpen < 0) break
|
|
102
|
-
const styleClose = ssrBodyContent.indexOf('</style>', styleOpen)
|
|
103
|
-
if (styleClose < 0) break
|
|
104
|
-
headParts.push(ssrBodyContent.slice(styleOpen, styleClose + 8))
|
|
105
|
-
ssrBodyContent = ssrBodyContent.slice(0, styleOpen) + ssrBodyContent.slice(styleClose + 8)
|
|
106
|
-
pos = styleOpen
|
|
107
|
-
}
|
|
108
|
-
ssrBodyContent = ssrBodyContent.trim()
|
|
109
|
-
// Inject the pre-rendered layout+page as light DOM of the app mount element
|
|
110
|
-
// so it is visible before JS boots, then the client router takes over.
|
|
111
|
-
let merged = clientTemplate
|
|
112
|
-
if (merged.includes('<cer-layout-view></cer-layout-view>')) {
|
|
113
|
-
merged = merged.replace('<cer-layout-view></cer-layout-view>',
|
|
114
|
-
'<cer-layout-view>' + ssrBodyContent + '</cer-layout-view>')
|
|
115
|
-
} else if (merged.includes('<div id="app"></div>')) {
|
|
116
|
-
merged = merged.replace('<div id="app"></div>',
|
|
117
|
-
'<div id="app">' + ssrBodyContent + '</div>')
|
|
118
|
-
}
|
|
119
|
-
const headAdditions = headParts.filter(Boolean).join('\\n')
|
|
120
|
-
if (headAdditions) {
|
|
121
|
-
// If SSR provides a <title>, replace the client template's <title> so the
|
|
122
|
-
// SSR title wins (client template title is the fallback default).
|
|
123
|
-
if (headAdditions.includes('<title>')) {
|
|
124
|
-
merged = merged.replace(/<title>[^<]*<\\/title>/, '')
|
|
125
|
-
}
|
|
126
|
-
merged = merged.replace('</head>', headAdditions + '\\n</head>')
|
|
127
|
-
}
|
|
128
|
-
return merged
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Per-request async setup: initialize a fresh router, resolve the matched
|
|
132
|
-
// route and layout, pre-load the page module, and call the data loader.
|
|
133
|
-
// Returns the vnode tree, router, head additions, and the raw loader data.
|
|
134
|
-
//
|
|
135
|
-
// loaderData is returned (not set on globalThis) so the handler can assign it
|
|
136
|
-
// synchronously right before renderToStringWithJITCSS — guaranteeing that
|
|
137
|
-
// concurrent renders (SSG concurrency > 1) never race on a shared global.
|
|
138
|
-
const _prepareRequest = async (req) => {
|
|
139
|
-
await _pluginsReady
|
|
140
|
-
const router = initRouter({ routes, initialUrl: req.url ?? '/' })
|
|
141
|
-
const current = router.getCurrent()
|
|
142
|
-
const { route, params } = router.matchRoute(current.path)
|
|
143
|
-
const layoutName = route?.meta?.layout ?? 'default'
|
|
144
|
-
const layoutTag = layouts[layoutName]
|
|
145
|
-
|
|
146
|
-
// Pre-load the page module so we can embed the component tag directly.
|
|
147
|
-
// This avoids the async router-view (which injects content via script tags
|
|
148
|
-
// and breaks Declarative Shadow DOM on initial parse).
|
|
149
|
-
let pageVnode = { tag: 'div', props: {}, children: [] }
|
|
150
|
-
let head
|
|
151
|
-
let loaderData = null
|
|
152
|
-
if (route?.load) {
|
|
153
|
-
try {
|
|
154
|
-
const mod = await route.load()
|
|
155
|
-
const pageTag = mod.default
|
|
156
|
-
if (pageTag) {
|
|
157
|
-
pageVnode = { tag: pageTag, props: { attrs: { ...params } }, children: [] }
|
|
158
|
-
}
|
|
159
|
-
if (typeof mod.loader === 'function') {
|
|
160
|
-
const query = current.query ?? {}
|
|
161
|
-
const data = await mod.loader({ params, query, req })
|
|
162
|
-
if (data !== undefined && data !== null) {
|
|
163
|
-
loaderData = data
|
|
164
|
-
head = \`<script>window.__CER_DATA__ = \${JSON.stringify(data)}</script>\`
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
} catch {
|
|
168
|
-
// Non-fatal: loader errors fall back to an empty page; client will refetch.
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const vnode = layoutTag
|
|
173
|
-
? { tag: layoutTag, props: {}, children: [pageVnode] }
|
|
174
|
-
: pageVnode
|
|
175
|
-
|
|
176
|
-
return { vnode, router, head, loaderData }
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export const handler = async (req, res) => {
|
|
180
|
-
const { vnode, router, head, loaderData } = await _prepareRequest(req)
|
|
181
|
-
|
|
182
|
-
// Set loader data on globalThis synchronously before the render so
|
|
183
|
-
// usePageData() can read it. Because renderToStringWithJITCSSDSD is entirely
|
|
184
|
-
// synchronous and JavaScript is single-threaded, no concurrent request can
|
|
185
|
-
// overwrite __CER_DATA__ between this assignment and the read inside setup().
|
|
186
|
-
if (loaderData !== null) {
|
|
187
|
-
;(globalThis).__CER_DATA__ = loaderData
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Begin collecting useHead() calls made during the synchronous render pass.
|
|
191
|
-
beginHeadCollection()
|
|
192
|
-
|
|
193
|
-
// dsdPolyfill: false — we inject the polyfill manually after merging so it
|
|
194
|
-
// lands at the end of <body>, not inside <cer-layout-view> light DOM where
|
|
195
|
-
// scripts may not execute.
|
|
196
|
-
const { htmlWithStyles } = renderToStringWithJITCSSDSD(vnode, {
|
|
197
|
-
dsdPolyfill: false,
|
|
198
|
-
router,
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
// Collect and serialize any useHead() calls from the rendered components.
|
|
202
|
-
const headTags = serializeHeadTags(endHeadCollection())
|
|
203
|
-
|
|
204
|
-
// Clear immediately after the synchronous render so the value never leaks
|
|
205
|
-
// to the next request on this same server process.
|
|
206
|
-
delete (globalThis).__CER_DATA__
|
|
207
|
-
|
|
208
|
-
// Merge loader data script + useHead() tags into the document head.
|
|
209
|
-
const headContent = [head, headTags].filter(Boolean).join('\\n')
|
|
210
|
-
|
|
211
|
-
// Wrap the rendered body in a full HTML document and inject the head additions
|
|
212
|
-
// (loader data script, useHead() tags, JIT styles). No polyfill in body yet.
|
|
213
|
-
const ssrHtml = \`<!DOCTYPE html><html><head>\${headContent}</head><body>\${htmlWithStyles}</body></html>\`
|
|
214
|
-
|
|
215
|
-
let finalHtml = _clientTemplate
|
|
216
|
-
? _mergeWithClientTemplate(ssrHtml, _clientTemplate)
|
|
217
|
-
: ssrHtml
|
|
218
|
-
|
|
219
|
-
// Inject DSD polyfill at end of <body>, outside <cer-layout-view>, so the
|
|
220
|
-
// browser runs it after parsing the declarative shadow roots.
|
|
221
|
-
finalHtml = finalHtml.includes('</body>')
|
|
222
|
-
? finalHtml.replace('</body>', DSD_POLYFILL_SCRIPT + '</body>')
|
|
223
|
-
: finalHtml + DSD_POLYFILL_SCRIPT
|
|
224
|
-
|
|
225
|
-
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
|
226
|
-
res.end(finalHtml)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
export { apiRoutes, plugins, layouts }
|
|
230
|
-
export default handler
|
|
231
|
-
`;
|
|
28
|
+
return ENTRY_SERVER_TEMPLATE;
|
|
232
29
|
}
|
|
233
30
|
/**
|
|
234
31
|
* Runs the dual (client + server) SSR build using Vite's programmatic API.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-ssr.js","sourceRoot":"","sources":["../../src/plugin/build-ssr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAmB,MAAM,MAAM,CAAA;AAC7C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AACrC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEhD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"build-ssr.js","sourceRoot":"","sources":["../../src/plugin/build-ssr.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Template string for `entry-server.ts`.
|
|
3
3
|
*
|
|
4
|
-
* This is the Node.js SSR entry point
|
|
5
|
-
* wires up the routing, and exports a
|
|
6
|
-
* Express/Fastify/Node http.
|
|
4
|
+
* This is the Node.js SSR entry point used for both development and production
|
|
5
|
+
* builds. It imports all virtual modules, wires up the routing, and exports a
|
|
6
|
+
* request handler compatible with Express/Fastify/Node http.
|
|
7
|
+
*
|
|
8
|
+
* Key features:
|
|
9
|
+
* - AsyncLocalStorage for race-condition-free concurrent renders (SSG concurrency > 1)
|
|
10
|
+
* - Declarative Shadow DOM via renderToStringWithJITCSSDSD (always on)
|
|
11
|
+
* - useHead() support via beginHeadCollection / endHeadCollection
|
|
12
|
+
* - DSD polyfill injected at end of <body> after client-template merge
|
|
7
13
|
*/
|
|
8
|
-
export declare const ENTRY_SERVER_TEMPLATE = "// Server-side entry \u2014 AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\nimport { readFileSync, existsSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { AsyncLocalStorage } from 'node:async_hooks'\nimport 'virtual:cer-components'\nimport routes from 'virtual:cer-routes'\nimport layouts from 'virtual:cer-layouts'\nimport plugins from 'virtual:cer-plugins'\nimport apiRoutes from 'virtual:cer-server-api'\nimport { registerBuiltinComponents } from '@jasonshimmy/custom-elements-runtime'\nimport { registerEntityMap, DSD_POLYFILL_SCRIPT } from '@jasonshimmy/custom-elements-runtime/ssr'\nimport entitiesJson from '@jasonshimmy/custom-elements-runtime/entities.json'\nimport { initRouter } from '@jasonshimmy/custom-elements-runtime/router'\nimport { createSSRHandler } from '@jasonshimmy/custom-elements-runtime/ssr-middleware'\n\nregisterBuiltinComponents()\n\n// Pre-load the full HTML entity map so named entities like — decode\n// correctly during SSR. Without this the bundled runtime falls back to a\n// minimal set (<, >, & \u2026) and re-escapes everything else.\nregisterEntityMap(entitiesJson)\n\n// Run plugins once at server startup so their provide() values are available\n// to useInject() during every SSR render pass. Stored on globalThis so all\n// dynamically-imported page chunks share the same reference (same pattern as\n// __CER_HEAD_COLLECTOR__ and __CER_DATA_STORE__).\nconst _pluginProvides = new Map()\n;(globalThis).__cerPluginProvides = _pluginProvides\nconst _pluginsReady = (async () => {\n const _bootstrapRouter = initRouter({ routes })\n for (const plugin of plugins) {\n if (plugin && typeof plugin.setup === 'function') {\n await plugin.setup({\n router: _bootstrapRouter,\n provide: (key, value) => _pluginProvides.set(key, value),\n config: {},\n })\n }\n }\n})()\n\n// Async-local storage for request-scoped SSR loader data.\n// Using AsyncLocalStorage ensures concurrent SSR renders (e.g. SSG with\n// concurrency > 1) never see each other's data \u2014 each request's async chain\n// carries its own store value, so usePageData() is always race-condition-free.\nconst _cerDataStore = new AsyncLocalStorage()\n// Expose the store so the usePageData() composable can read it server-side.\n;(globalThis).__CER_DATA_STORE__ = _cerDataStore\n\n// Load the Vite-built client index.html (dist/client/index.html) so every SSR\n// response includes the client-side scripts needed for hydration and routing.\n// The server bundle lives at dist/server/server.js, so ../client resolves correctly.\nconst _clientTemplatePath = join(dirname(fileURLToPath(import.meta.url)), '../client/index.html')\nconst _clientTemplate = existsSync(_clientTemplatePath)\n ? readFileSync(_clientTemplatePath, 'utf-8')\n : null\n\n// Merge the SSR handler's full HTML document with the Vite client shell so the\n// final page contains both pre-rendered DSD content and the client bundle scripts.\nfunction _mergeWithClientTemplate(ssrHtml, clientTemplate) {\n const headTag = '<head>', headCloseTag = '</head>'\n const bodyTag = '<body>', bodyCloseTag = '</body>'\n const headStart = ssrHtml.indexOf(headTag)\n const headEnd = ssrHtml.indexOf(headCloseTag)\n const bodyStart = ssrHtml.indexOf(bodyTag)\n const bodyEnd = ssrHtml.lastIndexOf(bodyCloseTag)\n const ssrHead = headStart >= 0 && headEnd > headStart\n ? ssrHtml.slice(headStart + headTag.length, headEnd).trim() : ''\n const ssrBody = bodyStart >= 0 && bodyEnd > bodyStart\n ? ssrHtml.slice(bodyStart + bodyTag.length, bodyEnd).trim() : ssrHtml\n // Hoist only top-level <style id=...> elements (cer-ssr-jit, cer-ssr-global)\n // from the SSR body into the document <head>. Plain <style> blocks without\n // an id attribute belong to shadow DOM templates and must stay in place \u2014\n // hoisting them to <head> breaks shadow DOM style encapsulation (document\n // styles do not pierce shadow roots), which is the root cause of FOUC.\n const headParts = ssrHead ? [ssrHead] : []\n let ssrBodyContent = ssrBody\n let pos = 0\n while (pos < ssrBodyContent.length) {\n const styleOpen = ssrBodyContent.indexOf('<style id=', pos)\n if (styleOpen < 0) break\n const styleClose = ssrBodyContent.indexOf('</style>', styleOpen)\n if (styleClose < 0) break\n headParts.push(ssrBodyContent.slice(styleOpen, styleClose + 8))\n ssrBodyContent = ssrBodyContent.slice(0, styleOpen) + ssrBodyContent.slice(styleClose + 8)\n pos = styleOpen\n }\n ssrBodyContent = ssrBodyContent.trim()\n // Inject the pre-rendered layout+page as light DOM of the app mount element\n // so it is visible before JS boots, then the client router takes over.\n let merged = clientTemplate\n if (merged.includes('<cer-layout-view></cer-layout-view>')) {\n merged = merged.replace('<cer-layout-view></cer-layout-view>',\n '<cer-layout-view>' + ssrBodyContent + '</cer-layout-view>')\n } else if (merged.includes('<div id=\"app\"></div>')) {\n merged = merged.replace('<div id=\"app\"></div>',\n '<div id=\"app\">' + ssrBodyContent + '</div>')\n }\n const headAdditions = headParts.filter(Boolean).join('\\n')\n if (headAdditions) {\n // If SSR provides a <title>, replace the client template's <title> so the\n // SSR title wins (client template title is the fallback default).\n if (headAdditions.includes('<title>')) {\n merged = merged.replace(/<title>[^<]*<\\/title>/, '')\n }\n merged = merged.replace('</head>', headAdditions + '\\n</head>')\n }\n return merged\n}\n\n/**\n * Per-request VNode factory \u2014 initializes a fresh router at the request URL,\n * resolves the active layout from the matched route's meta, pre-loads the\n * matched page component (bypassing the async router-view so DSD renders\n * synchronously), calls the route's data loader (if any), and injects the\n * serialized result into the document head as window.__CER_DATA__ for\n * client-side hydration.\n *\n * createStreamingSSRHandler threads the router through each component's SSR\n * context so concurrent renders never share state.\n */\nconst vnodeFactory = async (req) => {\n await _pluginsReady\n const router = initRouter({ routes, initialUrl: req.url ?? '/' })\n const current = router.getCurrent()\n const { route, params } = router.matchRoute(current.path)\n const layoutName = route?.meta?.layout ?? 'default'\n const layoutTag = layouts[layoutName]\n\n // Pre-load the page module so we can embed the component tag directly.\n // This avoids the async router-view (which injects content via script tags\n // and breaks Declarative Shadow DOM on initial parse).\n let pageVnode = { tag: 'div', props: {}, children: [] }\n let head\n if (route?.load) {\n try {\n const mod = await route.load()\n const pageTag = mod.default\n if (pageTag) {\n pageVnode = { tag: pageTag, props: { attrs: { ...params } }, children: [] }\n }\n if (typeof mod.loader === 'function') {\n const query = current.query ?? {}\n const data = await mod.loader({ params, query, req })\n if (data !== undefined && data !== null) {\n // Make data available to usePageData() during the SSR render pass.\n // enterWith() scopes the value to the current async context so\n // concurrent renders (SSG concurrency > 1) never share data.\n _cerDataStore.enterWith(data)\n head = `<script>window.__CER_DATA__ = ${JSON.stringify(data)}</script>`\n }\n }\n } catch {\n // Non-fatal: loader errors fall back to an empty page; client will refetch.\n }\n }\n\n const vnode = layoutTag\n ? { tag: layoutTag, props: {}, children: [pageVnode] }\n : pageVnode\n\n return { vnode, router, head }\n}\n\n// Capture the raw SSR handler and wrap it to merge the response with the\n// Vite client template before sending \u2014 this injects the JS/CSS asset bundles\n// so the browser can hydrate and enable client-side routing.\nconst _rawHandler = createSSRHandler(vnodeFactory, {\n render: { dsd: true, dsdPolyfill: false },\n})\n\n/**\n * The main request handler.\n * Compatible with Express, Fastify, and Node's raw http.createServer.\n *\n * Each request is run inside a fresh _cerDataStore.run() context so that\n * concurrent renders (e.g. SSG with concurrency > 1) get isolated stores.\n * vnodeFactory calls _cerDataStore.enterWith(loaderData) from within this\n * context, making the data visible to usePageData() during SSR rendering\n * without any global-state races.\n */\nexport const handler = async (req, res) => {\n if (!_clientTemplate) {\n // No client template \u2014 run handler normally, then inject DSD polyfill.\n let _html = ''\n await _cerDataStore.run(null, async () => {\n await _rawHandler(req, { setHeader: () => {}, end: (body) => { _html = body } })\n })\n // Inject DSD polyfill at end of <body>, outside any custom element light DOM.\n const _final = _html.includes('</body>')\n ? _html.replace('</body>', DSD_POLYFILL_SCRIPT + '</body>')\n : _html + DSD_POLYFILL_SCRIPT\n res.setHeader('Content-Type', 'text/html; charset=utf-8')\n return res.end(_final)\n }\n let _capturedHtml = ''\n // Wrap _rawHandler in an isolated async-local-storage context so that\n // vnodeFactory's enterWith() call is scoped to this request only.\n await _cerDataStore.run(null, async () => {\n // Omit write() to force the non-streaming collect-then-end code path.\n await _rawHandler(req, { setHeader: () => {}, end: (body) => { _capturedHtml = body } })\n })\n let _merged = _mergeWithClientTemplate(_capturedHtml, _clientTemplate)\n // Inject DSD polyfill at end of <body>, outside <cer-layout-view> light DOM.\n _merged = _merged.includes('</body>')\n ? _merged.replace('</body>', DSD_POLYFILL_SCRIPT + '</body>')\n : _merged + DSD_POLYFILL_SCRIPT\n res.setHeader('Content-Type', 'text/html; charset=utf-8')\n res.end(_merged)\n}\n\nexport { apiRoutes, plugins, layouts, routes }\nexport default handler\n";
|
|
14
|
+
export declare const ENTRY_SERVER_TEMPLATE = "// Server-side entry \u2014 AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app\nimport { readFileSync, existsSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { AsyncLocalStorage } from 'node:async_hooks'\nimport 'virtual:cer-components'\nimport routes from 'virtual:cer-routes'\nimport layouts from 'virtual:cer-layouts'\nimport plugins from 'virtual:cer-plugins'\nimport apiRoutes from 'virtual:cer-server-api'\nimport { registerBuiltinComponents } from '@jasonshimmy/custom-elements-runtime'\nimport { registerEntityMap, renderToStringWithJITCSSDSD, DSD_POLYFILL_SCRIPT } from '@jasonshimmy/custom-elements-runtime/ssr'\nimport entitiesJson from '@jasonshimmy/custom-elements-runtime/entities.json'\nimport { initRouter } from '@jasonshimmy/custom-elements-runtime/router'\nimport { beginHeadCollection, endHeadCollection, serializeHeadTags } from '@jasonshimmy/vite-plugin-cer-app/composables'\n\nregisterBuiltinComponents()\n\n// Pre-load the full HTML entity map so named entities like — decode\n// correctly during SSR. Without this the bundled runtime falls back to a\n// minimal set (<, >, & \u2026) and re-escapes everything else.\nregisterEntityMap(entitiesJson)\n\n// Run plugins once at server startup so their provide() values are available\n// to useInject() during every SSR/SSG render pass. Stored on globalThis so all\n// dynamically-imported page chunks share the same reference.\nconst _pluginProvides = new Map()\n;(globalThis).__cerPluginProvides = _pluginProvides\nconst _pluginsReady = (async () => {\n const _bootstrapRouter = initRouter({ routes })\n for (const plugin of plugins) {\n if (plugin && typeof plugin.setup === 'function') {\n await plugin.setup({\n router: _bootstrapRouter,\n provide: (key, value) => _pluginProvides.set(key, value),\n config: {},\n })\n }\n }\n})()\n\n// Async-local storage for request-scoped SSR loader data.\n// Using AsyncLocalStorage ensures concurrent SSR renders (e.g. SSG with\n// concurrency > 1) never see each other's data \u2014 each request's async chain\n// carries its own store value, so usePageData() is always race-condition-free.\nconst _cerDataStore = new AsyncLocalStorage()\n// Expose the store so the usePageData() composable can read it server-side.\n;(globalThis).__CER_DATA_STORE__ = _cerDataStore\n\n// Load the Vite-built client index.html (dist/client/index.html) so every SSR\n// response includes the client-side scripts needed for hydration and routing.\n// The server bundle lives at dist/server/server.js, so ../client resolves correctly.\nconst _clientTemplatePath = join(dirname(fileURLToPath(import.meta.url)), '../client/index.html')\nconst _clientTemplate = existsSync(_clientTemplatePath)\n ? readFileSync(_clientTemplatePath, 'utf-8')\n : null\n\n// Merge the SSR rendered body with the Vite client shell so the final page\n// contains both pre-rendered DSD content and the client bundle scripts.\nfunction _mergeWithClientTemplate(ssrHtml, clientTemplate) {\n const headTag = '<head>', headCloseTag = '</head>'\n const bodyTag = '<body>', bodyCloseTag = '</body>'\n const headStart = ssrHtml.indexOf(headTag)\n const headEnd = ssrHtml.indexOf(headCloseTag)\n const bodyStart = ssrHtml.indexOf(bodyTag)\n const bodyEnd = ssrHtml.lastIndexOf(bodyCloseTag)\n const ssrHead = headStart >= 0 && headEnd > headStart\n ? ssrHtml.slice(headStart + headTag.length, headEnd).trim() : ''\n const ssrBody = bodyStart >= 0 && bodyEnd > bodyStart\n ? ssrHtml.slice(bodyStart + bodyTag.length, bodyEnd).trim() : ssrHtml\n // Hoist only top-level <style id=...> elements (cer-ssr-jit, cer-ssr-global)\n // from the SSR body into the document <head>. Plain <style> blocks without\n // an id attribute belong to shadow DOM templates and must stay in place \u2014\n // hoisting them to <head> breaks shadow DOM style encapsulation (document\n // styles do not pierce shadow roots), which is the root cause of FOUC.\n const headParts = ssrHead ? [ssrHead] : []\n let ssrBodyContent = ssrBody\n let pos = 0\n while (pos < ssrBodyContent.length) {\n const styleOpen = ssrBodyContent.indexOf('<style id=', pos)\n if (styleOpen < 0) break\n const styleClose = ssrBodyContent.indexOf('</style>', styleOpen)\n if (styleClose < 0) break\n headParts.push(ssrBodyContent.slice(styleOpen, styleClose + 8))\n ssrBodyContent = ssrBodyContent.slice(0, styleOpen) + ssrBodyContent.slice(styleClose + 8)\n pos = styleOpen\n }\n ssrBodyContent = ssrBodyContent.trim()\n // Inject the pre-rendered layout+page as light DOM of the app mount element\n // so it is visible before JS boots, then the client router takes over.\n let merged = clientTemplate\n if (merged.includes('<cer-layout-view></cer-layout-view>')) {\n merged = merged.replace('<cer-layout-view></cer-layout-view>',\n '<cer-layout-view>' + ssrBodyContent + '</cer-layout-view>')\n } else if (merged.includes('<div id=\"app\"></div>')) {\n merged = merged.replace('<div id=\"app\"></div>',\n '<div id=\"app\">' + ssrBodyContent + '</div>')\n }\n const headAdditions = headParts.filter(Boolean).join('\\n')\n if (headAdditions) {\n // If SSR provides a <title>, replace the client template's <title> so the\n // SSR title wins (client template title is the fallback default).\n if (headAdditions.includes('<title>')) {\n merged = merged.replace(/<title>[^<]*<\\/title>/, '')\n }\n merged = merged.replace('</head>', headAdditions + '\\n</head>')\n }\n return merged\n}\n\n// Per-request async setup: initialize a fresh router, resolve the matched\n// route and layout, pre-load the page module, and call the data loader.\n// Loader data is scoped to the current AsyncLocalStorage context via enterWith()\n// so concurrent renders never share state.\nconst _prepareRequest = async (req) => {\n await _pluginsReady\n const router = initRouter({ routes, initialUrl: req.url ?? '/' })\n const current = router.getCurrent()\n const { route, params } = router.matchRoute(current.path)\n const layoutName = route?.meta?.layout ?? 'default'\n const layoutTag = layouts[layoutName]\n\n // Pre-load the page module so we can embed the component tag directly.\n // This avoids the async router-view (which injects content via script tags\n // and breaks Declarative Shadow DOM on initial parse).\n let pageVnode = { tag: 'div', props: {}, children: [] }\n let head\n if (route?.load) {\n try {\n const mod = await route.load()\n const pageTag = mod.default\n if (pageTag) {\n pageVnode = { tag: pageTag, props: { attrs: { ...params } }, children: [] }\n }\n if (typeof mod.loader === 'function') {\n const query = current.query ?? {}\n const data = await mod.loader({ params, query, req })\n if (data !== undefined && data !== null) {\n // enterWith() scopes the value to the current async context so\n // concurrent renders (SSG concurrency > 1) never share data.\n _cerDataStore.enterWith(data)\n head = `<script>window.__CER_DATA__ = ${JSON.stringify(data)}</script>`\n }\n }\n } catch {\n // Non-fatal: loader errors fall back to an empty page; client will refetch.\n }\n }\n\n const vnode = layoutTag\n ? { tag: layoutTag, props: {}, children: [pageVnode] }\n : pageVnode\n\n return { vnode, router, head }\n}\n\nexport const handler = async (req, res) => {\n await _cerDataStore.run(null, async () => {\n const { vnode, router, head } = await _prepareRequest(req)\n\n // Begin collecting useHead() calls made during the synchronous render pass.\n beginHeadCollection()\n\n // dsdPolyfill: false \u2014 we inject the polyfill manually after merging so it\n // lands at the end of <body>, not inside <cer-layout-view> light DOM where\n // scripts may not execute.\n const { htmlWithStyles } = renderToStringWithJITCSSDSD(vnode, {\n dsdPolyfill: false,\n router,\n })\n\n // Collect and serialize any useHead() calls from the rendered components.\n const headTags = serializeHeadTags(endHeadCollection())\n\n // Merge loader data script + useHead() tags into the document head.\n const headContent = [head, headTags].filter(Boolean).join('\\n')\n\n // Wrap the rendered body in a full HTML document and inject the head additions\n // (loader data script, useHead() tags, JIT styles). No polyfill in body yet.\n const ssrHtml = `<!DOCTYPE html><html><head>${headContent}</head><body>${htmlWithStyles}</body></html>`\n\n let finalHtml = _clientTemplate\n ? _mergeWithClientTemplate(ssrHtml, _clientTemplate)\n : ssrHtml\n\n // Inject DSD polyfill at end of <body>, outside <cer-layout-view>, so the\n // browser runs it after parsing the declarative shadow roots.\n finalHtml = finalHtml.includes('</body>')\n ? finalHtml.replace('</body>', DSD_POLYFILL_SCRIPT + '</body>')\n : finalHtml + DSD_POLYFILL_SCRIPT\n\n res.setHeader('Content-Type', 'text/html; charset=utf-8')\n res.end(finalHtml)\n })\n}\n\nexport { apiRoutes, plugins, layouts, routes }\nexport default handler\n";
|
|
9
15
|
//# sourceMappingURL=entry-server-template.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry-server-template.d.ts","sourceRoot":"","sources":["../../src/runtime/entry-server-template.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"entry-server-template.d.ts","sourceRoot":"","sources":["../../src/runtime/entry-server-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,giSAsMjC,CAAA"}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Template string for `entry-server.ts`.
|
|
3
3
|
*
|
|
4
|
-
* This is the Node.js SSR entry point
|
|
5
|
-
* wires up the routing, and exports a
|
|
6
|
-
* Express/Fastify/Node http.
|
|
4
|
+
* This is the Node.js SSR entry point used for both development and production
|
|
5
|
+
* builds. It imports all virtual modules, wires up the routing, and exports a
|
|
6
|
+
* request handler compatible with Express/Fastify/Node http.
|
|
7
|
+
*
|
|
8
|
+
* Key features:
|
|
9
|
+
* - AsyncLocalStorage for race-condition-free concurrent renders (SSG concurrency > 1)
|
|
10
|
+
* - Declarative Shadow DOM via renderToStringWithJITCSSDSD (always on)
|
|
11
|
+
* - useHead() support via beginHeadCollection / endHeadCollection
|
|
12
|
+
* - DSD polyfill injected at end of <body> after client-template merge
|
|
7
13
|
*/
|
|
8
14
|
export const ENTRY_SERVER_TEMPLATE = `// Server-side entry — AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app
|
|
9
15
|
import { readFileSync, existsSync } from 'node:fs'
|
|
@@ -16,10 +22,10 @@ import layouts from 'virtual:cer-layouts'
|
|
|
16
22
|
import plugins from 'virtual:cer-plugins'
|
|
17
23
|
import apiRoutes from 'virtual:cer-server-api'
|
|
18
24
|
import { registerBuiltinComponents } from '@jasonshimmy/custom-elements-runtime'
|
|
19
|
-
import { registerEntityMap, DSD_POLYFILL_SCRIPT } from '@jasonshimmy/custom-elements-runtime/ssr'
|
|
25
|
+
import { registerEntityMap, renderToStringWithJITCSSDSD, DSD_POLYFILL_SCRIPT } from '@jasonshimmy/custom-elements-runtime/ssr'
|
|
20
26
|
import entitiesJson from '@jasonshimmy/custom-elements-runtime/entities.json'
|
|
21
27
|
import { initRouter } from '@jasonshimmy/custom-elements-runtime/router'
|
|
22
|
-
import {
|
|
28
|
+
import { beginHeadCollection, endHeadCollection, serializeHeadTags } from '@jasonshimmy/vite-plugin-cer-app/composables'
|
|
23
29
|
|
|
24
30
|
registerBuiltinComponents()
|
|
25
31
|
|
|
@@ -29,9 +35,8 @@ registerBuiltinComponents()
|
|
|
29
35
|
registerEntityMap(entitiesJson)
|
|
30
36
|
|
|
31
37
|
// 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
|
|
34
|
-
// __CER_HEAD_COLLECTOR__ and __CER_DATA_STORE__).
|
|
38
|
+
// to useInject() during every SSR/SSG render pass. Stored on globalThis so all
|
|
39
|
+
// dynamically-imported page chunks share the same reference.
|
|
35
40
|
const _pluginProvides = new Map()
|
|
36
41
|
;(globalThis).__cerPluginProvides = _pluginProvides
|
|
37
42
|
const _pluginsReady = (async () => {
|
|
@@ -63,8 +68,8 @@ const _clientTemplate = existsSync(_clientTemplatePath)
|
|
|
63
68
|
? readFileSync(_clientTemplatePath, 'utf-8')
|
|
64
69
|
: null
|
|
65
70
|
|
|
66
|
-
// Merge the SSR
|
|
67
|
-
//
|
|
71
|
+
// Merge the SSR rendered body with the Vite client shell so the final page
|
|
72
|
+
// contains both pre-rendered DSD content and the client bundle scripts.
|
|
68
73
|
function _mergeWithClientTemplate(ssrHtml, clientTemplate) {
|
|
69
74
|
const headTag = '<head>', headCloseTag = '</head>'
|
|
70
75
|
const bodyTag = '<body>', bodyCloseTag = '</body>'
|
|
@@ -116,18 +121,11 @@ function _mergeWithClientTemplate(ssrHtml, clientTemplate) {
|
|
|
116
121
|
return merged
|
|
117
122
|
}
|
|
118
123
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
* serialized result into the document head as window.__CER_DATA__ for
|
|
125
|
-
* client-side hydration.
|
|
126
|
-
*
|
|
127
|
-
* createStreamingSSRHandler threads the router through each component's SSR
|
|
128
|
-
* context so concurrent renders never share state.
|
|
129
|
-
*/
|
|
130
|
-
const vnodeFactory = async (req) => {
|
|
124
|
+
// Per-request async setup: initialize a fresh router, resolve the matched
|
|
125
|
+
// route and layout, pre-load the page module, and call the data loader.
|
|
126
|
+
// Loader data is scoped to the current AsyncLocalStorage context via enterWith()
|
|
127
|
+
// so concurrent renders never share state.
|
|
128
|
+
const _prepareRequest = async (req) => {
|
|
131
129
|
await _pluginsReady
|
|
132
130
|
const router = initRouter({ routes, initialUrl: req.url ?? '/' })
|
|
133
131
|
const current = router.getCurrent()
|
|
@@ -151,7 +149,6 @@ const vnodeFactory = async (req) => {
|
|
|
151
149
|
const query = current.query ?? {}
|
|
152
150
|
const data = await mod.loader({ params, query, req })
|
|
153
151
|
if (data !== undefined && data !== null) {
|
|
154
|
-
// Make data available to usePageData() during the SSR render pass.
|
|
155
152
|
// enterWith() scopes the value to the current async context so
|
|
156
153
|
// concurrent renders (SSG concurrency > 1) never share data.
|
|
157
154
|
_cerDataStore.enterWith(data)
|
|
@@ -170,51 +167,44 @@ const vnodeFactory = async (req) => {
|
|
|
170
167
|
return { vnode, router, head }
|
|
171
168
|
}
|
|
172
169
|
|
|
173
|
-
// Capture the raw SSR handler and wrap it to merge the response with the
|
|
174
|
-
// Vite client template before sending — this injects the JS/CSS asset bundles
|
|
175
|
-
// so the browser can hydrate and enable client-side routing.
|
|
176
|
-
const _rawHandler = createSSRHandler(vnodeFactory, {
|
|
177
|
-
render: { dsd: true, dsdPolyfill: false },
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* The main request handler.
|
|
182
|
-
* Compatible with Express, Fastify, and Node's raw http.createServer.
|
|
183
|
-
*
|
|
184
|
-
* Each request is run inside a fresh _cerDataStore.run() context so that
|
|
185
|
-
* concurrent renders (e.g. SSG with concurrency > 1) get isolated stores.
|
|
186
|
-
* vnodeFactory calls _cerDataStore.enterWith(loaderData) from within this
|
|
187
|
-
* context, making the data visible to usePageData() during SSR rendering
|
|
188
|
-
* without any global-state races.
|
|
189
|
-
*/
|
|
190
170
|
export const handler = async (req, res) => {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
171
|
+
await _cerDataStore.run(null, async () => {
|
|
172
|
+
const { vnode, router, head } = await _prepareRequest(req)
|
|
173
|
+
|
|
174
|
+
// Begin collecting useHead() calls made during the synchronous render pass.
|
|
175
|
+
beginHeadCollection()
|
|
176
|
+
|
|
177
|
+
// dsdPolyfill: false — we inject the polyfill manually after merging so it
|
|
178
|
+
// lands at the end of <body>, not inside <cer-layout-view> light DOM where
|
|
179
|
+
// scripts may not execute.
|
|
180
|
+
const { htmlWithStyles } = renderToStringWithJITCSSDSD(vnode, {
|
|
181
|
+
dsdPolyfill: false,
|
|
182
|
+
router,
|
|
196
183
|
})
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
184
|
+
|
|
185
|
+
// Collect and serialize any useHead() calls from the rendered components.
|
|
186
|
+
const headTags = serializeHeadTags(endHeadCollection())
|
|
187
|
+
|
|
188
|
+
// Merge loader data script + useHead() tags into the document head.
|
|
189
|
+
const headContent = [head, headTags].filter(Boolean).join('\\n')
|
|
190
|
+
|
|
191
|
+
// Wrap the rendered body in a full HTML document and inject the head additions
|
|
192
|
+
// (loader data script, useHead() tags, JIT styles). No polyfill in body yet.
|
|
193
|
+
const ssrHtml = \`<!DOCTYPE html><html><head>\${headContent}</head><body>\${htmlWithStyles}</body></html>\`
|
|
194
|
+
|
|
195
|
+
let finalHtml = _clientTemplate
|
|
196
|
+
? _mergeWithClientTemplate(ssrHtml, _clientTemplate)
|
|
197
|
+
: ssrHtml
|
|
198
|
+
|
|
199
|
+
// Inject DSD polyfill at end of <body>, outside <cer-layout-view>, so the
|
|
200
|
+
// browser runs it after parsing the declarative shadow roots.
|
|
201
|
+
finalHtml = finalHtml.includes('</body>')
|
|
202
|
+
? finalHtml.replace('</body>', DSD_POLYFILL_SCRIPT + '</body>')
|
|
203
|
+
: finalHtml + DSD_POLYFILL_SCRIPT
|
|
204
|
+
|
|
201
205
|
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
let _capturedHtml = ''
|
|
205
|
-
// Wrap _rawHandler in an isolated async-local-storage context so that
|
|
206
|
-
// vnodeFactory's enterWith() call is scoped to this request only.
|
|
207
|
-
await _cerDataStore.run(null, async () => {
|
|
208
|
-
// Omit write() to force the non-streaming collect-then-end code path.
|
|
209
|
-
await _rawHandler(req, { setHeader: () => {}, end: (body) => { _capturedHtml = body } })
|
|
206
|
+
res.end(finalHtml)
|
|
210
207
|
})
|
|
211
|
-
let _merged = _mergeWithClientTemplate(_capturedHtml, _clientTemplate)
|
|
212
|
-
// Inject DSD polyfill at end of <body>, outside <cer-layout-view> light DOM.
|
|
213
|
-
_merged = _merged.includes('</body>')
|
|
214
|
-
? _merged.replace('</body>', DSD_POLYFILL_SCRIPT + '</body>')
|
|
215
|
-
: _merged + DSD_POLYFILL_SCRIPT
|
|
216
|
-
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
|
217
|
-
res.end(_merged)
|
|
218
208
|
}
|
|
219
209
|
|
|
220
210
|
export { apiRoutes, plugins, layouts, routes }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry-server-template.js","sourceRoot":"","sources":["../../src/runtime/entry-server-template.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"entry-server-template.js","sourceRoot":"","sources":["../../src/runtime/entry-server-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsMpC,CAAA"}
|