When transpiling JS with the TS compiler, use a custom JS module resolver that is significantly simpler than TypeScript's module resolver. Specifically, it does no inference and does not look at the file system. It accepts relative path imports, and workspace-rooted import paths (i.e. google3/foo/bar.js).

This change builds on top of https://github.com/angular/tsickle/pull/948

PiperOrigin-RevId: 223763346
diff --git a/internal/tsc_wrapped/compiler_host.ts b/internal/tsc_wrapped/compiler_host.ts
index cb1b1fc..f914eb9 100644
--- a/internal/tsc_wrapped/compiler_host.ts
+++ b/internal/tsc_wrapped/compiler_host.ts
@@ -52,7 +52,7 @@
   }
 }
 
-const TS_EXT = /(\.d)?\.tsx?$/;
+const SOURCE_EXT = /((\.d)?\.tsx?|\.js)$/;
 
 /**
  * CompilerHost that knows how to cache parsed files to improve compile times.
@@ -258,7 +258,7 @@
     }
     if (resolvedPath) {
       // Strip file extensions.
-      importPath = resolvedPath.replace(TS_EXT, '');
+      importPath = resolvedPath.replace(SOURCE_EXT, '');
       // Make sure all module names include the workspace name.
       if (importPath.indexOf(this.bazelOpts.workspaceName) !== 0) {
         importPath = path.posix.join(this.bazelOpts.workspaceName, importPath);
@@ -302,7 +302,7 @@
     if (!this.shouldNameModule(sf.fileName)) return undefined;
     // /build/work/bazel-out/local-fastbuild/bin/path/to/file.ts
     // -> path/to/file
-    let fileName = this.rootDirsRelative(sf.fileName).replace(TS_EXT, '');
+    let fileName = this.rootDirsRelative(sf.fileName).replace(SOURCE_EXT, '');
 
     let workspace = this.bazelOpts.workspaceName;
 
@@ -325,7 +325,7 @@
       const relativeFileName = path.posix.relative(this.bazelOpts.package, fileName);
       if (!relativeFileName.startsWith('..')) {
         if (this.bazelOpts.moduleRoot &&
-            this.bazelOpts.moduleRoot.replace(TS_EXT, '') ===
+            this.bazelOpts.moduleRoot.replace(SOURCE_EXT, '') ===
                 relativeFileName) {
           return this.bazelOpts.moduleName;
         }
diff --git a/internal/tsc_wrapped/tsc_wrapped.ts b/internal/tsc_wrapped/tsc_wrapped.ts
index 259f7d7..f2bb9ab 100644
--- a/internal/tsc_wrapped/tsc_wrapped.ts
+++ b/internal/tsc_wrapped/tsc_wrapped.ts
@@ -190,8 +190,12 @@
   const compilerHostDelegate =
       ts.createCompilerHost({target: ts.ScriptTarget.ES5});
 
+  const moduleResolver = bazelOpts.isJsTranspilation ?
+      makeJsModuleResolver(bazelOpts.workspaceName) :
+      ts.resolveModuleName;
   const compilerHost = new CompilerHost(
-      files, options, bazelOpts, compilerHostDelegate, fileLoader);
+      files, options, bazelOpts, compilerHostDelegate, fileLoader,
+      moduleResolver);
 
 
   const oldProgram = cache.getProgram(bazelOpts.target);
@@ -332,6 +336,70 @@
 }
 
 
+/**
+ * Resolve module filenames for JS modules.
+ *
+ * JS module resolution needs to be different because when transpiling JS we
+ * do not pass in any dependencies, so the TS module resolver will not resolve
+ * any files.
+ *
+ * Fortunately, JS module resolution is very simple. The imported module name
+ * must either a relative path, or the workspace root (i.e. 'google3'),
+ * so we can perform module resolution entirely based on file names, without
+ * looking at the filesystem.
+ */
+function makeJsModuleResolver(workspaceName: string) {
+  // The literal '/' here is cross-platform safe because it's matching on
+  // import specifiers, not file names.
+  const workspaceModuleSpecifierPrefix = `${workspaceName}/`;
+  const workspaceDir = `${path.sep}${workspaceName}${path.sep}`;
+  function jsModuleResolver(
+      moduleName: string, containingFile: string,
+      compilerOptions: ts.CompilerOptions, host: ts.ModuleResolutionHost):
+      ts.ResolvedModuleWithFailedLookupLocations {
+    let resolvedFileName;
+    if (containingFile === '') {
+      // In tsickle we resolve the filename against '' to get the goog module
+      // name of a sourcefile.
+      resolvedFileName = moduleName;
+    } else if (moduleName.startsWith(workspaceModuleSpecifierPrefix)) {
+      // Given a workspace name of 'foo', we want to resolve import specifiers
+      // like: 'foo/project/file.js' to the absolute filesystem path of
+      // project/file.js within the workspace.
+      const workspaceDirLocation = containingFile.indexOf(workspaceDir);
+      if (workspaceDirLocation < 0) {
+        return {resolvedModule: undefined};
+      }
+      const absolutePathToWorkspaceDir =
+          containingFile.slice(0, workspaceDirLocation);
+      resolvedFileName = path.join(absolutePathToWorkspaceDir, moduleName);
+    } else {
+      if (!moduleName.startsWith('./') && !moduleName.startsWith('../')) {
+        throw new Error(
+            `Unsupported module import specifier: ${
+                JSON.stringify(moduleName)}.\n` +
+            `JS module imports must either be relative paths ` +
+            `(beginning with '.' or '..'), ` +
+            `or they must begin with '${workspaceName}/'.`);
+      }
+      resolvedFileName = path.join(path.dirname(containingFile), moduleName);
+    }
+    return {
+      resolvedModule: {
+        resolvedFileName,
+        extension: ts.Extension.Js,  // js can only import js
+        // These two fields are cargo culted from what ts.resolveModuleName
+        // seems to return.
+        packageId: undefined,
+        isExternalLibraryImport: false,
+      }
+    };
+  }
+
+  return jsModuleResolver;
+}
+
+
 if (require.main === module) {
   // Do not call process.exit(), as that terminates the binary before
   // completing pending operations, such as writing to stdout or emitting the