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/17670/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