@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octanejs/vite-plugin",
3
- "version": "0.1.16",
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.12"
50
+ "@octanejs/app-core": "0.0.13"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "vite": "^8.0.16",
54
- "octane": "0.1.16"
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.16"
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 nothing is spliced into `<!--ssr-head-->`
41
- * anymore only the hydration data script goes there. `hydrateRoot()`
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
- // before consuming the one required head marker with request data.
153
- let html = injectHydrationEntry(template, '/@id/virtual:octane-hydrate', nonce);
154
- html = html.replace('<!--ssr-head-->', headContent);
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
- const [prefix, suffix] = splitSsrTemplate(html);
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