diff --git a/CHANGELOG.md b/CHANGELOG.md index df3b09f927a..59edd3c6ec7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,13 @@ ## Unreleased -* Reduce memory usage by 30-40% ([#304](https://github.com/evanw/esbuild/issues/304)) +* Reduce memory usage for large builds by 30-40% ([#304](https://github.com/evanw/esbuild/issues/304)) - This release reduces memory usage. Memory is reduced by ~30% for all builds by avoiding unnecessary per-file symbol maps, and is reduced by an additional ~10% for builds with source maps by preallocating some large arrays relating to source map output. + This release reduces memory usage. These specific percentages are likely only accurate for builds with a large number of files. Memory is reduced by ~30% for all builds by avoiding unnecessary per-file symbol maps, and is reduced by an additional ~10% for builds with source maps by preallocating some large arrays relating to source map output. + +* Replace `.js` and `.jsx` with `.ts` or `.tsx` when resolving ([#118](https://github.com/evanw/esbuild/issues/118)) + + This adds an import path resolution behavior that's specific to the TypeScript compiler where you can use an import path that ends in `.js` or `.jsx` when the correct import path actually ends in `.ts` or `.tsx` instead. See the discussion here for more historical context: https://github.com/microsoft/TypeScript/issues/4595. ## 0.6.18 diff --git a/internal/bundler/bundler_ts_test.go b/internal/bundler/bundler_ts_test.go index f9add93b25d..c2977b6e48b 100644 --- a/internal/bundler/bundler_ts_test.go +++ b/internal/bundler/bundler_ts_test.go @@ -759,3 +759,68 @@ func TestTSExportDefaultTypeIssue316(t *testing.T) { }, }) } + +func TestTSImplicitExtensions(t *testing.T) { + ts_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/entry.ts": ` + import './pick-js.js' + import './pick-ts.js' + import './pick-jsx.jsx' + import './pick-tsx.jsx' + import './order-js.js' + import './order-jsx.jsx' + `, + + "/pick-js.js": `console.log("correct")`, + "/pick-js.ts": `console.log("wrong")`, + + "/pick-ts.jsx": `console.log("wrong")`, + "/pick-ts.ts": `console.log("correct")`, + + "/pick-jsx.jsx": `console.log("correct")`, + "/pick-jsx.tsx": `console.log("wrong")`, + + "/pick-tsx.js": `console.log("wrong")`, + "/pick-tsx.tsx": `console.log("correct")`, + + "/order-js.ts": `console.log("correct")`, + "/order-js.tsx": `console.log("wrong")`, + + "/order-jsx.ts": `console.log("correct")`, + "/order-jsx.tsx": `console.log("wrong")`, + }, + entryPaths: []string{"/entry.ts"}, + options: config.Options{ + IsBundling: true, + AbsOutputFile: "/out.js", + }, + }) +} + +func TestTSImplicitExtensionsMissing(t *testing.T) { + ts_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/entry.ts": ` + import './mjs.mjs' + import './cjs.cjs' + import './js.js' + import './jsx.jsx' + `, + "/mjs.ts": ``, + "/mjs.tsx": ``, + "/cjs.ts": ``, + "/cjs.tsx": ``, + "/js.js.ts": ``, + "/jsx.jsx.tsx": ``, + }, + entryPaths: []string{"/entry.ts"}, + options: config.Options{ + IsBundling: true, + AbsOutputFile: "/out.js", + }, + expectedScanLog: `/entry.ts: error: Could not resolve "./mjs.mjs" +/entry.ts: error: Could not resolve "./cjs.cjs" +`, + }) +} diff --git a/internal/bundler/snapshots/snapshots_ts.txt b/internal/bundler/snapshots/snapshots_ts.txt index 4620f407eb4..12bce47926a 100644 --- a/internal/bundler/snapshots/snapshots_ts.txt +++ b/internal/bundler/snapshots/snapshots_ts.txt @@ -167,6 +167,29 @@ class Foo { // /a.ts console.log(new Foo()); +================================================================================ +TestTSImplicitExtensions +---------- /out.js ---------- +// /pick-js.js +console.log("correct"); + +// /pick-ts.ts +console.log("correct"); + +// /pick-jsx.jsx +console.log("correct"); + +// /pick-tsx.tsx +console.log("correct"); + +// /order-js.ts +console.log("correct"); + +// /order-jsx.ts +console.log("correct"); + +// /entry.ts + ================================================================================ TestTSImportEmptyNamespace ---------- /out.js ---------- diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go index 7f63effe670..97ab8122f23 100644 --- a/internal/resolver/resolver.go +++ b/internal/resolver/resolver.go @@ -806,6 +806,32 @@ func (r *resolver) loadAsFile(path string) (string, bool) { return path + ext, true } } + + // TypeScript-specific behavior: if the extension is ".js" or ".jsx", try + // replacing it with ".ts" or ".tsx". At the time of writing this specific + // behavior comes from the function "loadModuleFromFile()" in the file + // "moduleNameResolver.ts" in the TypeScript compiler source code. It + // contains this comment: + // + // If that didn't work, try stripping a ".js" or ".jsx" extension and + // replacing it with a TypeScript one; e.g. "./foo.js" can be matched + // by "./foo.ts" or "./foo.d.ts" + // + // We don't care about ".d.ts" files because we can't do anything with + // those, so we ignore that part of the behavior. + // + // See the discussion here for more historical context: + // https://github.com/microsoft/TypeScript/issues/4595 + if strings.HasSuffix(base, ".js") || strings.HasSuffix(base, ".jsx") { + lastDot := strings.LastIndexByte(base, '.') + // Note that the official compiler code always tries ".ts" before + // ".tsx" even if the original extension was ".jsx". + for _, ext := range []string{".ts", ".tsx"} { + if entries[base[:lastDot]+ext].Kind == fs.FileEntry { + return path[:len(path)-(len(base)-lastDot)] + ext, true + } + } + } } return "", false