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:
- the proxy's
setcompares the new value with the old one, and stops right there if they are equal; - the effects that read the
namekey are put in a queue; - the queue is processed in a microtask, with each effect running once only;
- 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
| File | Raw | gzip | brotli |
|---|---|---|---|
voodoo.core.min.js | 123 KB | 42 KB | 37 KB |
voodoo.min.js | 245 KB | 79 KB | 67 KB |
voodoo.full.min.js | 410 KB | 124 KB | 103 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
- Segmented interpolation. A text node with three expressions becomes a single effect that recomposes that node alone.
- Reordering by cursor.
v-formoves the existing blocks instead of recreating them. - Parser cache. Each expression is parsed once, and the tree is kept.
- CSS on demand. The style of an interface component only enters the document when that feature is used.
- Cancelled requests. A new request from the same element aborts the previous one.
- A single animation loop. Every active animation shares the same
requestAnimationFrame. - Automatic cleanup. Removing an element stops its effects, removes the listeners and shuts the observers down. Nothing leaks because you forgot.
- Cheap chart redraws. The SVG is generated as text and handed over in one go.
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.