Skip to content

Commit 8b5b3c5

Browse files
committed
Instrument the Promise for async modules instead of using a cache
We typically do this for Promises in general. Usually the ones we provide. This ensures that if Webpack replaces the module (HMR) then we'll get the new Promise when we require it again.
1 parent 91216ea commit 8b5b3c5

File tree

1 file changed

+38
-34
lines changed

1 file changed

+38
-34
lines changed

packages/react-server-dom-webpack/src/ReactFlightClientConfigWebpackBundler.js

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,33 @@ export function resolveServerReference<T>(
111111
// in Webpack but unfortunately it's not exposed so we have to
112112
// replicate it in user space. null means that it has already loaded.
113113
const chunkCache: Map<string, null | Promise<any>> = new Map();
114-
const asyncModuleCache: Map<string, Thenable<any>> = new Map();
114+
115+
function requireAsyncModule(id: string): null | Thenable<any> {
116+
// We've already loaded all the chunks. We can require the module.
117+
const promise = __webpack_require__(id);
118+
if (typeof promise.then !== 'function') {
119+
// This wasn't a promise after all.
120+
return null;
121+
} else if (promise.status === 'fulfilled') {
122+
// This module was already resolved earlier.
123+
return null;
124+
} else {
125+
// Instrument the Promise to stash the result.
126+
promise.then(
127+
value => {
128+
const fulfilledThenable: FulfilledThenable<mixed> = (promise: any);
129+
fulfilledThenable.status = 'fulfilled';
130+
fulfilledThenable.value = value;
131+
},
132+
reason => {
133+
const rejectedThenable: RejectedThenable<mixed> = (promise: any);
134+
rejectedThenable.status = 'rejected';
135+
rejectedThenable.reason = reason;
136+
},
137+
);
138+
return promise;
139+
}
140+
}
115141

116142
function ignoreReject() {
117143
// We rely on rejected promises to be handled by another listener.
@@ -138,32 +164,12 @@ export function preloadModule<T>(
138164
}
139165
}
140166
if (metadata.async) {
141-
const existingPromise = asyncModuleCache.get(metadata.id);
142-
if (existingPromise) {
143-
if (existingPromise.status === 'fulfilled') {
144-
return null;
145-
}
146-
return existingPromise;
167+
if (promises.length === 0) {
168+
return requireAsyncModule(metadata.id);
147169
} else {
148-
const modulePromise: Thenable<T> = Promise.all(promises).then(() => {
149-
return __webpack_require__(metadata.id);
170+
return Promise.all(promises).then(() => {
171+
return requireAsyncModule(metadata.id);
150172
});
151-
modulePromise.then(
152-
value => {
153-
const fulfilledThenable: FulfilledThenable<mixed> =
154-
(modulePromise: any);
155-
fulfilledThenable.status = 'fulfilled';
156-
fulfilledThenable.value = value;
157-
},
158-
reason => {
159-
const rejectedThenable: RejectedThenable<mixed> =
160-
(modulePromise: any);
161-
rejectedThenable.status = 'rejected';
162-
rejectedThenable.reason = reason;
163-
},
164-
);
165-
asyncModuleCache.set(metadata.id, modulePromise);
166-
return modulePromise;
167173
}
168174
} else if (promises.length > 0) {
169175
return Promise.all(promises);
@@ -175,18 +181,16 @@ export function preloadModule<T>(
175181
// Actually require the module or suspend if it's not yet ready.
176182
// Increase priority if necessary.
177183
export function requireModule<T>(metadata: ClientReference<T>): T {
178-
let moduleExports;
184+
let moduleExports = __webpack_require__(metadata.id);
179185
if (metadata.async) {
180-
// We assume that preloadModule has been called before, which
181-
// should have added something to the module cache.
182-
const promise: any = asyncModuleCache.get(metadata.id);
183-
if (promise.status === 'fulfilled') {
184-
moduleExports = promise.value;
186+
if (typeof moduleExports.then !== 'function') {
187+
// This wasn't a promise after all.
188+
} else if (moduleExports.status === 'fulfilled') {
189+
// This Promise should've been instrumented by preloadModule.
190+
moduleExports = moduleExports.value;
185191
} else {
186-
throw promise.reason;
192+
throw moduleExports.reason;
187193
}
188-
} else {
189-
moduleExports = __webpack_require__(metadata.id);
190194
}
191195
if (metadata.name === '*') {
192196
// This is a placeholder value that represents that the caller imported this

0 commit comments

Comments
 (0)