-
Notifications
You must be signed in to change notification settings - Fork 394
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: @W-12593632 clean up VFragments when resetting component root #3363
Conversation
if (!isNull(child) && !isUndefined(child.elm)) { | ||
// VFragments are special; their .elm property does not point to the root element since they have no root, | ||
// so we have to clean them up differently. | ||
if (!isNull(child) && isVFragment(child)) { |
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.
VFragment is the only VNode that represents a set of VNodes and has no root. I couldn't think of a way to deal with the them in a generic way here, so we just check whether this is a Fragment and react accordingly.
// VFragments are special; their .elm property does not point to the root element since they have no root, | ||
// so we have to clean them up differently. | ||
if (!isNull(child) && isVFragment(child)) { | ||
removeFragmentChildren(child, vm); |
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.
I debated just converting this entire function to traverse using a stack so we wouldn't need a separate function, but this felt like a cleaner way to isolate the impact of VFragments on non-vfragment use cases.
// Helper function to traverse a tree of VFragment nodes and remove all root children. | ||
// This is moved into a separate function to minimize the perf/mem impact of the stack traversal | ||
// on non-vfragment use cases | ||
function removeFragmentChildren(vnode: VFragment, vm: VM) { |
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.
Because VFragments can be nested inside other VFragments, we can't just remove the immediate children of the first fragment we find. We need to traverse the entire tree and grab all children that are immediate children of the root node.
<template>
<div>Immediate child of root</div>
VFragment(
<div>Nested 1: also immediate child of root</div>
VFragment(
<div>Nested 2: also immediate child of root</div>
)
)
</template>
<x-custom-component>
<div>Immediate child of root</div>
<div>Nested 1: also immediate child of root</div>
<div>Nested 2: also immediate child of root</div>
</x-custom-component>
import CustomRender from 'x/customRender'; | ||
|
||
describe('using custom renderer with lwc:if', () => { | ||
it('should just work test debug', async function () { |
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.
Did you mean to name it
this way? :)
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.
I thought I changed this....that's embarrassing 😓
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've all been there. I like yolo haha
myself
@@ -2,7 +2,7 @@ | |||
"files": [ | |||
{ | |||
"path": "packages/lwc/dist/engine-dom/umd/es2017/engine-dom.min.js", | |||
"maxSize": "20KB" | |||
"maxSize": "20.02KB" |
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.
Feel free to bump to 20.5 or 21; no need to be right above the limit :)
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.
LGTM, I am thinking the risk is quite low for the observable change. The previous behavior seems truly buggy, and lwc:if
is relatively new
Thread model / Risk assessment: To get this behavior, a customer would need to:
The end user behavior:
Other notes:
I think this has low risk to break customers, especially since we're limiting the change to VFragment nodes which are also new to 242. |
So effectively a multi-template component with an Also, the behavior is totally busted from the user's POV (they tried to swap out another template, it didn't work), so it seems unlikely anyone would take a dependency on it |
Yes that's correct. |
…3363) * fix: add initial test case * fix: recursively remove fragment children when resetting component root
while (!isUndefined((currentNode = ArrayPop.call(nodeStack)))) { | ||
if (!isNull(currentNode)) { | ||
if (isVFragment(currentNode)) { | ||
ArrayPush.call(nodeStack, ...currentNode.children); |
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.
The order of removal here is not following tree order. Not a problem now, because disconnectedCallback is being managed manually by runChildNodesDisconnectedCallback
. When the switch is made to native disconnectedCallback for custom elements, this will need to be fixed.
fyi @nolanlawson
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.
Thanks for calling that out. We should change it to match the disconnect order.
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.
Good catch @ravijayaramappa !
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.
@ravijayaramappa Looked into it a bit, you're right in that there's an issue with disconnectedCallback ordering, but not in the way you described; the order of removal here actually does match the order in runChildNodesDisconnectedCallback
; both remove/disconnect in reverse sibling order.
However, other issues have come up, and I'm hitting up against the inconsistent disconnect ordering mentioned in #2713. When you have some time, could you take a look at #3369 and give your input on what the "correct" order of removal should be in the various use cases?
Details
VFragments are not cleaned up properly when resetting component root because their .elm points to the end delimiter text node, not the root element.
Does this pull request introduce a breaking change?
Does this pull request introduce an observable change?
GUS work item