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

Bubble up Component dom changes up the virtual tree (+51 B) #1700

Merged
merged 23 commits into from
Jun 14, 2019

Conversation

andrewiggins
Copy link
Member

@andrewiggins andrewiggins commented Jun 10, 2019

If a component changed it's DOM node and was wrapped in other Components, the parent Components _dom and .base pointer could become out of date. Since this.base is a public API feature on Components, I figured this need to be fixed.

This might slightly decrease our memory footprint as we won't have pointers to useless DOM nodes anymore. Also fixes some cases where we would needlessly append DOM nodes to parents that are no longer mounted (see comment in fragments.test.js)

Total change: +36 B 😐
Total change: +48 B 😕
Total change: +51 B 😕

@@ -2575,7 +2575,6 @@ describe('Fragment', () => {
div('B')
].join(''), 'updateB');
expectDomLogToBe([
// TODO: Extra appends happen here in actual. Why?
Copy link
Member Author

Choose a reason for hiding this comment

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

These spurious appends no longer happen!

// Eagerly cleanup _lastDomChild. We don't need to persist the value because
// it is only used by `diffChildren` to determine where to resume the diff after
// diffing Components and Fragments.
childVNode._lastDomChild = null;
Copy link
Member Author

@andrewiggins andrewiggins Jun 10, 2019

Choose a reason for hiding this comment

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

Instead of trying to keep _lastDomChild up to date, I figured it was better to just clean up the pointer after we used it and no longer need it to reduce the likelihood of having out of date pointers to DOM nodes no longer in the DOM.

@coveralls
Copy link

coveralls commented Jun 10, 2019

Coverage Status

Coverage increased (+0.9%) to 99.782% when pulling e12a477 on fix-cbase into a19aee9 on master.

Copy link
Member

@JoviDeCroock JoviDeCroock left a comment

Choose a reason for hiding this comment

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

This is looking really good, I have to ask though was this issue occurring already in scenario's? Or is this something that is part of the previous PR?

Great work as always

@andrewiggins
Copy link
Member Author

I believe this issue was already occurring. I think it has always been broken in Preact X actually. However, I don't think c.base is a well known or often used property (though we use it at work as a forwardRef cheat).

I checked out 699111d (the PR before my _children refactor) and 1566a31 (alpha.1) and the tests fail in both those commits so it's been with us for sometime.

@marvinhagemeister
Copy link
Member

Can confirm that we always had this issue in X (maybe even in 8.x?). This PR is the missing piece for our new Fragment algorithm 👍 We may revisit it when we'll work on 2-phase render, but for now this is the best we can do for the current architecture 💯

src/component.js Outdated
if (newDom != oldDom) {
// Update parent component's _dom and c.base pointers
while (
vnode._parent
Copy link
Member

Choose a reason for hiding this comment

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

Are there cases where the _parent pointer can be null? If that's the case for root nodes we could try using our EMPTY_OBJ ref for that. Maybe that could save a byte or two 🎉

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh true! I'll investigate

Copy link
Member

@marvinhagemeister marvinhagemeister left a comment

Choose a reason for hiding this comment

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

So many new tests 😍 Let's do this 🎉💯🚀

expect(parentDom1.base).to.equalNode(scratch.firstChild);
});

it('should update parent c.base if first child becomes null', () => {
Copy link
Member

Choose a reason for hiding this comment

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

Had a look into this and it seems like sometimes the _dom pointer of components is not set properly. When I log the vnode in unmount() _dom is null whereas c.base points to the correct DOM node.

Copy link
Member Author

@andrewiggins andrewiggins Jun 12, 2019

Choose a reason for hiding this comment

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

Yea, when looking at your comment about a missing test scenario, I realized a whole bunch of edge cases I didn't handle lol. Still working on it

removeDom(child._children);
}
if (child._dom) {
if (typeof child.type !== 'function' && child._dom) {
Copy link
Member Author

Choose a reason for hiding this comment

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

@sventschui FYI, this PR enables the removeDom function to now only remove the first DOM children parents under Suspense, instead of recursing all the way down the tree and removing all the nodes bottom up. Should be a bit faster now for deep trees!

Copy link
Member

Choose a reason for hiding this comment

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

Awesome 🎉

src/component.js Outdated
if (newDom != oldDom) {
updateParentDomPointers(vnode);

// // 3496 B
Copy link
Member Author

Choose a reason for hiding this comment

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

TODO: Remove all the commented code before checking in. Wanted to first give people an idea of some of the golfing techniques I tried.

P.S. Surprised this iterative approach isn't smaller than the recursive function:

Minified:

// Iterative: 3496 B
for (; null != (o = o.__p) && null != o.__c; )
    for (o.__e = o.__c.base = null, r = 0; r < o.__k.length; r++)
        if (null != (i = o.__k[r]) && null != i.__e) {
            o.__e = o.__c.base = i.__e;
            break;
        }
// Recursive: 3484 B
function m(n) {
	var l, u;
	if (null != (n = n.__p) && null != n.__c) {
		for (n.__e = n.__c.base = null, l = 0; l < n.__k.length; l++)
			if (null != (u = n.__k[l]) && null != u.__e) {
				n.__e = n.__c.base = u.__e;
				break;
			}
		return m(n);
	}
}

Copy link
Member

Choose a reason for hiding this comment

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

Do we have some guidelines about bytes saving vs. function call overhead? Asking for a friend 😂

Copy link
Member Author

Choose a reason for hiding this comment

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

I've seen a bench (assuming it can be trusted 🤷‍♂) where in one situation recursion was faster than the iterative solution, so I typically go for the byte savings first on something like this, especially since this code is only called once per update.

I think it would be hard to measure the perf difference between an iterative solution and recursive in this case since it isn't called very often in a diff (as opposed to the children loop in diffChildren which is called for every VNode, for example).

But that's just me. I don't spend as much time doing perf tests as @developit or others, so I defer to them.

Copy link
Member

@JoviDeCroock JoviDeCroock left a comment

Choose a reason for hiding this comment

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

I'd say let's remove the comments and get this into master since it looks awesome!

Copy link
Member

@marvinhagemeister marvinhagemeister left a comment

Choose a reason for hiding this comment

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

Agree with @JoviDeCroock 👍 💯 🚀

@andrewiggins
Copy link
Member Author

I'll clean it up and merge it today

@andrewiggins andrewiggins changed the title Bubble up Component dom changes up the virtual tree (+36 B) Bubble up Component dom changes up the virtual tree (+51 B) Jun 14, 2019
@andrewiggins andrewiggins merged commit 3e3f3f9 into master Jun 14, 2019
@andrewiggins andrewiggins deleted the fix-cbase branch June 14, 2019 23:41
andrewiggins added a commit that referenced this pull request Jun 25, 2019
With #1700 complete, we can now rely on the sibling._dom pointer to tell us if that subtree contains a rendered DOM node and what the first dom is.
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.

5 participants