Skip to content

Commit 922dd7b

Browse files
authored
Revert the outer module object to an object (#26093)
This is because Webpack has a `typeof ... === 'object'` before its esm compat test. This is unfortunate because it means we can't have a nice error in CJS when someone does this: ``` const fn = require('client-fn'); fn(); ``` I also fixed some checks in the validator that read off the client ref. It shouldn't do those checks against a client ref, since those now throw.
1 parent 9d111ff commit 922dd7b

File tree

4 files changed

+122
-45
lines changed

4 files changed

+122
-45
lines changed

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

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ module.exports = function register() {
3333
// reference.
3434
case 'defaultProps':
3535
return undefined;
36-
case 'getDefaultProps':
37-
return undefined;
3836
// Avoid this attempting to be serialized.
3937
case 'toJSON':
4038
return undefined;
@@ -91,8 +89,6 @@ module.exports = function register() {
9189
// reference.
9290
case 'defaultProps':
9391
return undefined;
94-
case 'getDefaultProps':
95-
return undefined;
9692
// Avoid this attempting to be serialized.
9793
case 'toJSON':
9894
return undefined;
@@ -132,24 +128,13 @@ module.exports = function register() {
132128
// we should resolve that with a client reference that unwraps the Promise on
133129
// the client.
134130

135-
const innerModuleId = target.filepath;
136-
const clientReference: Function = Object.defineProperties(
137-
(function () {
138-
throw new Error(
139-
`Attempted to call the module exports of ${innerModuleId} from the server` +
140-
`but it's on the client. It's not possible to invoke a client function from ` +
141-
`the server, it can only be rendered as a Component or passed to props of a` +
142-
`Client Component.`,
143-
);
144-
}: any),
145-
{
146-
// Represents the whole object instead of a particular import.
147-
name: {value: '*'},
148-
$$typeof: {value: CLIENT_REFERENCE},
149-
filepath: {value: target.filepath},
150-
async: {value: true},
151-
},
152-
);
131+
const clientReference = Object.defineProperties(({}: any), {
132+
// Represents the whole Module object instead of a particular import.
133+
name: {value: '*'},
134+
$$typeof: {value: CLIENT_REFERENCE},
135+
filepath: {value: target.filepath},
136+
async: {value: true},
137+
});
153138
const proxy = new Proxy(clientReference, proxyHandlers);
154139

155140
// Treat this as a resolved Promise for React's use()
@@ -221,23 +206,13 @@ module.exports = function register() {
221206
// $FlowFixMe[prop-missing] found when upgrading Flow
222207
Module._extensions['.client.js'] = function (module, path) {
223208
const moduleId: string = (url.pathToFileURL(path).href: any);
224-
const clientReference: Function = Object.defineProperties(
225-
(function () {
226-
throw new Error(
227-
`Attempted to call the module exports of ${moduleId} from the server` +
228-
`but it's on the client. It's not possible to invoke a client function from ` +
229-
`the server, it can only be rendered as a Component or passed to props of a` +
230-
`Client Component.`,
231-
);
232-
}: any),
233-
{
234-
// Represents the whole object instead of a particular import.
235-
name: {value: '*'},
236-
$$typeof: {value: CLIENT_REFERENCE},
237-
filepath: {value: moduleId},
238-
async: {value: false},
239-
},
240-
);
209+
const clientReference = Object.defineProperties(({}: any), {
210+
// Represents the whole Module object instead of a particular import.
211+
name: {value: '*'},
212+
$$typeof: {value: CLIENT_REFERENCE},
213+
filepath: {value: moduleId},
214+
async: {value: false},
215+
});
241216
// $FlowFixMe[incompatible-call] found when upgrading Flow
242217
module.exports = new Proxy(clientReference, proxyHandlers);
243218
};

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,94 @@ describe('ReactFlightDOM', () => {
246246
expect(container.innerHTML).toBe('<p>@div</p>');
247247
});
248248

249+
// @gate enableUseHook
250+
it('should be able to esm compat test module references', async () => {
251+
const ESMCompatModule = {
252+
__esModule: true,
253+
default: function ({greeting}) {
254+
return greeting + ' World';
255+
},
256+
hi: 'Hello',
257+
};
258+
259+
function Print({response}) {
260+
return <p>{use(response)}</p>;
261+
}
262+
263+
function App({response}) {
264+
return (
265+
<Suspense fallback={<h1>Loading...</h1>}>
266+
<Print response={response} />
267+
</Suspense>
268+
);
269+
}
270+
271+
function interopWebpack(obj) {
272+
// Basically what Webpack's ESM interop feature testing does.
273+
if (typeof obj === 'object' && obj.__esModule) {
274+
return obj;
275+
}
276+
return Object.assign({default: obj}, obj);
277+
}
278+
279+
const {default: Component, hi} = interopWebpack(
280+
clientExports(ESMCompatModule),
281+
);
282+
283+
const {writable, readable} = getTestStream();
284+
const {pipe} = ReactServerDOMWriter.renderToPipeableStream(
285+
<Component greeting={hi} />,
286+
webpackMap,
287+
);
288+
pipe(writable);
289+
const response = ReactServerDOMReader.createFromReadableStream(readable);
290+
291+
const container = document.createElement('div');
292+
const root = ReactDOMClient.createRoot(container);
293+
await act(async () => {
294+
root.render(<App response={response} />);
295+
});
296+
expect(container.innerHTML).toBe('<p>Hello World</p>');
297+
});
298+
299+
// @gate enableUseHook
300+
it('should be able to render a named component export', async () => {
301+
const Module = {
302+
Component: function ({greeting}) {
303+
return greeting + ' World';
304+
},
305+
};
306+
307+
function Print({response}) {
308+
return <p>{use(response)}</p>;
309+
}
310+
311+
function App({response}) {
312+
return (
313+
<Suspense fallback={<h1>Loading...</h1>}>
314+
<Print response={response} />
315+
</Suspense>
316+
);
317+
}
318+
319+
const {Component} = clientExports(Module);
320+
321+
const {writable, readable} = getTestStream();
322+
const {pipe} = ReactServerDOMWriter.renderToPipeableStream(
323+
<Component greeting={'Hello'} />,
324+
webpackMap,
325+
);
326+
pipe(writable);
327+
const response = ReactServerDOMReader.createFromReadableStream(readable);
328+
329+
const container = document.createElement('div');
330+
const root = ReactDOMClient.createRoot(container);
331+
await act(async () => {
332+
root.render(<App response={response} />);
333+
});
334+
expect(container.innerHTML).toBe('<p>Hello World</p>');
335+
});
336+
249337
// @gate enableUseHook
250338
it('should unwrap async module references', async () => {
251339
const AsyncModule = Promise.resolve(function AsyncModule({text}) {

packages/react/src/ReactElementValidator.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import {setExtraStackFrame} from './ReactDebugCurrentFrame';
3535
import {describeUnknownElementTypeFrameInDEV} from 'shared/ReactComponentStackFrame';
3636
import hasOwnProperty from 'shared/hasOwnProperty';
3737

38+
const REACT_CLIENT_REFERENCE = Symbol.for('react.client.reference');
39+
3840
function setCurrentlyValidatingElement(element) {
3941
if (__DEV__) {
4042
if (element) {
@@ -165,10 +167,12 @@ function validateExplicitKey(element, parentType) {
165167
* @param {*} parentType node's parent's type.
166168
*/
167169
function validateChildKeys(node, parentType) {
168-
if (typeof node !== 'object') {
170+
if (typeof node !== 'object' || !node) {
169171
return;
170172
}
171-
if (isArray(node)) {
173+
if (node.$$typeof === REACT_CLIENT_REFERENCE) {
174+
// This is a reference to a client component so it's unknown.
175+
} else if (isArray(node)) {
172176
for (let i = 0; i < node.length; i++) {
173177
const child = node[i];
174178
if (isValidElement(child)) {
@@ -180,7 +184,7 @@ function validateChildKeys(node, parentType) {
180184
if (node._store) {
181185
node._store.validated = true;
182186
}
183-
} else if (node) {
187+
} else {
184188
const iteratorFn = getIteratorFn(node);
185189
if (typeof iteratorFn === 'function') {
186190
// Entry iterators used to provide implicit keys,
@@ -210,6 +214,9 @@ function validatePropTypes(element) {
210214
if (type === null || type === undefined || typeof type === 'string') {
211215
return;
212216
}
217+
if (type.$$typeof === REACT_CLIENT_REFERENCE) {
218+
return;
219+
}
213220
let propTypes;
214221
if (typeof type === 'function') {
215222
propTypes = type.propTypes;

packages/react/src/jsx/ReactJSXElementValidator.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
3232
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
3333
const ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
3434

35+
const REACT_CLIENT_REFERENCE = Symbol.for('react.client.reference');
36+
3537
function setCurrentlyValidatingElement(element) {
3638
if (__DEV__) {
3739
if (element) {
@@ -179,10 +181,12 @@ function validateExplicitKey(element, parentType) {
179181
*/
180182
function validateChildKeys(node, parentType) {
181183
if (__DEV__) {
182-
if (typeof node !== 'object') {
184+
if (typeof node !== 'object' || !node) {
183185
return;
184186
}
185-
if (isArray(node)) {
187+
if (node.$$typeof === REACT_CLIENT_REFERENCE) {
188+
// This is a reference to a client component so it's unknown.
189+
} else if (isArray(node)) {
186190
for (let i = 0; i < node.length; i++) {
187191
const child = node[i];
188192
if (isValidElement(child)) {
@@ -194,7 +198,7 @@ function validateChildKeys(node, parentType) {
194198
if (node._store) {
195199
node._store.validated = true;
196200
}
197-
} else if (node) {
201+
} else {
198202
const iteratorFn = getIteratorFn(node);
199203
if (typeof iteratorFn === 'function') {
200204
// Entry iterators used to provide implicit keys,
@@ -225,6 +229,9 @@ function validatePropTypes(element) {
225229
if (type === null || type === undefined || typeof type === 'string') {
226230
return;
227231
}
232+
if (type.$$typeof === REACT_CLIENT_REFERENCE) {
233+
return;
234+
}
228235
let propTypes;
229236
if (typeof type === 'function') {
230237
propTypes = type.propTypes;

0 commit comments

Comments
 (0)