@rangojs/router 0.0.0-experimental.145 → 0.0.0-experimental.146

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.
@@ -107,6 +107,11 @@ export type ClientChunks =
107
107
 
108
108
  // -- Plugin options ---------------------------------------------------------
109
109
 
110
+ /**
111
+ * Document script strategy. See {@link RangoBaseOptions.headScripts}.
112
+ */
113
+ export type HeadScriptsOption = "preinit" | "preload";
114
+
110
115
  /**
111
116
  * Base options shared by all presets
112
117
  */
@@ -126,6 +131,34 @@ interface RangoBaseOptions {
126
131
  */
127
132
  clientChunks?: ClientChunks;
128
133
 
134
+ /**
135
+ * How the document ships its JavaScript.
136
+ *
137
+ * - `"preinit"` (**default**): client-reference chunks render as EXECUTING
138
+ * `<script type="module" async>` tags hoisted into `<head>` (upgrading
139
+ * plugin-rsc's modulepreload hints in place), and the browser entry ships
140
+ * as Fizz `bootstrapModules` — a head `modulepreload fetchpriority=low`
141
+ * hint plus the executing end-of-shell `id="_R_"` module script. Chunk
142
+ * execution overlaps body streaming instead of waiting for the hydration
143
+ * import walk; under PPR everything lands in the stored shell prelude.
144
+ * - `"preload"`: the previous behavior — `<link rel="modulepreload">` hints
145
+ * only, entry as an inline `import()` script at end of shell. Chunks
146
+ * fetch+compile early but execute only when hydration imports them.
147
+ *
148
+ * Build-only for the chunk half: plugin-rsc resolves no JS deps per client
149
+ * reference in dev, so dev documents carry no head chunk scripts in either
150
+ * mode (the bootstrap conversion does apply in dev). Trades and upstream
151
+ * limits are documented in src/ssr/preinit-client-references.ts.
152
+ *
153
+ * Wired in the generated virtual SSR entry
154
+ * (`src/ssr/preinit-client-references.ts` has the mechanism); apps with a
155
+ * custom SSR entry choose per-handler via `SSRDependencies.headScripts` and
156
+ * `installClientReferencePreinit`.
157
+ *
158
+ * @default "preinit"
159
+ */
160
+ headScripts?: HeadScriptsOption;
161
+
129
162
  /**
130
163
  * Filter which files route discovery scans, by glob. Paths are matched
131
164
  * root-relative (e.g. `src/routes/**`). `include` restricts discovery to
@@ -1,3 +1,5 @@
1
+ import type { HeadScriptsOption } from "../plugin-types.js";
2
+
1
3
  export const VIRTUAL_ENTRY_BROWSER: string = `
2
4
  import {
3
5
  createFromReadableStream,
@@ -36,21 +38,49 @@ async function initializeApp() {
36
38
  initializeApp().catch(console.error);
37
39
  `.trim();
38
40
 
39
- export const VIRTUAL_ENTRY_SSR: string = `
40
- import { createFromReadableStream } from "@rangojs/router/internal/deps/ssr";
41
+ /**
42
+ * Generate the virtual SSR entry. `headScripts` mirrors the rango() plugin
43
+ * option: "preinit" (default) installs the client-reference preinit hook and
44
+ * lets the SSR handlers convert the bootstrap to `bootstrapModules`;
45
+ * "preload" omits the hook and pins the handlers to the hint-only strategy.
46
+ */
47
+ export function getVirtualEntrySSR(
48
+ headScripts: HeadScriptsOption = "preinit",
49
+ ): string {
50
+ const preinit = headScripts !== "preload";
51
+ // The preload variant drops exactly three preinit-only lines, all built
52
+ // here so the template below stays a single unconditional shape.
53
+ const depsImportNames = preinit
54
+ ? "createFromReadableStream,\n setOnClientReference,"
55
+ : "createFromReadableStream,";
56
+ const ssrImportNames = preinit ? "\n installClientReferencePreinit," : "";
57
+ const install = preinit
58
+ ? `
59
+ // Upgrade client-reference modulepreload hints to executing module scripts in
60
+ // the document head, for every render pass (live SSR, shell capture, resume).
61
+ // See src/ssr/preinit-client-references.ts for the full rationale.
62
+ installClientReferencePreinit(setOnClientReference);
63
+ `
64
+ : "";
65
+ const hs = JSON.stringify(headScripts);
66
+ return `
67
+ import {
68
+ ${depsImportNames}
69
+ } from "@rangojs/router/internal/deps/ssr";
41
70
  import { renderToReadableStream, resume } from "react-dom/server.edge";
42
71
  import { prerender } from "react-dom/static.edge";
43
72
  import { injectRSCPayload } from "@rangojs/router/internal/deps/html-stream-server";
44
73
  import {
45
74
  createSSRHandler,
46
75
  createShellCaptureHandler,
47
- createShellResumeHandler,
76
+ createShellResumeHandler,${ssrImportNames}
48
77
  } from "@rangojs/router/ssr";
49
-
78
+ ${install}
50
79
  export const renderHTML = createSSRHandler({
51
80
  createFromReadableStream,
52
81
  renderToReadableStream,
53
82
  injectRSCPayload,
83
+ headScripts: ${hs},
54
84
  loadBootstrapScriptContent: () =>
55
85
  import.meta.viteRsc.loadBootstrapScriptContent("index"),
56
86
  });
@@ -61,6 +91,7 @@ export const captureShellHTML = createShellCaptureHandler({
61
91
  injectRSCPayload,
62
92
  prerender,
63
93
  resume,
94
+ headScripts: ${hs},
64
95
  loadBootstrapScriptContent: () =>
65
96
  import.meta.viteRsc.loadBootstrapScriptContent("index"),
66
97
  });
@@ -71,10 +102,12 @@ export const resumeShellHTML = createShellResumeHandler({
71
102
  injectRSCPayload,
72
103
  prerender,
73
104
  resume,
105
+ headScripts: ${hs},
74
106
  loadBootstrapScriptContent: () =>
75
107
  import.meta.viteRsc.loadBootstrapScriptContent("index"),
76
108
  });
77
109
  `.trim();
110
+ }
78
111
 
79
112
  /**
80
113
  * Virtual modules an RSC entry must import at startup to register the data the
package/src/vite/rango.ts CHANGED
@@ -239,7 +239,11 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
239
239
  },
240
240
  });
241
241
 
242
- plugins.push(createVirtualEntriesPlugin(finalEntries));
242
+ plugins.push(
243
+ createVirtualEntriesPlugin(finalEntries, undefined, {
244
+ headScripts: resolvedOptions.headScripts,
245
+ }),
246
+ );
243
247
  plugins.push(performanceTracksPlugin());
244
248
  plugins.push(
245
249
  rsc({
@@ -476,7 +480,11 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
476
480
  },
477
481
  });
478
482
 
479
- plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
483
+ plugins.push(
484
+ createVirtualEntriesPlugin(finalEntries, routerRef, {
485
+ headScripts: resolvedOptions.headScripts,
486
+ }),
487
+ );
480
488
  plugins.push(performanceTracksPlugin());
481
489
  plugins.push(
482
490
  rsc({
@@ -5,12 +5,13 @@ import { getPublishedPackageName } from "./package-resolution.js";
5
5
  import { performanceTracksOptimizeDepsPlugin } from "../plugins/performance-tracks.js";
6
6
  import {
7
7
  VIRTUAL_ENTRY_BROWSER,
8
- VIRTUAL_ENTRY_SSR,
8
+ getVirtualEntrySSR,
9
9
  getVirtualEntryRSC,
10
10
  getVirtualEntryRSCHost,
11
11
  getVirtualVersionContent,
12
12
  VIRTUAL_IDS,
13
13
  } from "../plugins/virtual-entries.js";
14
+ import type { HeadScriptsOption } from "../plugin-types.js";
14
15
 
15
16
  // Cloudflare preset: @cloudflare/vite-plugin sets optimizeDeps.entries (string
16
17
  // or array) on the rsc environment. Single source for both the discovery plugin
@@ -109,6 +110,7 @@ export function normalizeHostRouterEntry(
109
110
  export function createVirtualEntriesPlugin(
110
111
  entries: { client: string; ssr: string; rsc?: string },
111
112
  routerPathRef?: { path?: string; kind?: "router" | "host" },
113
+ options?: { headScripts?: HeadScriptsOption },
112
114
  ): Plugin {
113
115
  // Build virtual modules map based on which entries use virtual IDs
114
116
  const virtualModules: Record<string, string> = {};
@@ -117,7 +119,7 @@ export function createVirtualEntriesPlugin(
117
119
  virtualModules[VIRTUAL_IDS.browser] = VIRTUAL_ENTRY_BROWSER;
118
120
  }
119
121
  if (entries.ssr === VIRTUAL_IDS.ssr) {
120
- virtualModules[VIRTUAL_IDS.ssr] = VIRTUAL_ENTRY_SSR;
122
+ virtualModules[VIRTUAL_IDS.ssr] = getVirtualEntrySSR(options?.headScripts);
121
123
  }
122
124
 
123
125
  // RSC entry is resolved lazily in load() because routerPath may be