@rsc-kit/core 0.20.2 → 0.20.4

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/dist/vite.js CHANGED
@@ -2264,7 +2264,6 @@ const NITRO_HANDLER_OPTIONS = ` props: (match, request) => ({
2264
2264
  ...match.params,
2265
2265
  ...Object.fromEntries(new URL(request.url).searchParams),
2266
2266
  }),
2267
- version: process.env.RSC_BUILD_VERSION,
2268
2267
  // A built server gzips what it answers, where the runtime can; the dev
2269
2268
  // server answers raw, which is what a person reading a response wants.
2270
2269
  compress: import.meta.env.PROD,
@@ -2394,6 +2393,7 @@ import { LoadingBoundary } from ${JSON.stringify(join(packageDir, "js/LoadingBou
2394
2393
  import { DocumentTitle } from ${JSON.stringify(join(packageDir, "js/DocumentTitle"))}
2395
2394
  import { SlotBoundary } from ${JSON.stringify(join(packageDir, "js/SlotBoundary"))}
2396
2395
  import { RouteErrorBoundary } from ${JSON.stringify(join(packageDir, "js/RouteErrorBoundary"))}
2396
+ import __rsc_assets from 'virtual:vite-rsc/assets-manifest'
2397
2397
  import { sectionComponent } from ${JSON.stringify(join(packageDir, "js/section"))}
2398
2398
  import { PathnameProvider } from ${JSON.stringify(join(packageDir, "js/PathnameProvider"))}
2399
2399
  import { DefaultRouteError } from ${JSON.stringify(join(packageDir, "js/DefaultRouteError"))}
@@ -2660,6 +2660,69 @@ export function manifest(): any {
2660
2660
  return ${JSON.stringify(routeManifest())}
2661
2661
  }
2662
2662
 
2663
+ /**
2664
+ * The stylesheet and the client entry every document of this build links,
2665
+ * from the build's own manifest, for the host's Link header - a CDN sends
2666
+ * it ahead as 103 Early Hints. The root layout's stylesheet is the one the
2667
+ * document carries; the entry's own is usually empty, and either way both
2668
+ * are named. See earlyHints.ts.
2669
+ */
2670
+ export function criticalAssets(): { styles: string[]; modules: string[]; fonts: string[] } {
2671
+ // Nothing to send ahead in development: the entry is Vite's own url, and
2672
+ // there is no CDN in front to hint.
2673
+ if (import.meta.env.DEV) return { styles: [], modules: [], fonts: [] }
2674
+
2675
+ const manifest = __rsc_assets as {
2676
+ clientEntryUrl?: string
2677
+ clientEntryDeps?: { css?: string[] }
2678
+ serverResources?: Record<string, { css?: string[] }>
2679
+ }
2680
+ const styles = new Set<string>(manifest.clientEntryDeps?.css ?? [])
2681
+
2682
+ const rootLayouts = ['app/layout.tsx', 'app/layout.jsx', 'app/layout.ts', 'app/layout.js']
2683
+
2684
+ for (const [file, resources] of Object.entries(manifest.serverResources ?? {})) {
2685
+ if (rootLayouts.some((name) => file === name || file.endsWith('/' + name))) {
2686
+ for (const href of resources.css ?? []) styles.add(href)
2687
+ }
2688
+ }
2689
+
2690
+ return {
2691
+ styles: [...styles],
2692
+ modules: manifest.clientEntryUrl ? [manifest.clientEntryUrl] : [],
2693
+ fonts: [],
2694
+ }
2695
+ }
2696
+
2697
+ let cachedBuildId: string | null = null
2698
+
2699
+ /**
2700
+ * What this build's client is, as a short id.
2701
+ *
2702
+ * Derived from the bootstrap script the document ships - the hashed name of
2703
+ * the browser entry, which changes with the client references and chunks
2704
+ * it maps - so two builds whose browsers would disagree never share one.
2705
+ * The host puts it on every answer and compares it against what a client
2706
+ * says it is running; a mismatch on a payload request is a 409, and the
2707
+ * client loads the document instead. Read once, on demand, because the
2708
+ * bootstrap content is a build product this bundle only reaches at runtime.
2709
+ */
2710
+ export async function buildId(): Promise<string> {
2711
+ if (cachedBuildId !== null) return cachedBuildId
2712
+
2713
+ const content: string = await (import.meta as any).viteRsc.loadBootstrapScriptContent('index')
2714
+ let hash = 2166136261
2715
+
2716
+ for (let i = 0; i < content.length; i++) {
2717
+ hash ^= content.charCodeAt(i)
2718
+ hash = Math.imul(hash, 16777619) >>> 0
2719
+ }
2720
+
2721
+ cachedBuildId = hash.toString(16).padStart(8, '0')
2722
+
2723
+ return cachedBuildId
2724
+ }
2725
+
2663
2726
  /**
2664
2727
  * For each server action id, whether a client built it. The build asks after
2665
2728
  * the bundle exists, because the answer is a mark on the loaded function and
@@ -3206,6 +3269,15 @@ async function renderTree(
3206
3269
  if (md.description != null) head.push(createElement('meta', { key: '__d', name: 'description', content: String(md.description) }))
3207
3270
  // The name only, never the version - see the identify option.
3208
3271
  if (manifest().build?.identify) head.push(createElement('meta', { key: '__g', name: 'generator', content: 'rsc-kit' }))
3272
+ }
3273
+
3274
+ // Which build this document is, for the client to say on every navigation
3275
+ // - read from the document rather than learned from the first answer,
3276
+ // because a document a service worker served from its cache is the last
3277
+ // build's while the answers are this one's. An id, not a version number.
3278
+ if (bootstrap) head.push(createElement('meta', { key: '__v', name: 'rsc-kit:build', content: await buildId() }))
3279
+
3280
+ if (md) {
3209
3281
 
3210
3282
  // robots is a string, or the object Next takes: index and follow as
3211
3283
  // their no- forms, the flags by name, the limits as name:value. googleBot
@@ -4437,6 +4509,8 @@ async function serve(request: Request): Promise<Response> {
4437
4509
  handleApiRoute,
4438
4510
  resolveMetadata,
4439
4511
  runRouteMiddleware,
4512
+ buildId,
4513
+ criticalAssets,
4440
4514
  } as never,
4441
4515
  ${NITRO_HANDLER_OPTIONS}${NITRO_PRERENDERED} maxActionBody: ${maxActionBody === undefined ? "undefined" : String(maxActionBody)},
4442
4516
  })
@@ -4520,6 +4594,7 @@ function generateEntrySsr() {
4520
4594
  const fallbackReport = join(packageDir, "js/fallbackReport");
4521
4595
  const redirectDigestModule = join(packageDir, "redirectDigest");
4522
4596
  const notFoundModule = join(packageDir, "notFound");
4597
+ const earlyClicks = join(packageDir, "js/earlyClicks");
4523
4598
  return `// GENERATED by rscKit() — do not edit.
