Skip to content

Dev tools and errors

Log levels, readable messages and the signy-dev tools.

Log ids

Signy logs and throws with short ids instead of sentences, so the framework stays small. A warning looks like this in the console:

[signy][WARN] view:unsafe-attribute { name: 'href' }

The id names the part of Signy and the problem. The object after it holds the details, here the attribute that was left out.

Turn warnings on

The default log level is error, so warnings and info do not show. Raise it while you develop:

import { globalConfig } from 'signy';

globalConfig.configure({ logLevel: 'warn' });

The levels, from quiet to loud, are 'error', 'warn', 'info', 'debug' and 'trace'. With the global build it is Signy.globalConfig.configure(...).

Some warnings, such as view:unsafe-attribute and view:lazy-outside-suspense, only exist in dev builds. Production builds still leave the unsafe attribute out, they just do not say so. The npm build is a production build when your bundler sets process.env.NODE_ENV to 'production', and the .min.js global builds are production builds.

Common ids

IdWhat happened
view:unsafe-attributeAn on attribute got a string, or a URL attribute got a javascript: URL. It was left out. See Security.
view:lazy-outside-suspenseA lazy() component rendered outside Suspense, so nothing shows while it loads.
reactivity:cleanup-unownedonCleanup() ran outside a component, effect or root. The cleanup never runs.
reactivity:cleanup-errorA cleanup function threw. The other cleanups still ran.
router:redirect-loopRedirects and guards kept redirecting past 10 steps. push() rejects with it.
router:loader-failedA route loader threw. The page shows without data.
app:invalid-stateAn app was mounted twice. Create a new one.
installable:already-installedThe same plugin was passed to use() twice.
component:app-requiredA component was mounted by hand. Mount the root with application(App).mount().

signy-dev

signy-dev is not on npm yet. It will be installed with npm install signy-dev --save-dev.

Importing signy-dev registers a sentence for each of the 44 log ids. Logs and errors then carry it after the id, and getLogMessage(id) returns it.

import 'signy-dev';
import { getLogMessage } from 'signy';

getLogMessage('view:lazy-outside-suspense');
// 'Lazy component rendered outside Suspense boundary. ...'

enableDevTools() also installs a profiler, an inspector and an event logger, and creates the global hook window.__SIGNY_DEVTOOLS_HOOK__ that browser extensions connect to.

import { enableDevTools } from 'signy-dev';

const devTools = enableDevTools({
  profiler: true,     // component renders, signal writes, effect runs
  inspector: true,    // mounted components and apps, signals, effects
  eventLogger: false, // a log of every reactive event
  globalHook: true,
});

console.log(devTools.profiler?.report());
console.log(devTools.inspector?.getComponents());

devTools.cleanup();

Load it only in development, so production bundles leave it out:

if (import.meta.env.DEV) {
  import('signy-dev').then(({ enableDevTools }) => enableDevTools());
}

The tools hold signals and effects weakly, so tracking never keeps one alive. A second enableDevTools() call replaces the first, so hot reload can run the setup again.

Names

Give names to what you want to find in the tools: new Signal(0, { name: 'count' }), computed(fn, { name: 'total' }), store(value, { name: 'cart' }), and static displayName = 'Cart' on a component, which keeps its name through a minifier.