Skip to content
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(engine-core): add abstraction for moving vfragments #3405

Merged
merged 19 commits into from
Mar 28, 2023

Conversation

ekashida
Copy link
Member

@ekashida ekashida commented Mar 20, 2023

Details

Implements an abstraction for moving vfragment-based nodes around and fixes both reported issues around insertion when the vfragment-based nodes are the reference node. Note that we already had logic for mounting/patching/removing.

Fixes #3396
Fixes #3377

Does this pull request introduce a breaking change?

No

Does this pull request introduce an observable change?

Yes

GUS work item

W-12630370

@ekashida
Copy link
Member Author

Downstream repo at salesforce-experience-platform-emu/lds-lightning-platform currently has a broken build.

Copy link
Contributor

@jye-sf jye-sf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requested to add or verify we have tests for a specific use case.

packages/@lwc/engine-core/src/framework/rendering.ts Outdated Show resolved Hide resolved
@ekashida ekashida changed the title fix(engine-core): insert before preceding text node for fragments in static/dynamic lists fix(engine-core): add abstraction for moving vfragments Mar 24, 2023
Copy link
Contributor

@nolanlawson nolanlawson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems strange to me that the diffing algo would have knowledge of fragment start and end text positions. My understanding is that the goal of fragments was to insert the text nodes and just let the diffing algo act accordingly.

Looking at how Vue does it, they seem to insert both the leading and trailing text nodes and then patch the content in before the trailing one. Can we use a similar technique?

@jye-sf
Copy link
Contributor

jye-sf commented Mar 24, 2023

@nolanlawson Patching the content in before the trailing anchor already works fine, and we're doing it the way you're suggesting (the way Vue does it). The problem here is not with patching/mounting the internal content of the fragment itself, it's with how the diffing algo deals with the VFragment node along with its sibling nodes. It's the difference between patch(fragmentNode) and patch(VNode{ children: [node, node, fragment, node] }). The first works fine. The latter is what we're trying to solve.

Thanks for linking how Vue does vdom. From what I can see, Vue's diffing algo actually does have knowledge of fragment start and end node positions:

This hits a good middle ground and I agree we should use a similar technique: fragment-specific start/end nodes are abstracted away into the base VNode definition itself (vnode.elm always leading, vnode.anchor for fragment tails, helper function to grab the next node element) and then the diffing algo just does its thing with minimal fragment-specific logic. I think we're being misguided trying to stick to a single-element-per-vnode approach.

cc - @ekashida

What do either of you think?

Copy link
Contributor

@jye-sf jye-sf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nolanlawson
Copy link
Contributor

@jye-sf Nice research! Yeah that makes sense to me. If VFragment can't just be "any old VNode," then let's go whole-hog and keep a reference to its start and end anchors explicitly.

@ekashida
Copy link
Member Author

ekashida commented Mar 27, 2023 via email

@nolanlawson
Copy link
Contributor

That should be ‘Node | VFragment’

I tested 0a18c80, and it compiles with TypeScript and passes the Karma tests. It looks like VNode (not Node) is correct there?

@ekashida
Copy link
Member Author

@nolanlawson Ah, I see what you mean.

Copy link
Contributor

@nolanlawson nolanlawson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small nitpick, otherwise LGTM!

@ekashida
Copy link
Member Author

@jye-sf another review please!

// trailing anchor as the argument to nextSibling():
// [..., [leading, ...content, trailing], nextSibling, ...]
let anchor: Node | null;
if (isVFragment(oldEndVnode)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit: we could save a check here if trailing was on VNode rather than Vfragment because we can do

anchor = renderer.nextSibling(oldEndVnode.trailing ? oldEndVnode.trailing.elm : oldEndVnode.elm)

but I don't think we need to hold up this PR for that change. We can consider that when refactoring.

@jye-sf jye-sf merged commit 43cc676 into master Mar 28, 2023
@jye-sf jye-sf deleted the ekashida/issue-3377 branch March 28, 2023 17:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

lwc:if renders conditional elements out of order lwc:if renders out of order with for:each
5 participants