mirror of https://github.com/sveltejs/svelte
fix: reduce if block nesting (#17662)
* fix: reduce if block nesting This reduces if block nesting similar to how we did it in #15250 (which got lost during the `await` feature introduction): If the if expression doesn't contain an await expression or is not dependent on a blocker that is not already resolved, then we can avoid creating a separate `$.if()` statement. The one trade-off is that we'll do re-invocations for all the conditions leading up to the condition that matches. Therefore non-simple if expressions are wrapper in `$.derived` to avoid excessive recomputations. closes #17659 (~320 markers in prod mode possible now; less in dev because of our "wrap this component with devtime info" method) helps with #15200 * tweak * feedbackpull/17696/head
parent
35c845e35d
commit
bd5480b9d0
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: reduce if block nesting
|
||||
@ -0,0 +1,12 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
mode: ['async-server', 'hydrate', 'client'],
|
||||
ssrHtml: `bar blocking`,
|
||||
|
||||
async test({ assert, target }) {
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, 'bar blocking');
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,21 @@
|
||||
<script>
|
||||
let foo = $state(false);
|
||||
let blocking = $derived(await foo);
|
||||
let bar = Promise.resolve(true);
|
||||
</script>
|
||||
|
||||
{#if foo}
|
||||
foo
|
||||
{:else if await bar}
|
||||
bar
|
||||
{:else}
|
||||
else
|
||||
{/if}
|
||||
|
||||
{#if foo}
|
||||
foo
|
||||
{:else if !blocking}
|
||||
blocking
|
||||
{:else}
|
||||
else
|
||||
{/if}
|
||||
@ -0,0 +1,7 @@
|
||||
<script>
|
||||
let { x } = $props();
|
||||
console.log(x);
|
||||
$effect(() => console.log('$effect: '+ x))
|
||||
</script>
|
||||
|
||||
{x}
|
||||
@ -0,0 +1,47 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
skip: true, // this fails on main, too; skip for now
|
||||
async test({ assert, target, logs }) {
|
||||
const [x, y, resolve] = target.querySelectorAll('button');
|
||||
|
||||
x.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['universe']);
|
||||
|
||||
y.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['universe', 'world', '$effect: world']);
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<button>x</button>
|
||||
<button>y++</button>
|
||||
<button>resolve</button>
|
||||
world
|
||||
`
|
||||
);
|
||||
|
||||
resolve.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, [
|
||||
'universe',
|
||||
'world',
|
||||
'$effect: world',
|
||||
'$effect: universe',
|
||||
'$effect: universe'
|
||||
]);
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<button>x</button>
|
||||
<button>y++</button>
|
||||
<button>resolve</button>
|
||||
universe
|
||||
universe
|
||||
universe
|
||||
`
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,28 @@
|
||||
<script>
|
||||
import Child from './Child.svelte';
|
||||
|
||||
let x = $state('world');
|
||||
let y = $state(0);
|
||||
let deferred = [];
|
||||
|
||||
function delay(s) {
|
||||
const d = Promise.withResolvers();
|
||||
deferred.push(() => d.resolve(s))
|
||||
return d.promise;
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={() => x = 'universe'}>x</button>
|
||||
|
||||
<button onclick={() => y++}>y++</button>
|
||||
|
||||
<button onclick={() => deferred.shift()()}>resolve</button>
|
||||
|
||||
{#if x === 'universe'}
|
||||
{await delay(x)}
|
||||
<Child {x} />
|
||||
{/if}
|
||||
|
||||
{#if y > 0}
|
||||
<Child {x} />
|
||||
{/if}
|
||||
@ -0,0 +1,3 @@
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({ compileOptions: { experimental: { async: true } } });
|
||||
@ -0,0 +1,201 @@
|
||||
import 'svelte/internal/disclose-version';
|
||||
import 'svelte/internal/flags/async';
|
||||
import * as $ from 'svelte/internal/client';
|
||||
|
||||
var root = $.from_html(`<!> <!> <!> <!> <!>`, 1);
|
||||
|
||||
export default function Async_if_chain($$anchor) {
|
||||
function complex1() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
let foo = true;
|
||||
var blocking;
|
||||
var $$promises = $.run([async () => blocking = await $.async_derived(() => foo)]);
|
||||
var fragment = root();
|
||||
var node = $.first_child(fragment);
|
||||
|
||||
$.async(node, [$$promises[0]], void 0, (node) => {
|
||||
var consequent = ($$anchor) => {
|
||||
var text = $.text('foo');
|
||||
|
||||
$.append($$anchor, text);
|
||||
};
|
||||
|
||||
var consequent_1 = ($$anchor) => {
|
||||
var text_1 = $.text('bar');
|
||||
|
||||
$.append($$anchor, text_1);
|
||||
};
|
||||
|
||||
var alternate = ($$anchor) => {
|
||||
var text_2 = $.text('else');
|
||||
|
||||
$.append($$anchor, text_2);
|
||||
};
|
||||
|
||||
$.if(node, ($$render) => {
|
||||
if (foo) $$render(consequent); else if (bar) $$render(consequent_1, 1); else $$render(alternate, false);
|
||||
});
|
||||
});
|
||||
|
||||
var node_1 = $.sibling(node, 2);
|
||||
|
||||
$.async(node_1, [$$promises[0]], [() => foo], (node_1, $$condition) => {
|
||||
var consequent_2 = ($$anchor) => {
|
||||
var text_3 = $.text('foo');
|
||||
|
||||
$.append($$anchor, text_3);
|
||||
};
|
||||
|
||||
var consequent_3 = ($$anchor) => {
|
||||
var text_4 = $.text('bar');
|
||||
|
||||
$.append($$anchor, text_4);
|
||||
};
|
||||
|
||||
var alternate_2 = ($$anchor) => {
|
||||
var fragment_1 = $.comment();
|
||||
var node_2 = $.first_child(fragment_1);
|
||||
|
||||
$.async(node_2, [], [() => baz], (node_2, $$condition) => {
|
||||
var consequent_4 = ($$anchor) => {
|
||||
var text_5 = $.text('baz');
|
||||
|
||||
$.append($$anchor, text_5);
|
||||
};
|
||||
|
||||
var alternate_1 = ($$anchor) => {
|
||||
var text_6 = $.text('else');
|
||||
|
||||
$.append($$anchor, text_6);
|
||||
};
|
||||
|
||||
$.if(
|
||||
node_2,
|
||||
($$render) => {
|
||||
if ($.get($$condition)) $$render(consequent_4); else $$render(alternate_1, false);
|
||||
},
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
$.append($$anchor, fragment_1);
|
||||
};
|
||||
|
||||
$.if(node_1, ($$render) => {
|
||||
if ($.get($$condition)) $$render(consequent_2); else if (bar) $$render(consequent_3, 1); else $$render(alternate_2, false);
|
||||
});
|
||||
});
|
||||
|
||||
var node_3 = $.sibling(node_1, 2);
|
||||
|
||||
$.async(node_3, [$$promises[0]], [async () => (await $.save(foo))() > 10], (node_3, $$condition) => {
|
||||
var consequent_5 = ($$anchor) => {
|
||||
var text_7 = $.text('foo');
|
||||
|
||||
$.append($$anchor, text_7);
|
||||
};
|
||||
|
||||
var consequent_6 = ($$anchor) => {
|
||||
var text_8 = $.text('bar');
|
||||
|
||||
$.append($$anchor, text_8);
|
||||
};
|
||||
|
||||
var alternate_4 = ($$anchor) => {
|
||||
var fragment_2 = $.comment();
|
||||
var node_4 = $.first_child(fragment_2);
|
||||
|
||||
$.async(node_4, [$$promises[0]], [async () => (await $.save(foo))() > 5], (node_4, $$condition) => {
|
||||
var consequent_7 = ($$anchor) => {
|
||||
var text_9 = $.text('baz');
|
||||
|
||||
$.append($$anchor, text_9);
|
||||
};
|
||||
|
||||
var alternate_3 = ($$anchor) => {
|
||||
var text_10 = $.text('else');
|
||||
|
||||
$.append($$anchor, text_10);
|
||||
};
|
||||
|
||||
$.if(
|
||||
node_4,
|
||||
($$render) => {
|
||||
if ($.get($$condition)) $$render(consequent_7); else $$render(alternate_3, false);
|
||||
},
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
$.append($$anchor, fragment_2);
|
||||
};
|
||||
|
||||
$.if(node_3, ($$render) => {
|
||||
if ($.get($$condition)) $$render(consequent_5); else if (bar) $$render(consequent_6, 1); else $$render(alternate_4, false);
|
||||
});
|
||||
});
|
||||
|
||||
var node_5 = $.sibling(node_3, 2);
|
||||
|
||||
{
|
||||
var consequent_8 = ($$anchor) => {
|
||||
var text_11 = $.text('foo');
|
||||
|
||||
$.append($$anchor, text_11);
|
||||
};
|
||||
|
||||
var consequent_9 = ($$anchor) => {
|
||||
var text_12 = $.text('bar');
|
||||
|
||||
$.append($$anchor, text_12);
|
||||
};
|
||||
|
||||
var consequent_10 = ($$anchor) => {
|
||||
var text_13 = $.text('baz');
|
||||
|
||||
$.append($$anchor, text_13);
|
||||
};
|
||||
|
||||
var d = $.derived(() => complex1() * complex2 > 100);
|
||||
|
||||
var alternate_5 = ($$anchor) => {
|
||||
var text_14 = $.text('else');
|
||||
|
||||
$.append($$anchor, text_14);
|
||||
};
|
||||
|
||||
$.if(node_5, ($$render) => {
|
||||
if (simple1) $$render(consequent_8); else if (simple2 > 10) $$render(consequent_9, 1); else if ($.get(d)) $$render(consequent_10, 2); else $$render(alternate_5, false);
|
||||
});
|
||||
}
|
||||
|
||||
var node_6 = $.sibling(node_5, 2);
|
||||
|
||||
$.async(node_6, [$$promises[0]], void 0, (node_6) => {
|
||||
var consequent_11 = ($$anchor) => {
|
||||
var text_15 = $.text('foo');
|
||||
|
||||
$.append($$anchor, text_15);
|
||||
};
|
||||
|
||||
var consequent_12 = ($$anchor) => {
|
||||
var text_16 = $.text('bar');
|
||||
|
||||
$.append($$anchor, text_16);
|
||||
};
|
||||
|
||||
var alternate_6 = ($$anchor) => {
|
||||
var text_17 = $.text('else');
|
||||
|
||||
$.append($$anchor, text_17);
|
||||
};
|
||||
|
||||
$.if(node_6, ($$render) => {
|
||||
if ($.get(blocking) > 10) $$render(consequent_11); else if ($.get(blocking) > 5) $$render(consequent_12, 1); else $$render(alternate_6, false);
|
||||
});
|
||||
});
|
||||
|
||||
$.append($$anchor, fragment);
|
||||
}
|
||||
@ -0,0 +1,110 @@
|
||||
import 'svelte/internal/flags/async';
|
||||
import * as $ from 'svelte/internal/server';
|
||||
|
||||
export default function Async_if_chain($$renderer) {
|
||||
function complex1() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
let foo = true;
|
||||
var blocking;
|
||||
var $$promises = $$renderer.run([async () => blocking = await foo]);
|
||||
|
||||
$$renderer.async_block([$$promises[0]], ($$renderer) => {
|
||||
if (foo) {
|
||||
$$renderer.push('<!--[-->');
|
||||
$$renderer.push(`foo`);
|
||||
} else if (bar) {
|
||||
$$renderer.push('<!--[1-->');
|
||||
$$renderer.push(`bar`);
|
||||
} else {
|
||||
$$renderer.push('<!--[!-->');
|
||||
$$renderer.push(`else`);
|
||||
}
|
||||
});
|
||||
|
||||
$$renderer.push(`<!--]--> `);
|
||||
|
||||
$$renderer.async_block([$$promises[0]], async ($$renderer) => {
|
||||
if ((await $.save(foo))()) {
|
||||
$$renderer.push('<!--[-->');
|
||||
$$renderer.push(`foo`);
|
||||
} else if (bar) {
|
||||
$$renderer.push('<!--[1-->');
|
||||
$$renderer.push(`bar`);
|
||||
} else {
|
||||
$$renderer.push('<!--[!-->');
|
||||
|
||||
$$renderer.child_block(async ($$renderer) => {
|
||||
if ((await $.save(baz))()) {
|
||||
$$renderer.push('<!--[-->');
|
||||
$$renderer.push(`baz`);
|
||||
} else {
|
||||
$$renderer.push('<!--[!-->');
|
||||
$$renderer.push(`else`);
|
||||
}
|
||||
});
|
||||
|
||||
$$renderer.push(`<!--]-->`);
|
||||
}
|
||||
});
|
||||
|
||||
$$renderer.push(`<!--]--> `);
|
||||
|
||||
$$renderer.async_block([$$promises[0]], async ($$renderer) => {
|
||||
if ((await $.save(foo))() > 10) {
|
||||
$$renderer.push('<!--[-->');
|
||||
$$renderer.push(`foo`);
|
||||
} else if (bar) {
|
||||
$$renderer.push('<!--[1-->');
|
||||
$$renderer.push(`bar`);
|
||||
} else {
|
||||
$$renderer.push('<!--[!-->');
|
||||
|
||||
$$renderer.async_block([$$promises[0]], async ($$renderer) => {
|
||||
if ((await $.save(foo))() > 5) {
|
||||
$$renderer.push('<!--[-->');
|
||||
$$renderer.push(`baz`);
|
||||
} else {
|
||||
$$renderer.push('<!--[!-->');
|
||||
$$renderer.push(`else`);
|
||||
}
|
||||
});
|
||||
|
||||
$$renderer.push(`<!--]-->`);
|
||||
}
|
||||
});
|
||||
|
||||
$$renderer.push(`<!--]--> `);
|
||||
|
||||
if (simple1) {
|
||||
$$renderer.push('<!--[-->');
|
||||
$$renderer.push(`foo`);
|
||||
} else if (simple2 > 10) {
|
||||
$$renderer.push('<!--[1-->');
|
||||
$$renderer.push(`bar`);
|
||||
} else if (complex1() * complex2 > 100) {
|
||||
$$renderer.push('<!--[2-->');
|
||||
$$renderer.push(`baz`);
|
||||
} else {
|
||||
$$renderer.push('<!--[!-->');
|
||||
$$renderer.push(`else`);
|
||||
}
|
||||
|
||||
$$renderer.push(`<!--]--> `);
|
||||
|
||||
$$renderer.async_block([$$promises[0]], ($$renderer) => {
|
||||
if (blocking > 10) {
|
||||
$$renderer.push('<!--[-->');
|
||||
$$renderer.push(`foo`);
|
||||
} else if (blocking > 5) {
|
||||
$$renderer.push('<!--[1-->');
|
||||
$$renderer.push(`bar`);
|
||||
} else {
|
||||
$$renderer.push('<!--[!-->');
|
||||
$$renderer.push(`else`);
|
||||
}
|
||||
});
|
||||
|
||||
$$renderer.push(`<!--]-->`);
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
<script>
|
||||
function complex1() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
let foo = $state(true);
|
||||
let blocking = $derived(await foo);
|
||||
</script>
|
||||
|
||||
<!-- simple chain - should have no nested $.if() -->
|
||||
{#if foo}
|
||||
foo
|
||||
{:else if bar}
|
||||
bar
|
||||
{:else}
|
||||
else
|
||||
{/if}
|
||||
|
||||
<!-- simple chain with await expressions - should have $.if() at each await expression -->
|
||||
{#if await foo}
|
||||
foo
|
||||
{:else if bar}
|
||||
bar
|
||||
{:else if await baz}
|
||||
baz
|
||||
{:else}
|
||||
else
|
||||
{/if}
|
||||
|
||||
<!-- simple chain with await expressions #2 - should have $.if() at each await expression (ideally we can detect that await foo is unnecessary to await multiple times and this is one $.if()) -->
|
||||
{#if await foo > 10}
|
||||
foo
|
||||
{:else if bar}
|
||||
bar
|
||||
{:else if await foo > 5}
|
||||
baz
|
||||
{:else}
|
||||
else
|
||||
{/if}
|
||||
|
||||
<!-- simple chain with some expressions that cause a $.derived - should be one $.if() -->
|
||||
{#if simple1}
|
||||
foo
|
||||
{:else if simple2 > 10}
|
||||
bar
|
||||
{:else if complex1() * complex2 > 100}
|
||||
baz
|
||||
{:else}
|
||||
else
|
||||
{/if}
|
||||
|
||||
<!-- simple chain with blocking expressions - should be one $.if() -->
|
||||
{#if blocking > 10}
|
||||
foo
|
||||
{:else if blocking > 5}
|
||||
bar
|
||||
{:else}
|
||||
else
|
||||
{/if}
|
||||
Loading…
Reference in new issue