@octanejs/app-core 0.0.12 → 0.0.13
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/package.json +3 -3
- package/src/server/production.js +31 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octanejs/app-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -79,10 +79,10 @@
|
|
|
79
79
|
"esbuild": "^0.28.1"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
|
-
"octane": "0.1.
|
|
82
|
+
"octane": "0.1.17"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@types/node": "^24.13.3",
|
|
86
|
-
"octane": "0.1.
|
|
86
|
+
"octane": "0.1.17"
|
|
87
87
|
}
|
|
88
88
|
}
|
package/src/server/production.js
CHANGED
|
@@ -279,39 +279,67 @@ export function createHandler(manifest, deps) {
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
const headContent = [...preloadTags, dataScript].join('\n');
|
|
282
|
-
const
|
|
282
|
+
const noncedTemplate = applyHydrationNonce(htmlTemplate, nonce);
|
|
283
283
|
|
|
284
284
|
const status = route.status ?? 200;
|
|
285
285
|
const headers = { 'Content-Type': 'text/html; charset=utf-8' };
|
|
286
286
|
|
|
287
|
-
|
|
287
|
+
// `headChannel: 'separate'` below: this handler renders the ROUTE into the
|
|
288
|
+
// template's `<div id="root">`, not a document, so core's default fold has
|
|
289
|
+
// no `</head>` to target and would prepend hoisted `<title>`/`<meta>`/
|
|
290
|
+
// `<link>` into the body, where a title loses to the template's and a
|
|
291
|
+
// canonical or description is ignored. Splicing at `<!--ssr-head-->`
|
|
292
|
+
// instead is also what makes hydration's `document.head` adoption find the
|
|
293
|
+
// ownership markers rather than creating duplicates.
|
|
294
|
+
//
|
|
295
|
+
// The replacement is a FUNCTION, not a string: a string replacement makes
|
|
296
|
+
// `$&`, `` $` ``, `$'` and `$1` in the inserted text expand against the
|
|
297
|
+
// match, and this text now carries author-controlled metadata as well as
|
|
298
|
+
// the serialized route data.
|
|
299
|
+
/** @param {string} hoistedHead */
|
|
300
|
+
const splitAroundBody = (hoistedHead) =>
|
|
301
|
+
splitSsrTemplate(noncedTemplate.replace('<!--ssr-head-->', () => headContent + hoistedHead));
|
|
288
302
|
|
|
289
303
|
if (manifest.render === 'buffered') {
|
|
290
304
|
// Await-everything fallback (`prerender` from octane/static): no
|
|
291
305
|
// streaming, one document. The deduped scoped-style tags lead the body
|
|
292
306
|
// markup inside #root — the same position they hold in the streamed
|
|
293
307
|
// shell — so hydrateRoot's leading-style skip applies unchanged.
|
|
294
|
-
const {
|
|
308
|
+
const {
|
|
309
|
+
html: body,
|
|
310
|
+
css,
|
|
311
|
+
head,
|
|
312
|
+
} = await prerender(RootComponent, undefined, {
|
|
295
313
|
nonce: nonce ?? undefined,
|
|
314
|
+
headChannel: 'separate',
|
|
296
315
|
signal: context.request.signal,
|
|
297
316
|
onError(/** @type {unknown} */ error) {
|
|
298
317
|
console.error('[octane] SSR render error:', error);
|
|
299
318
|
},
|
|
300
319
|
});
|
|
320
|
+
const [prefix, suffix] = splitAroundBody(head ?? '');
|
|
301
321
|
return new Response(prefix + css + body + suffix, { status, headers });
|
|
302
322
|
}
|
|
303
323
|
|
|
304
324
|
// Streaming (default): shell flushes at first await, suspense segments
|
|
305
325
|
// stream out-of-order behind it — identical to dev.
|
|
326
|
+
let hoistedHead = '';
|
|
306
327
|
/** @type {ReadableStream<Uint8Array>} */
|
|
307
328
|
const renderStream = await renderToReadableStream(RootComponent, undefined, {
|
|
308
329
|
nonce: nonce ?? undefined,
|
|
330
|
+
headChannel: 'separate',
|
|
331
|
+
// Fires before the shell is written, so the metadata is in hand before
|
|
332
|
+
// the template prefix (which carries `<head>`) is composed below.
|
|
333
|
+
onHeadReady(/** @type {string} */ head) {
|
|
334
|
+
hoistedHead = head;
|
|
335
|
+
},
|
|
309
336
|
signal: context.request.signal,
|
|
310
337
|
onError(/** @type {unknown} */ error) {
|
|
311
338
|
console.error('[octane] SSR render error:', error);
|
|
312
339
|
},
|
|
313
340
|
});
|
|
314
341
|
|
|
342
|
+
const [prefix, suffix] = splitAroundBody(hoistedHead);
|
|
315
343
|
const body = composeHtmlStream(prefix, renderStream, suffix);
|
|
316
344
|
|
|
317
345
|
return new Response(body, { status, headers });
|