rsnext/packages/next/build/webpack/loaders/next-flight-loader/module-proxy.ts
Jiachi Liu 295f9da393
Client directive (#40415)
## Feature
Change server components convention from using `.server.js` / `.client.js` file extension to determine it's a server or client component to using `'client'` js literal as a directive for determine client components boundary.
React RFC: https://github.com/reactjs/rfcs/pull/189
New behavior doesn't consume `.server.js` as server components any more, if you're enabling `serverComponents` flag, every `page.js` in app dir will become server components by default. If you adding a `'client'` directive to the page, then that page will become a client component. This rule also applies to the normal js components, client components will require a `'client'` directive to indicate its identity, instead of having a `.client.js` extension.
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`

Co-authored-by: Shu Ding <3676859+shuding@users.noreply.github.com>
2022-09-18 00:00:16 +00:00

102 lines
3.6 KiB
TypeScript

/**
* 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.
*/
// 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 PROMISE_PROTOTYPE = Promise.prototype
const proxyHandlers: ProxyHandler<object> = {
get: function (target: any, name: string, _receiver: 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
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,
}
return true
case '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,
}
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.
return then
}
break
default:
break
}
let cachedReference = target[name]
if (!cachedReference) {
cachedReference = target[name] = {
$$typeof: MODULE_REFERENCE,
filepath: target.filepath,
name: name,
async: target.async,
}
}
return cachedReference
},
getPrototypeOf(_target: 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 moduleReference = {
$$typeof: MODULE_REFERENCE,
filepath: moduleId,
name: '*', // Represents the whole object instead of a particular import.
async: false,
}
return new Proxy(moduleReference, proxyHandlers)
}