Skip to content

Commit 56580f4

Browse files
committed
Simplify Webpack References by encoding file path + export name as single id
We always look up these references in a map so it doesn't matter what their value is. It could be a hash for example. The loaders now encode a single $$id instead of filepath + name. This changes the react-client-manifest to have a single level. The value inside the map is still split into module id + export name because that's what gets looked up in webpack. The react-ssr-manifest is still two levels because that's a reverse lookup.
1 parent 5c633a4 commit 56580f4

17 files changed

+116
-153
lines changed

fixtures/flight/public/favicon.ico

24.3 KB
Binary file not shown.

fixtures/flight/server/region.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ app.post('/', bodyParser.text(), async function (req, res) {
9898
const {renderToPipeableStream} = await import(
9999
'react-server-dom-webpack/server'
100100
);
101-
const serverReference = JSON.parse(req.get('rsc-action'));
102-
const {filepath, name} = serverReference;
101+
const serverReference = req.get('rsc-action');
102+
const [filepath, name] = serverReference.split('#');
103103
const action = (await import(filepath))[name];
104104
// Validate that this is actually a function we intended to expose and
105105
// not the client trying to invoke arbitrary functions. In a real app,

fixtures/flight/src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ let data = ReactServerDOMReader.createFromFetch(
1818
method: 'POST',
1919
headers: {
2020
Accept: 'text/x-component',
21-
'rsc-action': JSON.stringify({filepath: id.id, name: id.name}),
21+
'rsc-action': id,
2222
},
2323
body: JSON.stringify(args),
2424
});

packages/react-client/src/ReactFlightClient.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ function createModelReject<T>(chunk: SomeChunk<T>): (error: mixed) => void {
473473

474474
function createServerReferenceProxy<A: Iterable<any>, T>(
475475
response: Response,
476-
metaData: any,
476+
metaData: {id: any, bound: Thenable<Array<any>>},
477477
): (...A) => Promise<T> {
478478
const callServer = response._callServer;
479479
const proxy = function (): Promise<T> {
@@ -482,12 +482,14 @@ function createServerReferenceProxy<A: Iterable<any>, T>(
482482
const p = metaData.bound;
483483
if (p.status === INITIALIZED) {
484484
const bound = p.value;
485-
return callServer(metaData, bound.concat(args));
485+
return callServer(metaData.id, bound.concat(args));
486486
}
487487
// Since this is a fake Promise whose .then doesn't chain, we have to wrap it.
488488
// TODO: Remove the wrapper once that's fixed.
489-
return Promise.resolve(p).then(function (bound) {
490-
return callServer(metaData, bound.concat(args));
489+
return ((Promise.resolve(p): any): Promise<Array<any>>).then(function (
490+
bound,
491+
) {
492+
return callServer(metaData.id, bound.concat(args));
491493
});
492494
};
493495
return proxy;

packages/react-server-dom-relay/src/ReactFlightDOMRelayServerHostConfig.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export function resolveClientReferenceMetadata<T>(
6969
export function resolveServerReferenceMetadata<T>(
7070
config: BundlerConfig,
7171
resource: ServerReference<T>,
72-
): ServerReferenceMetadata {
72+
): {id: ServerReferenceMetadata, bound: Promise<Array<any>>} {
7373
throw new Error('Not implemented.');
7474
}
7575

packages/react-server-dom-webpack/src/ReactFlightClientNodeBundlerConfig.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export opaque type ClientReferenceMetadata = {
2525
id: string,
2626
chunks: Array<string>,
2727
name: string,
28-
async: boolean,
2928
};
3029

3130
// eslint-disable-next-line no-unused-vars

packages/react-server-dom-webpack/src/ReactFlightDOMClientBrowser.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@ import {
2020
close,
2121
} from 'react-client/src/ReactFlightClientStream';
2222

23-
type CallServerCallback = <A, T>(
24-
{filepath: string, name: string},
25-
args: A,
26-
) => Promise<T>;
23+
type CallServerCallback = <A, T>(string, args: A) => Promise<T>;
2724

2825
export type Options = {
2926
callServer?: CallServerCallback,

packages/react-server-dom-webpack/src/ReactFlightServerWebpackBundlerConfig.js

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,24 @@
1010
import type {ReactModel} from 'react-server/src/ReactFlightServer';
1111

1212
type WebpackMap = {
13-
[filepath: string]: {
14-
[name: string]: ClientReferenceMetadata,
15-
},
13+
[id: string]: ClientReferenceMetadata,
1614
};
1715

1816
export type BundlerConfig = WebpackMap;
1917

2018
export type ServerReference<T: Function> = T & {
2119
$$typeof: symbol,
22-
$$filepath: string,
23-
$$name: string,
20+
$$id: string,
2421
$$bound: Array<ReactModel>,
2522
};
2623

27-
export type ServerReferenceMetadata = {
28-
id: string,
29-
name: string,
30-
bound: Promise<Array<ReactModel>>,
31-
};
24+
export type ServerReferenceMetadata = string;
3225

3326
// eslint-disable-next-line no-unused-vars
3427
export type ClientReference<T> = {
3528
$$typeof: symbol,
36-
filepath: string,
37-
name: string,
38-
async: boolean,
29+
$$id: string,
30+
$$async: boolean,
3931
};
4032

4133
export type ClientReferenceMetadata = {
@@ -53,12 +45,7 @@ const SERVER_REFERENCE_TAG = Symbol.for('react.server.reference');
5345
export function getClientReferenceKey(
5446
reference: ClientReference<any>,
5547
): ClientReferenceKey {
56-
return (
57-
reference.filepath +
58-
'#' +
59-
reference.name +
60-
(reference.async ? '#async' : '')
61-
);
48+
return reference.$$async ? reference.$$id + '#async' : reference.$$id;
6249
}
6350

6451
export function isClientReference(reference: Object): boolean {
@@ -73,9 +60,8 @@ export function resolveClientReferenceMetadata<T>(
7360
config: BundlerConfig,
7461
clientReference: ClientReference<T>,
7562
): ClientReferenceMetadata {
76-
const resolvedModuleData =
77-
config[clientReference.filepath][clientReference.name];
78-
if (clientReference.async) {
63+
const resolvedModuleData = config[clientReference.$$id];
64+
if (clientReference.$$async) {
7965
return {
8066
id: resolvedModuleData.id,
8167
chunks: resolvedModuleData.chunks,
@@ -90,10 +76,9 @@ export function resolveClientReferenceMetadata<T>(
9076
export function resolveServerReferenceMetadata<T>(
9177
config: BundlerConfig,
9278
serverReference: ServerReference<T>,
93-
): ServerReferenceMetadata {
79+
): {id: ServerReferenceMetadata, bound: Promise<Array<any>>} {
9480
return {
95-
id: serverReference.$$filepath,
96-
name: serverReference.$$name,
81+
id: serverReference.$$id,
9782
bound: Promise.resolve(serverReference.$$bound),
9883
};
9984
}

packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,7 @@ function transformServerModule(
187187
}
188188
newSrc += 'Object.defineProperties(' + local + ',{';
189189
newSrc += '$$typeof: {value: Symbol.for("react.server.reference")},';
190-
newSrc += '$$filepath: {value: ' + JSON.stringify(url) + '},';
191-
newSrc += '$$name: { value: ' + JSON.stringify(exported) + '},';
190+
newSrc += '$$id: {value: ' + JSON.stringify(url + '#' + exported) + '},';
192191
newSrc += '$$bound: { value: [] }';
193192
newSrc += '});\n';
194193
});
@@ -343,9 +342,8 @@ async function transformClientModule(
343342
');';
344343
}
345344
newSrc += '},{';
346-
newSrc += 'name: { value: ' + JSON.stringify(name) + '},';
347345
newSrc += '$$typeof: {value: CLIENT_REFERENCE},';
348-
newSrc += 'filepath: {value: ' + JSON.stringify(url) + '}';
346+
newSrc += '$$id: {value: ' + JSON.stringify(url + '#' + name) + '}';
349347
newSrc += '});\n';
350348
}
351349
return newSrc;

packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js

Lines changed: 28 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ module.exports = function register() {
2929
// $FlowFixMe[method-unbinding]
3030
const args = Array.prototype.slice.call(arguments, 1);
3131
newFn.$$typeof = SERVER_REFERENCE;
32-
newFn.$$filepath = this.$$filepath;
33-
newFn.$$name = this.$$name;
32+
newFn.$$id = this.$$id;
3433
newFn.$$bound = this.$$bound.concat(args);
3534
}
3635
return newFn;
@@ -44,14 +43,14 @@ module.exports = function register() {
4443
// These names are a little too common. We should probably have a way to
4544
// have the Flight runtime extract the inner target instead.
4645
return target.$$typeof;
47-
case 'filepath':
48-
return target.filepath;
46+
case '$$id':
47+
return target.$$id;
48+
case '$$async':
49+
return target.$$async;
4950
case 'name':
5051
return target.name;
5152
case 'displayName':
5253
return undefined;
53-
case 'async':
54-
return target.async;
5554
// We need to special case this because createElement reads it if we pass this
5655
// reference.
5756
case 'defaultProps':
@@ -69,20 +68,8 @@ module.exports = function register() {
6968
`that itself renders a Client Context Provider.`,
7069
);
7170
}
72-
let expression;
73-
switch (target.name) {
74-
case '':
75-
// eslint-disable-next-line react-internal/safe-string-coercion
76-
expression = String(name);
77-
break;
78-
case '*':
79-
// eslint-disable-next-line react-internal/safe-string-coercion
80-
expression = String(name);
81-
break;
82-
default:
83-
// eslint-disable-next-line react-internal/safe-string-coercion
84-
expression = String(target.name) + '.' + String(name);
85-
}
71+
// eslint-disable-next-line react-internal/safe-string-coercion
72+
const expression = String(target.name) + '.' + String(name);
8673
throw new Error(
8774
`Cannot access ${expression} on the server. ` +
8875
'You cannot dot into a client module from a server component. ' +
@@ -103,15 +90,13 @@ module.exports = function register() {
10390
switch (name) {
10491
// These names are read by the Flight runtime if you end up using the exports object.
10592
case '$$typeof':
106-
// These names are a little too common. We should probably have a way to
107-
// have the Flight runtime extract the inner target instead.
10893
return target.$$typeof;
109-
case 'filepath':
110-
return target.filepath;
94+
case '$$id':
95+
return target.$$id;
96+
case '$$async':
97+
return target.$$async;
11198
case 'name':
11299
return target.name;
113-
case 'async':
114-
return target.async;
115100
// We need to special case this because createElement reads it if we pass this
116101
// reference.
117102
case 'defaultProps':
@@ -125,7 +110,7 @@ module.exports = function register() {
125110
case '__esModule':
126111
// Something is conditionally checking which export to use. We'll pretend to be
127112
// an ESM compat module but then we'll check again on the client.
128-
const moduleId = target.filepath;
113+
const moduleId = target.$$id;
129114
target.default = Object.defineProperties(
130115
(function () {
131116
throw new Error(
@@ -136,12 +121,11 @@ module.exports = function register() {
136121
);
137122
}: any),
138123
{
124+
$$typeof: {value: CLIENT_REFERENCE},
139125
// This a placeholder value that tells the client to conditionally use the
140126
// whole object or just the default export.
141-
name: {value: ''},
142-
$$typeof: {value: CLIENT_REFERENCE},
143-
filepath: {value: target.filepath},
144-
async: {value: target.async},
127+
$$id: {value: target.$$id + '#'},
128+
$$async: {value: target.$$async},
145129
},
146130
);
147131
return true;
@@ -150,17 +134,15 @@ module.exports = function register() {
150134
// Use a cached value
151135
return target.then;
152136
}
153-
if (!target.async) {
137+
if (!target.$$async) {
154138
// If this module is expected to return a Promise (such as an AsyncModule) then
155139
// we should resolve that with a client reference that unwraps the Promise on
156140
// the client.
157141

158142
const clientReference = Object.defineProperties(({}: any), {
159-
// Represents the whole Module object instead of a particular import.
160-
name: {value: '*'},
161143
$$typeof: {value: CLIENT_REFERENCE},
162-
filepath: {value: target.filepath},
163-
async: {value: true},
144+
$$id: {value: target.$$id},
145+
$$async: {value: true},
164146
});
165147
const proxy = new Proxy(clientReference, proxyHandlers);
166148

@@ -176,10 +158,9 @@ module.exports = function register() {
176158
// If this is not used as a Promise but is treated as a reference to a `.then`
177159
// export then we should treat it as a reference to that name.
178160
{
179-
name: {value: 'then'},
180161
$$typeof: {value: CLIENT_REFERENCE},
181-
filepath: {value: target.filepath},
182-
async: {value: false},
162+
$$id: {value: target.$$id + '#then'},
163+
$$async: {value: false},
183164
},
184165
));
185166
return then;
@@ -206,8 +187,8 @@ module.exports = function register() {
206187
{
207188
name: {value: name},
208189
$$typeof: {value: CLIENT_REFERENCE},
209-
filepath: {value: target.filepath},
210-
async: {value: target.async},
190+
$$id: {value: target.$$id + '#' + name},
191+
$$async: {value: target.$$async},
211192
},
212193
);
213194
cachedReference = target[name] = new Proxy(
@@ -284,11 +265,10 @@ module.exports = function register() {
284265
if (useClient) {
285266
const moduleId: string = (url.pathToFileURL(filename).href: any);
286267
const clientReference = Object.defineProperties(({}: any), {
287-
// Represents the whole Module object instead of a particular import.
288-
name: {value: '*'},
289268
$$typeof: {value: CLIENT_REFERENCE},
290-
filepath: {value: moduleId},
291-
async: {value: false},
269+
// Represents the whole Module object instead of a particular import.
270+
$$id: {value: moduleId},
271+
$$async: {value: false},
292272
});
293273
// $FlowFixMe[incompatible-call] found when upgrading Flow
294274
this.exports = new Proxy(clientReference, proxyHandlers);
@@ -306,10 +286,9 @@ module.exports = function register() {
306286
if (typeof exports === 'function') {
307287
// The module exports a function directly,
308288
Object.defineProperties((exports: any), {
309-
// Represents the whole Module object instead of a particular import.
310289
$$typeof: {value: SERVER_REFERENCE},
311-
$$filepath: {value: moduleId},
312-
$$name: {value: '*'},
290+
// Represents the whole Module object instead of a particular import.
291+
$$id: {value: moduleId},
313292
$$bound: {value: []},
314293
});
315294
} else {
@@ -320,8 +299,7 @@ module.exports = function register() {
320299
if (typeof value === 'function') {
321300
Object.defineProperties((value: any), {
322301
$$typeof: {value: SERVER_REFERENCE},
323-
$$filepath: {value: moduleId},
324-
$$name: {value: key},
302+
$$id: {value: moduleId + '#' + key},
325303
$$bound: {value: []},
326304
});
327305
}

0 commit comments

Comments
 (0)