Skip to content
This repository was archived by the owner on Sep 16, 2021. It is now read-only.

Commit 37259c3

Browse files
TypeScript Teamalexeagle
TypeScript Team
authored andcommitted
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 angular/tsickle#948 PiperOrigin-RevId: 223763346
1 parent cd91a8c commit 37259c3

File tree

2 files changed

+73
-5
lines changed

2 files changed

+73
-5
lines changed

internal/tsc_wrapped/compiler_host.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ function validateBazelOptions(bazelOpts: BazelOptions) {
5252
}
5353
}
5454

55-
const TS_EXT = /(\.d)?\.tsx?$/;
55+
const SOURCE_EXT = /((\.d)?\.tsx?|\.js)$/;
5656

5757
/**
5858
* CompilerHost that knows how to cache parsed files to improve compile times.
@@ -258,7 +258,7 @@ export class CompilerHost implements ts.CompilerHost, tsickle.TsickleHost {
258258
}
259259
if (resolvedPath) {
260260
// Strip file extensions.
261-
importPath = resolvedPath.replace(TS_EXT, '');
261+
importPath = resolvedPath.replace(SOURCE_EXT, '');
262262
// Make sure all module names include the workspace name.
263263
if (importPath.indexOf(this.bazelOpts.workspaceName) !== 0) {
264264
importPath = path.posix.join(this.bazelOpts.workspaceName, importPath);
@@ -302,7 +302,7 @@ export class CompilerHost implements ts.CompilerHost, tsickle.TsickleHost {
302302
if (!this.shouldNameModule(sf.fileName)) return undefined;
303303
// /build/work/bazel-out/local-fastbuild/bin/path/to/file.ts
304304
// -> path/to/file
305-
let fileName = this.rootDirsRelative(sf.fileName).replace(TS_EXT, '');
305+
let fileName = this.rootDirsRelative(sf.fileName).replace(SOURCE_EXT, '');
306306

307307
let workspace = this.bazelOpts.workspaceName;
308308

@@ -325,7 +325,7 @@ export class CompilerHost implements ts.CompilerHost, tsickle.TsickleHost {
325325
const relativeFileName = path.posix.relative(this.bazelOpts.package, fileName);
326326
if (!relativeFileName.startsWith('..')) {
327327
if (this.bazelOpts.moduleRoot &&
328-
this.bazelOpts.moduleRoot.replace(TS_EXT, '') ===
328+
this.bazelOpts.moduleRoot.replace(SOURCE_EXT, '') ===
329329
relativeFileName) {
330330
return this.bazelOpts.moduleName;
331331
}

internal/tsc_wrapped/tsc_wrapped.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,12 @@ function runFromOptions(
190190
const compilerHostDelegate =
191191
ts.createCompilerHost({target: ts.ScriptTarget.ES5});
192192

193+
const moduleResolver = bazelOpts.isJsTranspilation ?
194+
makeJsModuleResolver(bazelOpts.workspaceName) :
195+
ts.resolveModuleName;
193196
const compilerHost = new CompilerHost(
194-
files, options, bazelOpts, compilerHostDelegate, fileLoader);
197+
files, options, bazelOpts, compilerHostDelegate, fileLoader,
198+
moduleResolver);
195199

196200

197201
const oldProgram = cache.getProgram(bazelOpts.target);
@@ -332,6 +336,70 @@ function mkdirp(base: string, subdir: string) {
332336
}
333337

334338

339+
/**
340+
* Resolve module filenames for JS modules.
341+
*
342+
* JS module resolution needs to be different because when transpiling JS we
343+
* do not pass in any dependencies, so the TS module resolver will not resolve
344+
* any files.
345+
*
346+
* Fortunately, JS module resolution is very simple. The imported module name
347+
* must either a relative path, or the workspace root (i.e. 'google3'),
348+
* so we can perform module resolution entirely based on file names, without
349+
* looking at the filesystem.
350+
*/
351+
function makeJsModuleResolver(workspaceName: string) {
352+
// The literal '/' here is cross-platform safe because it's matching on
353+
// import specifiers, not file names.
354+
const workspaceModuleSpecifierPrefix = `${workspaceName}/`;
355+
const workspaceDir = `${path.sep}${workspaceName}${path.sep}`;
356+
function jsModuleResolver(
357+
moduleName: string, containingFile: string,
358+
compilerOptions: ts.CompilerOptions, host: ts.ModuleResolutionHost):
359+
ts.ResolvedModuleWithFailedLookupLocations {
360+
let resolvedFileName;
361+
if (containingFile === '') {
362+
// In tsickle we resolve the filename against '' to get the goog module
363+
// name of a sourcefile.
364+
resolvedFileName = moduleName;
365+
} else if (moduleName.startsWith(workspaceModuleSpecifierPrefix)) {
366+
// Given a workspace name of 'foo', we want to resolve import specifiers
367+
// like: 'foo/project/file.js' to the absolute filesystem path of
368+
// project/file.js within the workspace.
369+
const workspaceDirLocation = containingFile.indexOf(workspaceDir);
370+
if (workspaceDirLocation < 0) {
371+
return {resolvedModule: undefined};
372+
}
373+
const absolutePathToWorkspaceDir =
374+
containingFile.slice(0, workspaceDirLocation);
375+
resolvedFileName = path.join(absolutePathToWorkspaceDir, moduleName);
376+
} else {
377+
if (!moduleName.startsWith('./') && !moduleName.startsWith('../')) {
378+
throw new Error(
379+
`Unsupported module import specifier: ${
380+
JSON.stringify(moduleName)}.\n` +
381+
`JS module imports must either be relative paths ` +
382+
`(beginning with '.' or '..'), ` +
383+
`or they must begin with '${workspaceName}/'.`);
384+
}
385+
resolvedFileName = path.join(path.dirname(containingFile), moduleName);
386+
}
387+
return {
388+
resolvedModule: {
389+
resolvedFileName,
390+
extension: ts.Extension.Js, // js can only import js
391+
// These two fields are cargo culted from what ts.resolveModuleName
392+
// seems to return.
393+
packageId: undefined,
394+
isExternalLibraryImport: false,
395+
}
396+
};
397+
}
398+
399+
return jsModuleResolver;
400+
}
401+
402+
335403
if (require.main === module) {
336404
// Do not call process.exit(), as that terminates the binary before
337405
// completing pending operations, such as writing to stdout or emitting the

0 commit comments

Comments
 (0)