Dart · Kotlin/JS · ClojureScript · Scala.js

Your source language is not JavaScript. The thing you ship still is.

Every framework guide on this site assumes you wrote the JavaScript. If a compiler wrote it, the protector still works — it reads what your toolchain emitted and knows nothing about what produced it. Four things genuinely change, though, and the second one is how compiled applications usually break: your compiler has already renamed most of it, its generated runtime dispatches on names that renaming will destroy, part of your payload may not be JavaScript at all, and your source maps expose something far more valuable than the intermediate output.

The Short Version

Overlap · Dispatch · Maps

Three findings decide whether this is worth doing and whether it will work.

Renaming overlaps, the rest does notStrings, control flow and dispatch tables survive your compiler untouched.
Name-based dispatch is the breakageReflection and interop declarations compile to lookups by string.
Your map reconstructs your real languageNot the intermediate JavaScript. Never publish it.
Why This Page Exists

A protector reads JavaScript. It does not care who wrote it.

Teams compiling from another language ask a compatibility question first, and it has a dull answer: yes, the tool parses the output like any other JavaScript program and emits an equivalent one. The interesting question is the one underneath it. A compiler has already transformed your code once, with goals that partially overlap the ones a protector has, so the honest subject of this page is marginal benefit — what is genuinely still exposed after your toolchain has finished, what a second transformation adds, and where the two interact badly enough to break a working application.

Compilers optimise for size

Shorter names, dead code removal, inlining. Those goals produce output that is unpleasant to read as a side effect, which is not the same thing as output designed to resist being read. The difference shows up precisely where size and comprehension diverge.

Protectors optimise for cost of understanding

Removing the navigational aids a reader relies on, destroying the correspondence between the shape of the code and the shape of the original program, and making findings from one build fail to transfer to the next. Size is a cost of that work, not its goal.

The overlap is narrower than it looks

Identifier renaming is the one place both tools do similar work. Everything else a protector does is untouched territory after a compilation pass, which is why the marginal benefit is real even when the output already looks unreadable.

What Survives Compilation

Open your own compiled bundle and search it for these four things

This is the exercise worth doing before any purchasing decision, and it takes about ten minutes. Load your production bundle in an editor and look for each of the following. Whatever you find is what a reader finds, and it is the honest measure of what a second transformation would add.

Your string literals, almost all of them

Error messages, endpoint paths, feature keys, field names, log text, regular expressions. Minification barely touches strings because shortening them changes behaviour. They are the fastest route into unfamiliar code, and the string transformations are the option with the least overlap with what your compiler did.

Your control flow, entirely intact

A compiler rearranges and inlines, but the resulting program still has the branches, loops and call structure of what you wrote. An analyst reading it can follow the algorithm. Control flow flattening and code transposition are aimed exactly here, and no compiler performs them because they make output slower and larger.

Names your language needed to keep

Anything exported for interoperability, anything serialised by field name, anything reached through reflection or dynamic dispatch. The compiler preserved these deliberately because the program breaks without them, which means they are readable and they describe your domain model.

The runtime's own structure

Compiled languages ship a runtime: class hierarchies, method tables, type descriptors, exception machinery. It is boilerplate that an experienced analyst recognises instantly, and its shape tells them which toolchain you used, which in turn tells them how to read everything else.

The Breakage

Name-based dispatch is where compiled builds fail, and it fails at run time

There is one failure mode that accounts for most bad first experiences on this path, and it has the same shape in every language. The compiler emitted a lookup that finds a property by name, using a string it deliberately preserved. Member renaming rewrites the property but not the string. Nothing fails during the build, nothing fails when the page loads, and then one code path throws or silently returns nothing.

Where the lookups come from

Reflection and runtime type information. Serialisation that maps object fields to JSON keys. Dependency injection resolving by name. Interoperability declarations that promise an external caller a specific property. Event or message dispatch keyed by string. Every one of these compiles into a property access the protector treats as ordinary.

Why the build stays green

Because renaming a property and renaming a string are different operations, and only one of them happened. The two halves of the relationship are no longer connected by anything a tool can see. This is the same class of defect that attribute-driven markup produces, arriving from a completely different direction.

The mechanism that prevents it

