Skip to content

Avoid unnecessary buildInfo read if host supports caching it (avoids in --build scenario) and some reporting cleanup #51403

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 5, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 23 additions & 26 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -705,17 +705,7 @@ namespace ts {
/** File that isnt present resulting in error or output files */
export type EmitUsingBuildInfoResult = string | readonly OutputFile[];

/*@internal*/
export interface EmitUsingBuildInfoHost extends ModuleResolutionHost {
getCurrentDirectory(): string;
getCanonicalFileName(fileName: string): string;
useCaseSensitiveFileNames(): boolean;
getNewLine(): string;
createHash?(data: string): string;
getBuildInfo?(fileName: string, configFilePath: string | undefined): BuildInfo | undefined;
}

function createSourceFilesFromBundleBuildInfo(bundle: BundleBuildInfo, buildInfoDirectory: string, host: EmitUsingBuildInfoHost): readonly SourceFile[] {
function createSourceFilesFromBundleBuildInfo(bundle: BundleBuildInfo, buildInfoDirectory: string, host: CompilerHost): readonly SourceFile[] {
const jsBundle = Debug.checkDefined(bundle.js);
const prologueMap = jsBundle.sources?.prologues && arrayToMap(jsBundle.sources.prologues, prologueInfo => prologueInfo.file);
return bundle.sourceFiles.map((fileName, index) => {
Expand Down Expand Up @@ -745,22 +735,29 @@ namespace ts {
/*@internal*/
export function emitUsingBuildInfo(
config: ParsedCommandLine,
host: EmitUsingBuildInfoHost,
host: CompilerHost,
getCommandLine: (ref: ProjectReference) => ParsedCommandLine | undefined,
customTransformers?: CustomTransformers
): EmitUsingBuildInfoResult {
tracing?.push(tracing.Phase.Emit, "emitUsingBuildInfo", {}, /*separateBeginAndEnd*/ true);
performance.mark("beforeEmit");
const result = emitUsingBuildInfoWorker(config, host, getCommandLine, customTransformers);
performance.mark("afterEmit");
performance.measure("Emit", "beforeEmit", "afterEmit");
tracing?.pop();
return result;
}

function emitUsingBuildInfoWorker(
config: ParsedCommandLine,
host: CompilerHost,
getCommandLine: (ref: ProjectReference) => ParsedCommandLine | undefined,
customTransformers?: CustomTransformers
): EmitUsingBuildInfoResult {
const createHash = maybeBind(host, host.createHash);
const { buildInfoPath, jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath } = getOutputPathsForBundle(config.options, /*forceDtsPaths*/ false);
let buildInfo: BuildInfo | undefined;
if (host.getBuildInfo) {
// If host directly provides buildinfo we can get it directly. This allows host to cache the buildinfo
buildInfo = host.getBuildInfo(buildInfoPath!, config.options.configFilePath);
}
else {
const buildInfoText = host.readFile(buildInfoPath!);
if (!buildInfoText) return buildInfoPath!;
buildInfo = getBuildInfo(buildInfoPath!, buildInfoText);
}
// If host directly provides buildinfo we can get it directly. This allows host to cache the buildinfo
const buildInfo = host.getBuildInfo!(buildInfoPath!, config.options.configFilePath);
if (!buildInfo) return buildInfoPath!;
if (!buildInfo.bundle || !buildInfo.bundle.js || (declarationFilePath && !buildInfo.bundle.dts)) return buildInfoPath!;

Expand Down Expand Up @@ -797,14 +794,14 @@ namespace ts {
/*onlyOwnText*/ true
);
const outputFiles: OutputFile[] = [];
const prependNodes = createPrependNodes(config.projectReferences, getCommandLine, f => host.readFile(f));
const prependNodes = createPrependNodes(config.projectReferences, getCommandLine, f => host.readFile(f), host);
const sourceFilesForJsEmit = createSourceFilesFromBundleBuildInfo(buildInfo.bundle, buildInfoDirectory, host);
let changedDtsText: string | undefined;
let changedDtsData: WriteFileCallbackData | undefined;
const emitHost: EmitHost = {
getPrependNodes: memoize(() => [...prependNodes, ownPrependInput]),
getCanonicalFileName: host.getCanonicalFileName,
getCommonSourceDirectory: () => getNormalizedAbsolutePath(buildInfo!.bundle!.commonSourceDirectory, buildInfoDirectory),
getCommonSourceDirectory: () => getNormalizedAbsolutePath(buildInfo.bundle!.commonSourceDirectory, buildInfoDirectory),
getCompilerOptions: () => config.options,
getCurrentDirectory: () => host.getCurrentDirectory(),
getNewLine: () => host.getNewLine(),
Expand All @@ -826,13 +823,13 @@ namespace ts {
break;
case buildInfoPath:
const newBuildInfo = data!.buildInfo!;
newBuildInfo.program = buildInfo!.program;
newBuildInfo.program = buildInfo.program;
if (newBuildInfo.program && changedDtsText !== undefined && config.options.composite) {
// Update the output signature
(newBuildInfo.program as ProgramBundleEmitBuildInfo).outSignature = computeSignature(changedDtsText, createHash, changedDtsData);
}
// Update sourceFileInfo
const { js, dts, sourceFiles } = buildInfo!.bundle!;
const { js, dts, sourceFiles } = buildInfo.bundle!;
newBuildInfo.bundle!.js!.sources = js!.sources;
if (dts) {
newBuildInfo.bundle!.dts!.sources = dts.sources;
Expand Down
44 changes: 31 additions & 13 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6750,6 +6750,17 @@ namespace ts {
declarationMapPath: string | undefined,
buildInfoPath: string | undefined
): InputFiles;
/*@internal*/
export function createInputFiles(
readFileText: (path: string) => string | undefined,
javascriptPath: string,
javascriptMapPath: string | undefined,
declarationPath: string,
declarationMapPath: string | undefined,
buildInfoPath: string | undefined,
host: CompilerHost, // eslint-disable-line @typescript-eslint/unified-signatures
options: CompilerOptions,
): InputFiles;
export function createInputFiles(
javascriptText: string,
declarationText: string,
Expand Down Expand Up @@ -6779,15 +6790,17 @@ namespace ts {
javascriptMapTextOrDeclarationPath?: string,
declarationMapPath?: string,
declarationMapTextOrBuildInfoPath?: string,
javascriptPath?: string | undefined,
declarationPath?: string | undefined,
javascriptPathOrHost?: string | CompilerHost | undefined,
declarationPathOrOptions?: string | CompilerOptions | undefined,
buildInfoPath?: string | undefined,
buildInfo?: BuildInfo,
oldFileOfCurrentEmit?: boolean
): InputFiles {
const node = parseNodeFactory.createInputFiles();
if (!isString(javascriptTextOrReadFileText)) {
const cache = new Map<string, string | false>();
const host = javascriptPathOrHost as CompilerHost | undefined;
const options = declarationPathOrOptions as CompilerOptions;
const textGetter = (path: string | undefined) => {
if (path === undefined) return undefined;
let value = cache.get(path);
Expand All @@ -6802,10 +6815,15 @@ namespace ts {
return result !== undefined ? result : `/* Input file ${path} was missing */\r\n`;
};
let buildInfo: BuildInfo | false;
const getAndCacheBuildInfo = (getText: () => string | undefined) => {
if (buildInfo === undefined) {
const result = getText();
buildInfo = result !== undefined ? getBuildInfo(node.buildInfoPath!, result) ?? false : false;
const getAndCacheBuildInfo = () => {
if (buildInfo === undefined && declarationMapTextOrBuildInfoPath) {
if (host?.getBuildInfo) {
buildInfo = host.getBuildInfo(declarationMapTextOrBuildInfoPath, options.configFilePath) ?? false;
}
else {
const result = textGetter(declarationMapTextOrBuildInfoPath);
buildInfo = result !== undefined ? getBuildInfo(declarationMapTextOrBuildInfoPath, result) ?? false : false;
}
}
return buildInfo || undefined;
};
Expand All @@ -6815,11 +6833,11 @@ namespace ts {
node.declarationMapPath = declarationMapPath;
node.buildInfoPath = declarationMapTextOrBuildInfoPath;
Object.defineProperties(node, {
javascriptText: { get() { return definedTextGetter(declarationTextOrJavascriptPath); } },
javascriptMapText: { get() { return textGetter(javascriptMapPath); } }, // TODO:: if there is inline sourceMap in jsFile, use that
declarationText: { get() { return definedTextGetter(Debug.checkDefined(javascriptMapTextOrDeclarationPath)); } },
declarationMapText: { get() { return textGetter(declarationMapPath); } }, // TODO:: if there is inline sourceMap in dtsFile, use that
buildInfo: { get() { return getAndCacheBuildInfo(() => textGetter(declarationMapTextOrBuildInfoPath)); } }
javascriptText: { get: () => definedTextGetter(declarationTextOrJavascriptPath) },
javascriptMapText: { get: () => textGetter(javascriptMapPath) }, // TODO:: if there is inline sourceMap in jsFile, use that
declarationText: { get: () => definedTextGetter(Debug.checkDefined(javascriptMapTextOrDeclarationPath)) },
declarationMapText: { get: () => textGetter(declarationMapPath) }, // TODO:: if there is inline sourceMap in dtsFile, use that
buildInfo: { get: getAndCacheBuildInfo },
});
}
else {
Expand All @@ -6829,8 +6847,8 @@ namespace ts {
node.declarationText = declarationTextOrJavascriptPath;
node.declarationMapPath = declarationMapPath;
node.declarationMapText = declarationMapTextOrBuildInfoPath;
node.javascriptPath = javascriptPath;
node.declarationPath = declarationPath;
node.javascriptPath = javascriptPathOrHost as string | undefined;
node.declarationPath = declarationPathOrOptions as string | undefined;
node.buildInfoPath = buildInfoPath;
node.buildInfo = buildInfo;
node.oldFileOfCurrentEmit = oldFileOfCurrentEmit;
Expand Down
77 changes: 51 additions & 26 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,16 @@ namespace ts {
}

/*@internal*/
export function createCompilerHostWorker(options: CompilerOptions, setParentNodes?: boolean, system = sys): CompilerHost {
const existingDirectories = new Map<string, boolean>();
const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames);
function getSourceFile(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void): SourceFile | undefined {
export function createGetSourceFile(
readFile: ProgramHost<any>["readFile"],
getCompilerOptions: () => CompilerOptions,
setParentNodes: boolean | undefined
): CompilerHost["getSourceFile"] {
return (fileName, languageVersionOrOptions, onError) => {
let text: string | undefined;
try {
performance.mark("beforeIORead");
text = compilerHost.readFile(fileName);
text = readFile(fileName, getCompilerOptions().charset);
performance.mark("afterIORead");
performance.measure("I/O Read", "beforeIORead", "afterIORead");
}
Expand All @@ -81,20 +83,16 @@ namespace ts {
text = "";
}
return text !== undefined ? createSourceFile(fileName, text, languageVersionOrOptions, setParentNodes) : undefined;
}

function directoryExists(directoryPath: string): boolean {
if (existingDirectories.has(directoryPath)) {
return true;
}
if ((compilerHost.directoryExists || system.directoryExists)(directoryPath)) {
existingDirectories.set(directoryPath, true);
return true;
}
return false;
}
};
}

function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) {
/*@internal*/
export function createWriteFileMeasuringIO(
actualWriteFile: (path: string, data: string, writeByteOrderMark: boolean) => void,
createDirectory: (path: string) => void,
directoryExists: (path: string) => boolean
): CompilerHost["writeFile"] {
return (fileName, data, writeByteOrderMark, onError) => {
try {
performance.mark("beforeIOWrite");

Expand All @@ -105,9 +103,10 @@ namespace ts {
fileName,
data,
writeByteOrderMark,
(path, data, writeByteOrderMark) => system.writeFile(path, data, writeByteOrderMark),
path => (compilerHost.createDirectory || system.createDirectory)(path),
path => directoryExists(path));
actualWriteFile,
createDirectory,
directoryExists
);

performance.mark("afterIOWrite");
performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite");
Expand All @@ -117,6 +116,22 @@ namespace ts {
onError(e.message);
}
}
};
}

/*@internal*/
export function createCompilerHostWorker(options: CompilerOptions, setParentNodes?: boolean, system = sys): CompilerHost {
const existingDirectories = new Map<string, boolean>();
const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames);
function directoryExists(directoryPath: string): boolean {
if (existingDirectories.has(directoryPath)) {
return true;
}
if ((compilerHost.directoryExists || system.directoryExists)(directoryPath)) {
existingDirectories.set(directoryPath, true);
return true;
}
return false;
}

function getDefaultLibLocation(): string {
Expand All @@ -126,10 +141,14 @@ namespace ts {
const newLine = getNewLineCharacter(options, () => system.newLine);
const realpath = system.realpath && ((path: string) => system.realpath!(path));
const compilerHost: CompilerHost = {
getSourceFile,
getSourceFile: createGetSourceFile(fileName => compilerHost.readFile(fileName), () => options, setParentNodes),
getDefaultLibLocation,
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
writeFile,
writeFile: createWriteFileMeasuringIO(
(path, data, writeByteOrderMark) => system.writeFile(path, data, writeByteOrderMark),
path => (compilerHost.createDirectory || system.createDirectory)(path),
path => directoryExists(path),
),
getCurrentDirectory: memoize(() => system.getCurrentDirectory()),
useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames,
getCanonicalFileName,
Expand Down Expand Up @@ -2002,7 +2021,8 @@ namespace ts {
const path = toPath(fileName);
const sourceFile = getSourceFileByPath(path);
return sourceFile ? sourceFile.text : filesByName.has(path) ? undefined : host.readFile(path);
}
},
host,
);
}

Expand Down Expand Up @@ -4292,7 +4312,12 @@ namespace ts {
}

/* @internal */
export function createPrependNodes(projectReferences: readonly ProjectReference[] | undefined, getCommandLine: (ref: ProjectReference, index: number) => ParsedCommandLine | undefined, readFile: (path: string) => string | undefined) {
export function createPrependNodes(
projectReferences: readonly ProjectReference[] | undefined,
getCommandLine: (ref: ProjectReference, index: number) => ParsedCommandLine | undefined,
readFile: (path: string) => string | undefined,
host: CompilerHost,
) {
if (!projectReferences) return emptyArray;
let nodes: InputFiles[] | undefined;
for (let i = 0; i < projectReferences.length; i++) {
Expand All @@ -4304,7 +4329,7 @@ namespace ts {
if (!out) continue;

const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(resolvedRefOpts.options, /*forceDtsPaths*/ true);
const node = createInputFiles(readFile, jsFilePath!, sourceMapFilePath, declarationFilePath!, declarationMapPath, buildInfoPath);
const node = createInputFiles(readFile, jsFilePath!, sourceMapFilePath, declarationFilePath!, declarationMapPath, buildInfoPath, host, resolvedRefOpts.options);
(nodes || (nodes = [])).push(node);
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/compiler/tsbuildPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ namespace ts {
// TODO: To do better with watch mode and normal build mode api that creates program and emits files
// This currently helps enable --diagnostics and --extendedDiagnostics
afterProgramEmitAndDiagnostics?(program: T): void;
/*@internal*/ beforeEmitBundle?(config: ParsedCommandLine): void;
/*@internal*/ afterEmitBundle?(config: ParsedCommandLine): void;

// For testing
Expand Down Expand Up @@ -253,7 +254,7 @@ namespace ts {
readonly projectPendingBuild: ESMap<ResolvedConfigFilePath, ConfigFileProgramReloadLevel>;
readonly projectErrorsReported: ESMap<ResolvedConfigFilePath, true>;

readonly compilerHost: CompilerHost;
readonly compilerHost: CompilerHost & ReadBuildProgramHost;
readonly moduleResolutionCache: ModuleResolutionCache | undefined;
readonly typeReferenceDirectiveResolutionCache: TypeReferenceDirectiveResolutionCache | undefined;

Expand Down Expand Up @@ -1087,6 +1088,7 @@ namespace ts {
// Update js, and source map
const { compilerHost } = state;
state.projectCompilerOptions = config.options;
state.host.beforeEmitBundle?.(config);
const outputFiles = emitUsingBuildInfo(
config,
compilerHost,
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7234,6 +7234,7 @@ namespace ts {
// For testing:
/*@internal*/ disableUseFileVersionAsSignature?: boolean;
/*@internal*/ storeFilesChangingSignatureDuringEmit?: boolean;
/*@internal*/ getBuildInfo?(fileName: string, configFilePath: string | undefined): BuildInfo | undefined;
}

/** true if --out otherwise source file name */
Expand Down
Loading