Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,11 @@
"random-seed": "0.3.0",
"react": "18.2.0",
"react-17": "npm:react@17.0.2",
"react-builtin": "npm:react@18.3.0-next-3ba7add60-20221201",
"react-builtin": "npm:react@18.3.0-next-4bf2113a1-20230206",
"react-dom": "18.2.0",
"react-dom-17": "npm:react-dom@17.0.2",
"react-dom-builtin": "npm:react-dom@18.3.0-next-3ba7add60-20221201",
"react-server-dom-webpack": "18.3.0-next-3ba7add60-20221201",
"react-dom-builtin": "npm:react-dom@18.3.0-next-4bf2113a1-20230206",
"react-server-dom-webpack": "18.3.0-next-4bf2113a1-20230206",
"react-ssr-prepass": "1.0.8",
"react-virtualized": "9.22.3",
"relay-compiler": "13.0.2",
Expand Down
3 changes: 3 additions & 0 deletions packages/next/src/build/is-client-reference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function isClientReference(reference: any): boolean {
return reference?.$$typeof === Symbol.for('react.client.reference')
}
17 changes: 14 additions & 3 deletions packages/next/src/build/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
overrideBuiltInReactPackages,
} from './webpack/require-hook'
import { AssetBinding } from './webpack/loaders/get-module-build-info'
import { isClientReference } from './is-client-reference'

loadRequireHook()
if (process.env.NEXT_PREBUNDLED_REACT) {
Expand Down Expand Up @@ -1100,14 +1101,18 @@ export const collectGenerateParams = async (
: segment[2]?.page?.[0]?.())
const config = collectAppConfig(mod)

const isClientComponent = isClientReference(mod)

const result = {
isLayout,
segmentPath: `/${parentSegments.join('/')}${
segment[0] && parentSegments.length > 0 ? '/' : ''
}${segment[0]}`,
config,
getStaticPaths: mod?.getStaticPaths,
generateStaticParams: mod?.generateStaticParams,
getStaticPaths: isClientComponent ? undefined : mod?.getStaticPaths,
generateStaticParams: isClientComponent
? undefined
: mod?.generateStaticParams,
}

if (segment[0]) {
Expand Down Expand Up @@ -1269,6 +1274,7 @@ export async function isPageStatic({
let encodedPrerenderRoutes: Array<string> | undefined
let prerenderFallback: boolean | 'blocking' | undefined
let appConfig: AppConfig = {}
let isClientComponent: boolean = false

if (isEdgeRuntime(pageRuntime)) {
const runtime = await getRuntimeContext({
Expand All @@ -1288,6 +1294,7 @@ export async function isPageStatic({
const mod =
runtime.context._ENTRIES[`middleware_${edgeInfo.name}`].ComponentMod

isClientComponent = isClientReference(mod)
componentsResult = {
Component: mod.default,
ComponentMod: mod,
Expand All @@ -1313,6 +1320,7 @@ export async function isPageStatic({
| undefined

if (pageType === 'app') {
isClientComponent = isClientReference(componentsResult.ComponentMod)
const tree = componentsResult.ComponentMod.tree
const generateParams = await collectGenerateParams(tree)

Expand Down Expand Up @@ -1458,7 +1466,10 @@ export async function isPageStatic({
}

const isNextImageImported = (globalThis as any).__NEXT_IMAGE_IMPORTED
const config: PageConfig = componentsResult.pageConfig
const config: PageConfig = isClientComponent
? {}
: componentsResult.pageConfig

return {
isStatic: !hasStaticProps && !hasGetInitialProps && !hasServerProps,
isHybridAmp: config.amp === 'hybrid',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

// Modified from https://github.com/facebook/react/blob/main/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js

const MODULE_REFERENCE = Symbol.for('react.module.reference')
const CLIENT_REFERENCE = Symbol.for('react.client.reference')
const PROMISE_PROTOTYPE = Promise.prototype

const proxyHandlers: ProxyHandler<object> = {
get: function (target: any, name: string, _receiver: any) {
const deepProxyHandlers = {
get: function (target: any, name: string, _receiver: ProxyHandler<any>) {
switch (name) {
// These names are read by the Flight runtime if you end up using the exports object.
case '$$typeof':
Expand All @@ -28,61 +28,167 @@ const proxyHandlers: ProxyHandler<object> = {
// 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
}
let expression
switch (target.name) {
case '':
expression = String(name)
break
case '*':
expression = String(name)
break
default:
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<any>) {
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 'filepath':
return target.filepath
case 'name':
return target.name
case 'async':
return target.async
// 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.
target.default = {
$$typeof: MODULE_REFERENCE,
filepath: target.filepath,
// This a placeholder value that tells the client to conditionally use the
// whole object or just the default export.
name: '',
async: target.async,
}
const moduleId = target.filepath
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.`
)
},
{
// This a placeholder value that tells the client to conditionally use the
// whole object or just the default export.
name: { value: '' },
$$typeof: { value: CLIENT_REFERENCE },
filepath: { value: target.filepath },
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 then = function then(
resolve: (res: any) => void,
_reject: (err: any) => void
) {
const moduleReference: Record<string, any> = {
$$typeof: MODULE_REFERENCE,
filepath: target.filepath,
name: '*', // Represents the whole object instead of a particular import.
async: true,

const clientReference = Object.defineProperties(
{},
{
// Represents the whole Module object instead of a particular import.
name: { value: '*' },
$$typeof: { value: CLIENT_REFERENCE },
filepath: { value: target.filepath },
async: { value: true },
}
return Promise.resolve(
resolve(new Proxy(moduleReference, proxyHandlers))
)
}
// 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.
then.$$typeof = MODULE_REFERENCE
then.filepath = target.filepath
// then.name is conveniently already "then" which is the export name we need.
// This will break if it's minified though.
)
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.
{
name: { value: 'then' },
$$typeof: { value: CLIENT_REFERENCE },
filepath: { value: target.filepath },
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
}
break
default:
break
}
let cachedReference = target[name]
if (!cachedReference) {
cachedReference = target[name] = {
$$typeof: MODULE_REFERENCE,
filepath: target.filepath,
name: name,
async: target.async,
}
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.`
)
},
{
name: { value: name },
$$typeof: { value: CLIENT_REFERENCE },
filepath: { value: target.filepath },
async: { value: target.async },
}
)
cachedReference = target[name] = new Proxy(reference, deepProxyHandlers)
}
return cachedReference
},
getPrototypeOf(_target: object) {
getPrototypeOf(_target: any): object {
// Pretend to be a Promise in case anyone asks.
return PROMISE_PROTOTYPE
},
Expand All @@ -92,11 +198,15 @@ const proxyHandlers: ProxyHandler<object> = {
}

export function createProxy(moduleId: string) {
const moduleReference = {
$$typeof: MODULE_REFERENCE,
filepath: moduleId,
name: '*', // Represents the whole object instead of a particular import.
async: false,
}
return new Proxy(moduleReference, proxyHandlers)
const clientReference = Object.defineProperties(
{},
{
// Represents the whole object instead of a particular import.
name: { value: '*' },
$$typeof: { value: CLIENT_REFERENCE },
filepath: { value: moduleId },
async: { value: false },
}
)
return new Proxy(clientReference, proxyHandlers)
}
Loading