The reserved names list. It accepts regular expressions for names to preserve and maps onto the variable exclusion list the engine already maintains, so a pattern covering your exported surface is one entry rather than hundreds. The exclusion reference covers the syntax and the member protection page covers what renaming touches.

Where to get the list from

You very likely already have most of it. A toolchain that runs an aggressive optimiser needs the same information for the same reason and keeps it in an externs or exports declaration. Start from that file, add anything your own code serialises by name, and keep the two lists reviewed together so they cannot drift apart.

Literals that must stay verbatim

The mirror image of the same problem. Where your code compares against a literal that a template, a configuration file or a server response also names, the reserved strings mechanism keeps matching literals out of the string transformations. It accepts up to a hundred patterns, which is a fair signal that it is meant for a short deliberate list.

How to find it before your users do

Run your existing end-to-end suite against the compiled and protected artifact, not against your source. That single change catches nearly all of this class, because a run-time lookup failure shows up the moment the code path executes. The testing article covers building that stage into a pipeline.

By Toolchain

What differs between the common source languages

The principles above are shared. These are the specifics that change the configuration, and the flag names in your own toolchain move between releases, so treat these as the shape of the question rather than as a copyable command line.

Dart and Flutter Web

Compiled Dart output is minified and largely name-free already, so renaming adds least here and string and control flow work adds most. The important boundary is the renderer: a build that ships a compiled graphics module is shipping something that is not JavaScript, and it passes through the protection step untouched. Your application logic is protected; that module is not.

Kotlin/JS

The export surface is explicit, which is unusually convenient: anything annotated for JavaScript consumption is by definition a name an external caller depends on, and that is exactly your reserved names list. Derive it from the annotations rather than maintaining it by hand, and the list stays correct as the codebase changes.

ClojureScript

An advanced optimisation build already renames properties aggressively and already requires an externs declaration to survive doing so. That declaration is the closest thing to a ready-made exclusion list you will find in any of these ecosystems. The overlap with renaming is the highest here, so the case for protection rests on the string and control flow transformations.

Scala.js

A fully optimised build runs the output through the same class of aggressive optimiser, with the same consequences: strong existing renaming, an existing declaration of what must be preserved, and untouched control flow. Treat exported members and anything reached reflectively as reserved, and expect renaming to be the transformation that earns least.

Purely functional compilers

Output from languages with no reflection and no dynamic dispatch is the easiest case on this page, because the failure mode above mostly cannot arise. The reserved list shrinks to the module's public exports and interoperability shims, and the rest of the build behaves like ordinary JavaScript.

Compiled native targets in general

The rule that covers all of them: this product transforms JavaScript and nothing else. Assemblies, bytecode files and compiled modules in other formats are separate assets. The .NET guide makes the same point about a browser build that ships intermediate language, and the WebAssembly article covers what moving code into another format does and does not buy.

Maps And Debugging

You now have three representations, and the map only reverses one hop

This is the operational cost of the whole approach and the part most worth planning before the first protected release. An ordinary JavaScript team has two representations of their program and one map between them. A compiled team that also protects has three, and the maps do not compose automatically.

The source map is worse to leak than usual

A map from a compiler reconstructs your original language, not the intermediate JavaScript — your real modules, your real names, your real structure. Publishing it next to a protected bundle hands over something considerably better than the file you protected. The source map article covers how routinely this ships by accident.

Reversing a production stack trace takes two steps

The identifier map from the protection step returns you to the compiler's output. Your compiler's own map takes you from there to your source. Keep both artifacts from the same build, stored together and retained for as long as you support that release. Symbolication covers the first hop in detail.

Rehearse it before you need it

Take a real error from a production build, run it through both steps, and confirm you arrive at a line of your actual source. Teams discover a missing or mismatched map during an incident otherwise, which is the worst possible moment. A fixed seed makes builds reproducible, which is what lets you regenerate a map you failed to keep.

Frequently Asked

Compiled-to-JavaScript protection, answered

Does the protector work on output from a compiler rather than hand-written source?

