Compatibility
Published
If your API hands out 64-bit identifiers, you have probably already been bitten once by JavaScript numbers. The question people ask before protecting such a codebase is whether obfuscation adds a second way to lose precision. It does not, and the measurements are easy to state.
What was measured
A file containing a decimal BigInt literal, a hex BigInt literal, a value constructed from a string, and arithmetic across them was run through the protector in three configurations: the default output target, the modern output target, and a configuration with string moving and encoding enabled.
In all three, 9007199254740993n came out as 9007199254740993n. 0xFFn came out as 0xFFn. BigInt("123456789012345678901234567890") kept its argument intact, packed into the string table in the configuration where string moving was on. Running the protected output produced the same three values as the source and typeof still reported bigint.
The reason this is boring is structural. A BigInt literal is a numeric token with an n suffix; the tokeniser reads it as one unit and the writer emits it as one unit. There is no arithmetic performed at protection time on which precision could be lost.
Number encoding does not touch BigInt
The natural follow-up is whether the number-encoding option, which rewrites numeric literals into less readable equivalent expressions, will try to do the same to a BigInt and quietly convert it to a double along the way. It does not. The suffix makes the token a different kind of literal and it is not a candidate for that transform.
This matters because the failure would be invisible in the way that hurts. A 64-bit identifier that silently loses its bottom digits still looks like a plausible identifier, still serialises, still compares equal to itself, and only fails when it reaches a system that knows what the real value should have been.
The hazard that is real, and predates obfuscation
The genuine risk with large identifiers has nothing to do with protection. It is that a value which needs to be a BigInt spends part of its life as an ordinary number, and by far the most common way that happens is JSON.parse.
JSON has no BigInt. A response containing {"id": 9007199254740993} parses to a plain number and the value is already wrong before any of your code sees it. No transform applied to your bundle can recover it. The same is true in reverse: JSON.stringify throws on a BigInt rather than guessing, which is at least a loud failure.
The fix is the same as it always was and is worth restating because protection is often the moment someone audits this properly: carry large identifiers as strings across the wire, convert to BigInt at the boundary if you need arithmetic, and never let them become numbers. If you do that, protection is a non-event for them. If you do not, you have a precision bug that a protected build will surface at exactly the same rate an unprotected one does.
Where identifiers do interact with the transforms
One place deserves attention, and it is about names rather than values. If you enable member renaming and your code reads an identifier off an object with dotted access, the property name is subject to renaming like any other. A value read with bracket access using a string key is not.
So a payload field is safe when you reach it as row["accountId"] and is a renaming candidate when you reach it as row.accountId. This is not specific to large integers, but it bites hardest on identifier fields because they are usually read straight off a parsed server response, where the property names are fixed by a contract you do not control. Scope member renaming with a pattern that excludes your wire-format fields, or read them with bracket access.
Frequently asked questions
Does obfuscation lose precision on BigInt values?
No. BigInt literals are carried through as single tokens and no arithmetic is performed on them at protection time. Measured across the default target, the modern target and a string-table configuration, decimal literals, hex literals and values built with BigInt() all produced identical results before and after protection, with typeof still reporting bigint.
Will number encoding convert my BigInt to a regular number?
No. The n suffix makes it a distinct kind of literal and it is not a candidate for the numeric-literal transform. That is the important case, because a silent conversion to a double would produce a plausible-looking but wrong identifier.
Why did my 64-bit ID break anyway after I protected the build?
Almost certainly at JSON.parse rather than in the transform. JSON has no BigInt type, so a large integer in a response becomes an ordinary number and loses precision before your code touches it. Protecting the bundle neither causes nor fixes this. Carry such identifiers as strings over the wire.
Can I send a BigInt to a server with JSON.stringify?
Not directly. JSON.stringify throws on a BigInt rather than silently converting it. Convert to a string yourself at the boundary, which is also what you want on the way in, so the value never exists as a double at any point.
Does member renaming affect fields that hold large identifiers?
It can, in the same way it affects any property read with dotted access. Renaming rewrites dotted member access and object-literal keys; it does not rewrite a key you supply as a string in bracket access. Fields whose names are fixed by a server contract should be excluded from renaming or read with bracket access.
Is BigInt supported by the older output target?
The literal is preserved in both output targets, so the syntax reaches the browser either way. Support is then a question about your browser matrix rather than about the transform: BigInt is an ES2020 feature and a browser old enough to lack it will not run the literal regardless of how the file was protected.
Related reading