Skip to Content

Strip debug code

@ttsc/strip removes configured function calls and debugger statements from emitted code.

Install

npm install -D @ttsc/strip

@ttsc/strip ships the ttsc.plugin auto-discovery marker in its package.json, so the install alone activates it, no compilerOptions.plugins[] entry needed: the default patterns below start disappearing from your emit on the next build. The explicit entry is equivalent and takes precedence; to turn a discovered plugin off without uninstalling it, declare the entry with enabled: false (see Plugin protocol).

// tsconfig.json { "compilerOptions": { "plugins": [{ "transform": "@ttsc/strip" }] } }

With no extra configuration, the defaults are:

  • console.log
  • console.debug
  • assert.*
  • debugger

Custom patterns

Drop a strip.config.ts next to your tsconfig.json:

// strip.config.ts import type { ITtscStripConfig } from "@ttsc/strip"; export default { calls: ["logger.debug", "metrics.devOnly", "assert.*"], statements: ["debugger"], } satisfies ITtscStripConfig;
  • calls: statement-level calls. Supports a .* wildcard at the end (e.g. assert.* matches assert.equal, assert.deepStrictEqual, …).
  • statements: bare statements. Currently supports debugger.

The config is a Node module and runs under the module format Node would give it: an explicit .cts/.mts extension decides on its own, and an ambiguous .ts or .js config follows the nearest package.json "type". So __dirname works in a CommonJS package and import.meta.dirname in a "type": "module" package, and Node’s globals need no /// <reference types="node" /> directive as long as @types/node is installed beside the config.

When the system temporary directory contains the TypeScript config directory, including through a link, the evaluator can use the already opened ttsc cache’s identities directory to avoid changing the config directory. On Windows, it also uses identities when TEMP is on another drive, provided the cache is on the config path’s drive. An independent temporary directory keeps its existing placement. If identities is unavailable, the evaluator uses its existing TEMP or nearest node_modules/.cache or config-directory fallback and removes its temporary files when it finishes.

@ttsc/strip discovers its config by walking upward from the tsconfig directory, looking for strip.config.{ts,cts,mts,js,cjs,mjs,json}. When a build integration compiles through a generated tsconfig outside the project (the bundler adapters do this whenever an alias or a compilerOptions overlay is set), the integration declares the real project root (pluginConfigDir on TtscCompiler) and the walk starts there instead of the temp directory. To point at a specific file, set configFile on the tsconfig entry (a relative path resolves against the same base directory):

// tsconfig.json { "compilerOptions": { "plugins": [ { "transform": "@ttsc/strip", "configFile": "./config/strip.config.ts" } ] } }

What “statement-level” means

The plugin only strips calls that are the entire statement:

console.log("debug"); // ← removed const result = compute(); console.log(result); // ← removed const data = console.log("x"); // ← NOT removed (the call is an expression)

Calls used within expressions are retained because removing them could change the expression’s value.

Use cases

  • Production builds: strip console.log and debugger, keep console.warn and console.error.
  • Test code in published artifacts: strip assert.* from the package emit if your library uses node:assert internally.
  • Feature-flag tooling: wrap dev-only code in metrics.devOnly(...) calls that get stripped in prod.

When not to use this

If you want a real minifier (dead-code elimination, mangling, …), use Terser or esbuild via @ttsc/unplugin. @ttsc/strip is the targeted “remove these specific patterns” pass.

See also

Last updated on