После дня, проведенного за тяжелыми тестами и копаниями, я научился нескольким вещам.
Похоже, что наилучшие результаты можно получить, если кэшировать без изменений SourceFile
и перехватить CompilerHost
для возврата. из кеша, когда доступно. (Мне недавно сказали, что LanguageService
работает аналогичным образом)
Если вы изменяете только несколько файлов, это может значительно повысить производительность. Когда я не изменил ни один из файлов 230 TS, Program
последовательно перезагружался в 1ms
без указания oldProgram
и 100ms
при его предоставлении.
Однако, даже если было изменено много файлов, усиление может быть все еще быть реализованным.
Большой вывод, для меня, заключается в том, что каждый новый SourceFile
, который получает обслуживание от CompilerHost
, должен будет снова пройти свои типы, что может дороже.
Таким образом, способы повышения скорости следующие:
Создайте CompilerHost
и ловушку getSourceFile()
для обслуживания SourceFile
из кэша карта, если она доступна.
Если вы можете избежать использования TypeChecker
во время ts.transform()
, не создавайте экземпляр Program
до тех пор, пока не преобразуетесь. (используйте свой compilerHost, чтобы загрузить SourceFile[]
для использования с ts.transform()
)
Во время преобразования следите за тем, какие файлы действительно изменяются, и обновляйте только те файлы, которые находятся в вашем кэше.
Пример кода:
import * as ts from 'typescript';
import * as glob from 'glob';
import {
CompilerHost, CompilerOptions, HeritageClause, IndexedAccessTypeNode, SourceFile, SyntaxKind, TypeReferenceNode
} from 'typescript';
/* ********************************************************* *
* Helpers
* ********************************************************* */
export const nodeIsKind = <T extends ts.Node = never>(node: ts.Node, ...kind: ts.SyntaxKind[]): node is T =>
kind.some(k => node.kind === k);
/* ********************************************************* *
* Compiler
* ********************************************************* */
function createHookedCompilerHost(hostFiles: Map<string, SourceFile>, compilerOptions: CompilerOptions) {
const host = ts.createCompilerHost(compilerOptions);
const originalGetSourceFile = host.getSourceFile as Function;
return Object.assign(host, {
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget) {
return hostFiles.get(fileName) || originalGetSourceFile(...arguments);
}
});
}
function transformNodes(program: ts.Program) {
const srcFiles = program.getSourceFiles() as SourceFile[];
const updatedFiles = new Set<string>();
const transformer = (context: ts.TransformationContext) => {
function visit(fileName: string) {
return (node: ts.Node): ts.Node => {
/* Ignore these */
if (nodeIsKind<HeritageClause>(node, SyntaxKind.HeritageClause)) return node;
/* Wrap in tuple */
if (nodeIsKind<TypeReferenceNode>(node, SyntaxKind.TypeReference) || nodeIsKind<IndexedAccessTypeNode>(node, SyntaxKind.IndexedAccessType)) {
updatedFiles.add(fileName); // Mark file as modified
return ts.createTupleTypeNode([ node ]);
}
return ts.visitEachChild(node, visit(fileName), context);
}
}
return (sourceFile: ts.SourceFile) => ts.visitNode(sourceFile, visit(sourceFile.fileName));
};
const { transformed } = ts.transform(srcFiles, [ transformer ], program.getCompilerOptions());
return transformed.filter(sourceFile => updatedFiles.has(sourceFile.fileName));
}
/* ********************************************************* *
* Config
* ********************************************************* */
const fileNames = glob.sync('./test/assets/**/*.ts');
const compilerOptions = {
noEmit: true,
target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.CommonJS,
strictNullChecks: false,
};
/* ********************************************************* *
* Main
* ********************************************************* */
/* Setup Host & Program */
const hostFiles = new Map<string, SourceFile>();
const host = createHookedCompilerHost(hostFiles, compilerOptions);
/* Load SourceFiles */
const sourceFiles = fileNames.map(fileName => host.getSourceFile(fileName, compilerOptions.target));
/* Build Program */
let program = ts.createProgram(fileNames, compilerOptions, host);
/* Pre-cache sourceFiles */
program.getSourceFiles().forEach(srcFile => hostFiles.set(srcFile.fileName, srcFile));
/* Transform files & update affected SourceFiles */
const transformed = transformNodes(program);
for (const sourceFile of transformed)
hostFiles.set(
sourceFile.fileName,
ts.createSourceFile(sourceFile.fileName, ts.createPrinter().printFile(sourceFile), sourceFile.languageVersion)
);
/* Re-generate Program */
program = ts.createProgram(fileNames, compilerOptions, host);
/* Do what you need with the TypeChecker here */
Не стесняйтесь оставлять комментарии, если у вас есть вопросы или вам нужна помощь.