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 all 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
57 changes: 27 additions & 30 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 All @@ -783,28 +780,28 @@ namespace ts {
if (declarationMapPath && computeSignature(declarationMapText!, createHash) !== buildInfo.bundle.dts!.mapHash) return declarationMapPath;

const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath!, host.getCurrentDirectory()));
const ownPrependInput = createInputFiles(
const ownPrependInput = createInputFilesWithFileTexts(
jsFilePath,
jsFileText,
declarationText!,
sourceMapFilePath,
sourceMapText,
declarationFilePath,
declarationText!,
declarationMapPath,
declarationMapText,
jsFilePath,
declarationFilePath,
buildInfoPath,
buildInfo,
/*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
170 changes: 100 additions & 70 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6742,14 +6742,6 @@ namespace ts {
javascriptText: string,
declarationText: string
): InputFiles;
export function createInputFiles(
readFileText: (path: string) => string | undefined,
javascriptPath: string,
javascriptMapPath: string | undefined,
declarationPath: string,
declarationMapPath: string | undefined,
buildInfoPath: string | undefined
): InputFiles;
export function createInputFiles(
javascriptText: string,
declarationText: string,
Expand All @@ -6758,19 +6750,13 @@ namespace ts {
declarationMapPath: string | undefined,
declarationMapText: string | undefined
): InputFiles;
/*@internal*/
export function createInputFiles(
javascriptText: string,
declarationText: string,
readFileText: (path: string) => string | undefined,
javascriptPath: string,
javascriptMapPath: string | undefined,
javascriptMapText: string | undefined,
declarationPath: string,
declarationMapPath: string | undefined,
declarationMapText: string | undefined,
javascriptPath: string | undefined,
declarationPath: string | undefined,
buildInfoPath?: string | undefined,
buildInfo?: BuildInfo,
oldFileOfCurrentEmit?: boolean
buildInfoPath: string | undefined
): InputFiles;
export function createInputFiles(
javascriptTextOrReadFileText: string | ((path: string) => string | undefined),
Expand All @@ -6779,62 +6765,106 @@ namespace ts {
javascriptMapTextOrDeclarationPath?: string,
declarationMapPath?: string,
declarationMapTextOrBuildInfoPath?: string,
javascriptPath?: string | undefined,
declarationPath?: string | undefined,
buildInfoPath?: string | undefined,
buildInfo?: BuildInfo,
oldFileOfCurrentEmit?: boolean
): InputFiles {
return !isString(javascriptTextOrReadFileText) ?
createInputFilesWithFilePaths(
javascriptTextOrReadFileText,
declarationTextOrJavascriptPath,
javascriptMapPath,
javascriptMapTextOrDeclarationPath!,
declarationMapPath,
declarationMapTextOrBuildInfoPath,
) :
createInputFilesWithFileTexts(
/*javascriptPath*/ undefined,
javascriptTextOrReadFileText,
javascriptMapPath,
javascriptMapTextOrDeclarationPath,
/*declarationPath*/ undefined,
declarationTextOrJavascriptPath,
declarationMapPath,
declarationMapTextOrBuildInfoPath,
);
}
/*@internal*/
export function createInputFilesWithFilePaths(
readFileText: (path: string) => string | undefined,
javascriptPath: string,
javascriptMapPath: string | undefined,
declarationPath: string,
declarationMapPath: string | undefined,
buildInfoPath: string | undefined,
host?: CompilerHost,
options?: CompilerOptions,
): InputFiles {
const node = parseNodeFactory.createInputFiles();
if (!isString(javascriptTextOrReadFileText)) {
const cache = new Map<string, string | false>();
const textGetter = (path: string | undefined) => {
if (path === undefined) return undefined;
let value = cache.get(path);
if (value === undefined) {
value = javascriptTextOrReadFileText(path);
cache.set(path, value !== undefined ? value : false);
node.javascriptPath = javascriptPath;
node.javascriptMapPath = javascriptMapPath;
node.declarationPath = declarationPath;
node.declarationMapPath = declarationMapPath;
node.buildInfoPath = buildInfoPath;
const cache = new Map<string, string | false>();
const textGetter = (path: string | undefined) => {
if (path === undefined) return undefined;
let value = cache.get(path);
if (value === undefined) {
value = readFileText(path);
cache.set(path, value !== undefined ? value : false);
}
return value !== false ? value as string : undefined;
};
const definedTextGetter = (path: string) => {
const result = textGetter(path);
return result !== undefined ? result : `/* Input file ${path} was missing */\r\n`;
};
let buildInfo: BuildInfo | false;
const getAndCacheBuildInfo = () => {
if (buildInfo === undefined && buildInfoPath) {
if (host?.getBuildInfo) {
buildInfo = host.getBuildInfo(buildInfoPath, options!.configFilePath) ?? false;
}
return value !== false ? value as string : undefined;
};
const definedTextGetter = (path: string) => {
const result = textGetter(path);
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;
else {
const result = textGetter(buildInfoPath);
buildInfo = result !== undefined ? getBuildInfo(buildInfoPath, result) ?? false : false;
}
return buildInfo || undefined;
};
node.javascriptPath = declarationTextOrJavascriptPath;
node.javascriptMapPath = javascriptMapPath;
node.declarationPath = Debug.checkDefined(javascriptMapTextOrDeclarationPath);
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)); } }
});
}
else {
node.javascriptText = javascriptTextOrReadFileText;
node.javascriptMapPath = javascriptMapPath;
node.javascriptMapText = javascriptMapTextOrDeclarationPath;
node.declarationText = declarationTextOrJavascriptPath;
node.declarationMapPath = declarationMapPath;
node.declarationMapText = declarationMapTextOrBuildInfoPath;
node.javascriptPath = javascriptPath;
node.declarationPath = declarationPath;
node.buildInfoPath = buildInfoPath;
node.buildInfo = buildInfo;
node.oldFileOfCurrentEmit = oldFileOfCurrentEmit;
}
}
return buildInfo || undefined;
};
Object.defineProperties(node, {
javascriptText: { get: () => definedTextGetter(javascriptPath) },
javascriptMapText: { get: () => textGetter(javascriptMapPath) }, // TODO:: if there is inline sourceMap in jsFile, use that
declarationText: { get: () => definedTextGetter(Debug.checkDefined(declarationPath)) },
declarationMapText: { get: () => textGetter(declarationMapPath) }, // TODO:: if there is inline sourceMap in dtsFile, use that
buildInfo: { get: getAndCacheBuildInfo },
});
return node;
}
/*@internal*/
export function createInputFilesWithFileTexts(
javascriptPath: string | undefined,
javascriptText: string,
javascriptMapPath: string | undefined,
javascriptMapText: string | undefined,
declarationPath: string | undefined,
declarationText: string,
declarationMapPath: string | undefined,
declarationMapText: string | undefined,
buildInfoPath?: string,
buildInfo?: BuildInfo,
oldFileOfCurrentEmit?: boolean,
): InputFiles {
const node = parseNodeFactory.createInputFiles();
node.javascriptPath = javascriptPath;
node.javascriptText = javascriptText;
node.javascriptMapPath = javascriptMapPath;
node.javascriptMapText = javascriptMapText;
node.declarationPath = declarationPath;
node.declarationText = declarationText;
node.declarationMapPath = declarationMapPath;
node.declarationMapText = declarationMapText;
node.buildInfoPath = buildInfoPath;
node.buildInfo = buildInfo;
node.oldFileOfCurrentEmit = oldFileOfCurrentEmit;
return node;
}

Expand Down
Loading