diff --git a/internal/bundler/bundler_test.go b/internal/bundler/bundler_test.go index 065cf4d2843..7bb10904a1a 100644 --- a/internal/bundler/bundler_test.go +++ b/internal/bundler/bundler_test.go @@ -1386,6 +1386,72 @@ console.log(main_esm_default()); }) } +func TestTsConfigPaths(t *testing.T) { + expectBundled(t, bundled{ + files: map[string]string{ + "/Users/user/project/src/entry.js": ` + import fn from 'core/test' + import fn2 from 'testing/test' + + console.log(fn()) + console.log(fn2()) + + `, + "/Users/user/project/tsconfig.json": ` + { + "compilerOptions": { + "baseUrl": ".", + "paths": { + "core/*": ["./src/*"], + "testing": ["./someotherdir/*"] + } + } + } + `, + "/Users/user/project/src/test.js": ` + module.exports = function() { + return 123 + } + `, + "/Users/user/project/someotherdir/test.js": ` + module.exports = function() { + return 123 + } + `, + }, + entryPaths: []string{"/Users/user/project/src/entry.js"}, + parseOptions: parser.ParseOptions{ + IsBundling: true, + }, + bundleOptions: BundleOptions{ + IsBundling: true, + AbsOutputFile: "/Users/user/project/out.js", + }, + expected: map[string]string{ + "/Users/user/project/out.js": `// /Users/user/project/src/test.js +var require_test2 = __commonJS((exports, module) => { + module.exports = function() { + return 123; + }; +}); + +// /Users/user/project/someotherdir/test.js +var require_test = __commonJS((exports, module) => { + module.exports = function() { + return 123; + }; +}); + +// /Users/user/project/src/entry.js +const test = __toModule(require_test2()); +const test2 = __toModule(require_test()); +console.log(test.default()); +console.log(test2.default()); +`, + }, + }) +} + func TestPackageJsonBrowserString(t *testing.T) { expectBundled(t, bundled{ files: map[string]string{ diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go index 6512c48b8be..19f10bf597b 100644 --- a/internal/resolver/resolver.go +++ b/internal/resolver/resolver.go @@ -230,7 +230,8 @@ type packageJson struct { } type tsConfigJson struct { - absPathBaseUrl *string // The absolute path of "compilerOptions.baseUrl" + absPathBaseUrl *string // The absolute path of "compilerOptions.baseUrl" + paths map[string][]string // The absolute paths of "compilerOptions.paths" } type dirInfo struct { @@ -301,6 +302,25 @@ func (r *resolver) parseJsTsConfig(file string, path string, info *dirInfo) { info.tsConfigJson.absPathBaseUrl = &baseUrl } } + if pathsJson, ok := getProperty(compilerOptionsJson, "paths"); ok { + if paths, ok := pathsJson.Data.(*ast.EObject); ok { + info.tsConfigJson.paths = map[string][]string{} + for _, prop := range paths.Properties { + if key, ok := getString(prop.Key); ok { + if value, ok := getProperty(pathsJson, key); ok { + if array, ok := value.Data.(*ast.EArray); ok { + for _, item := range array.Items { + if str, ok := getString(item); ok { + // If this is a string, it's a replacement module + info.tsConfigJson.paths[key] = append(info.tsConfigJson.paths[key], str) + } + } + } + } + } + } + } + } } } } @@ -578,13 +598,47 @@ func (r *resolver) loadAsFileOrDirectory(path string) (string, bool) { return "", false } +func isTsConfigPathMatch(pattern string, path string) (string, bool) { + starIndex := strings.IndexRune(pattern, '*') + if starIndex == -1 { + firstPath := strings.Split(path, "/")[0] + return strings.Replace(path, pattern+"/", "", 1), firstPath == pattern + } + + elements := strings.Split(pattern, "*") + if starIndex == 0 { + suffix := elements[1] + return strings.Replace(path, suffix, "", 1), strings.HasSuffix(path, suffix) + } + prefix := elements[0] + return strings.Replace(path, prefix, "", 1), strings.HasPrefix(path, prefix) +} + func (r *resolver) loadNodeModules(path string, dirInfo *dirInfo) (string, bool) { for { // Handle TypeScript base URLs for TypeScript code - if dirInfo.tsConfigJson != nil && dirInfo.tsConfigJson.absPathBaseUrl != nil { - basePath := r.fs.Join(*dirInfo.tsConfigJson.absPathBaseUrl, path) - if absolute, ok := r.loadAsFileOrDirectory(basePath); ok { - return absolute, true + if dirInfo.tsConfigJson != nil { + + if dirInfo.tsConfigJson.absPathBaseUrl != nil { + if dirInfo.tsConfigJson.paths != nil { + for key, originalPaths := range dirInfo.tsConfigJson.paths { + for _, originalPath := range originalPaths { + if parts, ok := isTsConfigPathMatch(key, path); ok { + absoluteOriginalPath := r.fs.Join(*dirInfo.tsConfigJson.absPathBaseUrl, originalPath) + basePath := strings.Replace(absoluteOriginalPath, "*", parts, 1) + if absolute, ok := r.loadAsFileOrDirectory(basePath); ok { + return absolute, true + } + } + } + + } + } + basePath := r.fs.Join(*dirInfo.tsConfigJson.absPathBaseUrl, path) + if absolute, ok := r.loadAsFileOrDirectory(basePath); ok { + return absolute, true + } + } }