forked from vercel/next.js
/
module-proxy.ts
119 lines (111 loc) · 3.89 KB
/
module-proxy.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/**
* 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,
ssr: target.ssr,
ssg: target.ssg,
}
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,
ssr: target.ssr,
ssg: target.ssg,
}
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
case 'ssg':
return target.ssg
case 'ssr':
return target.ssr
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,
{ ssr, ssg }: { ssr: boolean; ssg: boolean }
) {
const moduleReference = {
$$typeof: MODULE_REFERENCE,
filepath: moduleId,
name: '*', // Represents the whole object instead of a particular import.
async: false,
ssr,
ssg,
}
return new Proxy(moduleReference, proxyHandlers)
}