perf(compiler-cli): reduce filesystem hits during resource resolution (#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
This commit is contained in:
parent
7c161e1679
commit
ade6da95e4
@ -11,15 +11,20 @@ import * as ts from 'typescript';
|
|||||||
import {ResourceLoader} from '../../annotations';
|
import {ResourceLoader} from '../../annotations';
|
||||||
import {NgCompilerAdapter} from '../../core/api';
|
import {NgCompilerAdapter} from '../../core/api';
|
||||||
import {AbsoluteFsPath, join, PathSegment} from '../../file_system';
|
import {AbsoluteFsPath, join, PathSegment} from '../../file_system';
|
||||||
|
import {RequiredDelegations} from '../../util/src/typescript';
|
||||||
|
|
||||||
const CSS_PREPROCESSOR_EXT = /(\.scss|\.sass|\.less|\.styl)$/;
|
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.
|
* `ResourceLoader` which delegates to an `NgCompilerAdapter`'s resource loading methods.
|
||||||
*/
|
*/
|
||||||
export class AdapterResourceLoader implements ResourceLoader {
|
export class AdapterResourceLoader implements ResourceLoader {
|
||||||
private cache = new Map<string, string>();
|
private cache = new Map<string, string>();
|
||||||
private fetching = new Map<string, Promise<void>>();
|
private fetching = new Map<string, Promise<void>>();
|
||||||
|
private lookupResolutionHost = createLookupResolutionHost(this.adapter);
|
||||||
|
|
||||||
canPreload = !!this.adapter.readResource;
|
canPreload = !!this.adapter.readResource;
|
||||||
|
|
||||||
@ -167,7 +172,7 @@ export class AdapterResourceLoader implements ResourceLoader {
|
|||||||
ts.ResolvedModuleWithFailedLookupLocations&{failedLookupLocations: ReadonlyArray<string>};
|
ts.ResolvedModuleWithFailedLookupLocations&{failedLookupLocations: ReadonlyArray<string>};
|
||||||
|
|
||||||
// clang-format off
|
// 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
|
// clang-format on
|
||||||
if (failedLookup.failedLookupLocations === undefined) {
|
if (failedLookup.failedLookupLocations === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -176,7 +181,40 @@ export class AdapterResourceLoader implements ResourceLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return failedLookup.failedLookupLocations
|
return failedLookup.failedLookupLocations
|
||||||
.filter(candidate => candidate.endsWith('.$ngresource$.ts'))
|
.filter(candidate => candidate.endsWith(RESOURCE_MARKER_TS))
|
||||||
.map(candidate => candidate.replace(/\.\$ngresource\$\.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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user