diff --git a/CHANGELOG.md b/CHANGELOG.md index 007d0986121..67843fdcc85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,12 @@ With this release, esbuild now removes simplified statement-level expressions if the simplified result is a literal expression even when minification is disabled. Previously this was only done when minification is enabled. This change was only made because some people are bothered by seeing top-level literal expressions. This change has no effect on code behavior. +* Ignore `.d.ts` rules in `paths` in `tsconfig.json` files ([#2074](https://github.com/evanw/esbuild/issues/2074), [#2075](https://github.com/evanw/esbuild/pull/2075)) + + TypeScript's `tsconfig.json` configuration file has a `paths` field that lets you remap import paths to alternative files on the file system. This field is interpreted by esbuild during bundling so that esbuild's behavior matches that of the TypeScript type checker. However, people sometimes override import paths to JavaScript files to instead point to a `.d.ts` TypeScript type declaration file for that JavaScript file. The intent of this is to just use the remapping for type information and not to actually import the `.d.ts` file during the build. + + With this release, esbuild will now ignore rules in `paths` that result in a `.d.ts` file during path resolution. This means code that does this should now be able to be bundled without modifying its `tsconfig.json` file to remove the `.d.ts` rule. This change was contributed by [@magic-akari](https://github.com/magic-akari). + ## 0.14.23 * Update feature database to indicate that node 16.14+ supports import assertions ([#2030](https://github.com/evanw/esbuild/issues/2030)) diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go index 54e1d14ff4f..3d6f1732bb9 100644 --- a/internal/resolver/resolver.go +++ b/internal/resolver/resolver.go @@ -1459,11 +1459,17 @@ func (r resolverQuery) loadAsMainField(dirInfo *dirInfo, path string, extensionO return PathPair{}, false, nil } +func hasCaseInsensitiveSuffix(s string, suffix string) bool { + return len(s) >= len(suffix) && strings.EqualFold(s[len(s)-len(suffix):], suffix) +} + // This closely follows the behavior of "tryLoadModuleUsingPaths()" in the // official TypeScript compiler func (r resolverQuery) matchTSConfigPaths(tsConfigJSON *TSConfigJSON, path string) (PathPair, bool, *fs.DifferentCase) { if r.debugLogs != nil { r.debugLogs.addNote(fmt.Sprintf("Matching %q against \"paths\" in %q", path, tsConfigJSON.AbsPath)) + r.debugLogs.increaseIndent() + defer r.debugLogs.decreaseIndent() } absBaseURL := tsConfigJSON.BaseURLForPaths @@ -1486,7 +1492,11 @@ func (r resolverQuery) matchTSConfigPaths(tsConfigJSON *TSConfigJSON, path strin r.debugLogs.addNote(fmt.Sprintf("Found an exact match for %q in \"paths\"", key)) } for _, originalPath := range originalPaths { - if strings.HasSuffix(originalPath, ".d.ts") || strings.HasSuffix(strings.ToLower(originalPath), ".d.ts") { + // Ignore ".d.ts" files because this rule is obviously only here for type checking + if hasCaseInsensitiveSuffix(originalPath, ".d.ts") { + if r.debugLogs != nil { + r.debugLogs.addNote(fmt.Sprintf("Ignoring substitution %q because it ends in \".d.ts\"", originalPath)) + } continue } @@ -1547,7 +1557,11 @@ func (r resolverQuery) matchTSConfigPaths(tsConfigJSON *TSConfigJSON, path strin matchedText := path[len(longestMatch.prefix) : len(path)-len(longestMatch.suffix)] originalPath = strings.Replace(originalPath, "*", matchedText, 1) - if strings.HasSuffix(originalPath, ".d.ts") || strings.HasSuffix(strings.ToLower(originalPath), ".d.ts") { + // Ignore ".d.ts" files because this rule is obviously only here for type checking + if hasCaseInsensitiveSuffix(originalPath, ".d.ts") { + if r.debugLogs != nil { + r.debugLogs.addNote(fmt.Sprintf("Ignoring substitution %q because it ends in \".d.ts\"", originalPath)) + } continue }