mirror of https://github.com/sveltejs/svelte
`is_standalone` already lets a component render into its parent block's anchor
rather than creating a comment anchor of its own. It was gated off for dynamic
components, so every namespaced component call site paid for an anchor.
`{#each}` is excluded. Its items are reordered by their node boundary, and a
deferred branch is committed at the item's own anchor, so removing that anchor
reorders incorrectly when a swap is still pending.
`$.component` claims the DOM boundary and steps over the closing hydration
marker when there is no anchor to do it, which is otherwise `$.append`'s job.
Saves two DOM nodes per instance at every qualifying call site.
pull/18734/head
parent
6374a2a0db
commit
7e9b3c2e8f
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
perf: render a lone dynamic component into its parent block's anchor
|
||||
@ -0,0 +1,5 @@
|
||||
<script>
|
||||
let { id, gate } = $props();
|
||||
</script>
|
||||
|
||||
<span>a{await gate(id)}</span>
|
||||
@ -0,0 +1,5 @@
|
||||
<script>
|
||||
let { id, gate } = $props();
|
||||
</script>
|
||||
|
||||
<span>b{await gate(id)}</span>
|
||||
@ -0,0 +1,25 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
async test({ assert, target }) {
|
||||
await tick();
|
||||
const [flip, swap, release] = target.querySelectorAll('button');
|
||||
const html = () => [...target.querySelectorAll('span')].map((s) => s.textContent).join('');
|
||||
|
||||
assert.equal(html(), 'a0a1a2', 'initial');
|
||||
|
||||
// swap: the new branches are deferred, so they land offscreen in a fragment
|
||||
swap.click();
|
||||
await tick();
|
||||
// reorder while the swap is still pending
|
||||
flip.click();
|
||||
await tick();
|
||||
for (let i = 0; i < 3; i++) {
|
||||
release.click();
|
||||
await tick();
|
||||
}
|
||||
await tick();
|
||||
assert.equal(html(), 'b2b1b0', 'reorder while swap pending');
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,24 @@
|
||||
<script>
|
||||
import A from './A.svelte';
|
||||
import B from './B.svelte';
|
||||
|
||||
let items = $state([{ id: 0 }, { id: 1 }, { id: 2 }]);
|
||||
let Component = $state(A);
|
||||
const queue = [];
|
||||
|
||||
function gate(id) {
|
||||
if (Component === A) return id;
|
||||
const p = Promise.withResolvers();
|
||||
queue.push(() => p.resolve(id));
|
||||
return p.promise;
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={() => items.reverse()}>flip</button>
|
||||
<button onclick={() => (Component = B)}>swap</button>
|
||||
<button onclick={() => queue.shift()?.()}>release</button>
|
||||
|
||||
<svelte:boundary>
|
||||
{#each items as item (item.id)}<Component id={item.id} {gate} />{/each}
|
||||
{#snippet pending()}<i>pending</i>{/snippet}
|
||||
</svelte:boundary>
|
||||
@ -0,0 +1,5 @@
|
||||
<script>
|
||||
let { item } = $props();
|
||||
</script>
|
||||
|
||||
<span>{item}</span>
|
||||
@ -0,0 +1,12 @@
|
||||
import 'svelte/internal/disclose-version';
|
||||
import * as $ from 'svelte/internal/client';
|
||||
|
||||
var root = $.from_html(`<span> </span>`);
|
||||
|
||||
export default function A($$anchor, $$props) {
|
||||
var span = root();
|
||||
var text = $.only_child(span, true);
|
||||
|
||||
$.template_effect(() => $.set_text(text, $$props.item));
|
||||
$.append($$anchor, span);
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
import 'svelte/internal/disclose-version';
|
||||
import * as $ from 'svelte/internal/client';
|
||||
import A from './A.svelte';
|
||||
|
||||
var root = $.from_html(`<!><span></span>`, 1);
|
||||
var root_1 = $.from_html(`<!> <!> <!>`, 1);
|
||||
|
||||
export default function Lone_dynamic_component($$anchor) {
|
||||
let Component = $.proxy(A);
|
||||
let items = $.proxy([1, 2, 3]);
|
||||
let show = true;
|
||||
var fragment = root_1();
|
||||
var node = $.first_child(fragment);
|
||||
|
||||
{
|
||||
var consequent = ($$anchor) => {
|
||||
$.component($$anchor, () => Component, ($$anchor, Component_1) => {
|
||||
Component_1($$anchor, {});
|
||||
});
|
||||
};
|
||||
|
||||
$.if(node, ($$render) => {
|
||||
if (show) $$render(consequent);
|
||||
});
|
||||
}
|
||||
|
||||
var node_1 = $.sibling(node, 2);
|
||||
|
||||
{
|
||||
var consequent_1 = ($$anchor) => {
|
||||
var fragment_2 = root();
|
||||
var node_2 = $.first_child(fragment_2);
|
||||
|
||||
$.component(node_2, () => Component, ($$anchor, Component_2) => {
|
||||
Component_2($$anchor, {});
|
||||
});
|
||||
|
||||
$.next();
|
||||
$.append($$anchor, fragment_2);
|
||||
};
|
||||
|
||||
$.if(node_1, ($$render) => {
|
||||
if (show) $$render(consequent_1);
|
||||
});
|
||||
}
|
||||
|
||||
var node_3 = $.sibling(node_1, 2);
|
||||
|
||||
$.each(node_3, 17, () => items, $.index, ($$anchor, item) => {
|
||||
var fragment_3 = $.comment();
|
||||
var node_4 = $.first_child(fragment_3);
|
||||
|
||||
$.component(node_4, () => Component, ($$anchor, Component_3) => {
|
||||
Component_3($$anchor, {
|
||||
get item() {
|
||||
return $.get(item);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$.append($$anchor, fragment_3);
|
||||
});
|
||||
|
||||
$.append($$anchor, fragment);
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
import * as $ from 'svelte/internal/server';
|
||||
|
||||
export default function A($$renderer, $$props) {
|
||||
let { item } = $$props;
|
||||
|
||||
$$renderer.push(`<span>${$.escape(item)}</span>`);
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
import * as $ from 'svelte/internal/server';
|
||||
import A from './A.svelte';
|
||||
|
||||
export default function Lone_dynamic_component($$renderer) {
|
||||
let Component = A;
|
||||
let items = [1, 2, 3];
|
||||
let show = true;
|
||||
|
||||
if (show) {
|
||||
$$renderer.push('<!--[0-->');
|
||||
|
||||
if (Component) {
|
||||
$$renderer.push('<!--[-->');
|
||||
Component($$renderer, {});
|
||||
$$renderer.push('<!--]-->');
|
||||
} else {
|
||||
$$renderer.push('<!--[!-->');
|
||||
$$renderer.push('<!--]-->');
|
||||
}
|
||||
} else {
|
||||
$$renderer.push('<!--[-1-->');
|
||||
}
|
||||
|
||||
$$renderer.push(`<!--]--> `);
|
||||
|
||||
if (show) {
|
||||
$$renderer.push('<!--[0-->');
|
||||
|
||||
if (Component) {
|
||||
$$renderer.push('<!--[-->');
|
||||
Component($$renderer, {});
|
||||
$$renderer.push('<!--]-->');
|
||||
} else {
|
||||
$$renderer.push('<!--[!-->');
|
||||
$$renderer.push('<!--]-->');
|
||||
}
|
||||
|
||||
$$renderer.push(`<span></span>`);
|
||||
} else {
|
||||
$$renderer.push('<!--[-1-->');
|
||||
}
|
||||
|
||||
$$renderer.push(`<!--]--> <!--[-->`);
|
||||
|
||||
const each_array = $.ensure_array_like(items);
|
||||
|
||||
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
|
||||
let item = each_array[$$index];
|
||||
|
||||
if (Component) {
|
||||
$$renderer.push('<!--[-->');
|
||||
Component($$renderer, { item });
|
||||
$$renderer.push('<!--]-->');
|
||||
} else {
|
||||
$$renderer.push('<!--[!-->');
|
||||
$$renderer.push('<!--]-->');
|
||||
}
|
||||
}
|
||||
|
||||
$$renderer.push(`<!--]-->`);
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
<script>
|
||||
import A from './A.svelte';
|
||||
|
||||
let Component = $state(A);
|
||||
let items = $state([1, 2, 3]);
|
||||
let show = $state(true);
|
||||
</script>
|
||||
|
||||
<!-- sole child of the block, so it renders into the block's anchor -->
|
||||
{#if show}<Component />{/if}
|
||||
|
||||
<!-- not the sole child, so it still needs an anchor of its own -->
|
||||
{#if show}<Component /><span></span>{/if}
|
||||
|
||||
<!-- `{#each}` items are reordered by their node boundary, and a deferred branch is
|
||||
committed at the item's own anchor, so the anchor stays -->
|
||||
{#each items as item}<Component {item} />{/each}
|
||||
Loading…
Reference in new issue