Skip to content

Commit

Permalink
fix #2386: ignore tsconfig.json in all packages
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jun 9, 2023
1 parent 8e56452 commit ab7921d
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 46 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@
Previously if you wanted to override esbuild's automatic `tsconfig.json` file detection, you had to create a new `tsconfig.json` file and pass the file name to esbuild via the `--tsconfig=` flag. With this release, you can now optionally use `--tsconfig-raw=` instead to pass the contents of `tsconfig.json` to esbuild directly instead of passing the file name. For example, you can now use `--tsconfig-raw={"compilerOptions":{"experimentalDecorators":true}}` to enable TypeScript experimental decorators directly using a command-line flag (assuming you escape the quotes correctly using your current shell's quoting rules). The `--tsconfig-raw=` flag previously only worked with transform API calls but with this release, it now works with build API calls too.
* Ignore all `tsconfig.json` files in `node_modules` ([#276](https://github.com/evanw/esbuild/issues/276), [#2386](https://github.com/evanw/esbuild/issues/2386))
This changes esbuild's behavior that applies `tsconfig.json` to all files in the subtree of the directory containing `tsconfig.json`. In version 0.12.7, esbuild started ignoring `tsconfig.json` files inside `node_modules` folders. The rationale is that people typically do this by mistake and that doing this intentionally is a rare use case that doesn't need to be supported. However, this change only applied to certain syntax-specific settings (e.g. `jsxFactory`) but did not apply to path resolution settings (e.g. `paths`). With this release, esbuild will now ignore all `tsconfig.json` files in `node_modules` instead of only ignoring certain settings.
These changes are intended to improve esbuild's compatibility with `tsc` and reduce the number of unfortunate behaviors regarding `tsconfig.json` and esbuild.

* Add a workaround for bugs in Safari 16.2 and earlier ([#3072](https://github.com/evanw/esbuild/issues/3072))
Expand Down
47 changes: 47 additions & 0 deletions internal/bundler_tests/bundler_tsconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1629,6 +1629,53 @@ func TestTsConfigPathsExtendsBaseURL(t *testing.T) {
})
}

func TestTsConfigPathsInNodeModulesIssue2386(t *testing.T) {
tsconfig_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/main.js": `
import first from "wow/first";
import next from "wow/next";
console.log(first, next);
`,
"/Users/user/project/node_modules/wow/package.json": `{
"name": "wow",
"type": "module",
"private": true,
"exports": {
"./*": "./dist/*.js"
},
"typesVersions": {
"*": {
"*": [
"dist/*"
]
}
}
}`,
"/Users/user/project/node_modules/wow/tsconfig.json": `{
"compilerOptions": {
"paths": { "wow/*": [ "./*" ] }
}
}`,
"/Users/user/project/node_modules/wow/dist/first.js": `
export default "dist";
`,
"/Users/user/project/node_modules/wow/dist/next.js": `
import next from "wow/first";
export default next;
`,
"/Users/user/project/node_modules/wow/first.ts": `
export default "source";
`,
},
entryPaths: []string{"/Users/user/project/main.js"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/Users/user/project/out.js",
},
})
}

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

================================================================================
TestTsConfigPathsInNodeModulesIssue2386
---------- /Users/user/project/out.js ----------
// Users/user/project/node_modules/wow/dist/first.js
var first_default = "dist";

// Users/user/project/node_modules/wow/dist/next.js
var next_default = first_default;

// Users/user/project/main.js
console.log(first_default, next_default);

================================================================================
TestTsConfigPathsNoBaseURL
---------- /Users/user/project/out.js ----------
Expand Down
94 changes: 48 additions & 46 deletions internal/resolver/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -711,44 +711,22 @@ func (r resolverQuery) finalizeResolve(result *ResolveResult) {
// Copy various fields from the nearest enclosing "tsconfig.json" file if present
if path == &result.PathPair.Primary {
if tsConfigJSON := r.tsConfigForDir(dirInfo); tsConfigJSON != nil {
// Except don't do this if we're inside a "node_modules" directory. Package
// authors often publish their "tsconfig.json" files to npm because of
// npm's default-include publishing model and because these authors
// probably don't know about ".npmignore" files.
//
// People trying to use these packages with esbuild have historically
// complained that esbuild is respecting "tsconfig.json" in these cases.
// The assumption is that the package author published these files by
// accident.
//
// Ignoring "tsconfig.json" files inside "node_modules" directories breaks
// the use case of publishing TypeScript code and having it be transpiled
// for you, but that's the uncommon case and likely doesn't work with
// many other tools anyway. So now these files are ignored.
if helpers.IsInsideNodeModules(result.PathPair.Primary.Text) {
if r.debugLogs != nil {
r.debugLogs.addNote(fmt.Sprintf("Ignoring %q because %q is inside \"node_modules\"",
tsConfigJSON.AbsPath,
result.PathPair.Primary.Text))
result.TSConfig = &tsConfigJSON.Settings
result.TSConfigJSX = tsConfigJSON.JSXSettings
result.TSAlwaysStrict = tsConfigJSON.TSAlwaysStrictOrStrict()

if r.debugLogs != nil {
r.debugLogs.addNote(fmt.Sprintf("This import is under the effect of %q",
tsConfigJSON.AbsPath))
if result.TSConfigJSX.JSXFactory != nil {
r.debugLogs.addNote(fmt.Sprintf("\"jsxFactory\" is %q due to %q",
strings.Join(result.TSConfigJSX.JSXFactory, "."),
tsConfigJSON.AbsPath))
}
} else {
result.TSConfig = &tsConfigJSON.Settings
result.TSConfigJSX = tsConfigJSON.JSXSettings
result.TSAlwaysStrict = tsConfigJSON.TSAlwaysStrictOrStrict()

if r.debugLogs != nil {
r.debugLogs.addNote(fmt.Sprintf("This import is under the effect of %q",
if result.TSConfigJSX.JSXFragmentFactory != nil {
r.debugLogs.addNote(fmt.Sprintf("\"jsxFragment\" is %q due to %q",
strings.Join(result.TSConfigJSX.JSXFragmentFactory, "."),
tsConfigJSON.AbsPath))
if result.TSConfigJSX.JSXFactory != nil {
r.debugLogs.addNote(fmt.Sprintf("\"jsxFactory\" is %q due to %q",
strings.Join(result.TSConfigJSX.JSXFactory, "."),
tsConfigJSON.AbsPath))
}
if result.TSConfigJSX.JSXFragmentFactory != nil {
r.debugLogs.addNote(fmt.Sprintf("\"jsxFragment\" is %q due to %q",
strings.Join(result.TSConfigJSX.JSXFragmentFactory, "."),
tsConfigJSON.AbsPath))
}
}
}
}
Expand Down Expand Up @@ -1402,16 +1380,40 @@ func (r resolverQuery) dirInfoUncached(path string) *dirInfo {
tsConfigPath = r.fs.Join(path, "jsconfig.json")
}
if tsConfigPath != "" {
var err error
info.enclosingTSConfigJSON, err = r.parseTSConfig(tsConfigPath, make(map[string]bool))
if err != nil {
if err == syscall.ENOENT {
r.log.AddError(nil, logger.Range{}, fmt.Sprintf("Cannot find tsconfig file %q",
PrettyPath(r.fs, logger.Path{Text: tsConfigPath, Namespace: "file"})))
} else if err != errParseErrorAlreadyLogged {
r.log.AddID(logger.MsgID_TSConfigJSON_Missing, logger.Debug, nil, logger.Range{},
fmt.Sprintf("Cannot read file %q: %s",
PrettyPath(r.fs, logger.Path{Text: tsConfigPath, Namespace: "file"}), err.Error()))
isInsideNodeModules := false
for dir := info; dir != nil; dir = dir.parent {
if dir.isNodeModules {
isInsideNodeModules = true
break
}
}

// Except don't do this if we're inside a "node_modules" directory. Package
// authors often publish their "tsconfig.json" files to npm because of
// npm's default-include publishing model and because these authors
// probably don't know about ".npmignore" files.
//
// People trying to use these packages with esbuild have historically
// complained that esbuild is respecting "tsconfig.json" in these cases.
// The assumption is that the package author published these files by
// accident.
//
// Ignoring "tsconfig.json" files inside "node_modules" directories breaks
// the use case of publishing TypeScript code and having it be transpiled
// for you, but that's the uncommon case and likely doesn't work with
// many other tools anyway. So now these files are ignored.
if !isInsideNodeModules {
var err error
info.enclosingTSConfigJSON, err = r.parseTSConfig(tsConfigPath, make(map[string]bool))
if err != nil {
if err == syscall.ENOENT {
r.log.AddError(nil, logger.Range{}, fmt.Sprintf("Cannot find tsconfig file %q",
PrettyPath(r.fs, logger.Path{Text: tsConfigPath, Namespace: "file"})))
} else if err != errParseErrorAlreadyLogged {
r.log.AddID(logger.MsgID_TSConfigJSON_Missing, logger.Debug, nil, logger.Range{},
fmt.Sprintf("Cannot read file %q: %s",
PrettyPath(r.fs, logger.Path{Text: tsConfigPath, Namespace: "file"}), err.Error()))
}
}
}
}
Expand Down

0 comments on commit ab7921d

Please sign in to comment.