Distribution

Should you obfuscate an npm package you publish?

Usually not — and the reason is not licensing or etiquette, it is that a published package is an input to someone else's build. Protection is designed to be the last step before shipping, and a package on npm is nowhere near the last step. That mismatch is where the costs come from. There are cases where it is still right, so here is how to tell them apart.

The structural problem

When you ship a web app, protection runs after bundling and minification, and nothing downstream needs to understand the output — the build order works out. When you publish a package, your consumer's bundler is downstream of you, and it needs to understand your code to do its job.

So you have inverted the pipeline: the step whose purpose is to destroy analysability now runs before several steps that depend on analysability. Everything below follows from that one fact.

What it costs

  • Tree shaking stops working for your consumers. This is the big one. A bundler drops your unused exports by tracing references; once member access is a computed lookup, it cannot prove anything is unused and keeps all of it. Someone who imports one helper from your package ships your whole package. For a library sold on being lightweight, that is a product regression.
  • Your sideEffects declaration stops being trustworthy. Protection rewrites the expressions that /*#__PURE__*/ annotations were attached to, so the metadata your consumers' bundlers rely on no longer matches the code.
  • Debugging gets worse for the person paying you. When your package throws inside their app, they see generated names in a file they cannot read. Every such incident becomes a support ticket for you, because they cannot self-serve. You have moved your own debugging cost onto your support queue.
  • Double protection. If your consumer also obfuscates their bundle, your already-protected code goes through it again — slower, larger, and occasionally broken, with a failure that is nobody's obvious responsibility.
  • Trust friction. Reviewers, security teams, and licence scanners treat unreadable code in a dependency as a finding. Some organisations forbid it outright. Obfuscated code in a dependency is also the shape a supply-chain attack takes, which means you will be asked about it — and it contributes to the same pattern-matching problem described in why obfuscated JavaScript trips antivirus.
  • It buys less than you think. A package on a public registry is downloadable by anyone with or without protection. You are raising the cost of reading, not restricting distribution — and against a competitor motivated enough to care, the debugger works as well on your package as on anything else (can DevTools deobfuscate your JavaScript?).

When it is genuinely the right call

Several real cases survive all of that:

  • A private, paid SDK on a private registry. Your consumers are licensees, not the public. Bundle size is a known quantity, support is a contract, and the code embodies something you sell.
  • A licence-enforcement or entitlement client. If the package's job is to check a licence, leaving that check readable defeats its purpose. Protect the check specifically, not the whole package — and read protecting a JavaScript licence check first, because a client-side check has limits no obfuscator changes.
  • An embedded widget shipped as a single pre-built file. A payment or chat widget consumers include as a script tag, not an import, is already terminal in the pipeline. Tree shaking was never going to apply. This is the case where the costs mostly evaporate.
  • A proprietary algorithm that is the product. A pricing model, a fraud rule, a codec. Here the calculus flips: the code is the asset, and a bigger bundle is an acceptable price.

Notice what those have in common — either the consumer relationship is commercial rather than public, or the artifact is already a final bundle rather than a build input.

If you go ahead, do these three things

  • Protect narrowly. Do not run maximum protection over the package. Protect the module that holds the asset and leave the rest — the public API surface, the type definitions, the glue — readable, so tree shaking and debugging keep working for everything else. Inline directives mark exactly which regions to protect; named configuration sets scope it by path.
  • Keep the public surface stable and documented. Every exported name, every option key, and every field on an object you accept or return is a contract. Put them in your exclusion list explicitly rather than relying on the default — the reasoning is in Protect Members, and export names specifically are covered in obfuscating ES modules.
  • Say so in your README, and keep the map. Tell consumers which parts are protected and why; it converts a discovered surprise into a documented decision. Then retain the identifier map for every published version so you can demangle the stack traces they send you — see symbolication. Without it, supporting a protected package is guesswork.

Also ship both formats. Publish an unprotected ESM build for bundler consumers alongside a protected UMD or IIFE build for script-tag consumers, and point module and main at the right ones. Most vendors who think carefully about this land here: protection on the artifact that is already final, none on the artifact that is meant to be built further.

The alternative worth considering first

If the concern is that a competitor will read your library and copy it, obfuscation addresses the symptom. Two other approaches address the cause more directly:

  • Move the asset server-side. If the valuable logic runs in your API, the package becomes a thin client and there is nothing in it worth protecting. This is the only approach that actually keeps something secret, for the reason in you cannot hide an API key in JavaScript.
  • Use licensing rather than obscurity. A clear commercial licence gives you legal recourse that obfuscation never provides, and it is what most successful commercial JavaScript libraries actually rely on — see is JavaScript obfuscation legal for how the two interact.

Obfuscation composes with both. It is a poor substitute for either.

The short version

A public, general-purpose library: do not obfuscate. You break tree shaking for every consumer, degrade their debugging, invite security questions, and gain little. A private SDK, an embedded widget, a licence client, or a module that is the product: protect that specific part, keep the public surface excluded and documented, keep the identifier maps, and ship an unprotected build for people who need to bundle you.

Related reading