Compatibility

Does obfuscation break switch statements and string comparison?

The string table is the transform customers ask about most, because it is the one that visibly removes their text from the file. The natural worry is that a case label which is no longer a literal will stop matching the value it used to match. Measured with the table enabled, every comparison, switch and fallthrough behaved exactly as before, and the reason comes down to how primitives compare.

What the string table actually does

With MoveStrings and EncodeStrings enabled, string literals stop appearing in the source as readable text. They are collected into a table and each original site is replaced by a call that returns the corresponding value at runtime. Reading the file no longer tells you the messages, URLs or key names the program uses.

That is a real change to the shape of the code, and it is why the question is worth asking rather than assuming. A switch compares a value against each case label. If a case label is now the result of a function call rather than a literal, does the comparison still land?

It does, and the reason is not that the transform special-cases switch. It is that the comparison was never comparing literals in the first place.

Why a decoded string is still the same string

JavaScript primitives compare by value. Two strings are strictly equal when they contain the same characters, regardless of how each was produced. A literal in the source, a value read from the table, a value assembled by concatenation and a value parsed out of JSON are indistinguishable to === once they exist.

This is worth stating plainly because developers coming from languages with interning sometimes expect otherwise. In some languages two equal strings can be distinct objects and reference comparison can fail even when the text matches, which makes a decode step genuinely risky. JavaScript has no such hazard for primitives: value equality is the only equality a primitive string has.

switch uses strict equality for its case comparisons, so it inherits that property directly. The same reasoning covers object property lookup, which converts a key to a string and matches on the characters, and it covers indexOf, includes and sorting, which all compare content.

What was measured

A sample was built around the comparisons that would fail first if any of this were wrong: a strict equality check against a literal, a switch on a string, a fallthrough chain where two case labels share a body and a third falls into the next, a switch exercising strict matching across a number, a numeric string, a boolean and null, a string used as an object key, concatenation and template output, JSON.parse, several string methods, and a frozen object holding string values.

It was run in Node, protected, and run again across five configurations: the default ES5 target, the modern target, a realistic renaming build, that build on the modern target, and a string-table build with MoveStrings and EncodeStrings both on. All five matched the original exactly.

The fallthrough results are the ones worth quoting, because fallthrough is where a rewritten switch would break first: the chain produced low+mid, mid, high and none for its four inputs, identically before and after. The strict-matching case returned number one for 1 and string one for "1", so no coercion was introduced anywhere.

Fallthrough and default are structural, not incidental

It is worth being explicit that fallthrough survived, because a naive rewrite of a switch into a chain of conditionals is a well-known way to lose it. A case without a break continues into the next case body, and that behaviour is load bearing in real code: grouped labels sharing one body, and deliberate cascades that accumulate.

Measured, both forms came through. Two labels sharing a body still shared it, a case that fell into the following one still executed both bodies in order, and default still ran only when nothing matched regardless of where it sat.

The strict-matching result is the companion guarantee. A switch must not coerce, so a numeric 1 must not match the string "1". Both were tested in the same statement and each selected its own branch after protection, which also rules out any accidental normalisation of case labels through the table.

The limits worth knowing

Two honest limits sit alongside this result, and neither is a compatibility problem.

The first is what the table buys you. Moving literals out of the source raises the effort of reading your text out of a file; it does not put that text beyond reach, because the running program must be able to produce it and anything the program can produce a reader with a console can also produce. Treat the table as a way to remove casual readability, not as a place to keep a secret. Credentials and authority belong on a server regardless of how the client is built.

The second is the interaction with member renaming, which is a different switch. A string you use as a property key through bracket access is not a rename site, so it keeps its text while the dotted spelling of the same property is renamed. If one part of your code writes obj.total and another reads obj["total"], enabling member renaming separates them. That is a naming asymmetry rather than anything to do with string comparison, and it is covered in its own article.

The practical check is the same one that applies to every result here: run your own suite against a protected build with your own configuration. Comparisons and switch logic are already covered by ordinary unit tests, so in most projects this costs nothing beyond pointing the test run at the protected output.

Frequently asked questions

Does the string table stop a switch statement from matching?

No. A switch compares with strict equality, and JavaScript primitives compare by value rather than by reference, so a string produced by a table lookup is strictly equal to the same characters written as a literal. Measured with MoveStrings and EncodeStrings enabled, every case matched exactly as before.

Is fallthrough preserved after protection?

Yes. Two case labels sharing one body still shared it, a case falling into the next still executed both bodies in order, and default still ran only when nothing matched. The measured chain produced the same four results before and after.

Does a switch start coercing types after protection?

No. A statement containing case labels for the number 1, the string "1", true and null selected the correct branch for each input in both builds, so no coercion or normalisation of case labels is introduced.

Do strings still work as object keys once they move into the table?

Yes. Property lookup converts a key to a string and matches on its characters, so a key produced at runtime behaves the same as a literal. The measured object had the same single key and the same JSON output in both builds.

How much does moving strings into a table actually protect them?

It removes casual readability from the file, which is a real but bounded benefit. The running program has to produce the text, so a reader with a console can produce it too. Anything that must stay unknown to the user belongs behind a server call rather than in the bundle.

What should I test in my own project?

Point your existing unit tests at a protected build with your project's configuration. Comparison and switch logic is usually already covered, so this costs little, and it checks your configuration rather than a generic one.

Related reading