Nostics
Guide

Production builds

Make diagnostics production-lean, drop catalog text from surviving codes and strip report-only calls.

A diagnostic catalog carries user-facing why/fix text. It's invaluable in development, but mostly dead weight in production, where you usually want two things:

  • the codes you throw to keep working, but without shipping all their catalog text;
  • report-only diagnostics (bare diagnostics.X() calls) to disappear entirely.

Both are plain source changes. The first needs no build tooling and matters most for a library. The second you can do by hand too, or automate with a plugin.

Keep surviving diagnostics lean

Diagnostics you throw, return, assign, or pass to a function are runtime behavior, so they stay in the bundle. Each one keeps the whole defineDiagnostics() catalog reachable, so every code's why/fix text ships too, even codes you never throw.

defineProdDiagnostics removes that text with no build tooling. Pick a lean catalog at definition time with a process.env.NODE_ENV ternary:

diagnostics.ts
import { createConsoleReporter, defineDiagnostics, defineProdDiagnostics } from 'nostics'

const docsBase = (code: string) => `https://mylib.dev/e/${code.toLowerCase()}`

export const diagnostics
  = process.env.NODE_ENV === 'production'
    ? /*#__PURE__*/ defineProdDiagnostics({ docsBase })
    : /*#__PURE__*/ defineDiagnostics({
        docsBase,
        reporters: [/*#__PURE__*/ createConsoleReporter()],
        codes: { /* all your why/fix text */ },
      })

This is plain JavaScript. Any bundler that replaces process.env.NODE_ENV (Vite, Rollup, webpack, esbuild) drops the defineDiagnostics({ codes }) branch (all the text) in a production build and tree-shakes the defineDiagnostics import out. Your consumers' build needs no setup, which makes this a good fit for a published library: their bundler does the work.

The /*#__PURE__*/ markers tell the bundler it can drop the whole diagnostics binding (and the reporter factory) when nothing references it. See Remove report-only diagnostics for the details.

defineProdDiagnostics returns a Proxy that answers for any code you access. The code becomes the instance name, docs is still derived from docsBase, and the message (why) points to the docs URL (empty when there is no docsBase, so the thrown header is just the code):

throw diagnostics.NUXT_B2011() // NUXT_B2011: https://docs.example.com/nuxt_b2011

So a thrown diagnostic keeps its code (as the name) and a working docs link, but no catalog text reaches the bundle.

By default the prod branch has no reporters, so a surviving throw won't also log through a reporter and resurface as an uncaught error. To keep reporting in production (say a createFetchReporter that phones home), pass reporters to defineProdDiagnostics too.

Remove report-only diagnostics

Report-only diagnostics are bare statements. They don't affect control flow, so production can drop them entirely:

diagnostics.NUXT_B2011()
condition && diagnostics.NUXT_B5001()

These stay, because they are behavior, not reports:

throw diagnostics.NUXT_B2011()
return diagnostics.NUXT_B2011()
const diagnostic = diagnostics.NUXT_B2011()
fn(diagnostics.NUXT_B2011())

To strip the report-only ones by hand, add two annotations in your source:

  1. Mark the catalog pure so an unused one tree-shakes: put /*#__PURE__*/ before defineDiagnostics( and before each reporter factory call inside it.
  2. Dev-guard every report-only call site.
const diagnostics = /*#__PURE__*/ defineDiagnostics({
  reporters: [/*#__PURE__*/ createConsoleReporter()],
  codes: { /* ... */ },
})

process.env.NODE_ENV !== 'production' && diagnostics.NUXT_B2011()

In a production build the guarded calls drop. Once they're gone, the catalog is unused and tree-shakes away. If you already applied the ternary above, the full defineDiagnostics() only lives in the dev branch, so the pure annotation doesn't matter there, but the dev guard still removes the bare call in production.

Automate stripping with nosticsStrip

Rather not annotate call sites by hand? The nosticsStrip build plugin adds the /*#__PURE__*/ annotations and NODE_ENV guards for you. It ships in the separate @nostics/unplugin package:

pnpm add -D @nostics/unplugin

A library bundled with tsdown uses the .rolldown() adapter. Keep nostics external so your published code keeps the conditional import for your consumers' bundler to resolve:

import { nosticsStrip } from '@nostics/unplugin/strip-transform'
import { defineConfig } from 'vite'

export default defineConfig({ plugins: [nosticsStrip.vite()] })

It is built with unplugin, so beyond .vite() and .rolldown() it also exposes .rollup(), .webpack(), .rspack(), .esbuild(), and .farm(). It can follow a diagnostics object across one relative import. For that to work, use relative imports, export the defineDiagnostics() result (or the prod/dev ternary above) directly, and avoid factory wrappers or deep barrel re-exports.

OptionDefaultUse
packageName'nostics'Detect imports from another package name

The plugin only automates report-only stripping. Pair it with defineProdDiagnostics when you also want surviving diagnostics lean.

Verify the output

After a production build, search the bundle:

rg 'NUXT_B2011|Plugin mode conflicts' dist/

Report-only diagnostics should be gone, and surviving diagnostics should show only their code, not the why/fix text.

Copyright © 2026