-
Notifications
You must be signed in to change notification settings - Fork 392
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
refactor(engine-core): POC splitting up diffing from rehydration #2608
Conversation
|
||
function throwHydrationError() { | ||
assert.fail('Server rendered elements do not match client side generated elements'); | ||
} |
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.
One benefit of having hydration in one place is that we can potentially remove this code for environments that don't use hydration. Svelte has a hydratable
option that it uses for such optimizations: sveltejs/svelte#6525
Merging #2598 will make it easier to remove |
break; | ||
|
||
case VNodeType.CustomElement: { | ||
const vm = getAssociatedVMIfPresent(vnode.elm); |
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.
@pmdartus in your original refactor, we were doing vnode.vm
, and I changed it back to getAssociatedVMIfPresent
. What get's my attention is that it only was observable on the hydration tests. I can say for sure out the top of my mind, but maybe we should set a VM in the vnode as part of the hydration process?
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.
@pmdartus first pass, all looking good. i still need to dive into patch/mount(Element/customElement)
ctor: Ctor, | ||
owner: vmBeingRendered, | ||
mode: 'open', // TODO [#1294]: this should be defined in Ctor | ||
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.
where is this vm
being set?
function processElement(vnode: VElement, node: Node) { | ||
const elm = node as Element; |
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.
function processElement(vnode: VElement, node: Node) { | |
const elm = node as Element; | |
function processElement(vnode: VElement, elm: Element) { |
break; | ||
|
||
case VNodeType.Element: | ||
processElement(vnode, node); |
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.
processElement(vnode, node); | |
processElement(vnode, node as Element); |
function processCustomElement(vnode: VCustomElement, node: Node) { | ||
const elm = node as Element; | ||
|
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.
function processCustomElement(vnode: VCustomElement, node: Node) { | |
const elm = node as Element; | |
function processCustomElement(vnode: VCustomElement, elm: Element) { |
break; | ||
|
||
case VNodeType.CustomElement: | ||
processCustomElement(vnode, node); |
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.
processCustomElement(vnode, node); | |
processCustomElement(vnode, node as Element); |
renderer.addEventListener(elm, name, listener); | ||
for (const name in on) { | ||
const handler = on[name]; | ||
renderer.addEventListener(elm, name, (event: Event) => { |
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.
Some questions:
- before we used to have 1 handler for all events, now we have 1 handler per event (as many handlers as events). Pls, confirm.
- why can we skip
on && on[type]
check? because it will always be defined? is the handler is our owninvokeEventListener
from the bind function? - why not just do
renderer.addEventListener(elm, name, handler);
?
} | ||
} | ||
|
||
function patch(n1: VNode | null, n2: VNode, parent: ParentNode, anchor: Node | null) { |
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.
@pmdartus something feels off about this function. basically is doing 2 different things based on the parameters. if n1
is null, then is mounting (create+insert right?), otherwise patching (I'm still trying to figure out the fix me comment). Then in the process*
we make the same differentiation with a condition for isNull
(mount/patch).
What do you think if we separate the patch
logic into patch
and mount
, and also the process*
into patch*
and mount*
(you already have them for Element and CustomeElements)? (imho) Such a way is clearer, and we can skip the multiple isNull
check which are on the critical path of the diffing algo.
} else { | ||
n2.elm = n1.elm; | ||
|
||
// FIXME: Comment nodes should be static, we shouldn't need to diff them together. However |
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.
true, but, iirc text and comments do not have keys. I'm thinking of foreachs, maybe ifs (this is the same as texts), in which the diff may reuse other vnodses. we diff the texts so we do not need to access the DOM API.
// FIXME: Comment nodes should be static, we shouldn't need to diff them together. However | ||
// it is the case today. | ||
if (n2.text !== n1.text) { | ||
updateTextContent(n2); |
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 like the abstraction. don't know if will help but here is super micro-optimization: we could pass (node, renderer, text)
and avoid destructing the vnode in updateTextContent
. we have all those here.
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.
couldn't find any issues in the individual hooks migrations. just one question
throw new TypeError(`Incorrect Component Constructor`); | ||
} | ||
|
||
applyStyleScoping(elm, vnode); |
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.
applyStyleScoping
was done within the callback to UpgradableConstructor
(part of createViewModelHook
). Do you foresee any issues moving it outside?
Sorry for bitrotting this PR; I really like the direction of getting rid of hooks. |
Superseded by #2677 |
Details
This PR is a proof-of-concept for splitting up the diffing logic from the rehydration logic. VNode hooks are mixing up today both diffing and hydration logics. By removing the concept of hooks, it becomes quite straightforward to split up to the logic.
The VNode
hook
field can be replaced with atype
field. Thetype
field is a new field present on all VNodes. It is used to invoke the appropriate mounting or patching function. One of the nice side-effects of this change is that it makes the diffing algo code way easier to follow and resonate about. With this change the code is now broken down:src/framework/vnode.ts
: VNode type definitionsrc/framework/rendering.ts
: Component mounting and diffing logic withpatchChildren
for entry pointsrc/framework/hydration.ts
: Component rehydration logic withhydrateChildren
for entry pointHere are some other observations related to this change:
src/framework/modules/events.ts
is never invoked.hooks.ts
contains some duplicated code that can be simplified.VM.velements
is worth the performance improvement.VNode.owner
is not as simple as what I originally thought, mainly because of slotted content.Does this pull request introduce a breaking change?
Does this pull request introduce an observable change?