diff --git a/CHANGELOG.md b/CHANGELOG.md index 623ba39605c..68d90c7fdae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +* Fix a regression in 0.19.0 regarding `paths` in `tsconfig.json` ([#3354](https://github.com/evanw/esbuild/issues/3354)) + + The fix in esbuild version 0.19.0 to process `tsconfig.json` aliases before the `--packages=external` setting unintentionally broke an edge case in esbuild's handling of certain `tsconfig.json` aliases where there are multiple files with the same name in different directories. This release adjusts esbuild's behavior for this edge case so that it passes while still processing aliases before `--packages=external`. Please read the linked issue for more details. + * Fix a CSS `font` property minification bug ([#3452](https://github.com/evanw/esbuild/issues/3452)) This release fixes a bug where esbuild's CSS minifier didn't insert a space between the font size and the font family in the `font` CSS shorthand property in the edge case where the original source code didn't already have a space and the leading string token was shortened to an identifier: diff --git a/internal/bundler_tests/bundler_tsconfig_test.go b/internal/bundler_tests/bundler_tsconfig_test.go index eb2e56b0aaf..f709dd4b86c 100644 --- a/internal/bundler_tests/bundler_tsconfig_test.go +++ b/internal/bundler_tests/bundler_tsconfig_test.go @@ -2538,3 +2538,46 @@ func TestTsconfigJsonBaseUrlIssue3307(t *testing.T) { }, }) } + +// https://github.com/evanw/esbuild/issues/3354 +func TestTsconfigJsonAsteriskNameCollisionIssue3354(t *testing.T) { + tsconfig_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/Users/user/project/src/entry.ts": ` + import {foo} from "foo"; + foo(); + `, + "/Users/user/project/src/tsconfig.json": ` + { + "compilerOptions": { + "baseUrl": ".", + "paths": { + "*": ["web/*"] + } + } + } + `, + "/Users/user/project/src/web/foo.ts": ` + import {foo as barFoo} from 'bar/foo'; + export function foo() { + console.log('web/foo'); + barFoo(); + } + `, + "/Users/user/project/src/web/bar/foo/foo.ts": ` + export function foo() { + console.log('bar/foo'); + } + `, + "/Users/user/project/src/web/bar/foo/index.ts": ` + export {foo} from './foo' + `, + }, + entryPaths: []string{"entry.ts"}, + absWorkingDir: "/Users/user/project/src", + options: config.Options{ + Mode: config.ModeBundle, + AbsOutputFile: "/Users/user/project/out.js", + }, + }) +} diff --git a/internal/bundler_tests/snapshots/snapshots_tsconfig.txt b/internal/bundler_tests/snapshots/snapshots_tsconfig.txt index bb09ce69762..796d7207824 100644 --- a/internal/bundler_tests/snapshots/snapshots_tsconfig.txt +++ b/internal/bundler_tests/snapshots/snapshots_tsconfig.txt @@ -206,6 +206,23 @@ var require_util = __commonJS({ var import_util = __toESM(require_util()); console.log((0, import_util.default)()); +================================================================================ +TestTsconfigJsonAsteriskNameCollisionIssue3354 +---------- /Users/user/project/out.js ---------- +// web/bar/foo/foo.ts +function foo() { + console.log("bar/foo"); +} + +// web/foo.ts +function foo2() { + console.log("web/foo"); + foo(); +} + +// entry.ts +foo2(); + ================================================================================ TestTsconfigJsonBaseUrl ---------- /Users/user/project/out.js ---------- diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go index 194a82a2a15..7c79325b1ea 100644 --- a/internal/resolver/resolver.go +++ b/internal/resolver/resolver.go @@ -993,36 +993,12 @@ func (r resolverQuery) resolveWithoutSymlinks(sourceDir string, sourceDirInfo *d } } - // First, check path overrides from the nearest enclosing TypeScript "tsconfig.json" file - if sourceDirInfo != nil { - if tsConfigJSON := r.tsConfigForDir(sourceDirInfo); tsConfigJSON != nil && tsConfigJSON.Paths != nil { - if absolute, ok, diffCase := r.matchTSConfigPaths(tsConfigJSON, importPath); ok { - return &ResolveResult{PathPair: absolute, DifferentCase: diffCase} - } - } - } - // Check both relative and package paths for CSS URL tokens, with relative // paths taking precedence over package paths to match Webpack behavior. isPackagePath := IsPackagePath(importPath) checkRelative := !isPackagePath || r.kind.IsFromCSS() checkPackage := isPackagePath - // "import 'pkg'" when all packages are external (vs. "import './pkg'"). This - // is deliberately done after we check for "tsconfig.json" aliases because - // people want to be able to make things look like packages but have them not - // be packages. - if r.options.ExternalPackages && isPackagePath && !strings.HasPrefix(importPath, "#") { - if r.debugLogs != nil { - r.debugLogs.addNote("Marking this path as external because it's a package path") - } - - r.flushDebugLogs(flushDueToSuccess) - return &ResolveResult{ - PathPair: PathPair{Primary: logger.Path{Text: importPath}, IsExternal: true}, - } - } - if checkRelative { absPath := r.fs.Join(sourceDir, importPath) @@ -2301,11 +2277,21 @@ func (r resolverQuery) loadNodeModules(importPath string, dirInfo *dirInfo, forb defer r.debugLogs.decreaseIndent() } - // Try looking up the path relative to the base URL from "tsconfig.json" - if tsConfigJSON := r.tsConfigForDir(dirInfo); tsConfigJSON != nil && tsConfigJSON.BaseURL != nil { - basePath := r.fs.Join(*tsConfigJSON.BaseURL, importPath) - if absolute, ok, diffCase := r.loadAsFileOrDirectory(basePath); ok { - return absolute, true, diffCase + // First, check path overrides from the nearest enclosing TypeScript "tsconfig.json" file + if tsConfigJSON := r.tsConfigForDir(dirInfo); tsConfigJSON != nil { + // Try path substitutions first + if tsConfigJSON.Paths != nil { + if absolute, ok, diffCase := r.matchTSConfigPaths(tsConfigJSON, importPath); ok { + return absolute, true, diffCase + } + } + + // Try looking up the path relative to the base URL + if tsConfigJSON.BaseURL != nil { + basePath := r.fs.Join(*tsConfigJSON.BaseURL, importPath) + if absolute, ok, diffCase := r.loadAsFileOrDirectory(basePath); ok { + return absolute, true, diffCase + } } } @@ -2320,6 +2306,14 @@ func (r resolverQuery) loadNodeModules(importPath string, dirInfo *dirInfo, forb return r.loadPackageImports(importPath, dirInfoPackageJSON) } + // "import 'pkg'" when all packages are external (vs. "import './pkg'") + if r.options.ExternalPackages && IsPackagePath(importPath) { + if r.debugLogs != nil { + r.debugLogs.addNote("Marking this path as external because it's a package path") + } + return PathPair{Primary: logger.Path{Text: importPath}, IsExternal: true}, true, nil + } + // If Yarn PnP is active, use it to find the package if r.pnpManifest != nil { if result := r.resolveToUnqualified(importPath, dirInfo.absPath, r.pnpManifest); result.status.isError() {