@octanejs/vite-plugin 0.1.13 → 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.13",
3
+ "version": "0.1.17",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -46,18 +46,18 @@
46
46
  }
47
47
  },
48
48
  "dependencies": {
49
- "@ripple-ts/adapter": "^0.3.108",
50
- "@octanejs/app-core": "0.0.9"
49
+ "@ripple-ts/adapter": "^0.3.112",
50
+ "@octanejs/app-core": "0.0.13"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "vite": "^8.0.16",
54
- "octane": "0.1.13"
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.13"
61
+ "octane": "0.1.17"
62
62
  }
63
63
  }
package/src/index.js CHANGED
@@ -9,6 +9,7 @@ import { AsyncLocalStorage } from 'node:async_hooks';
9
9
  import { createRequire } from 'node:module';
10
10
 
11
11
  import { octane as octaneCompiler } from 'octane/compiler/vite';
12
+ import { handleRpcRequest as handleServerRpcRequest } from '@octanejs/app-core';
12
13
 
13
14
  import { createRouter } from './server/router.js';
14
15
  import { createContext, runMiddlewareChain } from './server/middleware.js';
@@ -35,7 +36,7 @@ import {
35
36
  } from './project-codegen.js';
36
37
  import { createClientAssetMap } from './client-assets.js';
37
38
 
38
- import { patch_global_fetch, is_rpc_request, handle_rpc_request } from '@ripple-ts/adapter/rpc';
39
+ import { patch_global_fetch, is_rpc_request } from '@ripple-ts/adapter/rpc';
39
40
 
40
41
  import { get_route_entry_path } from './routes.js';
41
42
 
@@ -52,6 +53,7 @@ export {
52
53
  get_route_entry_export_name,
53
54
  get_route_entry_id,
54
55
  get_route_entry_path,
56
+ handleRpcRequest,
55
57
  handleServerRoute,
56
58
  is_rpc_request,
57
59
  runMiddlewareChain,
@@ -214,7 +216,12 @@ function collect_hydrate_module_paths(config) {
214
216
  * `handler`/`nodeHandler` module; webworker-target adapters get an importable
215
217
  * `createWebWorkerHandler` factory for their deployment wrapper.
216
218
  *
217
- * @param {{ hmr?: boolean, profile?: boolean, exclude?: string[], requireDirective?: boolean, renderers?: import('@octanejs/app-core').ExperimentalRendererConfigOptions }} [inlineOptions]
219
+ * `devtools: true` enables the compiler's profile mode in DEV ONLY: profiling
220
+ * is on in `vite dev` and fully off in `vite build` (so production tree-shakes
221
+ * it). An explicit `profile` (true or false) always takes precedence over
222
+ * `devtools`.
223
+ *
224
+ * @param {{ hmr?: boolean, profile?: boolean, devtools?: boolean, exclude?: string[], requireDirective?: boolean, renderers?: import('@octanejs/app-core').ExperimentalRendererConfigOptions }} [inlineOptions]
218
225
  * @returns {Plugin[]}
219
226
  */
220
227
  export function octane(inlineOptions = {}) {
@@ -873,7 +880,7 @@ export function octane(inlineOptions = {}) {
873
880
  /**
874
881
  * @type {{
875
882
  * hmr?: boolean,
876
- * profile?: boolean,
883
+ * profile?: boolean | 'auto',
877
884
  * exclude?: string[],
878
885
  * requireDirective?: boolean,
879
886
  * renderers?: import('@octanejs/app-core').ExperimentalRendererConfigOptions,
@@ -881,7 +888,10 @@ export function octane(inlineOptions = {}) {
881
888
  */
882
889
  const compilerOptions = {};
883
890
  if (inlineOptions.hmr !== undefined) compilerOptions.hmr = inlineOptions.hmr;
891
+ // Explicit `profile` wins; otherwise `devtools: true` opts into the
892
+ // command-aware `'auto'` signal the compiler resolves to DEV-only profiling.
884
893
  if (inlineOptions.profile !== undefined) compilerOptions.profile = inlineOptions.profile;
894
+ else if (inlineOptions.devtools === true) compilerOptions.profile = 'auto';
885
895
  if (inlineOptions.exclude !== undefined) compilerOptions.exclude = inlineOptions.exclude;
886
896
  if (inlineOptions.requireDirective !== undefined) {
887
897
  compilerOptions.requireDirective = inlineOptions.requireDirective;
@@ -948,7 +958,7 @@ async function handleRpcRequest(req, res, vite, trustProxy, config) {
948
958
  const webRequest = nodeRequestToWebRequest(req);
949
959
  const asyncContext = getDevAsyncContext(config);
950
960
 
951
- const response = await handle_rpc_request(webRequest, {
961
+ const response = await handleServerRpcRequest(webRequest, {
952
962
  async resolveFunction(hash) {
953
963
  const rpcModules = /** @type {any} */ (globalThis).rpc_modules;
954
964
  if (!rpcModules) return null;
@@ -966,6 +976,9 @@ async function handleRpcRequest(req, res, vite, trustProxy, config) {
966
976
  },
967
977
  asyncContext,
968
978
  trustProxy,
979
+ middlewares: config?.middlewares ?? [],
980
+ allowedOrigins: config?.server?.rpc?.allowedOrigins,
981
+ maxBodyBytes: config?.server?.rpc?.maxBodyBytes,
969
982
  });
970
983
 
971
984
  await sendWebResponse(res, response);
@@ -973,7 +986,7 @@ async function handleRpcRequest(req, res, vite, trustProxy, config) {
973
986
  console.error('[@octanejs/vite-plugin] RPC error:', error);
974
987
  res.statusCode = 500;
975
988
  res.setHeader('Content-Type', 'application/json');
976
- res.end(JSON.stringify({ error: error instanceof Error ? error.message : 'RPC failed' }));
989
+ res.end(JSON.stringify({ error: 'Internal Server Error' }));
977
990
  }
978
991
  }
979
992
 
@@ -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