Skip to content

Commit e0241b6

Browse files
authored
Simplify Webpack References by encoding file path + export name as single id (#26300)
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 25685d8 commit e0241b6

18 files changed

+120
-157
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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import isArray from 'shared/isArray';
1919

2020
export type ClientReference<T> = JSResourceReference<T>;
2121
export type ServerReference<T> = T;
22-
export type ServerReferenceMetadata = {};
22+
export type ServerReferenceId = {};
2323

2424
import type {
2525
Destination,
@@ -69,7 +69,7 @@ export function resolveClientReferenceMetadata<T>(
6969
export function resolveServerReferenceMetadata<T>(
7070
config: BundlerConfig,
7171
resource: ServerReference<T>,
72-
): ServerReferenceMetadata {
72+
): {id: ServerReferenceId, 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 ServerReferenceId = 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: ServerReferenceId, 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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy