11import assert from 'node:assert'
2+ import { createHash } from 'node:crypto'
23import fs from 'node:fs'
34import { createRequire } from 'node:module'
45import path from 'node:path'
@@ -78,6 +79,14 @@ const isRolldownVite = 'rolldownVersion' in vite
7879
7980const BUILD_ASSETS_MANIFEST_NAME = '__vite_rsc_assets_manifest.js'
8081
82+ const COMPATIBILITY_RUNTIME_PACKAGES = [
83+ '@vitejs/plugin-rsc' ,
84+ 'react' ,
85+ 'react-dom' ,
86+ 'react-server-dom-webpack' ,
87+ 'vite' ,
88+ ]
89+
8190type ClientReferenceMeta = {
8291 importId : string
8392 // same as `importId` during dev. hashed id during build.
@@ -97,6 +106,24 @@ type ServerRerferenceMeta = {
97106 exportNames : string [ ]
98107}
99108
109+ export type RscCompatibilityManifest = {
110+ version : 1
111+ base : string
112+ runtime : Record < string , string >
113+ clientReferences : Array < {
114+ id : string
115+ referenceKey : string
116+ packageSource ?: string
117+ renderedExports : string [ ]
118+ } >
119+ serverReferences : Array < {
120+ id : string
121+ referenceKey : string
122+ exportNames : string [ ]
123+ } >
124+ serverActionEncryptionKeyHash ?: string
125+ }
126+
100127const PKG_NAME = '@vitejs/plugin-rsc'
101128const REACT_SERVER_DOM_NAME = `${ PKG_NAME } /vendor/react-server-dom`
102129
@@ -126,6 +153,7 @@ class RscPluginManager {
126153 clientReferenceGroups : Record < /* group name*/ string , ClientReferenceMeta [ ] > =
127154 { }
128155 serverReferenceMetaMap : Record < string , ServerRerferenceMeta > = { }
156+ serverActionEncryptionKeyHash : string | undefined
129157 serverResourcesMetaMap : Record < string , { key : string } > = { }
130158 environmentImportMetaMap : Record <
131159 string , // sourceEnv
@@ -166,6 +194,85 @@ class RscPluginManager {
166194 writeEnvironmentImportsManifest ( ) : void {
167195 writeEnvironmentImportsManifest ( this )
168196 }
197+
198+ getCompatibilityManifest ( ) : RscCompatibilityManifest {
199+ const manifest : RscCompatibilityManifest = {
200+ version : 1 ,
201+ base : this . config . base ,
202+ runtime : Object . fromEntries (
203+ COMPATIBILITY_RUNTIME_PACKAGES . map ( ( packageName ) => [
204+ packageName ,
205+ getPackageVersion ( packageName ) ,
206+ ] ) ,
207+ ) ,
208+ clientReferences : Object . values ( this . clientReferenceMetaMap )
209+ . map ( ( meta ) => ( {
210+ id : this . toCompatibilityId ( meta . importId ) ,
211+ referenceKey : meta . referenceKey ,
212+ packageSource : meta . packageSource ,
213+ renderedExports : [ ...meta . renderedExports ] . sort ( ) ,
214+ } ) )
215+ . sort ( compareClientCompatibilityReferences ) ,
216+ serverReferences : Object . values ( this . serverReferenceMetaMap )
217+ . map ( ( meta ) => ( {
218+ id : this . toCompatibilityId ( meta . importId ) ,
219+ referenceKey : meta . referenceKey ,
220+ exportNames : [ ...meta . exportNames ] . sort ( ) ,
221+ } ) )
222+ . sort ( compareServerCompatibilityReferences ) ,
223+ }
224+ if ( this . serverActionEncryptionKeyHash ) {
225+ manifest . serverActionEncryptionKeyHash =
226+ this . serverActionEncryptionKeyHash
227+ }
228+ return manifest
229+ }
230+
231+ getCompatibilityVersion ( ) : string {
232+ return hashCompatibilityValue (
233+ JSON . stringify ( this . getCompatibilityManifest ( ) ) ,
234+ )
235+ }
236+
237+ private toCompatibilityId ( id : string ) : string {
238+ return normalizePath ( path . isAbsolute ( id ) ? this . toRelativeId ( id ) : id )
239+ }
240+ }
241+
242+ function compareClientCompatibilityReferences (
243+ a : RscCompatibilityManifest [ 'clientReferences' ] [ number ] ,
244+ b : RscCompatibilityManifest [ 'clientReferences' ] [ number ] ,
245+ ) : number {
246+ return (
247+ a . referenceKey . localeCompare ( b . referenceKey ) ||
248+ a . id . localeCompare ( b . id ) ||
249+ ( a . packageSource ?? '' ) . localeCompare ( b . packageSource ?? '' )
250+ )
251+ }
252+
253+ function compareServerCompatibilityReferences (
254+ a : RscCompatibilityManifest [ 'serverReferences' ] [ number ] ,
255+ b : RscCompatibilityManifest [ 'serverReferences' ] [ number ] ,
256+ ) : number {
257+ return (
258+ a . referenceKey . localeCompare ( b . referenceKey ) || a . id . localeCompare ( b . id )
259+ )
260+ }
261+
262+ function hashCompatibilityValue ( value : string ) : string {
263+ return createHash ( 'sha256' ) . update ( value ) . digest ( 'hex' ) . slice ( 0 , 16 )
264+ }
265+
266+ function getPackageVersion ( packageName : string ) : string {
267+ try {
268+ const packageJsonPath = require . resolve ( `${ packageName } /package.json` )
269+ const packageJson = JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf-8' ) )
270+ return typeof packageJson . version === 'string'
271+ ? packageJson . version
272+ : 'unknown'
273+ } catch {
274+ return 'unknown'
275+ }
169276}
170277
171278export type RscPluginOptions = {
@@ -338,7 +445,7 @@ export function vitePluginRscMinimal(
338445 ...vitePluginRscCore ( ) ,
339446 ...vitePluginUseClient ( rscPluginOptions , manager ) ,
340447 ...vitePluginUseServer ( rscPluginOptions , manager ) ,
341- ...vitePluginDefineEncryptionKey ( rscPluginOptions ) ,
448+ ...vitePluginDefineEncryptionKey ( rscPluginOptions , manager ) ,
342449 {
343450 name : 'rsc:reference-validation' ,
344451 apply : 'serve' ,
@@ -1820,6 +1927,7 @@ function vitePluginDefineEncryptionKey(
18201927 RscPluginOptions ,
18211928 'defineEncryptionKey' | 'environment'
18221929 > ,
1930+ manager : RscPluginManager ,
18231931) : Plugin [ ] {
18241932 let defineEncryptionKey : string
18251933 let emitEncryptionKey = false
@@ -1833,6 +1941,7 @@ function vitePluginDefineEncryptionKey(
18331941 name : 'rsc:encryption-key' ,
18341942 async configEnvironment ( name , _config , env ) {
18351943 if ( name === serverEnvironmentName && ! env . isPreview ) {
1944+ manager . serverActionEncryptionKeyHash = undefined
18361945 defineEncryptionKey =
18371946 useServerPluginOptions . defineEncryptionKey ??
18381947 JSON . stringify ( toBase64 ( await generateEncryptionKey ( ) ) )
@@ -1863,6 +1972,8 @@ function vitePluginDefineEncryptionKey(
18631972 if ( code . includes ( KEY_PLACEHOLDER ) ) {
18641973 assert . equal ( this . environment . name , serverEnvironmentName )
18651974 emitEncryptionKey = true
1975+ manager . serverActionEncryptionKeyHash =
1976+ hashCompatibilityValue ( defineEncryptionKey )
18661977 const normalizedPath = normalizeRelativePath (
18671978 path . relative ( path . join ( chunk . fileName , '..' ) , KEY_FILE ) ,
18681979 )
0 commit comments