-
-
Notifications
You must be signed in to change notification settings - Fork 8.4k
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(custom-element): Use asynchronous custom element nesting to avoid errors #9351
Changes from 5 commits
459afba
9fd32ac
cfea662
3f89d3b
b08616b
c52fd83
b2be78c
38878ec
a6b472c
63e84d4
eb29fc7
4063664
965c12b
473945a
c63a635
c02daeb
acbf0b0
959ba37
1ff5d82
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 |
---|---|---|
|
@@ -179,6 +179,8 @@ export class VueElement extends BaseClass { | |
private _numberProps: Record<string, true> | null = null | ||
private _styles?: HTMLStyleElement[] | ||
private _ob?: MutationObserver | null = null | ||
private _ce_parent?: VueElement | null = null | ||
private _ce_children?: VueElement[] | null = null | ||
constructor( | ||
private _def: InnerComponentDef, | ||
private _props: Record<string, any> = {}, | ||
|
@@ -208,7 +210,31 @@ export class VueElement extends BaseClass { | |
if (this._resolved) { | ||
this._update() | ||
} else { | ||
this._resolveDef() | ||
let parent: Node | null = this | ||
let isParentResolved = true | ||
// Recursively find the parent's custom element and set it to '_ce_parent‘ | ||
baiwusanyu-c marked this conversation as resolved.
Show resolved
Hide resolved
|
||
while ( | ||
(parent = | ||
parent && (parent.parentNode || (parent as ShadowRoot).host)) | ||
) { | ||
if (parent instanceof VueElement) { | ||
this._ce_parent = parent as VueElement | ||
// If the parent's custom element is asynchronous or not yet resolved | ||
if ( | ||
(parent._def as ComponentOptions).__asyncLoader || | ||
!parent._resolved | ||
) { | ||
// Store the current custom element in the parent's _ce_children | ||
// and wait for the parent to be resolved before executing | ||
;(parent._ce_children || (parent._ce_children = [])).push(this) | ||
isParentResolved = false | ||
} | ||
break | ||
} | ||
} | ||
if (isParentResolved) { | ||
this._resolveDef() | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -231,8 +257,6 @@ export class VueElement extends BaseClass { | |
* resolve inner component definition (handle possible async component) | ||
*/ | ||
private _resolveDef() { | ||
this._resolved = true | ||
|
||
// set initial attrs | ||
for (let i = 0; i < this.attributes.length; i++) { | ||
this._setAttr(this.attributes[i].name) | ||
|
@@ -248,6 +272,7 @@ export class VueElement extends BaseClass { | |
this._ob.observe(this, { attributes: true }) | ||
|
||
const resolve = (def: InnerComponentDef, isAsync = false) => { | ||
this._resolved = true | ||
const { props, styles } = def | ||
|
||
// cast Number-type props set before resolve | ||
|
@@ -278,6 +303,12 @@ export class VueElement extends BaseClass { | |
|
||
// initial render | ||
this._update() | ||
|
||
// The asynchronous custom element needs to call | ||
// the resolveDef function of the descendant custom element at the end. | ||
if (this._ce_children) { | ||
this._ce_children.forEach(child => child._resolveDef()) | ||
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. I'm wondering about what happens if children are removed from the DOM:
Here's an example. The async component has a child, which is removed before the async component resolves. But because the component is in The logging shows that the child is still mounted, which doesn't seem right. I think it's also worth noting that there's a similar problem when the async component itself is removed. This problem already exists on The problem in this second example is probably outside the scope of this PR, though maybe both of these examples could be fixed in the same way? I'm not sure. |
||
} | ||
} | ||
|
||
const asyncDef = (this._def as ComponentOptions).__asyncLoader | ||
|
@@ -397,17 +428,9 @@ export class VueElement extends BaseClass { | |
} | ||
} | ||
|
||
// locate nearest Vue custom element parent for provide/inject | ||
let parent: Node | null = this | ||
while ( | ||
(parent = | ||
parent && (parent.parentNode || (parent as ShadowRoot).host)) | ||
) { | ||
if (parent instanceof VueElement) { | ||
instance.parent = parent._instance | ||
instance.provides = parent._instance!.provides | ||
break | ||
} | ||
if (this._ce_parent) { | ||
instance.parent = this._ce_parent._instance | ||
instance.provides = this._ce_parent._instance!.provides | ||
} | ||
} | ||
} | ||
|
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.
We normally have a blank line between tests.