Skip to content

Commit 13587ce

Browse files
committed
fix #2468: allow ts import x = to be tree shaken
1 parent 6d4f902 commit 13587ce

File tree

4 files changed

+110
-3
lines changed

4 files changed

+110
-3
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,24 @@
22

33
## Unreleased
44

5+
* Consider TypeScript import assignments to be side-effect free ([#2468](https://github.com/evanw/esbuild/issues/2468))
6+
7+
TypeScript has a [legacy import syntax](https://www.typescriptlang.org/docs/handbook/namespaces.html#aliases) for working with TypeScript namespaces that looks like this:
8+
9+
```ts
10+
import { someNamespace } from './some-file'
11+
import bar = someNamespace.foo;
12+
13+
// some-file.ts
14+
export namespace someNamespace {
15+
export let foo = 123
16+
}
17+
```
18+
19+
Since esbuild converts TypeScript into JavaScript one file at a time, it doesn't know if `bar` is supposed to be a value or a type (or both, which TypeScript actually allows in this case). This is problematic because values are supposed to be kept during the conversion but types are supposed to be removed during the conversion. Currently esbuild keeps `bar` in the output, which is done because `someNamespace.foo` is a property access and property accesses run code that could potentially have a side effect (although there is no side effect in this case).
20+
21+
With this release, esbuild will now consider `someNamespace.foo` to have no side effects. This means `bar` will now be removed when bundling and when tree shaking is enabled. Note that it will still not be removed when tree shaking is disabled. This is because in this mode, esbuild supports adding additional code to the end of the generated output that's in the same scope as the module. That code could potentially make use of `bar`, so it would be incorrect to remove it. If you want `bar` to be removed, you'll have to enable tree shaking (which tells esbuild that nothing else depends on the unexported top-level symbols in the generated output).
22+
523
* Change the order of the banner and the `"use strict"` directive ([#2467](https://github.com/evanw/esbuild/issues/2467))
624

725
Previously the top of the file contained the following things in order:

internal/bundler/bundler_ts_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,69 @@ func TestTSImportEqualsEliminationTest(t *testing.T) {
684684
})
685685
}
686686

687+
func TestTSImportEqualsTreeShakingFalse(t *testing.T) {
688+
ts_suite.expectBundled(t, bundled{
689+
files: map[string]string{
690+
"/entry.ts": `
691+
import { foo } from 'pkg'
692+
import used = foo.used
693+
import unused = foo.unused
694+
export { used }
695+
`,
696+
},
697+
entryPaths: []string{"/entry.ts"},
698+
options: config.Options{
699+
Mode: config.ModePassThrough,
700+
AbsOutputFile: "/out.js",
701+
TreeShaking: false,
702+
},
703+
})
704+
}
705+
706+
func TestTSImportEqualsTreeShakingTrue(t *testing.T) {
707+
ts_suite.expectBundled(t, bundled{
708+
files: map[string]string{
709+
"/entry.ts": `
710+
import { foo } from 'pkg'
711+
import used = foo.used
712+
import unused = foo.unused
713+
export { used }
714+
`,
715+
},
716+
entryPaths: []string{"/entry.ts"},
717+
options: config.Options{
718+
Mode: config.ModePassThrough,
719+
AbsOutputFile: "/out.js",
720+
TreeShaking: true,
721+
},
722+
})
723+
}
724+
725+
func TestTSImportEqualsBundle(t *testing.T) {
726+
ts_suite.expectBundled(t, bundled{
727+
files: map[string]string{
728+
"/entry.ts": `
729+
import { foo } from 'pkg'
730+
import used = foo.used
731+
import unused = foo.unused
732+
export { used }
733+
`,
734+
},
735+
entryPaths: []string{"/entry.ts"},
736+
options: config.Options{
737+
Mode: config.ModeBundle,
738+
AbsOutputFile: "/out.js",
739+
ExternalSettings: config.ExternalSettings{
740+
PreResolve: config.ExternalMatchers{
741+
Exact: map[string]bool{
742+
"pkg": true,
743+
},
744+
},
745+
},
746+
},
747+
})
748+
}
749+
687750
func TestTSMinifiedBundleES6(t *testing.T) {
688751
ts_suite.expectBundled(t, bundled{
689752
files: map[string]string{

internal/bundler/snapshots/snapshots_ts.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,16 @@ function foo() {
541541
}
542542
foo();
543543

544+
================================================================================
545+
TestTSImportEqualsBundle
546+
---------- /out.js ----------
547+
// entry.ts
548+
import { foo } from "pkg";
549+
var used = foo.used;
550+
export {
551+
used
552+
};
553+
544554
================================================================================
545555
TestTSImportEqualsEliminationTest
546556
---------- /out.js ----------
@@ -553,6 +563,21 @@ export {
553563
bar
554564
};
555565

566+
================================================================================
567+
TestTSImportEqualsTreeShakingFalse
568+
---------- /out.js ----------
569+
import { foo } from "pkg";
570+
const used = foo.used;
571+
const unused = foo.unused;
572+
export { used };
573+
574+
================================================================================
575+
TestTSImportEqualsTreeShakingTrue
576+
---------- /out.js ----------
577+
import { foo } from "pkg";
578+
const used = foo.used;
579+
export { used };
580+
556581
================================================================================
557582
TestTSImportMissingUnusedES6
558583
---------- /out.js ----------

internal/js_parser/ts_parser.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1315,9 +1315,10 @@ func (p *parser) parseTypeScriptImportEqualsStmt(loc logger.Loc, opts parseStmtO
13151315
for p.lexer.Token == js_lexer.TDot {
13161316
p.lexer.Next()
13171317
value.Data = &js_ast.EDot{
1318-
Target: value,
1319-
Name: p.lexer.Identifier.String,
1320-
NameLoc: p.lexer.Loc(),
1318+
Target: value,
1319+
Name: p.lexer.Identifier.String,
1320+
NameLoc: p.lexer.Loc(),
1321+
CanBeRemovedIfUnused: true,
13211322
}
13221323
p.lexer.Expect(js_lexer.TIdentifier)
13231324
}

0 commit comments

Comments
 (0)