-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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: store DOM boundaries on effects #12215
Conversation
|
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.
why was this test removed?
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.
With a template that starts with a <!>
comment, the start
node is null
(or undefined
in the static component/render tag case), and when it's time to remove nodes Svelte finds the actual start node from the first child effect.
Currently, <noscript>
is also replaced with a <!>
in the client-side template, but there's no corresponding child effect, so the start
node is incorrect and nodes are left behind when the parent effect is destroyed.
The easy fix was to just leave <noscript>
elements in the template (albeit without children), but that changed the result of the test. The alternative would be to add a new flag, but that feels like more complexity (and we're running out of room for flags — a bitmask can only hold 31)
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.
Nice work :)
The refactoring in #12215 didn't take HMR into account. As a result, the anchor was passed to the HMR block, which was subsequently cleaned up on destroy - but the anchor could be shared with other components and therefore needs to stay in the dom. Passing `null` instead solves the problem. Fixes #12228
The refactoring in #12215 didn't take HMR into account. As a result, the anchor was passed to the HMR block, which was subsequently cleaned up on destroy - but the anchor could be shared with other components and therefore needs to stay in the dom. Passing `null` instead solves the problem. Fixes #12228 Fixes #12230 Fixes #12233
This is an alternative to #12182 and #11690; fixes #12177 and #12198. WIP because it needs tidying up and documenting, but I'll try and summarise the changes:
Instead of
effect.dom
being a node or an array of nodes, which is written to in an unpredictable order (leading to bugs like the aforementioned, unless you do a lot of splicing which I think we're better off avoiding), block/branch effects have aneffect.nodes
property withstart
,end
andanchor
properties.This property is set when the effect's template is cloned. For example in the demo app's case...
...the
<button>
element is the main effect'snodes.start
andnodes.end
. When it's time to destroy the effect, we just remove everything fromnodes.start
tonodes.end
, inclusive.In a trickier case...
...while the
if
block effect'snodes
is straightforward — the<p>
is bothstart
andend
— the effect containing theif
block has a trickier job:During hydration we can just use the opening hydration boundary as the
start
, but in themount
case we can't do that — the first node might be the<p>
inside theif
, or it might be theif
block's<!>
anchor, depending on the value offoo
. For that reason, the effect'sstart
node isnull
, which means thatget_first_node
consultseffect.first
, recursively, until we figure out which is the first node.Components and render tags are an even trickier case as in many cases they don't create an effect at all. For that reason if the first item inside an effect is one of those components/render tags, the
start
isundefined
but can be replaced withnull
or a node when the item is rendered.This is all quite complicated to describe but it actually makes most implementation details much simpler. For example dynamic elements no longer need to reach up inside their parent to muck about with
parent_effect.dom
, and{@html ...}
tags have an easier time as well.One notable change is that in addition to relinking each block items as we move them around, we also need to relink their effects. We probably should have been doing that anyway. Currently, this is slightly inefficient but we can probably do the relinking of both items and effects at the same time, minimising the number of operations.
A nice thing about this way of doing things is that it hopefully opens the door to a more efficient approach to hydration. Currently, we have to iterate over every
NodeList
in the hydrated DOM twice (once to build up thehydrate_nodes
array that becomeseffect.dom
, once inside everysibling
call), but if there's noeffect.dom
then it stands to reason that we could skip the first traversal in favour of populatingeffect.nodes.end
at the end of the process, insideappend
. That's left as future work, however.Before submitting the PR, please make sure you do the following
feat:
,fix:
,chore:
, ordocs:
.Tests and linting
pnpm test
and lint the project withpnpm lint