Skip to content

Commit 629c921

Browse files
committed
fix #3247: use tsconfig in package.json
1 parent 60004e7 commit 629c921

File tree

5 files changed

+208
-18
lines changed

5 files changed

+208
-18
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88

99
Starting with this release, that last step can now be replaced with telling esbuild to serve a specific HTML file using the `--serve-fallback=` option. This can be used to provide a "not found" page for missing URLs. It can also be used to implement a [single-page app](https://en.wikipedia.org/wiki/Single-page_application) that mutates the current URL and therefore requires the single app entry point to be served when the page is loaded regardless of whatever the current URL is.
1010

11+
* Use the `tsconfig` field in `package.json` during `extends` resolution ([#3247](https://github.com/evanw/esbuild/issues/3247))
12+
13+
This release adds a feature from [TypeScript 3.2](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-2.html#tsconfigjson-inheritance-via-nodejs-packages) where if a `tsconfig.json` file specifies a package name in the `extends` field and that package's `package.json` file has a `tsconfig` field, the contents of that field are used in the search for the base `tsconfig.json` file.
14+
1115
* Implement CSS nesting without `:is()` when possible ([#1945](https://github.com/evanw/esbuild/issues/1945))
1216

1317
Previously esbuild would always produce a warning when transforming nested CSS for a browser that doesn't support the `:is()` pseudo-class. This was because the nesting transform needs to generate an `:is()` in some complex cases which means the transformed CSS would then not work in that browser. However, the CSS nesting transform can often be done without generating an `:is()`. So with this release, esbuild will no longer warn when targeting browsers that don't support `:is()` in the cases where an `:is()` isn't needed to represent the nested CSS.

internal/bundler_tests/bundler_tsconfig_test.go

+134
Original file line numberDiff line numberDiff line change
@@ -1199,6 +1199,140 @@ func TestTsconfigJsonNodeModulesImplicitFile(t *testing.T) {
11991199
})
12001200
}
12011201

1202+
func TestTsconfigJsonNodeModulesTsconfigPathExact(t *testing.T) {
1203+
tsconfig_suite.expectBundled(t, bundled{
1204+
files: map[string]string{
1205+
"/Users/user/project/src/app/entry.tsx": `
1206+
console.log(<div/>)
1207+
`,
1208+
"/Users/user/project/src/tsconfig.json": `
1209+
{
1210+
"extends": "foo"
1211+
}
1212+
`,
1213+
"/Users/user/project/src/node_modules/foo/package.json": `
1214+
{
1215+
"tsconfig": "over/here.json"
1216+
}
1217+
`,
1218+
"/Users/user/project/src/node_modules/foo/over/here.json": `
1219+
{
1220+
"compilerOptions": {
1221+
"jsx": "react",
1222+
"jsxFactory": "worked"
1223+
}
1224+
}
1225+
`,
1226+
},
1227+
entryPaths: []string{"/Users/user/project/src/app/entry.tsx"},
1228+
options: config.Options{
1229+
Mode: config.ModeBundle,
1230+
AbsOutputFile: "/Users/user/project/out.js",
1231+
},
1232+
})
1233+
}
1234+
1235+
func TestTsconfigJsonNodeModulesTsconfigPathImplicitJson(t *testing.T) {
1236+
tsconfig_suite.expectBundled(t, bundled{
1237+
files: map[string]string{
1238+
"/Users/user/project/src/app/entry.tsx": `
1239+
console.log(<div/>)
1240+
`,
1241+
"/Users/user/project/src/tsconfig.json": `
1242+
{
1243+
"extends": "foo"
1244+
}
1245+
`,
1246+
"/Users/user/project/src/node_modules/foo/package.json": `
1247+
{
1248+
"tsconfig": "over/here"
1249+
}
1250+
`,
1251+
"/Users/user/project/src/node_modules/foo/over/here.json": `
1252+
{
1253+
"compilerOptions": {
1254+
"jsx": "react",
1255+
"jsxFactory": "worked"
1256+
}
1257+
}
1258+
`,
1259+
},
1260+
entryPaths: []string{"/Users/user/project/src/app/entry.tsx"},
1261+
options: config.Options{
1262+
Mode: config.ModeBundle,
1263+
AbsOutputFile: "/Users/user/project/out.js",
1264+
},
1265+
})
1266+
}
1267+
1268+
func TestTsconfigJsonNodeModulesTsconfigPathDirectory(t *testing.T) {
1269+
tsconfig_suite.expectBundled(t, bundled{
1270+
files: map[string]string{
1271+
"/Users/user/project/src/app/entry.tsx": `
1272+
console.log(<div/>)
1273+
`,
1274+
"/Users/user/project/src/tsconfig.json": `
1275+
{
1276+
"extends": "foo"
1277+
}
1278+
`,
1279+
"/Users/user/project/src/node_modules/foo/package.json": `
1280+
{
1281+
"tsconfig": "over/here"
1282+
}
1283+
`,
1284+
"/Users/user/project/src/node_modules/foo/over/here/tsconfig.json": `
1285+
{
1286+
"compilerOptions": {
1287+
"jsx": "react",
1288+
"jsxFactory": "worked"
1289+
}
1290+
}
1291+
`,
1292+
},
1293+
entryPaths: []string{"/Users/user/project/src/app/entry.tsx"},
1294+
options: config.Options{
1295+
Mode: config.ModeBundle,
1296+
AbsOutputFile: "/Users/user/project/out.js",
1297+
},
1298+
})
1299+
}
1300+
1301+
func TestTsconfigJsonNodeModulesTsconfigPathBad(t *testing.T) {
1302+
tsconfig_suite.expectBundled(t, bundled{
1303+
files: map[string]string{
1304+
"/Users/user/project/src/app/entry.tsx": `
1305+
console.log(<div/>)
1306+
`,
1307+
"/Users/user/project/src/tsconfig.json": `
1308+
{
1309+
"extends": "foo"
1310+
}
1311+
`,
1312+
"/Users/user/project/src/node_modules/foo/package.json": `
1313+
{
1314+
"tsconfig": "over/here.json"
1315+
}
1316+
`,
1317+
"/Users/user/project/src/node_modules/foo/tsconfig.json": `
1318+
{
1319+
"compilerOptions": {
1320+
"jsx": "react",
1321+
"jsxFactory": "THIS SHOULD NOT BE LOADED"
1322+
}
1323+
}
1324+
`,
1325+
},
1326+
entryPaths: []string{"/Users/user/project/src/app/entry.tsx"},
1327+
options: config.Options{
1328+
Mode: config.ModeBundle,
1329+
AbsOutputFile: "/Users/user/project/out.js",
1330+
},
1331+
expectedScanLog: `Users/user/project/src/tsconfig.json: WARNING: Cannot find base config file "foo"
1332+
`,
1333+
})
1334+
}
1335+
12021336
func TestTsconfigJsonInsideNodeModules(t *testing.T) {
12031337
tsconfig_suite.expectBundled(t, bundled{
12041338
files: map[string]string{

internal/bundler_tests/snapshots/snapshots_tsconfig.txt

+24
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,30 @@ TestTsconfigJsonNodeModulesImplicitFile
283283
// Users/user/project/src/app/entry.tsx
284284
console.log(/* @__PURE__ */ worked("div", null));
285285

286+
================================================================================
287+
TestTsconfigJsonNodeModulesTsconfigPathBad
288+
---------- /Users/user/project/out.js ----------
289+
// Users/user/project/src/app/entry.tsx
290+
console.log(/* @__PURE__ */ React.createElement("div", null));
291+
292+
================================================================================
293+
TestTsconfigJsonNodeModulesTsconfigPathDirectory
294+
---------- /Users/user/project/out.js ----------
295+
// Users/user/project/src/app/entry.tsx
296+
console.log(/* @__PURE__ */ worked("div", null));
297+
298+
================================================================================
299+
TestTsconfigJsonNodeModulesTsconfigPathExact
300+
---------- /Users/user/project/out.js ----------
301+
// Users/user/project/src/app/entry.tsx
302+
console.log(/* @__PURE__ */ worked("div", null));
303+
304+
================================================================================
305+
TestTsconfigJsonNodeModulesTsconfigPathImplicitJson
306+
---------- /Users/user/project/out.js ----------
307+
// Users/user/project/src/app/entry.tsx
308+
console.log(/* @__PURE__ */ worked("div", null));
309+
286310
================================================================================
287311
TestTsconfigJsonOverrideMissing
288312
---------- /Users/user/project/out.js ----------

internal/resolver/package_json.go

+15
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ type packageJSON struct {
2121
mainFields map[string]mainField
2222
moduleTypeData js_ast.ModuleTypeData
2323

24+
// "TypeScript will first check whether package.json contains a "tsconfig"
25+
// field, and if it does, TypeScript will try to load a configuration file
26+
// from that field. If neither exists, TypeScript will try to read from a
27+
// tsconfig.json at the root."
28+
//
29+
// See: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-2.html#tsconfigjson-inheritance-via-nodejs-packages
30+
tsconfig string
31+
2432
// Present if the "browser" field is present. This field is intended to be
2533
// used by bundlers and lets you redirect the paths of certain 3rd-party
2634
// modules that don't work in the browser to other modules that shim that
@@ -323,6 +331,13 @@ func (r resolverQuery) parsePackageJSON(inputPath string) *packageJSON {
323331
}
324332
}
325333

334+
// Read the "tsconfig" field
335+
if tsconfigJSON, _, ok := getProperty(json, "tsconfig"); ok {
336+
if tsconfigValue, ok := getString(tsconfigJSON); ok {
337+
packageJSON.tsconfig = tsconfigValue
338+
}
339+
}
340+
326341
// Read the "main" fields
327342
mainFields := r.options.MainFields
328343
if mainFields == nil {

internal/resolver/resolver.go

+31-18
Original file line numberDiff line numberDiff line change
@@ -1142,38 +1142,51 @@ func (r resolverQuery) parseTSConfigFromSource(source logger.Source, visited map
11421142
for {
11431143
// Skip "node_modules" folders
11441144
if r.fs.Base(current) != "node_modules" {
1145-
// if "package.json" exists, try checking the "exports" map. The
1146-
// ability to use "extends" like this was added in TypeScript 5.0.
1145+
join := r.fs.Join(current, "node_modules", extends)
1146+
1147+
// Check to see if "package.json" exists
11471148
pkgDir := r.fs.Join(current, "node_modules", esmPackageName)
11481149
pjFile := r.fs.Join(pkgDir, "package.json")
11491150
if _, err, originalError := r.fs.ReadFile(pjFile); err == nil {
1150-
if packageJSON := r.parsePackageJSON(pkgDir); packageJSON != nil && packageJSON.exportsMap != nil {
1151-
if r.debugLogs != nil {
1152-
r.debugLogs.addNote(fmt.Sprintf("Looking for %q in \"exports\" map in %q", esmPackageSubpath, packageJSON.source.KeyPath.Text))
1153-
r.debugLogs.increaseIndent()
1154-
defer r.debugLogs.decreaseIndent()
1151+
if packageJSON := r.parsePackageJSON(pkgDir); packageJSON != nil {
1152+
// Try checking the "tsconfig" field of "package.json". The ability to use "extends" like this was added in TypeScript 3.2:
1153+
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-2.html#tsconfigjson-inheritance-via-nodejs-packages
1154+
if packageJSON.tsconfig != "" {
1155+
join = packageJSON.tsconfig
1156+
if !r.fs.IsAbs(join) {
1157+
join = r.fs.Join(pkgDir, join)
1158+
}
11551159
}
11561160

1157-
// Note: TypeScript appears to always treat this as a "require" import
1158-
conditions := r.esmConditionsRequire
1159-
resolvedPath, status, debug := r.esmPackageExportsResolve("/", esmPackageSubpath, packageJSON.exportsMap.root, conditions)
1160-
resolvedPath, status, debug = r.esmHandlePostConditions(resolvedPath, status, debug)
1161+
// Try checking the "exports" map. The ability to use "extends" like this was added in TypeScript 5.0:
1162+
// https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/
1163+
if packageJSON.exportsMap != nil {
1164+
if r.debugLogs != nil {
1165+
r.debugLogs.addNote(fmt.Sprintf("Looking for %q in \"exports\" map in %q", esmPackageSubpath, packageJSON.source.KeyPath.Text))
1166+
r.debugLogs.increaseIndent()
1167+
defer r.debugLogs.decreaseIndent()
1168+
}
1169+
1170+
// Note: TypeScript appears to always treat this as a "require" import
1171+
conditions := r.esmConditionsRequire
1172+
resolvedPath, status, debug := r.esmPackageExportsResolve("/", esmPackageSubpath, packageJSON.exportsMap.root, conditions)
1173+
resolvedPath, status, debug = r.esmHandlePostConditions(resolvedPath, status, debug)
11611174

1162-
// This is a very abbreviated version of our ESM resolution
1163-
if status == pjStatusExact || status == pjStatusExactEndsWithStar {
1164-
fileToCheck := r.fs.Join(pkgDir, resolvedPath)
1165-
base, err := r.parseTSConfig(fileToCheck, visited)
1175+
// This is a very abbreviated version of our ESM resolution
1176+
if status == pjStatusExact || status == pjStatusExactEndsWithStar {
1177+
fileToCheck := r.fs.Join(pkgDir, resolvedPath)
1178+
base, err := r.parseTSConfig(fileToCheck, visited)
11661179

1167-
if result, shouldReturn := maybeFinishOurSearch(base, err, fileToCheck); shouldReturn {
1168-
return result
1180+
if result, shouldReturn := maybeFinishOurSearch(base, err, fileToCheck); shouldReturn {
1181+
return result
1182+
}
11691183
}
11701184
}
11711185
}
11721186
} else if r.debugLogs != nil && originalError != nil {
11731187
r.debugLogs.addNote(fmt.Sprintf("Failed to read file %q: %s", pjFile, originalError.Error()))
11741188
}
11751189

1176-
join := r.fs.Join(current, "node_modules", extends)
11771190
filesToCheck := []string{r.fs.Join(join, "tsconfig.json"), join, join + ".json"}
11781191
for _, fileToCheck := range filesToCheck {
11791192
base, err := r.parseTSConfig(fileToCheck, visited)

0 commit comments

Comments
 (0)