esbuild
esbuild copied to clipboard
Inconsistent minify output caused by type import
Even though importing a type won't affect the behavior of a bundle, it causes ESBuild to use a different identifier, resulting in inconsistent output.
Reproduction
The reproduction steps are listed here: https://github.com/aleclarson/repro/tree/esbuild/consistent-minify#readme
git clone https://github.com/aleclarson/repro -b esbuild/consistent-minify
Just occurred to me that the Terser comparison is unnecessary. If I first compile the TypeScript to JavaScript, then minify after with a separate esbuild --minify command, the issue doesn't occur, of course, because there won't be a type import at the time of minification.
I can see the difference in output (e.g. this vs. this). But this doesn't seem to be a bug. The two results are functionally equivalent so this doesn't indicate a correctness issue.
It's only a bug if, like me, you're using the output to determine whether the implementation has actually changed (e.g. between a PR and its target branch). In my case, this is useful to bail out of a GitHub action early (e.g. avoid running benchmarks) if only the JSDoc was updated or whatever.
Anyway, fixing this issue is very low priority (on my end anyway), because the workaround of running esbuild.build() twice (once for bundling, then for minifying) works well enough.
Even after switching to a two-step process of "bundle then minify", identifiers were not always deterministic despite unchanged code (but only in CI for some reason; specifically ubuntu-latest in a GitHub action).
Therefore, I wrote a function for deterministic identifiers: https://github.com/radashi-org/radashi/blob/9aa272cd666b4278ca5df186aa5f8dbeca451bde/scripts/benchmarks/src/normalizeIdentifiers.ts
I'm closing this as I don't consider this to be a correctness issue. The minifier uses heuristics that attempt to generate minified names that compress better with gzip enabled by biasing the alphabet used for minified names toward characters that are present more often in the original source code (see the implementation for details). That way characters used in strings can be reused for identifier names using a potentially smaller encoding in the gzip stream. It's a little more complicated than that (the contents of comments, import paths, and certain symbols are excluded) but it's a heuristic and is intentionally designed for speed, and not for the kind of precision you're asking for.
I ran into this same issue, so I'm leaving a comment though I agree that it's low priority at best. I was attempting to demonstrate that a PR introducing sweeping changes to the type system of a multi-bundle project does not change the output code, and therefore doesn't require testing, but found that certain bundles respond to certain type names (even if those types are unused) by changing the naming of variables in the minified code.