-
Notifications
You must be signed in to change notification settings - Fork 47.7k
/
Copy pathReactFlightClientWebpackBundlerConfig.js
145 lines (136 loc) · 4.42 KB
/
ReactFlightClientWebpackBundlerConfig.js
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {
Thenable,
FulfilledThenable,
RejectedThenable,
} from 'shared/ReactTypes';
export type WebpackSSRMap = {
[clientId: string]: {
[clientExportName: string]: ModuleMetaData,
},
};
export type BundlerConfig = null | WebpackSSRMap;
export opaque type ModuleMetaData = {
id: string,
chunks: Array<string>,
name: string,
async: boolean,
};
// eslint-disable-next-line no-unused-vars
export opaque type ModuleReference<T> = ModuleMetaData;
export function resolveModuleReference<T>(
bundlerConfig: BundlerConfig,
moduleData: ModuleMetaData,
): ModuleReference<T> {
if (bundlerConfig) {
const resolvedModuleData = bundlerConfig[moduleData.id][moduleData.name];
if (moduleData.async) {
return {
id: resolvedModuleData.id,
chunks: resolvedModuleData.chunks,
name: resolvedModuleData.name,
async: true,
};
} else {
return resolvedModuleData;
}
}
return moduleData;
}
// The chunk cache contains all the chunks we've preloaded so far.
// If they're still pending they're a thenable. This map also exists
// in Webpack but unfortunately it's not exposed so we have to
// replicate it in user space. null means that it has already loaded.
const chunkCache: Map<string, null | Promise<any>> = new Map();
const asyncModuleCache: Map<string, Thenable<any>> = new Map();
function ignoreReject() {
// We rely on rejected promises to be handled by another listener.
}
// Start preloading the modules since we might need them soon.
// This function doesn't suspend.
export function preloadModule<T>(
moduleData: ModuleReference<T>,
): null | Thenable<any> {
const chunks = moduleData.chunks;
const promises = [];
for (let i = 0; i < chunks.length; i++) {
const chunkId = chunks[i];
const entry = chunkCache.get(chunkId);
if (entry === undefined) {
const thenable = __webpack_chunk_load__(chunkId);
promises.push(thenable);
// $FlowFixMe[method-unbinding]
const resolve = chunkCache.set.bind(chunkCache, chunkId, null);
thenable.then(resolve, ignoreReject);
chunkCache.set(chunkId, thenable);
} else if (entry !== null) {
promises.push(entry);
}
}
if (moduleData.async) {
const existingPromise = asyncModuleCache.get(moduleData.id);
if (existingPromise) {
if (existingPromise.status === 'fulfilled') {
return null;
}
return existingPromise;
} else {
const modulePromise: Thenable<T> = Promise.all(promises).then(() => {
return __webpack_require__(moduleData.id);
});
modulePromise.then(
value => {
const fulfilledThenable: FulfilledThenable<mixed> = (modulePromise: any);
fulfilledThenable.status = 'fulfilled';
fulfilledThenable.value = value;
},
reason => {
const rejectedThenable: RejectedThenable<mixed> = (modulePromise: any);
rejectedThenable.status = 'rejected';
rejectedThenable.reason = reason;
},
);
asyncModuleCache.set(moduleData.id, modulePromise);
return modulePromise;
}
} else if (promises.length > 0) {
return Promise.all(promises);
} else {
return null;
}
}
// Actually require the module or suspend if it's not yet ready.
// Increase priority if necessary.
export function requireModule<T>(moduleData: ModuleReference<T>): T {
let moduleExports;
if (moduleData.async) {
// We assume that preloadModule has been called before, which
// should have added something to the module cache.
const promise: any = asyncModuleCache.get(moduleData.id);
if (promise.status === 'fulfilled') {
moduleExports = promise.value;
} else {
throw promise.reason;
}
} else {
moduleExports = __webpack_require__(moduleData.id);
}
if (moduleData.name === '*') {
// This is a placeholder value that represents that the caller imported this
// as a CommonJS module as is.
return moduleExports;
}
if (moduleData.name === '') {
// This is a placeholder value that represents that the caller accessed the
// default property of this if it was an ESM interop module.
return moduleExports.__esModule ? moduleExports.default : moduleExports;
}
return moduleExports[moduleData.name];
}