Extending and debugging

The reactivity inspector

xray runs inside the page itself, with no browser extension. Switched on, it outlines every element that has directives, shows the scope of each one and flashes the element every time a reactive effect writes into it. You can watch the reactivity happening.

voodoo.full.min.js only

The inspector exists only in the full build.

Switching it on

V.xray();        // on and off
V.xray(true);    // force it on
V.xray(false);   // force it off

The shortcut Ctrl + Shift + X is installed on the first call. To have the shortcut available from the start, without switching anything on:

V.enableXrayShortcut();

What it shows

TabWhat it brings
StateEvery scope on the page, with the variables visible in each. Simple values are editable right there
ComponentsMounted instances, with props, state and the host element
StoresThe global stores and the contents of each
EventsEvents fired by directives, with the element they came from
NetworkRequests, with method, URL, status and duration
PerformanceThe count of effects per element and how many times each one has re-run

Clicking an element on the page selects the matching scope. Clicking an element in the panel highlights it and scrolls the page to it.

How to read the performance tab

The count adds up, for each element, the effects created by its own directives plus the effects of the interpolated texts that are its direct children. A high number on a small element usually means too much interpolation in one place, and it is worth breaking the block up — each stretch then re-runs on its own, instead of all of them together.

The cost in production

The module registers nothing when it is imported: no listener, no style and no timer exists before the first call. Even so, in production the safer road is to serve voodoo.min.js, which does not carry the inspector.

Debugging without the inspector

Detailed warnings

V.config.devtools = true;

With this option on, the anchor comments created by v-if and v-for get names, which makes the tree far more readable in the browser inspector. Unregistered components start warning in the console as well. The same thing exists as an attribute on the script tag: data-devtools.

The global error handler

V.onError((err, context) => {
  console.error('[app]', context, err);
  sendToMonitoring(err, context);
});

The context says where it came from: directive v-click, expression "...", hook mounted, request GET /api/x, event click ("save()") and so on. It is the hook for shipping production errors to a monitoring service.

Inspecting from the console

V.scope.data;                                  // the root scope
V.getScope(document.querySelector('#list'));   // the element's scope, if it created one
V.findScope(document.querySelector('li'));     // the effective scope, walking up the ancestors
V.instances;                                   // a Set of the mounted components
V.components;                                  // a Map of the registered definitions
V.directives;                                  // a Map of the registered directives
V.magics;                                      // a Map of the magic variables
V.stores;                                      // every store

Logging from the HTML

<div v-data="{ n: 0 }">
  <button @click="n++; $log('n is now', n)">Add and log</button>
  <p>Open the browser console to see the messages.</p>
</div>

$log writes to the console with the prefix [Voodoo].

Forcing and stopping the processing

V.start();                     // walks and initializes from the body
V.start(document.querySelector('#area'));
V.walk(element, scope);        // initializes a stretch with a specific scope
V.refresh(element);            // reinitializes a root
V.destroy(element);            // unmounts, stopping effects and removing listeners
V.stopObserving();             // switches the MutationObserver off

The devtools event bus

Any module — yours included — can report activity to the panel. The Network tab lists everything that arrives through network, even when the request did not go through the V.http client.

V.devtools.emit('network', {
  method: 'GET',
  url: '/api/users',
  status: 200,
  ok: true,
  duration: 128,
  source: 'my-plugin'
});

const off = V.devtools.on('network', (event) => console.log(event.url));
off();

The types are network, event, navigation, locale and update. Emitting with no listener registered costs one Map lookup and nothing else, so reporting is cheap.