Skip to content

You are reading the documentation for version 0.6, which is not the latest release.

How it works

@mars-tech/vite-debug-plugin is a Vite plugin that exposes a global $debug function during development. Each call renders simultaneously in the browser console and the Vite dev server terminal, with consistent formatting and colors per severity. All $debug calls are stripped from production builds, including their argument expressions.

Works across any Vite-powered framework: Vue 3.5, React, Angular (via Analog), Nuxt 4, SvelteKit, Astro server, Vite SSR, etc. Both client and server-rendered (SSR) code paths are supported. See the framework recipes for call sites per stack, and Server-side rendering (SSR) for the server side.

See it in action

Each line starts with MARS-TECH, the default prefix. Change it with the prefix option.

How the plugin works, in 3 steps: the same $debug() calls printed in the browser console and in the Vite terminal.

Start the dev server. Start the dev server, open the app and press +1 twice. Every $debug call, first while the app boots and then in the Pinia store action, prints the same line in the browser console and in the Vite terminal: level, tag, message and a source location. In 0.6, a call made in the browser reports the plugin’s own runtime module as its location instead of the call site; version 1.0 fixes this.

Browser address:localhost:4312/
Demo Vue app, Home page: the header with its three navigation links, a banner inviting you to open the browser console and the Vite terminal, then the Pinia counter card showing count: 2 after two clicks on +1.
Browser console — The browser console shows the same lines as the terminal.
Terminal — vite dev
$ npm run dev -- --port 4312 --strictPort
MARS-TECH Info [main] - bootstrapping Vue app (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
MARS-TECH Trace [component/App] - App.vue setup (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
MARS-TECH Trace [router/beforeEach] - nav: / → / (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
MARS-TECH Trace [shell/mounted] - PageShell mounted (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
MARS-TECH Info [SomeButton/mounted] - SomeButton mounted with variant=primary (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
MARS-TECH Trace [shell/mounted] - PageShell mounted (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
MARS-TECH Trace [shell/mounted] - PageShell mounted (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
MARS-TECH Success [view/Home] - Home mounted (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
MARS-TECH Info [store/counter] - counter incremented to 1 (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
MARS-TECH Info [store/counter] - counter incremented to 2 (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)

Navigate to another page. Open the About page and press Fire delayed (500 ms). Lifecycle hooks, a method called after a timer and a watcher all log through $debug.

Browser address:localhost:4312/about
Demo Vue app, About page: an Options API showcase card listing lifecycle hooks, a watcher, an async method and a computed label; after a click on Fire delayed (500 ms), the status reads fired ×1.
Browser console — The browser console shows the same lines as the terminal.
Terminal — vite dev
MARS-TECH Trace [router/beforeEach] - nav: / → /about (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
MARS-TECH Trace [view/About/lifecycle] - About created (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
MARS-TECH Trace [shell/mounted] - PageShell mounted (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
MARS-TECH Success [view/About/lifecycle] - About mounted (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
MARS-TECH Info [view/About/method] - delayed fire #1 (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
MARS-TECH Debug [view/About/watch] - delayedCount: 0 → 1 (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)

Hit an error. Open the Sandbox page and press Throw + log. Leaving About logs its beforeUnmount warning, and the caught error is logged with its stack trace.

Browser address:localhost:4312/sandbox
Demo Vue app, Sandbox page: a router and error paths card with Throw + log and Navigate to /missing buttons; below them, last error: inline throw inside Sandbox handler.
Browser console — The browser console shows the same lines as the terminal.
Terminal — vite dev
MARS-TECH Trace [router/beforeEach] - nav: /about → /sandbox (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
MARS-TECH Warning [view/About/lifecycle] - About beforeUnmount (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
MARS-TECH Trace [shell/mounted] - PageShell mounted (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
MARS-TECH Trace [view/Sandbox] - Sandbox mounted (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
MARS-TECH Error [view/Sandbox/handler] - inline throw inside Sandbox handler (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
  Error: inline throw inside Sandbox handler
      at throwInline (http://localhost:4312/src/views/Sandbox.vue:38:11)
      at callWithErrorHandling (http://localhost:4312/node_modules/.vite/deps/vue.runtime.esm-bundler-BbnC8aGa.js?v=ad0296fb:1889:17)
      at callWithAsyncErrorHandling (http://localhost:4312/node_modules/.vite/deps/vue.runtime.esm-bundler-BbnC8aGa.js?v=ad0296fb:1896:15)
      at HTMLButtonElement.invoker (http://localhost:4312/node_modules/.vite/deps/vue.runtime.esm-bundler-BbnC8aGa.js?v=ad0296fb:7677:10)
Text version of this animation

The browser console and the Vite terminal print the same lines. In the browser the level is coloured and the source location is in italics; in the terminal the source location is dimmed. A stack trace follows its line in the terminal, and sits in a collapsed “stack” group in the console.

  1. Step 1 of 3 — Start the dev server. Start the dev server, open the app and press +1 twice. Every $debug call, first while the app boots and then in the Pinia store action, prints the same line in the browser console and in the Vite terminal: level, tag, message and a source location. In 0.6, a call made in the browser reports the plugin’s own runtime module as its location instead of the call site; version 1.0 fixes this.

    Output:

    $ npm run dev -- --port 4312 --strictPort
    MARS-TECH Info [main] - bootstrapping Vue app (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
    MARS-TECH Trace [component/App] - App.vue setup (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
    MARS-TECH Trace [router/beforeEach] - nav: / → / (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
    MARS-TECH Trace [shell/mounted] - PageShell mounted (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
    MARS-TECH Info [SomeButton/mounted] - SomeButton mounted with variant=primary (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
    MARS-TECH Trace [shell/mounted] - PageShell mounted (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
    MARS-TECH Trace [shell/mounted] - PageShell mounted (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
    MARS-TECH Success [view/Home] - Home mounted (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
    MARS-TECH Info [store/counter] - counter incremented to 1 (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
    MARS-TECH Info [store/counter] - counter incremented to 2 (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
  2. Step 2 of 3 — Navigate to another page. Open the About page and press Fire delayed (500 ms). Lifecycle hooks, a method called after a timer and a watcher all log through $debug.

    Output:

    MARS-TECH Trace [router/beforeEach] - nav: / → /about (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
    MARS-TECH Trace [view/About/lifecycle] - About created (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
    MARS-TECH Trace [shell/mounted] - PageShell mounted (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
    MARS-TECH Success [view/About/lifecycle] - About mounted (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
    MARS-TECH Info [view/About/method] - delayed fire #1 (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
    MARS-TECH Debug [view/About/watch] - delayedCount: 0 → 1 (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
  3. Step 3 of 3 — Hit an error. Open the Sandbox page and press Throw + log. Leaving About logs its beforeUnmount warning, and the caught error is logged with its stack trace.

    Output:

    MARS-TECH Trace [router/beforeEach] - nav: /about → /sandbox (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
    MARS-TECH Warning [view/About/lifecycle] - About beforeUnmount (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
    MARS-TECH Trace [shell/mounted] - PageShell mounted (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
    MARS-TECH Trace [view/Sandbox] - Sandbox mounted (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
    MARS-TECH Error [view/Sandbox/handler] - inline throw inside Sandbox handler (http://localhost:4312/@mars-tech-vite-debug/runtime:201:3)
      Error: inline throw inside Sandbox handler
          at throwInline (http://localhost:4312/src/views/Sandbox.vue:38:11)
          at callWithErrorHandling (http://localhost:4312/node_modules/.vite/deps/vue.runtime.esm-bundler-BbnC8aGa.js?v=ad0296fb:1889:17)
          at callWithAsyncErrorHandling (http://localhost:4312/node_modules/.vite/deps/vue.runtime.esm-bundler-BbnC8aGa.js?v=ad0296fb:1896:15)
          at HTMLButtonElement.invoker (http://localhost:4312/node_modules/.vite/deps/vue.runtime.esm-bundler-BbnC8aGa.js?v=ad0296fb:7677:10)

Production behavior

In vite build (client and SSR), every call to the configured global identifier (default $debug, set by the globalName option) is replaced with void 0. The argument expressions are not evaluated.

ts
// before
$debug("debug", expensiveCompute());

// after build
void 0;

This means it is safe to put cost in the arguments — no production runtime impact and no SSR runtime ships.

Method and chained forms strip too: $debug.time(...) → void 0, and an inline $debug.ns('x')('info') collapses to void 0 (the whole chain, so it never degrades to void 0(...)). A scoped-logger factory ($debug.ns('x')) strips to a callable no-op (() => {}) instead of void 0, so a logger stored in a variable (const log = $debug.ns('x')) stays safe to call after the strip — and the no-op is then tree-shaken away.

Publishing a library that calls $debug? See Library authoring: allowDebugBuild emits a stripped variant and a debug variant in the same build.