Какой самый быстрый путь для восстановления типов в экземпляре программы после изменения * некоторых * файлов? - PullRequest
1 голос
/ 15 марта 2020

У меня есть экземпляр Program, в котором я изменил несколько узлов за несколько SourceFile. Я хотел бы восстановить типы для измененных SourceFile наиболее эффективным способом. (Примечание: я не излучаю)

Первоначальный способ, которым я это сделал:

  • Создайте CompilerHost и подключите getSourceFile к разрешить обслуживание измененных SourceFile, если доступно
  • Запустить преобразователь на всех SourceFile, обновить измененную коллекцию из результата
  • Восстановить Program, используя тот же compilerHost (обслуживает обновленные файлы)

Я пытаюсь определить, есть ли более оптимальный способ сделать это.

Я исследовал возможность использования LanguageService, но похоже, что ts.createLanguageService просто запускает воссоздание Program при изменении файлов.

Другой маршрут, на который я смотрел, - ts.createWatchProgram. Похоже, что для реализации функции isProgramUptoDate(), которая запускает createProgram, если имена файлов изменились, используется похожая логика c. Для моих целей, похоже, что оба добавят ненужную сложность и, вероятно, будут перетаскивать мой исходный маршрут.

Однако, поскольку у меня пока нет большого опыта с этим, возможно, я что-то упустил.

1 Ответ

1 голос
/ 16 марта 2020

После дня, проведенного за тяжелыми тестами и копаниями, я научился нескольким вещам.

Похоже, что наилучшие результаты можно получить, если кэшировать без изменений SourceFile и перехватить CompilerHost для возврата. из кеша, когда доступно. (Мне недавно сказали, что LanguageService работает аналогичным образом)

Если вы изменяете только несколько файлов, это может значительно повысить производительность. Когда я не изменил ни один из файлов 230 TS, Program последовательно перезагружался в 1ms без указания oldProgram и 100ms при его предоставлении.

Однако, даже если было изменено много файлов, усиление может быть все еще быть реализованным.

Большой вывод, для меня, заключается в том, что каждый новый SourceFile, который получает обслуживание от CompilerHost, должен будет снова пройти свои типы, что может дороже.

Таким образом, способы повышения скорости следующие:

  1. Создайте CompilerHost и ловушку getSourceFile() для обслуживания SourceFile из кэша карта, если она доступна.

  2. Если вы можете избежать использования TypeChecker во время ts.transform(), не создавайте экземпляр Program до тех пор, пока не преобразуетесь. (используйте свой compilerHost, чтобы загрузить SourceFile[] для использования с ts.transform())

  3. Во время преобразования следите за тем, какие файлы действительно изменяются, и обновляйте только те файлы, которые находятся в вашем кэше.

Пример кода:

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 */

Не стесняйтесь оставлять комментарии, если у вас есть вопросы или вам нужна помощь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...