-
Notifications
You must be signed in to change notification settings - Fork 47.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix Issue with Undefined Lazy Imports By Refactoring Lazy Initialization Order #21642
Changes from all commits
80983f2
c964339
0805df5
c490caf
cc42e7b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,7 +28,7 @@ type PendingPayload = { | |
|
||
type ResolvedPayload<T> = { | ||
_status: 1, | ||
_result: T, | ||
_result: {default: T}, | ||
}; | ||
|
||
type RejectedPayload = { | ||
|
@@ -53,43 +53,64 @@ function lazyInitializer<T>(payload: Payload<T>): T { | |
const ctor = payload._result; | ||
const thenable = ctor(); | ||
// Transition to the next state. | ||
const pending: PendingPayload = (payload: any); | ||
pending._status = Pending; | ||
pending._result = thenable; | ||
// This might throw either because it's missing or throws. If so, we treat it | ||
// as still uninitialized and try again next time. Which is the same as what | ||
// happens if the ctor or any wrappers processing the ctor throws. This might | ||
// end up fixing it if the resolution was a concurrency bug. | ||
thenable.then( | ||
moduleObject => { | ||
if (payload._status === Pending) { | ||
const defaultExport = moduleObject.default; | ||
if (__DEV__) { | ||
if (defaultExport === undefined) { | ||
console.error( | ||
'lazy: Expected the result of a dynamic import() call. ' + | ||
'Instead received: %s\n\nYour code should look like: \n ' + | ||
// Break up imports to avoid accidentally parsing them as dependencies. | ||
'const MyComponent = lazy(() => imp' + | ||
"ort('./MyComponent'))", | ||
moduleObject, | ||
); | ||
} | ||
} | ||
if (payload._status === Pending || payload._status === Uninitialized) { | ||
// Transition to the next state. | ||
const resolved: ResolvedPayload<T> = (payload: any); | ||
resolved._status = Resolved; | ||
resolved._result = defaultExport; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change needs to be in lockstep right? Not that it affects anything but I remember some pain rolling out a change to lazy on RN. Maybe it's not hard anymore now that we check in the bundles. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that this is only changing one package so there's no lockstep. This is a nice feature of the new protocol. The reconciler just calls the function and expects a value (or throw or suspense). The implementation of this is can be whatever - like Flight has a different one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh ok this is nice. |
||
resolved._result = moduleObject; | ||
} | ||
}, | ||
error => { | ||
if (payload._status === Pending) { | ||
if (payload._status === Pending || payload._status === Uninitialized) { | ||
// Transition to the next state. | ||
const rejected: RejectedPayload = (payload: any); | ||
rejected._status = Rejected; | ||
rejected._result = error; | ||
} | ||
}, | ||
); | ||
if (payload._status === Uninitialized) { | ||
// In case, we're still uninitialized, then we're waiting for the thenable | ||
// to resolve. Set it as pending in the meantime. | ||
const pending: PendingPayload = (payload: any); | ||
pending._status = Pending; | ||
pending._result = thenable; | ||
} | ||
} | ||
if (payload._status === Resolved) { | ||
return payload._result; | ||
const moduleObject = payload._result; | ||
if (__DEV__) { | ||
if (moduleObject === undefined) { | ||
console.error( | ||
'lazy: Expected the result of a dynamic import() call. ' + | ||
'Instead received: %s\n\nYour code should look like: \n ' + | ||
// Break up imports to avoid accidentally parsing them as dependencies. | ||
'const MyComponent = lazy(() => imp' + | ||
"ort('./MyComponent'))\n\n" + | ||
'Did you accidentally put curly braces around the import?', | ||
moduleObject, | ||
); | ||
} | ||
} | ||
if (__DEV__) { | ||
if (!('default' in moduleObject)) { | ||
console.error( | ||
'lazy: Expected the result of a dynamic import() call. ' + | ||
'Instead received: %s\n\nYour code should look like: \n ' + | ||
// Break up imports to avoid accidentally parsing them as dependencies. | ||
'const MyComponent = lazy(() => imp' + | ||
"ort('./MyComponent'))", | ||
moduleObject, | ||
); | ||
} | ||
} | ||
return moduleObject.default; | ||
} else { | ||
throw payload._result; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if it was a sync thenable though? Wouldn't you miss it due to
if (payload._status === Pending) {
check inonFulfill
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea I fixed it here. cc42e7b
That check is really unnecessary. It's more of a safety thing in case someone has a bad thennable. We could probably remove it.