sveltejs / svelte

web development for the rest of us
https://svelte.dev
MIT License
79.83k stars 4.24k forks source link

Track binding values #13266

Open Rich-Harris opened 1 month ago

Rich-Harris commented 1 month ago

Describe the problem

There are many cases where knowing what values a binding could potentially hold would allow us to generate more optimal code. For example, in a situation like this...

<script>
  let name = $state('Rich');
</script>

<input bind:value={name} />
<p>Hello {name}!</p>

...we don't need the ?? "" in the generated code, because we can determine that name is always defined:

$.template_effect(() => $.set_text(text, `Hello ${$.get(name) ?? ""}!`));

The same goes for references to an index (and, if we were sophisticated enough, the item itself) inside an each block:

{#each 'abc' as l, i}
  <p>{i}: {l}</p>
{/each}
-$.template_effect(() => $.set_text(text, `${i ?? ""}: ${l ?? ""}`));
+$.template_effect(() => $.set_text(text, `${i}: ${l ?? ""}`));

Post-#13264, it would also allow us to avoid creating effects in cases where we know an expression doesn't contain state. Right now, code like this...

<script>
  let min = 0;
  let max = 100;
  let number = 50;
</script>

<p>{Math.max(min, Math.min(max, number))}</p>

...yields output like this...

var p = root();

p.textContent = Math.max(min, Math.min(max, number));

...but as of #13264 yields this unnecessary overhead:

var p = root();
var text = $.child(p);

$.template_effect(() => $.set_text(text, Math.max(min, Math.min(max, number))));
$.reset(p);

Describe the proposed solution

Track all the assignments (along with the scopes in which they occur) when first analysing bindings, so that we can later determine things like:

There are limits to what we can know with such an analysis — often, the answer to 'what could this value be?' is :shrug:. It's no substitute for knowing the entirety of your program (though we could one day augment it with knowledge of prop values and imports, for example), and nor is it a replacement for an type system (though unlike types, code can't lie about the range of possible values). But it's a relatively simple and cheap way to get a decent chunk of the benefits of a more comprehensive system.

Importance

nice to have

adiguba commented 1 month ago

I may be saying something stupid, but why not also replace the reactive $.get(name) ?? "" by something like $.txt(name) ? With txt() defined like this :

function txt(signal) {
    return get(signal) ?? "";
}
Rich-Harris commented 1 month ago

Because then you're adding another function call, which is more expensive. And not everything is of the $.get(foo) form, sometimes it's just foo or foo() or whatever

adiguba commented 1 month ago

Another stupid idea : why not using a tagged template for cleaning null/undefined

-$.template_effect(() => $.set_text(text, `${i ?? ""}: ${l ?? ""}`));
+$.template_effect(() => $.set_text(text, $.TXT`${i}: ${l}`));

With TXT defined like that :

function TXT(strings, ...args) {
  for (let i = args.length-1; i>=0; i--) {
    args[i] ??= '';
  }
  return String.raw(strings, ...args);
}
Rich-Harris commented 1 month ago

The same reason — you're adding a function call which will make everything slower.