@octanejs/vite-plugin 0.1.16 → 0.1.17
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 +4 -4
- package/src/server/render-route.js +22 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octanejs/vite-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -47,17 +47,17 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@ripple-ts/adapter": "^0.3.112",
|
|
50
|
-
"@octanejs/app-core": "0.0.
|
|
50
|
+
"@octanejs/app-core": "0.0.13"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"vite": "^8.0.16",
|
|
54
|
-
"octane": "0.1.
|
|
54
|
+
"octane": "0.1.17"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/node": "^24.13.3",
|
|
58
58
|
"playwright": "^1.61.1",
|
|
59
59
|
"type-fest": "^5.8.0",
|
|
60
60
|
"vite": "^8.1.5",
|
|
61
|
-
"octane": "0.1.
|
|
61
|
+
"octane": "0.1.17"
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -37,8 +37,9 @@ import {
|
|
|
37
37
|
*
|
|
38
38
|
* - Scoped CSS rides the STREAM (the shell emits its deduped
|
|
39
39
|
* `<style data-octane>` tags ahead of the body markup, per-wave styles ride
|
|
40
|
-
* their segment chunk), so
|
|
41
|
-
*
|
|
40
|
+
* their segment chunk), so styles are not spliced into `<!--ssr-head-->`.
|
|
41
|
+
* That marker receives the hydration data script and the shell's hoisted
|
|
42
|
+
* `<title>`/`<meta>`/`<link>` (see `headChannel` below). `hydrateRoot()`
|
|
42
43
|
* skips the shell's leading style tags when adopting.
|
|
43
44
|
* - A render error BEFORE the shell completes still produces the dev 500
|
|
44
45
|
* page (`renderToReadableStream` rejects on shell errors). An error AFTER
|
|
@@ -148,17 +149,27 @@ export async function handleRenderRoute(route, context, vite, octaneConfig) {
|
|
|
148
149
|
// Apply Vite's HTML transforms (HMR client, module resolution, etc.).
|
|
149
150
|
template = await vite.transformIndexHtml(context.url.pathname, template);
|
|
150
151
|
|
|
151
|
-
// Validate the raw SSR template and inject the request-nonced hydrate entry
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
html =
|
|
152
|
+
// Validate the raw SSR template and inject the request-nonced hydrate entry.
|
|
153
|
+
// The one required head marker is consumed AFTER the render, once the
|
|
154
|
+
// hoisted metadata is in hand, validation still happens up front here.
|
|
155
|
+
const html = injectHydrationEntry(template, '/@id/virtual:octane-hydrate', nonce);
|
|
155
156
|
|
|
156
157
|
// Start the render. This await resolves at SHELL-ready (so a synchronous
|
|
157
158
|
// render error still falls into the catch below and produces the dev 500
|
|
158
159
|
// page); segments keep flushing through the returned stream afterwards.
|
|
160
|
+
//
|
|
161
|
+
// `headChannel: 'separate'` matches production (see production.js): the
|
|
162
|
+
// route renders into the template's `#root`, not a document, so hoisted
|
|
163
|
+
// `<title>`/`<meta>`/`<link>` must be spliced at `<!--ssr-head-->` rather
|
|
164
|
+
// than folded into a body that has no `</head>`.
|
|
165
|
+
let hoistedHead = '';
|
|
159
166
|
/** @type {ReadableStream<Uint8Array>} */
|
|
160
167
|
const renderStream = await renderToReadableStream(RootComponent, undefined, {
|
|
161
168
|
nonce: nonce ?? undefined,
|
|
169
|
+
headChannel: 'separate',
|
|
170
|
+
onHeadReady(/** @type {string} */ head) {
|
|
171
|
+
hoistedHead = head;
|
|
172
|
+
},
|
|
162
173
|
signal: context.request.signal,
|
|
163
174
|
onError(/** @type {unknown} */ error) {
|
|
164
175
|
if (error instanceof Error) vite.ssrFixStacktrace(error);
|
|
@@ -169,7 +180,11 @@ export async function handleRenderRoute(route, context, vite, octaneConfig) {
|
|
|
169
180
|
const status = route.status ?? 200;
|
|
170
181
|
const headers = { 'Content-Type': 'text/html; charset=utf-8' };
|
|
171
182
|
|
|
172
|
-
|
|
183
|
+
// Function replacement, so `$&`/`` $` ``/`$'`/`$1` inside the route data or
|
|
184
|
+
// author-controlled metadata cannot expand against the match.
|
|
185
|
+
const [prefix, suffix] = splitSsrTemplate(
|
|
186
|
+
html.replace('<!--ssr-head-->', () => headContent + hoistedHead),
|
|
187
|
+
);
|
|
173
188
|
|
|
174
189
|
// Template prefix → render stream (shell, then out-of-order segments) →
|
|
175
190
|
// template suffix. The hydration <script> is in the SUFFIX, so by the time
|