-
Notifications
You must be signed in to change notification settings - Fork 0
Stripping checks
By default check() is inert in production — the cost is a single global-property read and an early return (see API → check). For most apps that is already cheap enough to ship as-is.
When you want invariants gone entirely from a release bundle — zero bytes, zero calls — check() is designed to be removable by a builder. The call signature is static and recognizable (a plain named/default import, no dynamic dispatch), so a bundler can drop it.
When not to strip: if you call
setAbsentBehavior(throwOnFail)(or any enforcing behavior) you want invariants live in production. Stripping removes them. Strip only when the production behavior is inert and you want the footprint gone too.
There are two approaches. The alias approach is simpler and rename-proof; the AST approach removes the call sites themselves.
Point your bundler at a stub module that exports the same surface as no-ops. Every check(...) becomes a call to () => {}, which a minifier's dead-code elimination then removes along with any argument expressions it can prove side-effect-free.
Create the stub once, e.g. build/invariant-noop.js:
export const hasHost = false;
export const setAbsentBehavior = () => {};
export const throwOnFail = () => {};
export const warnOnFail = () => {};
export class InvariantError extends Error {}
const check = () => {};
export default check;
export {check};Then alias tape-six-invariant to it in your bundler.
esbuild
import {build} from 'esbuild';
await build({
// …
alias: {'tape-six-invariant': './build/invariant-noop.js'}
});Rollup (@rollup/plugin-alias)
import alias from '@rollup/plugin-alias';
export default {
// …
plugins: [
alias({entries: [{find: 'tape-six-invariant', replacement: './build/invariant-noop.js'}]})
]
};webpack
module.exports = {
// …
resolve: {
alias: {'tape-six-invariant': require.resolve('./build/invariant-noop.js')}
}
};Vite
export default {
resolve: {
alias: {'tape-six-invariant': '/build/invariant-noop.js'}
}
};Because this replaces the module, it is unaffected by how callers import check — default or named, renamed or not (import c from 'tape-six-invariant' still resolves to the stub).
Gate the alias on your production condition (e.g. only when NODE_ENV === 'production') so development and test builds keep the real library.
To delete check(...) expressions outright, use an unassert-style transform. babel-plugin-unassert targets node:assert, so for check write a small Babel/SWC plugin (or a codemod) that drops CallExpressions whose callee resolves to the check binding imported from tape-six-invariant.
Track the binding, not the name — with the default export a caller may rename it:
import verify from 'tape-six-invariant';
verify(x > 0, 'positive'); // the transform must follow `verify` back to the importA binding-aware transform handles both the default and named import and any local alias. This is more work than the alias approach but removes the calls (and their arguments) from the source rather than relying on the minifier to DCE a no-op.
-
API → check — the
hasHostgate for skipping expensive pre-check computation (a runtime complement to stripping). - The project's
ARCHITECTURE.md§ Design boundaries — whycheckstays statically matchable.