diff --git a/packages/next/src/build/webpack/loaders/next-flight-loader/action-proxy.ts b/packages/next/src/build/webpack/loaders/next-flight-loader/action-proxy.ts index c85600167cc1..8971228745b1 100644 --- a/packages/next/src/build/webpack/loaders/next-flight-loader/action-proxy.ts +++ b/packages/next/src/build/webpack/loaders/next-flight-loader/action-proxy.ts @@ -1,3 +1,5 @@ +const SERVER_REFERENCE_TAG = Symbol.for('react.server.reference') + export function createActionProxy( id: string, bound: null | any[], @@ -30,8 +32,18 @@ export function createActionProxy( return newAction } - action.$$typeof = Symbol.for('react.server.reference') - action.$$id = id - action.$$bound = bound - action.bind = bindImpl.bind(action) + Object.defineProperties(action, { + $$typeof: { + value: SERVER_REFERENCE_TAG, + }, + $$id: { + value: id, + }, + $$bound: { + value: bound, + }, + bind: { + value: bindImpl, + }, + }) } diff --git a/packages/next/src/build/webpack/loaders/next-flight-loader/module-proxy.ts b/packages/next/src/build/webpack/loaders/next-flight-loader/module-proxy.ts index babf89a956be..17314c55282d 100644 --- a/packages/next/src/build/webpack/loaders/next-flight-loader/module-proxy.ts +++ b/packages/next/src/build/webpack/loaders/next-flight-loader/module-proxy.ts @@ -1,196 +1,5 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ +/* eslint-disable import/no-extraneous-dependencies */ +import { createClientModuleProxy } from 'react-server-dom-webpack/server.edge' -// Modified from https://github.com/facebook/react/blob/main/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js - -const CLIENT_REFERENCE = Symbol.for('react.client.reference') -const PROMISE_PROTOTYPE = Promise.prototype - -const deepProxyHandlers = { - get: function (target: any, name: string, _receiver: ProxyHandler) { - switch (name) { - // These names are read by the Flight runtime if you end up using the exports object. - case '$$typeof': - // These names are a little too common. We should probably have a way to - // have the Flight runtime extract the inner target instead. - return target.$$typeof - case '$$id': - return target.$$id - case '$$async': - return target.$$async - case 'name': - return target.name - case 'displayName': - return undefined - // We need to special case this because createElement reads it if we pass this - // reference. - case 'defaultProps': - return undefined - // Avoid this attempting to be serialized. - case 'toJSON': - return undefined - case Symbol.toPrimitive.toString(): - // @ts-ignore - return Object.prototype[Symbol.toPrimitive] - case 'Provider': - throw new Error( - `Cannot render a Client Context Provider on the Server. ` + - `Instead, you can export a Client Component wrapper ` + - `that itself renders a Client Context Provider.` - ) - default: - break - } - const expression = String(target.name) + '.' + String(name) - throw new Error( - `Cannot access ${expression} on the server. ` + - 'You cannot dot into a client module from a server component. ' + - 'You can only pass the imported name through.' - ) - }, - set: function () { - throw new Error('Cannot assign to a client module from a server module.') - }, -} - -const proxyHandlers = { - get: function (target: any, name: string, _receiver: ProxyHandler) { - switch (name) { - // These names are read by the Flight runtime if you end up using the exports object. - case '$$typeof': - return target.$$typeof - case '$$id': - return target.$$id - case '$$async': - return target.$$async - case 'name': - return target.name - // We need to special case this because createElement reads it if we pass this - // reference. - case 'defaultProps': - return undefined - // Avoid this attempting to be serialized. - case 'toJSON': - return undefined - case Symbol.toPrimitive.toString(): - // @ts-ignore - return Object.prototype[Symbol.toPrimitive] - case '__esModule': - // Something is conditionally checking which export to use. We'll pretend to be - // an ESM compat module but then we'll check again on the client. - const moduleId = target.$$id - target.default = Object.defineProperties( - function () { - throw new Error( - `Attempted to call the default export of ${moduleId} from the server ` + - `but it's on the client. It's not possible to invoke a client function from ` + - `the server, it can only be rendered as a Component or passed to props of a ` + - `Client Component.` - ) - }, - { - $$typeof: { value: CLIENT_REFERENCE }, - // This a placeholder value that tells the client to conditionally use the - // whole object or just the default export. - $$id: { value: target.$$id + '#' }, - $$async: { value: target.$$async }, - } - ) - return true - case 'then': - if (target.then) { - // Use a cached value - return target.then - } - if (!target.$$async) { - // If this module is expected to return a Promise (such as an AsyncModule) then - // we should resolve that with a client reference that unwraps the Promise on - // the client. - - const clientReference = Object.defineProperties( - {}, - { - $$typeof: { value: CLIENT_REFERENCE }, - $$id: { value: target.$$id }, - $$async: { value: true }, - } - ) - const proxy = new Proxy(clientReference, proxyHandlers) - - // Treat this as a resolved Promise for React's use() - target.status = 'fulfilled' - target.value = proxy - - const then = (target.then = Object.defineProperties( - function then(resolve: any, _reject: any) { - // Expose to React. - return Promise.resolve( - // $FlowFixMe[incompatible-call] found when upgrading Flow - resolve(proxy) - ) - }, - // If this is not used as a Promise but is treated as a reference to a `.then` - // export then we should treat it as a reference to that name. - { - $$typeof: { value: CLIENT_REFERENCE }, - $$id: { value: target.$$id }, - $$async: { value: false }, - } - )) - return then - } else { - // Since typeof .then === 'function' is a feature test we'd continue recursing - // indefinitely if we return a function. Instead, we return an object reference - // if we check further. - return undefined - } - default: - break - } - let cachedReference = target[name] - if (!cachedReference) { - const reference = Object.defineProperties( - function () { - throw new Error( - `Attempted to call ${String(name)}() from the server but ${String( - name - )} is on the client. ` + - `It's not possible to invoke a client function from the server, it can ` + - `only be rendered as a Component or passed to props of a Client Component.` - ) - }, - { - $$typeof: { value: CLIENT_REFERENCE }, - $$id: { value: target.$$id + '#' + name }, - $$async: { value: target.$$async }, - } - ) - cachedReference = target[name] = new Proxy(reference, deepProxyHandlers) - } - return cachedReference - }, - getPrototypeOf(_target: any): object { - // Pretend to be a Promise in case anyone asks. - return PROMISE_PROTOTYPE - }, - set: function () { - throw new Error('Cannot assign to a client module from a server module.') - }, -} - -export function createProxy(moduleId: string) { - const clientReference = Object.defineProperties( - {}, - { - $$typeof: { value: CLIENT_REFERENCE }, - // Represents the whole Module object instead of a particular import. - $$id: { value: moduleId }, - $$async: { value: false }, - } - ) - return new Proxy(clientReference, proxyHandlers) -} +// Re-assign to make it typed. +export const createProxy: (moduleId: string) => any = createClientModuleProxy diff --git a/packages/next/src/lib/client-reference.ts b/packages/next/src/lib/client-reference.ts index f1e2fdac81c3..8a7122ab8335 100644 --- a/packages/next/src/lib/client-reference.ts +++ b/packages/next/src/lib/client-reference.ts @@ -1,19 +1,3 @@ -/** - * filepath export module key - * "file" '*' "file" - * "file" '' "file#" - * "file" '' "file#" - * - * @param filepath file path to the module - * @param exports '' | '*' | '' - */ -export function getClientReferenceModuleKey( - filepath: string, - exportName: string -): string { - return exportName === '*' ? filepath : filepath + '#' + exportName -} - export function isClientReference(reference: any): boolean { return reference?.$$typeof === Symbol.for('react.client.reference') } diff --git a/packages/next/types/misc.d.ts b/packages/next/types/misc.d.ts index e0f9735b2bca..61a33ebfe952 100644 --- a/packages/next/types/misc.d.ts +++ b/packages/next/types/misc.d.ts @@ -21,6 +21,7 @@ declare module 'next/dist/compiled/react-dom/server.edge' declare module 'next/dist/compiled/react-dom/server.browser' declare module 'next/dist/compiled/browserslist' declare module 'react-server-dom-webpack/client' +declare module 'react-server-dom-webpack/server.edge' declare module 'react-dom/server.browser' declare module 'react-dom/server.edge'