Code Transposition moves global code into a generated wrapper so functions and statements no longer appear in the same straightforward structure as the original source. It is also the transform that makes several other options possible, because it creates the private scope they need.
How it works
The option collects global statements and functions into a generated scope, then rewrites references so the output relies on local indirection. Names that must stay reachable from outside are declared at the top and assigned from inside the wrapper, which keeps the external contract intact while everything internal becomes local.
Without Code Transposition:
function logit() {
}
function mylogic() {
logit();
}
With Code Transposition enabled:
var logit, mylogic;
(function () {
function a() {
}
function b() {
a();
}
logit = a;
mylogic = b;
})();
Why the wrapper matters
Two things change, and the second is the more important one.
First, top-level structure stops being a readable index. An unwrapped file lets a reader scroll the global scope and get a table of contents for free. After transposition the top of the file is a declaration list and the bodies are inside an anonymous scope, in an order that no longer has to match how you wrote them.
Second — and this is why the option sits underneath so much else — identifiers that were global become local. A global name is part of the page's shared namespace and generally cannot be renamed safely, because anything on the page might reference it. Once it is local to a wrapper, it is renameable like any other local. Transposition is what lets name mangling reach code that would otherwise have to be left alone.
Configuration
{
"options": {
"ReorderCode": true,
"ReplaceNames": true
}
}
These options build on the wrapper this one creates:
- Code Transposition Eval (
ReorderCodeEval) packs the wrapper into an evaluated string. Requires unsafe-eval in your CSP.
- Protect Object Declaration (
ReorderCodeObjectDeclare) puts its object-builder helper in the wrapper scope.
- Flat Transform rewrites control flow into dispatch inside it.
- Dead-code injection also needs the wrapper to have somewhere to put unreachable branches.
Enabling any of those causes the wrapper to be generated even for a file that would not otherwise need one.
When the wrapper is skipped
If a file has nothing to hoist — no functions, classes, variables, imports, or exports at global scope — the wrapper is not added, because an extra scope level would be pure cost. That case is real: a file that is already one big IIFE, as many distributed libraries are, has no global declarations to collect. Adding another scope around it once caused variable-resolution breakage in IIFE-wrapped libraries, so the engine now leaves those files alone unless one of the options above specifically needs the wrapper.
The practical consequence: if you enable this option and the output looks unchanged at the top level, the file probably had nothing at global scope. That is correct behavior, not a failure.
What to test
- Runtime function replacement. Code that reassigns a top-level function after load — monkey-patching, test doubles, feature toggles that swap an implementation — interacts with this transform, because the declaration is now an assignment from inside a closure. Assigning a function to a variable is more predictable here than relying on a top-level declaration.
- Hoisting expectations. Function declarations hoist within their scope; moving them into a wrapper changes which scope that is. Code that calls a function before its declaration point still works, but code that reads a
var before assignment may see a different moment of initialisation.
- Anything reached by string. A global your HTML references in an
onclick attribute, or that another script looks up as window["name"], must be in your exclusion list. The wrapper preserves the declared names it knows about; it cannot know about a reference that only exists as text in your markup.
- Module files. ES module semantics differ from script semantics at the top level. If you ship
.mjs, read obfuscating ES modules for what an obfuscator can and cannot restructure there.
Compatibility note: test code that depends on replacing function declarations at runtime. In those cases, assigning a function to a variable can be more predictable than relying on a top-level declaration.
Frequently asked questions
What does Code Transposition actually change in the output?
It collects global statements and functions into a generated wrapper scope and rewrites references so the output relies on local indirection. Names that must remain reachable from outside are declared at the top and assigned from inside the wrapper, which keeps the external contract intact while everything internal becomes local. The visible effect is that the top of the file becomes a declaration list rather than a readable table of contents, and the bodies sit inside an anonymous scope in an order that no longer has to match how you wrote them.
Why do so many other options depend on this one?
Because it is what turns globals into locals, and locals are what other transforms can work on. A global name is part of the page's shared namespace and generally cannot be renamed safely, since anything on the page might reference it. Once a name is local to a wrapper it is renameable like any other local, which is how name mangling reaches code it would otherwise have to leave alone. The evaluated variant, the object declaration helper, the flat control-flow transform and dead-code injection all need that private scope, so enabling any of them causes the wrapper to be generated.
I enabled it and the output looks unchanged at the top level. Is it broken?
Almost certainly not. If a file has nothing to hoist, meaning no functions, classes, variables, imports or exports at global scope, the wrapper is not added because an extra scope level would be pure cost. That case is common: a file that is already one big immediately invoked expression, as many distributed libraries are, has no global declarations to collect. Adding another scope around such a file once caused variable resolution breakage, so the engine now leaves those files alone unless another option specifically needs the wrapper.
What kind of code interacts badly with the wrapper?
Code that replaces a top-level function after load. Monkey patching, test doubles and feature toggles that swap an implementation all behave differently once the declaration has become an assignment from inside a closure. Assigning a function to a variable is more predictable here than relying on a top-level declaration. Hoisting expectations shift for the same reason: calling a function before its declaration point still works, but reading a variable before assignment may see a different moment of initialisation.
What about names referenced from HTML or from a string?
Those must go in your exclusion list, without exception. A global that your markup calls from an onclick attribute, or that another script looks up by building the name as a string, is invisible to the protector. The wrapper preserves the declared names it knows about, and it cannot know about a reference that exists only as text in your markup or as a value assembled at run time. This is the single most common cause of a page that loads correctly but has one dead control.
Does this behave the same way in an ES module?
Not entirely, because module semantics differ from script semantics at the top level, and the difference is about what the top level means rather than about this option. If you ship module files it is worth reading the dedicated guidance on what an obfuscator can and cannot restructure in that setting before enabling cross-file transforms, since the export surface is a contract the tool has to preserve.