Skip to content

Commit

Permalink
fix #913: tsconfig "baseUrl" override of "paths"
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Mar 5, 2021
1 parent 15b36c5 commit 11fa9ae
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

* Fix a discrepancy with esbuild's `tsconfig.json` implementation ([#913](https://github.com/evanw/esbuild/issues/913))

If a `tsconfig.json` file contains a `"baseUrl"` value and `"extends"` another `tsconfig.json` file that contains a `"paths"` value, the base URL used for interpreting the paths should be the overridden value. Previously esbuild incorrectly used the inherited value, but with this release esbuild will now use the overridden value instead.

* Work around the Jest testing framework breaking node's `Buffer` API ([#914](https://github.com/evanw/esbuild/issues/914))

Running esbuild within a Jest test fails because Jest causes `Buffer` instances to not be considered `Uint8Array` instances, which then breaks the code esbuild uses to communicate with its child process. More info is here: https://github.com/facebook/jest/issues/4422. This release contains a workaround that copies each `Buffer` object into a `Uint8Array` object when this invariant is broken. That should prevent esbuild from crashing when it's run from within a Jest test.
Expand Down
110 changes: 110 additions & 0 deletions internal/bundler/bundler_tsconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,116 @@ Users/user/project/tsconfig.json: warning: Non-relative path "http://bad" is not
})
}

// https://github.com/evanw/esbuild/issues/913
func TestTsConfigPathsOverriddenBaseURL(t *testing.T) {
tsconfig_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/src/entry.ts": `
import test from '#/test'
console.log(test)
`,
"/Users/user/project/src/test.ts": `
export default 123
`,
"/Users/user/project/tsconfig.json": `
{
"extends": "./tsconfig.paths.json",
"compilerOptions": {
"baseUrl": "./src"
}
}
`,
"/Users/user/project/tsconfig.paths.json": `
{
"compilerOptions": {
"paths": {
"#/*": ["./*"]
}
}
}
`,
},
entryPaths: []string{"/Users/user/project/src/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/Users/user/project/out.js",
},
})
}

func TestTsConfigPathsOverriddenBaseURLDifferentDir(t *testing.T) {
tsconfig_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/src/entry.ts": `
import test from '#/test'
console.log(test)
`,
"/Users/user/project/src/test.ts": `
export default 123
`,
"/Users/user/project/src/tsconfig.json": `
{
"extends": "../tsconfig.paths.json",
"compilerOptions": {
"baseUrl": "./"
}
}
`,
"/Users/user/project/tsconfig.paths.json": `
{
"compilerOptions": {
"paths": {
"#/*": ["./*"]
}
}
}
`,
},
entryPaths: []string{"/Users/user/project/src/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/Users/user/project/out.js",
},
})
}

func TestTsConfigPathsMissingBaseURL(t *testing.T) {
tsconfig_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/src/entry.ts": `
import test from '#/test'
console.log(test)
`,
"/Users/user/project/src/test.ts": `
export default 123
`,
"/Users/user/project/src/tsconfig.json": `
{
"extends": "../tsconfig.paths.json",
"compilerOptions": {
}
}
`,
"/Users/user/project/tsconfig.paths.json": `
{
"compilerOptions": {
"paths": {
"#/*": ["./*"]
}
}
}
`,
},
entryPaths: []string{"/Users/user/project/src/entry.ts"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/Users/user/project/out.js",
},
expectedScanLog: `Users/user/project/src/entry.ts: error: Could not resolve "#/test" (mark it as external to exclude it from the bundle)
`,
})
}

func TestTsConfigJSX(t *testing.T) {
tsconfig_suite.expectBundled(t, bundled{
files: map[string]string{
Expand Down
18 changes: 18 additions & 0 deletions internal/bundler/snapshots/snapshots_tsconfig.txt
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,24 @@ var extended_default = {
// Users/user/project/entry.ts
console.log(simple_default, extended_default);

================================================================================
TestTsConfigPathsOverriddenBaseURL
---------- /Users/user/project/out.js ----------
// Users/user/project/src/test.ts
var test_default = 123;

// Users/user/project/src/entry.ts
console.log(test_default);

================================================================================
TestTsConfigPathsOverriddenBaseURLDifferentDir
---------- /Users/user/project/out.js ----------
// Users/user/project/src/test.ts
var test_default = 123;

// Users/user/project/src/entry.ts
console.log(test_default);

================================================================================
TestTsconfigJsonAbsoluteBaseUrl
---------- /Users/user/project/out.js ----------
Expand Down
13 changes: 11 additions & 2 deletions internal/resolver/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1242,14 +1242,23 @@ func (r *resolver) loadAsFileOrDirectory(path string, kind ast.ImportKind) (Path
// This closely follows the behavior of "tryLoadModuleUsingPaths()" in the
// official TypeScript compiler
func (r *resolver) matchTSConfigPaths(tsConfigJSON *TSConfigJSON, path string, kind ast.ImportKind) (PathPair, bool, *fs.DifferentCase) {
absBaseURL := tsConfigJSON.BaseURLForPaths

// The explicit base URL should take precedence over the implicit base URL
// if present. This matters when a tsconfig.json file overrides "baseUrl"
// from another extended tsconfig.json file but doesn't override "paths".
if tsConfigJSON.BaseURL != nil {
absBaseURL = *tsConfigJSON.BaseURL
}

// Check for exact matches first
for key, originalPaths := range tsConfigJSON.Paths {
if key == path {
for _, originalPath := range originalPaths {
// Load the original path relative to the "baseUrl" from tsconfig.json
absoluteOriginalPath := originalPath
if !r.fs.IsAbs(originalPath) {
absoluteOriginalPath = r.fs.Join(tsConfigJSON.BaseURLForPaths, originalPath)
absoluteOriginalPath = r.fs.Join(absBaseURL, originalPath)
}
if absolute, ok, diffCase := r.loadAsFileOrDirectory(absoluteOriginalPath, kind); ok {
return absolute, true, diffCase
Expand Down Expand Up @@ -1302,7 +1311,7 @@ func (r *resolver) matchTSConfigPaths(tsConfigJSON *TSConfigJSON, path string, k
// Load the original path relative to the "baseUrl" from tsconfig.json
absoluteOriginalPath := originalPath
if !r.fs.IsAbs(originalPath) {
absoluteOriginalPath = r.fs.Join(tsConfigJSON.BaseURLForPaths, originalPath)
absoluteOriginalPath = r.fs.Join(absBaseURL, originalPath)
}
if absolute, ok, diffCase := r.loadAsFileOrDirectory(absoluteOriginalPath, kind); ok {
return absolute, true, diffCase
Expand Down

0 comments on commit 11fa9ae

Please sign in to comment.