Skip to content

Commit

Permalink
perf(compiler-cli): reduce filesystem hits during resource resolution (
Browse files Browse the repository at this point in the history
…#39604)

The resource loader uses TypeScript's module resolution system to
determine at which locations it needs to look for a resource file. A
marker string is used to force the module resolution to fail, such that
all failed lookup locations can then be considered for actual resource
resolution. Any filesystem requests targeting files/directories that
contain the marker are known not to exist, so no filesystem request
needs to be done at all.

PR Close #39604
  • Loading branch information
JoostK authored and atscott committed Nov 12, 2020
1 parent 7ab31c9 commit a7adcbd
Showing 1 changed file with 41 additions and 3 deletions.
44 changes: 41 additions & 3 deletions packages/compiler-cli/src/ngtsc/resource/src/loader.ts
Expand Up @@ -11,15 +11,20 @@ import * as ts from 'typescript';
import {ResourceLoader} from '../../annotations';
import {NgCompilerAdapter} from '../../core/api';
import {AbsoluteFsPath, join, PathSegment} from '../../file_system';
import {RequiredDelegations} from '../../util/src/typescript';

const CSS_PREPROCESSOR_EXT = /(\.scss|\.sass|\.less|\.styl)$/;

const RESOURCE_MARKER = '.$ngresource$';
const RESOURCE_MARKER_TS = RESOURCE_MARKER + '.ts';

/**
* `ResourceLoader` which delegates to an `NgCompilerAdapter`'s resource loading methods.
*/
export class AdapterResourceLoader implements ResourceLoader {
private cache = new Map<string, string>();
private fetching = new Map<string, Promise<void>>();
private lookupResolutionHost = createLookupResolutionHost(this.adapter);

canPreload = !!this.adapter.readResource;

Expand Down Expand Up @@ -167,7 +172,7 @@ export class AdapterResourceLoader implements ResourceLoader {
ts.ResolvedModuleWithFailedLookupLocations&{failedLookupLocations: ReadonlyArray<string>};

// clang-format off
const failedLookup = ts.resolveModuleName(url + '.$ngresource$', fromFile, this.options, this.adapter) as ResolvedModuleWithFailedLookupLocations;
const failedLookup = ts.resolveModuleName(url + RESOURCE_MARKER, fromFile, this.options, this.lookupResolutionHost) as ResolvedModuleWithFailedLookupLocations;
// clang-format on
if (failedLookup.failedLookupLocations === undefined) {
throw new Error(
Expand All @@ -176,7 +181,40 @@ export class AdapterResourceLoader implements ResourceLoader {
}

return failedLookup.failedLookupLocations
.filter(candidate => candidate.endsWith('.$ngresource$.ts'))
.map(candidate => candidate.replace(/\.\$ngresource\$\.ts$/, ''));
.filter(candidate => candidate.endsWith(RESOURCE_MARKER_TS))
.map(candidate => candidate.slice(0, -RESOURCE_MARKER_TS.length));
}
}

/**
* Derives a `ts.ModuleResolutionHost` from a compiler adapter that recognizes the special resource
* marker and does not go to the filesystem for these requests, as they are known not to exist.
*/
function createLookupResolutionHost(adapter: NgCompilerAdapter):
RequiredDelegations<ts.ModuleResolutionHost> {
return {
directoryExists(directoryName: string): boolean {
if (directoryName.includes(RESOURCE_MARKER)) {
return false;
} else if (adapter.directoryExists !== undefined) {
return adapter.directoryExists(directoryName);
} else {
// TypeScript's module resolution logic assumes that the directory exists when no host
// implementation is available.
return true;
}
},
fileExists(fileName: string): boolean {
if (fileName.includes(RESOURCE_MARKER)) {
return false;
} else {
return adapter.fileExists(fileName);
}
},
readFile: adapter.readFile.bind(adapter),
getCurrentDirectory: adapter.getCurrentDirectory.bind(adapter),
getDirectories: adapter.getDirectories?.bind(adapter),
realpath: adapter.realpath?.bind(adapter),
trace: adapter.trace?.bind(adapter),
};
}

0 comments on commit a7adcbd

Please sign in to comment.