Skip to content

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

Library authoring with allowDebugBuild

If you publish a Vite library that uses $debug internally, you want the published bundle to ship two parallel variants, so consumers get debug logs in dev without paying for them in production. The plugin handles both variants in a single build when you set allowDebugBuild: true:

ts
// B's vite.config.ts (the library)
import mtDebug from "@mars-tech/vite-debug-plugin";

export default defineConfig({
  plugins: [mtDebug({ allowDebugBuild: true })],
  build: {
    lib: { entry: "src/index.ts", formats: ["es", "cjs"] },
    rollupOptions: { external: ["vue", "pinia"] },
    sourcemap: true,
  },
});

Build output

After vite build the library's dist/ contains:

text
dist/
├── index.js          ← standard variant, every $debug call stripped → DCE'd
├── index.cjs
├── index.d.ts        ← single shared type file
├── index.js.map
├── index.cjs.map
└── debug/
    ├── index.js      ← debug variant, $debug calls preserved + fallback shim prepended
    ├── index.cjs
    ├── index.js.map
    └── index.cjs.map

Conditional exports

Wire conditional exports in the library's package.json so Vite picks the right variant automatically:

json
{
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "development": {
        "import": "./dist/debug/index.js",
        "require": "./dist/debug/index.cjs"
      },
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  }
}

If you enable allowDebugBuild but forget to add the development condition to your package.json exports, the plugin emits a build warning with the exact snippet to paste. Otherwise the debug variant is emitted but never resolved, leaving consumers without dev logs.

Three-tier behavior

TierStageWhat happens
A plugin (this package)—Provides the runtime and the dual-build transform.
B library (e.g. a Pinia plugin)vite buildallowDebugBuild: true → dist/index.* stripped, dist/debug/index.* keeps calls + auto-prepended fallback shim.
C end-user app with Avite (dev)Vite picks the development condition → debug variant resolved → A's browser runtime defines the real $debug first → the fallback shim leaves it alone → logs appear.
C end-user app with Avite buildVite picks the default condition → standard variant resolved → zero $debug references in C's bundle, regardless of what C does in its own source.
C end-user app without Avite (dev)Debug variant resolved → the fallback shim installs a no-op $debug and emits a one-time console.warn advising to install A.
C end-user app without Avite buildStandard variant resolved → already stripped → silent, no crash.

Fallback shim in 0.6

The 0.6 fallback shim installs a bare function with no methods. In an app that does not load the plugin, base calls ($debug(level, payload)) are safe no-ops. But $debug.ns(...), assert, time/timeEnd, count, group/groupEnd, enable, disable, setLevel and setPrefixFilter throw a TypeError. In library code, either use only base calls, or build with libraryPrefix: true: its shadow guards every method except setPrefixFilter. Fixed in 1.0.

allowDebugBuild removes the need for consumers to know anything about this plugin. Library authors don't write a fallback shim by hand: the plugin auto-prepends it to every debug-variant entry chunk.

Optional peer dependency

Declare the plugin in B's package.json as an optional peer, so consumers that don't want logs can still install the library:

json
{
  "peerDependencies": {
    "@mars-tech/vite-debug-plugin": ">=0.2.0"
  },
  "peerDependenciesMeta": {
    "@mars-tech/vite-debug-plugin": { "optional": true }
  }
}

Note

For libraries you can't rebuild with allowDebugBuild, the consuming app can strip a dependency's baked-in $debug calls at its own build with the stripInDeps option.

Branding library logs with libraryPrefix

By default a library's $debug calls render with the consuming app's prefix: the app's plugin instance installs the global $debug, and the library's bare calls inherit whatever prefix it was configured with. So a log from inside your library shows up as App Info …, giving the developer no signal that it came from your library.

Set libraryPrefix: true (alongside allowDebugBuild) to keep your library's logs branded with its own prefix wherever it's used:

ts
// library vite.config.ts
plugins: [
  mtDebug({ allowDebugBuild: true, libraryPrefix: true, prefix: "foo" }),
];

Now every library log renders with foo regardless of the consuming app's own prefix (here the app is configured with prefix: "bar"):

text
foo Info [cart/action] - cart.add(Widget ×2)      ← from the library
bar Info - user clicked checkout                  ← from the app

How it works: the debug variant gets a tiny module-local $debug shadow prepended to each chunk. It forwards to the app's real $debug but forces { prefix } as a last-write option, so the library's prefix cannot be overridden by the consumer or a per-call option. Your library code stays plain $debug(...): nothing to remember at each call site, nothing consumers can turn off. If the consuming app has no $debug at all, the shadow warns once and no-ops (and, unlike the 0.6 fallback shim, ns, assert and the helper methods are no-ops too; only setPrefixFilter is not guarded).

The branded forms are the base call, $debug.ns(...) and $debug.assert(...). Utility helpers (time, count, group) take no per-call options, so their output still shows the consumer's prefix.

To mute or isolate a branded library in the consuming app, see Prefix filtering.