fix #4507: import assignment tree-shaking bug

This commit is contained in:
Evan Wallace
2026-08-08 11:02:44 -04:00
parent 2500c0ce2d
commit 07f6e8c506
4 changed files with 66 additions and 6 deletions
+9
View File
@@ -2,6 +2,15 @@
## Unreleased
* Fix tree shaking bug due to TypeScript import alias ([#4507](https://github.com/evanw/esbuild/issues/4507))
This release fixes a bug that could cause esbuild to incorrectly tree-shake imports that are used in a TypeScript type alias under certain circumstances. Affected code uses a TypeScript-specific `import` assignment and looks something like this:
```ts
import Base from './dep.js';
import Alias = Base.SomeType;
```
* Avoid overwriting input files without `--allow-overwrite` ([#4484](https://github.com/evanw/esbuild/issues/4484))
For example: `esbuild input.js --outfile=input.js` tells esbuild to overwrite `input.js` with the output of running esbuild on it. This was supposed to already be prevented by default, but it accidentally regressed in version 0.17.0 and apparently didn't have any test coverage. The error message was being printed but the input file was still being overwritten. Oops.
+26
View File
@@ -3064,3 +3064,29 @@ func TestParameterPropsUseDefineForClassFieldsTrueLowered(t *testing.T) {
},
})
}
// See: https://github.com/evanw/esbuild/issues/4507
func TestTreeShakingImportAliasIssue4507(t *testing.T) {
ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import Base from './dep.js';
import Alias = Base.SomeType;
export function make() {
return new Base();
}
`,
"/dep.js": `
export default class Base {
static hello() { return 'dep code is present'; }
}
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}
@@ -2251,3 +2251,21 @@ if (nested) {
new Bar(bar2(objBar));
}
var bar;
================================================================================
TestTreeShakingImportAliasIssue4507
---------- /out.js ----------
// dep.js
var Base = class {
static hello() {
return "dep code is present";
}
};
// entry.ts
function make() {
return new Base();
}
export {
make
};
+13 -6
View File
@@ -1752,12 +1752,16 @@ func (p *parser) ignoreUsage(ref ast.Ref) {
// Roll back the use count increment in recordUsage()
if !p.isControlFlowDead {
p.symbols[ref.InnerIndex].UseCountEstimate--
use := p.symbolUses[ref]
use.CountEstimate--
if use.CountEstimate == 0 {
delete(p.symbolUses, ref)
} else {
p.symbolUses[ref] = use
// Only remove this usage if we're currently processing a part
if p.symbolUses != nil {
use := p.symbolUses[ref]
use.CountEstimate--
if use.CountEstimate == 0 {
delete(p.symbolUses, ref)
} else {
p.symbolUses[ref] = use
}
}
}
@@ -17729,6 +17733,9 @@ func (p *parser) appendPart(parts []js_ast.Part, stmts []js_ast.Stmt) []js_ast.P
part.Scopes = p.scopesForCurrentPart
parts = append(parts, part)
}
// Reset the state for this part so we don't accidentally mutate it
p.symbolUses = nil
return parts
}