@rsc-kit/core 0.20.12 → 0.20.13

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
@@ -3903,9 +3903,20 @@ export async function handleRscResume(
3903
3903
  )
3904
3904
 
3905
3905
  const ssr = await (import.meta as any).viteRsc.loadModule('ssr', 'index')
3906
- const htmlStream = await ssr.handleSsrResume(flight, postponed, nonce)
3906
+ // Whether React could replay the recorded tree at all. A bundler that
3907
+ // merges scopes renames components, and a name is how React matches a
3908
+ // slot: the same page, built once and bundled again - which is what
3909
+ // bun build --compile does - has different names at runtime than the
3910
+ // shell recorded. Reported so the host can stop resuming this shell and
3911
+ // render the page whole, rather than serving one whose holes are all
3912
+ // client-rendered. Set before the stream ends, because that is when
3913
+ // React reports it.
3914
+ let replayFailed = false
3915
+ const htmlStream = await ssr.handleSsrResume(flight, postponed, nonce, () => {
3916
+ replayFailed = true
3917
+ })
3907
3918
 
3908
- return { htmlStream }
3919
+ return { htmlStream, replayed: () => !replayFailed }
3909
3920
  }
3910
3921
 
3911
3922
  // Server action (worker: rsc-action).
@@ -4977,6 +4988,11 @@ export async function handleSsrResume(
4977
4988
  rscStream: ReadableStream,
4978
4989
  postponed: unknown,
4979
4990
  nonce?: string,
4991
+ // Told when React refuses to replay: the tree it recorded and the tree
4992
+ // rendering now put different components in a slot, so every hole below
4993
+ // is client-rendered instead of filled here. The caller decides what to
4994
+ // do about it; see the host.
4995
+ onReplayMismatch?: () => void,
4980
4996
  ): Promise<ReadableStream> {
4981
4997
  const root = await createFromReadableStream(rscStream)
4982
4998
 
@@ -4987,7 +5003,13 @@ export async function handleSsrResume(
4987
5003
  // the designed path, on every resume of a shell whose hole holds one -
4988
5004
  // printed as "[rsc-kit:resume] Error: useSearchParams() was read..." on
4989
5005
  // every request, with advice the app had already followed.
4990
- onError: reportRenderError('resume'),
5006
+ onError: (error: unknown, info: unknown) => {
5007
+ const message = String((error as { message?: string } | null)?.message ?? error)
5008
+
5009
+ if (message.includes('instead it rendered') || message.includes('resumable slots')) onReplayMismatch?.()
5010
+
5011
+ return reportRenderError('resume')(error, info)
5012
+ },
4991
5013
  })
4992
5014
 
4993
5015
  return DEV_ORIGIN ? rewriteViteDevUrlStream(html, DEV_ORIGIN) : html
@@ -6031,6 +6053,32 @@ export function rscKit(options = {}) {
6031
6053
  external: serverExternals,
6032
6054
  // Same table, same reason.
6033
6055
  checks: { ineffectiveDynamicImport: false },
6056
+ output: {
6057
+ // One copy of a dependency, however many client components
6058
+ // reach for it.
6059
+ //
6060
+ // Every client component is its own entry here - the SSR
6061
+ // side of a client reference, imported by id - and a
6062
+ // dependency two of them share is copied into each one's
6063
+ // chunk group. Two copies of one component are two
6064
+ // different functions, and that is fine until something
6065
+ // merges the module scopes: `bun build --compile` does,
6066
+ // renames the second copy, and a partially prerendered
6067
+ // page records its tree BY COMPONENT NAME. The shell then
6068
+ // says <J>, the binary renders <J2>, React refuses the
6069
+ // replay and every hole falls to the browser - a page
6070
+ // that arrives looking finished with nothing wired up. A
6071
+ // port found it on one page, in production, on cold
6072
+ // loads only.
6073
+ //
6074
+ // Grouping node_modules into one chunk means a shared
6075
+ // dependency exists once, so there is nothing to collide.
6076
+ // The server does not pay for the bigger chunk the way a
6077
+ // browser would: it reads from disk, once, at startup.
6078
+ advancedChunks: {
6079
+ groups: [{ name: "vendor", test: /[\\/]node_modules[\\/]/ }],
6080
+ },
6081
+ },
6034
6082
  },
6035
6083
  },
6036
6084
  resolve: { noExternal: bundledClientPackages },