Yes, and it does not know the difference. The tool parses JavaScript and emits JavaScript, so whatever produced the input is irrelevant to it. What changes is not whether it works but what it adds, because a compiler has usually already done part of the job. The right question is therefore not compatibility, it is marginal benefit, and the answer depends on which of your remaining artifacts still carry meaning: the strings, the structure, the dispatch tables and the parts of the payload that were never JavaScript.

If my compiler already minifies and renames, what is left to protect?

More than the file size suggests. A compiler renames to make output small, which is a different goal from making it hard to follow, and the two diverge in three places. String literals survive minification almost entirely, and they are how a reader navigates unfamiliar code. Control flow survives completely, so the shape of your algorithms is intact. And the generated runtime keeps readable metadata wherever the language needs reflection or dispatch. Renaming is the transformation with the most overlap; string handling, control flow flattening and virtualised bytecode have almost none.

What is the most common way this breaks a compiled application?

Member renaming meeting a generated runtime that dispatches on names. Languages that offer reflection, dynamic dispatch, serialisation by field name or interoperability declarations compile into JavaScript that looks up properties using strings the compiler preserved deliberately. Rename those properties and the lookup fails at run time rather than at build time, which is the expensive kind of failure. The mechanism to prevent it is the reserved names list, which accepts regular expressions for names to preserve and maps onto the variable exclusion list the engine already uses.

Is a WebAssembly component in my build protected too?

No, and it is worth being direct about that boundary. A JavaScript protector transforms JavaScript. A compiled module in another format is a separate asset that your page fetches, and it passes through untouched. The bundler plugins make this concrete: they filter emitted assets down to JavaScript files by default, so a module in another format is never even a candidate for the protection step. If your renderer or your numeric core ships as a compiled module, the honest position is that the JavaScript around it is protected and the module is not.

Do source maps from these toolchains create a bigger problem than usual?

Considerably bigger, because of what they contain. A source map from an ordinary JavaScript build reconstructs your JavaScript. A source map from a compiler reconstructs your original language, which is the actual codebase your team works in, with its real structure and names. Publishing one alongside a protected bundle undoes the entire exercise and hands over something better than the intermediate output. Keep maps out of the public deployment and upload them to your error reporting service through a private channel instead.

How much harder does this make debugging a production problem?

There is one more layer than teams expect, and planning for it is the whole answer. Ordinarily you have two representations, your source and the shipped bundle, and a symbol map connects them. Here you have three: your original language, the compiler output, and the protected output. The identifier map from the protection step reverses the last hop only, returning you to the compiler output rather than to your source. Getting back to your source needs the compiler's own map applied afterwards, so keep both artifacts from the same build and rehearse the two-step before you need it.

Does protection change which browsers my compiled output runs in?

No, because the engine does not translate between language levels. The one setting called target selects a runtime profile rather than a language version, there is no downlevelling or output-version option anywhere in the configuration, and modern syntax in the input is preserved rather than rewritten into an older form. Whatever your compiler decided to emit is what ships. That is convenient here, since compiler output tends to be conservative already, but it also means the protection step is not a safety net: if your toolchain emits syntax an old browser rejects, protecting the file leaves that exactly as it was.

What is the shortest sensible configuration for a compiled build?

Five decisions. Protect the compiler's final optimised output as the last step before deployment, never the intermediate files. Turn on the transformations your compiler did not do, which means string handling and control flow work rather than another pass of renaming. Build a reserved names list from everything your language exports, serialises by name, or reaches through interoperability declarations, and keep it in version control next to the compiler's own externs. Keep both symbol maps from every release. And test the compiled and protected artifact end to end, because the source you can read is now two transformations away from the code that runs.

Start Now

Read your own compiled bundle first

Before configuring anything, open the production build your compiler already produces and search it for endpoint paths, error messages and the names of your domain objects. What you find in five minutes is what an analyst finds in five minutes, and it tells you which transformations are worth turning on far better than any feature list. Then protect that same artifact, run your end-to-end suite against the result, and compare.

Related Guides

Protecting other JavaScript targets

Does obfuscation break WebAssembly interop? · .NET web applications · ISO 21434 and vehicle interface JavaScript · TypeScript · Node.js source · HTML5 games · Variable exclusion list · WebAssembly instead of obfuscation · Source maps and your source code · Browser support matrix