4524
4599
  import { createFromReadableStream } from '@vitejs/plugin-rsc/ssr'
4525
4600
  import { renderToReadableStream, resume } from 'react-dom/server.edge'
@@ -4529,6 +4604,7 @@ import { noteFallback } from ${JSON.stringify(request)}
4529
4604
  import { cancelledByConsumer, caughtByLoading } from ${JSON.stringify(fallbackReport)}
4530
4605
  import { parseRedirectDigest } from ${JSON.stringify(redirectDigestModule)}
4531
4606
  import { isNotFoundDigest } from ${JSON.stringify(notFoundModule)}
4607
+ import { EARLY_CLICKS } from ${JSON.stringify(earlyClicks)}
4532
4608
 
4533
4609
  // Set only by the dev server. @vitejs/plugin-rsc emits its bootstrap and CSS
4534
4610
  // links root-relative in dev, which would send the browser to the host for
@@ -4547,8 +4623,11 @@ export async function handleSsr(
4547
4623
  // Without the bootstrap the page ships no client runtime at all: no React,
4548
4624
  // no Flight client, no router. HTML only. A page with nothing interactive on
4549
4625
  // it has no use for 70kB of hydration.
4626
+ // The click listener first, then the runtime's import: a tap in the
4627
+ // seconds before the runtime hydrates is held for it rather than given
4628
+ // to the browser as a document load. See earlyClicks.ts.
4550
4629
  const bootstrapScriptContent = bootstrap
4551
- ? await (import.meta as any).viteRsc.loadBootstrapScriptContent('index')
4630
+ ? EARLY_CLICKS + (await (import.meta as any).viteRsc.loadBootstrapScriptContent('index'))
4552
4631
  : undefined
4553
4632
 
4554
4633
  // Without an onError handler React rejects each abortable task on its own,
@@ -4590,7 +4669,7 @@ export async function handleSsrPrerender(
4590
4669
  const bootstrapScriptContent =
4591
4670
  options.bootstrap === false
4592
4671
  ? undefined
4593
- : await (import.meta as any).viteRsc.loadBootstrapScriptContent('index')
4672
+ : EARLY_CLICKS + (await (import.meta as any).viteRsc.loadBootstrapScriptContent('index'))
4594
4673
 
4595
4674
  const { prelude, postponed } = await prerender(root as any, {
4596
4675
  bootstrapScriptContent,
@@ -5420,6 +5499,21 @@ export function rscKit(options = {}) {
5420
5499
  if (typeof nitro.options.compressPublicAssets !== "object")
5421
5500
  nitro.options.compressPublicAssets = true;
5422
5501
  nitro.options.virtual ??= {};
5502
+ // The names of functions survive Nitro's bundle. The pages were
5503
+ // prerendered against Vite's own server bundles, where a dependency
5504
+ // is loaded from node_modules under the names it was written with;
5505
+ // Nitro then bundles that dependency into the server, and renames a
5506
+ // function whose name collides with another's - J becomes J2 - and
5507
+ // `name` follows the identifier. React's resume of a stored shell
5508
+ // compares the component names it recorded with the ones it meets,
5509
+ // and a renamed one is "Expected the resume to render <J> in this
5510
+ // slot but instead it rendered <J2>", on every request, with the
5511
+ // hole rendered in the browser instead. keepNames pins `name` to
5512
+ // what the source said, whatever the identifier became.
5513
+ nitro.options.rollupConfig = {
5514
+ ...(nitro.options.rollupConfig ?? {}),
5515
+ output: { keepNames: true, ...(nitro.options.rollupConfig?.output ?? {}) },
5516
+ };
5423
5517
  // A polyfill a dependency checks for at module evaluation, loaded
5424
5518
  // before any service. The bundler places an external import after
5425
5519
  // the chunk imports of the module that had it, whatever the source