Quality and migration

Performance

There is no Virtual DOM, there is no tree diffing, there is no component rerender. The model is a Proxy that tracks reads key by key, and one effect per piece of DOM.

How the granular updates work

<div v-data="{ name: 'Ana', age: 30 }">
  <p v-text="name"></p>          <!-- effect 1, depends on "name" -->
  <p v-text="age"></p>           <!-- effect 2, depends on "age" -->
  <p>{ name } is { age }</p>     <!-- effect 3, depends on both -->
</div>

When name changes:

  1. the proxy's set compares the new value with the old one, and stops right there if they are equal;
  2. the effects that read the name key are put in a queue;
  3. the queue is processed in a microtask, with each effect running once only;
  4. effect 1 writes into a textContent, and effect 3 recomposes its own text.

Effect 2 never runs: the second paragraph is not read, not compared and not touched. The path from the change to the pixel is set on the proxy, queue, textContent. Nothing else.

Batching and the microtask

state.a = 1;
state.b = 2;
state.c = 3;
// the affected effects run once only, in the microtask
await V.nextTick();

Duplicate effects are deduplicated within the round. If an effect reruns too many times in the same round, the scheduler notices the loop and stops with a warning, instead of freezing the tab.

Size

FileRawgzipbrotli
voodoo.core.min.js123 KB42 KB37 KB
voodoo.min.js245 KB79 KB67 KB
voodoo.full.min.js410 KB124 KB103 KB

The numbers change with every version. The npm run size script measures the real files and fails when one of them blows past its declared budget, so a size regression is caught in CI and not in production.

Good practices, in order of payoff

Use :key on v-for

Without a key, the blocks are identified by their position. With a key, they are reused when the list is reordered, and the state inside them, focus, typed value, scroll position, animation , survives.

Prefer v-show over v-if for frequent toggling

<div v-show="activeTab === 'profile'">...</div>   <!-- toggles a lot -->
<div v-if="user.admin">...</div>                  <!-- decides once -->

Keep the expressions short

Every attribute expression is re-evaluated when a dependency changes. Expensive computation is better off in a component computed or in a scope function.

<!-- re-evaluates the whole list on every change -->
<span>{ orders.filter(o => o.paid).reduce((s, o) => s + o.total, 0) }</span>

<!-- computes once and reuses it -->
<span>{ paidTotal }</span>

Mark what does not need to be reactive

V.data({
  map: V.markRaw(new google.maps.Map(el)),
  editor: V.markRaw(createEditor())
});

Instances of outside libraries, DOM elements and large structures you replace whole gain nothing from becoming a proxy, and they pay for the walk.

Debounce, cache and load on demand

<input v-model.debounce="search" v-debounce="300">
<div v-resource="countries: /api/countries" v-cache="1h"></div>
<img v-lazy-src="/photos/large.jpg" alt="">
<section v-load-visible="/partials/testimonials.html">Loading...</section>

Prefer paginated lists

v-for renders every item of the source, with no virtualization: a list with ten thousand rows creates ten thousand elements. Paginate, or use infinite scrolling with v-infinite-scroll.

Turn the observer off when you do not need it

<script src="voodoo.full.min.js" data-no-observer defer></script>
V.walk(newElement);  // initialize by hand when you need to

Pick the right file

If the page has no chart, no route, no translation and no built-in component, use voodoo.min.js. If it also has no validated form, no mask and no interface components, voodoo.core.min.js does the job. That is tens of kilobytes of difference per visit.

What the library already does for you

Measuring

V.config.devtools = true;   // warnings and named anchors
V.xray();                   // performance tab, with effects per element

On the inspector's performance tab, each element shows how many effects depend on it and how many times each of them reran. A high number in a small place is usually too much expression in a single block. To measure the size in your own project, npm run size in the repository measures the real files.