Module: lib/core/runtime/ComponentScope

How a component resolves the names an expression uses.

Why this is its own object

Now that expressions are compiled at build time, the scope is what remains of the compiler/runtime boundary inside the runtime: the compiler emits closures that take one argument, and this is the thing it is given. Everything about what a component's expressions can see — its state, its computed values, its actions, its props, its bridges, its resources, the values injected into it, the names its own module imported — is decided here and nowhere else.

It used to be four private methods and a cache field spread across a 2,900-line class, which made two things hard to see. It hid the ordering rule, which is load-bearing and easy to get wrong. And it hid the fact that a scope was being constructed per evaluation: on the compiled path that is one allocation and one layer walk per binding, per update, for an object whose contents almost never change.

Precedence, and why it is what it is

Layers are consulted highest first:

  1. per-call extras — a <@for> item, an event, an action's args
  2. injected values (provide / inject)
  3. framework names — props, styles, $route, $emit, $watch, …
  4. the component's own actions
  5. state and computed values, read from the live reactive proxy
  6. resources
  7. bridges
  8. mixin properties
  9. state itself
  10. the names the component's module imported

Bridges sit below the component's own declarations so a bridge cannot silently shadow a <state> key or an action; the compiler reports such a collision separately. Imports sit last because an import is the least specific thing in scope.

Nothing is read until it is named

The layers are a Proxy, not a merged object. Spreading the reactive state into a plain object — which is what this replaced — read every key eagerly, which made bare identifiers non-reactive, reported cycles that did not exist, and tied every render to every state key. A get here resolves through the layers and, for a state or computed name, reads the live proxy inside whichever watcher is evaluating, so an expression depends on exactly the names it mentions.

Caching

The base scope — the one with no per-call extras — is built once and reused. It is invalidated when something changes what a name resolves to: the set of state keys, the action map, the injected values. A scope with extras derives from it rather than rebuilding, which is why deriveScope exists: a spread would reintroduce the eager read this whole design removes.

Source:

Classes

ComponentScope