Русский
Русский
English
Статистика
Реклама

Из песочницы Typescript Compiler API возьми управление компилятором в свои руки



В разработке приложений на Typescript всегда есть этап сборки проекта. Обычно для этого используются системы сборки и автоматизации workflow, такие как webpack или gulp, обвешанные достаточным количеством плагинов, либо процесс сборки размазывается в командах package.json и шелл-скриптах с использованием нативного tsc или команд CLI используемого в проекте фреймворка. Все эти решения имеют свои плюсы и минусы. Зачастую в процессе сборки нужно сделать что-то нестандартное, и оказывается, что используемая система сборки не предоставляет нужную функциональность из коробки, а имеющиеся плагины делают не совсем то, что надо. В такие моменты работа над проектом встает, и начинается судорожное ковыряние в конфигах и поиск подходящего плагина. В какой-то момент понимаешь, что за время, потраченное на поиск подходящего костыля, можно было написать свое решение.

Во многих случаях критичные процессы в проекте можно автоматизировать скриптами на javascript, выразительность и функциональность которого вполне позволяет описать нужный workflow и выбирать из всего разнообразия библиотек, не заморачиваясь наличием для них плагинов под конкретную систему сборки. Важное преимущество такого подхода полный контроль над процессами и максимальная гибкость. Для проектов, в которых используется Typescript в качестве основного языка разработки, возникает вопрос, как встроить процесс его компиляции в свой workflow. Здесь на помощь приходит Typescript Compiler API. В этой статье мы посмотрим, как его можно использовать для того, чтобы выполнить компиляцию проекта, реализованного на Typescript, взаимодействуя с компилятором на разных этапах его работы и напишем скрипт для hot-reloadingа REST-сервера, разработанного на Nest.js.

У меня есть проект на ноде, над которым я работаю в свободное время. Изначально он предназначался для небольшого бизнеса, который так и не взлетел, а теперь я использую его как полигон для своих экспериментов. Проект, который изначально строился на Nuxt.js в связке с Fastify, пережил множество трансформаций. Здесь и переход на typescript и преобразование в монорепозиторий, и замена Fastify на Nest.js. Поскольку проект небольшой и серьезной нагрузки не предполагалось, все это должно было крутится на одном сервере, поэтому я использовал единый инстанс Express как для REST сервера так для отдачи фронта через Nuxt.js. Для этого Express, который работает под капотом Nest.js, получает рендер-функцию Nuxt.js.

Это решение хорошо работает в продакшене, но мне, поскольку я работал над проектом один, было удобно параллельно писать функции API и делать интерфейс. Для это перезапуск Nest.js при изменении файлов не должен приводить к перезапуску работающего в режиме HMR Nuxt.js, иначе он начинал процесс сборки заново, что лишало смысла всю затею. Но тут возникла проблема, дело в том, что хотя в CLI Nest.js есть режим watch, который умеет делать hot-reload, загружает он его в отдельном процессе, который перезапускается при перекомпиляции проекта. Из-за этого каждый перезапуск сервера в таком режиме будет приводить к потере контекста для Nuxt и затем будет запускаться его полная сборка.

После того, как все это выяснилось, я решил сделать в проекте свой hot-reload. Первоначально я попробовал реализовать решение с использованием webpack, как предлагается в документации Nest.js , но оказалось, что start-server-webpack-plugin, который там используется для реализации перезапуска использовал ту же стратегию, что CLI, т.е. запускал его в отдельном процессе и перезапускал по необходимости. Этот плагин я переписал, чтобы он работал как нужно мне, но с использованием Webpack решение получилось довольно тяжеловесно и не без проблем, которые выплывали тут и там, мешая сосредоточится на разработке проекта.

Тогда я решил, что надо менять подход, мне хотелось напрямую использовать механизмы, заложенные в компилятор tsc, который всегда работал как часы быстро и стабильно. В итоге я нашел хорошую статью, в которой были примеры использования TypeScript Compiler API. Оказалось, что инструменты, встроенные в библиотеку Typescript позволяют реализовать нужные мне функции и достаточно удобны в применении. К сожалению, я столкнулся с тем, что по Typescript Compiler API очень мало информации, поэтому решил поделится тем, что мне удалось узнать. Отмечу также что данный API на момент написания статьи находится в процессе разработки и некоторые моменты со временем могут поменяться.

Простая компиляция


Чтобы разобраться с принципами работы TypeScript Compiler API для начала попробуем просто выполнить с его помощью компиляцию проекта. Проект, на котором я буду экспериментировать, имеет стандартную для nest.js структуру: исходники расположены в папке src, точка входа файл ./src/main.ts.

Для того, чтобы компилятор успешно скомпилировал проект, ему надо передать параметры компиляции. Самый простой способ, это захаркодить его прямо в скрипте. Создадим файл tsconfig.js:

const {ModuleResolutionKind, ModuleKind, ScriptTarget} = require("typescript")module.exports =  {    moduleResolution: ModuleResolutionKind.NodeJs,    module: ModuleKind.CommonJS,    target: ScriptTarget.ES2019,    declaration: true,    removeComments: true,    emitDecoratorMetadata: true,    experimentalDecorators: true,    sourceMap: true,    outDir: "./dist",    baseUrl: "./",    allowJs: true,    skipLibCheck: true,  }

Обратите внимание, что некоторые параметры, такие как moduleResolution, module, target определяются через перечисления, поэтому напрямую забрать параметры из tsconfig.json, где они прописаны в виде строк не выйдет, так что для реализации простейшего примера я его использовать не буду. Как корректно забрать параметры из tsconfig.json мы разберемся немного позже.

Теперь у нас есть параметры компиляции и можно перейти к следующему шагу. Для этого создадим файл ts-compiler.js со следующим содержимым:

const ts = require('typescript');function compile() {  const compilerOptions = require('./tsconfig');  const program = ts.createProgram(['./src/main.ts'], compilerOptions);  const emitResult = program.emit();}compile();

Если теперь запустить этот скрипт на исполнение, в директории ./dist должны появится скомпилированные файлы программы. Компиляция происходит в два этапа: сначала команда createProgram анализирует исходные файлы и создает список файлов для компиляции, после чего выполняем компиляцию и сохранение скомпилированных файлов при помощи функции emit. Довольно просто.

Для повышения удобства использования теперь надо реализовать загрузку параметров компиляции из файла tsconfig.json. Добавим в скрипт следующую функцию:

const formatHost = {  getCanonicalFileName: path => path,  getCurrentDirectory: ts.sys.getCurrentDirectory,  getNewLine: () => ts.sys.newLine,};function getTSConfig() {  const configPath = ts.findConfigFile('./', ts.sys.fileExists, 'tsconfig.json');  const readConfigFileResult = ts.readConfigFile(configPath, ts.sys.readFile);  if (readConfigFileResult.error) {    throw new Error(ts.formatDiagnostic(readConfigFileResult.error, formatHost));  }  const jsonConfig = readConfigFileResult.config;  const convertResult = ts.convertCompilerOptionsFromJson(jsonConfig.compilerOptions, './');  if (convertResult.errors && convertResult.errors.length > 0) {    throw new Error(ts.formatDiagnostics(convertResult.errors, formatHost));  }  const compilerOptions = convertResult.options;  return compilerOptions;}

Для работы с файлами конфигурации API предоставляет ряд функций, которые позволяют найти, загрузить и преобразовать конфигурацию в тот вид, который понимает компилятор. Здесь используется функция findConfigFile чтобы определить путь к файлу конфигурации, потом он загружается как json при помощи readConfigFile, и далее, если нет ошибок, при помощи convertCompilerOptionsFromJson получаем параметры компиляции.

В случае, если при загрузке конфига что-то пошло не так, наша функция генерирует исключения, используя механизмы передачи сообщений об ошибках, заложенные в API. Все сообщения об ошибках и предупреждения библиотека возвращает в виде объектов класса Diagnostic. Чтобы на их основе сформировать сообщение для вывода в консоль, можно использовать стандартные функции форматирования библиотеки, в данном случае это formatDiagnostic для одного сообщения и formatDiagnostics для массива. Для корректной работы функций форматирования требуется объект formatHost, который содержит важные для форматирования сообщения параметры.

Теперь, чтобы получить параметры компилятора, мы можем вызвать в нашем скрипте функцию getTSConfig.

function compile() {  const compilerOptions = getTSConfig();}

На данном этапе, если компилятор найдет ошибки в коде, мы об этом не узнаем, давайте исправим это. Все ошибки в коде, которые были обнаружены на этапе компиляции функция emit возвращает в поле diagnostcs объекта EmitResult. Для красивого отображения ошибок в коде можно использовать функцию formatDiagnosticsWithColorAndContext. Также имеет смысл проверить ошибки, обнаруженные на этапе создания программы. Для этого используем функцию getPreEmitDiagnostics. Дополним наш скрипт следующим образом:

function compile() {  const compilerOptions = getTSConfig();  const program = ts.createProgram(['./src/main.ts'], compilerOptions);  console.log(    ts.formatDiagnosticsWithColorAndContext(      ts.getPreEmitDiagnostics(program),      formatHost,    ),  );  const emitResult = program.emit();  console.log(    ts.formatDiagnosticsWithColorAndContext(emitResult.diagnostics, formatHost),  );  return emitResult.diagnostics.length === 0;}

Если теперь внести в наши исходники ошибку, получим в консоли красиво отформатированное сообщение:



Итак, мы получили скрипт, который будет компилировать наши исходники и отображать в консоли ошибки в случае их возникновения. Как видите, такой подход дает возможность очень гибко реализовать процесс компиляции, поскольку у нас есть доступ ко всем этапам и результатам компиляции. Дополнительной гибкости можно достичь, кастомизировав CompilerHost, который содержит необходимый компилятору набор функций. Допустим, мы захотели отображать в консоли пути к файлам, которые читает компилятор в процессе работы. Это несложно сделать, передав в функцию createProgram кастомизированный CompilerHost. Посмотрим, как это работает на практике.

Я не хочу засорять вывод портянкой из сотен строк путей к файлам, поэтому напишу функцию, которая будет выводить пути в одной строчке:

function displayFilename(originalFunc, operationName) {  let displayEnabled = false;  function displayFunction() {    const fileName = arguments[0];    if (displayEnabled) {      process.stdout.clearLine();      process.stdout.cursorTo(0);      process.stdout.write(`${operationName}: ${fileName}`);    }    return originalFunc(...arguments);  }  displayFunction.originalFunc = originalFunc;  displayFunction.enableDisplay = () => {    if (process.stdout.isTTY) {      displayEnabled = true;    }  };  displayFunction.disableDisplay = () => {    if (displayEnabled) {      displayEnabled = false;      process.stdout.clearLine();      process.stdout.cursorTo(0);    }  };  return displayFunction;}

Теперь дополним код функции compile().

function compile() {  const compilerOptions = getTSConfig();  const compilerHost = ts.createCompilerHost(compilerOptions);  compilerHost.readFile = displayFilename(compilerHost.readFile, 'Reading');  compilerHost.readFile.enableDisplay();  const program = ts.createProgram(    ['./src/main.ts'],    compilerOptions,    compilerHost,  );  compilerHost.readFile.disableDisplay();  console.log(    ts.formatDiagnosticsWithColorAndContext(      ts.getPreEmitDiagnostics(program),      formatHost,    ),  );  compilerHost.writeFile = displayFilename(compilerHost.writeFile, 'Emitting');  compilerHost.writeFile.enableDisplay()  const emitResult = program.emit();  compilerHost.writeFile.disableDisplay();  console.log(    ts.formatDiagnosticsWithColorAndContext(emitResult.diagnostics, formatHost),  );  return emitResult.diagnostics.length === 0;}

Итак, теперь мы создаем хост с помощью функции createCompilerHost и заменяем нашей реализацией функции readFile и writeFile. Также функция compile() возвращает истину, если после окончания работы не обнаружено ошибок. Теперь в случае, если компиляция прошла без ошибок можно сразу запустить скомпилированный сервер. Посмотрим, что получилось:



Вот полный код получившегося скрипта:

ts-compiler.js
const ts = require('typescript');const formatHost = {  getCanonicalFileName: path => path,  getCurrentDirectory: ts.sys.getCurrentDirectory,  getNewLine: () => ts.sys.newLine,};function getTSConfig() {  const configPath = ts.findConfigFile(    './',    ts.sys.fileExists,    'tsconfig.json',  );  const readConfigFileResult = ts.readConfigFile(configPath, ts.sys.readFile);  if (readConfigFileResult.error) {    throw new Error(      ts.formatDiagnostic(readConfigFileResult.error, formatHost),    );  }  const jsonConfig = readConfigFileResult.config;  const convertResult = ts.convertCompilerOptionsFromJson(    jsonConfig.compilerOptions,    './',  );  if (convertResult.errors && convertResult.errors.length > 0) {    throw new Error(ts.formatDiagnostics(convertResult.errors, formatHost));  }  const compilerOptions = convertResult.options;  return compilerOptions;}function displayFilename(originalFunc, operationName) {  let displayEnabled = false;  function displayFunction() {    const fileName = arguments[0];    if (displayEnabled) {      process.stdout.clearLine();      process.stdout.cursorTo(0);      process.stdout.write(`${operationName}: ${fileName}`);    }    return originalFunc(...arguments);  }  displayFunction.originalFunc = originalFunc;  displayFunction.enableDisplay = () => {    if (process.stdout.isTTY) {      displayEnabled = true;    }  };  displayFunction.disableDisplay = () => {    if (displayEnabled) {      displayEnabled = false;      process.stdout.clearLine();      process.stdout.cursorTo(0);    }  };  return displayFunction;}function compile() {  const compilerOptions = getTSConfig();  const compilerHost = ts.createCompilerHost(compilerOptions);  compilerHost.readFile = displayFilename(compilerHost.readFile, 'Reading');  compilerHost.readFile.enableDisplay();  const program = ts.createProgram(    ['./src/main.ts'],    compilerOptions,    compilerHost,  );  compilerHost.readFile.disableDisplay();  console.log(    ts.formatDiagnosticsWithColorAndContext(      ts.getPreEmitDiagnostics(program),      formatHost,    ),  );  compilerHost.writeFile = displayFilename(compilerHost.writeFile, 'Emitting');  compilerHost.writeFile.enableDisplay()  const emitResult = program.emit();  compilerHost.writeFile.disableDisplay();  console.log(    ts.formatDiagnosticsWithColorAndContext(emitResult.diagnostics, formatHost),  );  return emitResult.diagnostics.length === 0;}compile() && require('./dist/main');


Incremental program watcher своими руками


В предыдущем разделе мы посмотрели, как можно использовать TypeScript Compiler API для того, чтобы настроить процесс компиляции Typescript. Теперь пришла пора сделать нечто более насущное и полезное, что реально может повысить скорость и удобство разработки. В процессе компиляции программы компилятору необходимо разрешить все зависимости в программе.

Чтобы это сделать он последовательно считывает и анализирует файлы проекта и файлы библиотек. Даже для компиляции относительно небольшого проекта как у меня на этапе подготовки компилятору нужно считать, распарсить и проанализировать более 2000 файлов. Это приводит к значительному замедлению процесса компиляции, например у меня на ноутбуке компиляция занимает приблизительно 20 секунд. При этом в процессе разработки обычно между запусками компилятора меняются всего несколько файлов, поэтому логично, что начиная с версии 2.7 в Typescript появилась возможность инкрементальной компиляции, которая совместно с watch-режимом, позволяющим следить за изменениями файлов на диске, значительно повысила скорость повторной компиляции.

Эти функции использует и CLI Nest.js, а начать работу в этому режиме можно при помощи команды

nest start --watch

Другая возможность использовать преимущества такого режима разработки запускать tsc c ключом --watch. Но, как я уже писал во вступлении, если возможностей стандартных команд в какой-то момент перестает хватать, самое время заглянуть под капот Typescript и узнать, как там оно работает.

Для начала, как и в предыдущем случае, создадим ts-wather.js со следующим содержимым:

const ts = require('typescript');const formatHost = {  getCanonicalFileName: path => path,  getCurrentDirectory: ts.sys.getCurrentDirectory,  getNewLine: () => ts.sys.newLine,};async function watchMain() {  const configPath = ts.findConfigFile(    './',    ts.sys.fileExists,    'tsconfig.json',  );  const host = ts.createWatchCompilerHost(    configPath,    {},    ts.sys,    ts.createEmitAndSemanticDiagnosticsBuilderProgram,    diagnostic =>      console.log(        ts.formatDiagnosticsWithColorAndContext([diagnostic], formatHost),      ),    diagnostic =>      console.log(        'Watch status: ',        ts.formatDiagnosticsWithColorAndContext([diagnostic], formatHost),      ),  );  ts.createWatchProgram(host);}watchMain();

Запустим скрипт и посмотрим, как всё работает:



После запуска компилятор отслеживает и перекомпилирует измененные файлы, выдает красиво оформленные сообщения об ошибках, в общем работает очень похоже на tsc с опцией --watch. Обратите внимание, как быстро происходит повторная компиляция! Но какой же профит нам от этого, спросите вы, если наш скрипт делает все тоже самое что tsc или nest start --watch, не проще ли использовать готовый инструмент? А профит наш в том, что мы теперь можем с помощью хуков делать разные штуки, которые изрядно облегчат нам жизнь.

Для того, чтобы нам было удобно хукать разные штуки в хосте, давайте напишем небольшой хелпер:

function on(host, functionName, before, after) {  const originalFunction = host[functionName];  host[functionName] = function() {    before && before(...arguments);    const result = originalFunction && originalFunction(...arguments);    after && after(result);    return result;  };}

Он позволит выполнять нужные нам действия до и после вызываемой функции в хосте, при этом получать аргументы, которые ей передают и результат выполнения. Теперь этот хэлпер можно использовать, чтобы удобно вмешиваться в работу компилятора на разных этапах. Для примера давайте добавим сообщения в консоль на этапе подготовки к компиляции:

    on(    host,    'createProgram',    () => {      console.log("** We're about to create the program! **");    },    () => {      console.log('** Program created **');    },  );

Теперь посмотрим, что поменялось:



Работает! А теперь давайте добавим строку с отображением читаемых файлов. Для этого воспользуемся функцией displayFilename из предыдущего примера:

  host.readFile = displayFilename(host.readFile, 'Reading');  on(    host,    'createProgram',    () => {      console.log("** We're about to create the program! **");      host.readFile.enableDisplay();    },    () => {      host.readFile.disableDisplay();      console.log('** Program created **');    },  );

Запустим и посмотрим, что получилось:



Хорошо! Теперь сделаем то, ради чего все затевалось перезапуск скомпилированной программы. Для этого надо сделать хук, который будет срабатывать после окончания записи на диск скомпилированных файлов.

let currentProgram;  on(    host,    'afterProgramCreate',    (program) => {      console.log('** We finished making the program! Emitting... **');      currentProgram = program;    },    () => {      console.log('** Emit complete! **');      const onAppClosed = () => {        if (app) {          setTimeout(onAppClosed, 100);        } else {          clearCache();          require('./dist/bootstrap')            .bootstrap()            .then((res) => {              app = res;            });        }      };      if (currentProgram && currentProgram.getSemanticDiagnostics().length === 0) {        onAppClosed();      }    },  );

Давайте разбираться, что здесь происходит. Во-первых переменная app, в которую мы запишем результат выполнения функции bootstrap нам понадобится для того, чтобы потом, после изменения кода и начала повторной компиляции корректно закрыть сервер. В ней будет содержаться хэндлер для вызова команды остановки сервера. Во-вторых запуск сервера происходит только в том случае, если в процессе компиляции не найдены ошибки в коде. Для этого мы проверяем при помощи getSemanticDiagnostics чтобы количество диагностик равнялось нулю. Ну и в-третьих поскольку модульная система кэширует загруженные модули, после внесения изменений в код нам надо кэш почистить от зависимостей. Для этого я написал небольшую вспомогательную функцию clearCache, которую мы предварительно вызываем.

function clearCache() {  const cacheKeys = Object.keys(require.cache);  const paths = [    join(__dirname, 'dist'),    dirname(require.resolve('typeorm')),    dirname(require.resolve('@nestjs/typeorm')),  ];  cacheKeys    .filter((item) => paths.filter((p) => item.startsWith(p)).length > 0)    .forEach((item, index, arr) => {      delete require.cache[item];      );    });}

Здесь производится очистка кэша от зависимостей, которые могут нам помешать корректно перезапустить сервер. В нашем случае из кэша надо удалить все модули нашего проекта из каталога dist. Также я удаляю модули typeorm, поскольку особенности их реализации мешают серверу перезапуститься (почему так происходит, это уже другая история). При помощи таймаута дожидаемся момента, когда сервер будет закрыт и переменная app будет очищена. Закрытие сервера мы будем производить в момент, когда компилятор обнаруживает изменения файлов и приступает к созданию программы с измененными файлами. Для этого дополним хук на функции createProgram.

  let app;  on(    host,    'createProgram',    () => {      console.log("** We're about to create the program! **");      app && app.close().then(() => (app = undefined));      host.readFile.enableDisplay();    },    () => {      host.readFile.disableDisplay();      console.log('** Program created **');    },  );

Как видите функция закрытия сервера асинхронная, именно поэтому нам приходится городить огород с таймаутом.

Последний штрих это подмена process.exit().

process.exit = (code) => {  console.log('!!!!!!!!!!!!!!!!!!!!!!! WARNING!!!!!!!!!!!!!!!!!!!!!!!!!');  console.trace(`Process try to exit with code ${code}.`);};

Вообще так делать не стоит, но Nest.js имеет дурную привычку ее вызывать в случае, если в процессе запуска что-то пошло не так, поэтому мы просто заменим функцию выхода из программы на сообщение об ошибке. В этом случае наш скрипт продолжит работать после вызова process.exit(). Это надо сделать перед вызовом функции watchMain.

Итак, теперь запустим наш скрипт и посмотрим, что получилось:



Тут я вношу ошибку в код сервера, после этого сервер не запускается и выводятся сообщения об ошибках, после того как ошибка исправлена, сервер вновь успешно запускается. Ниже привожу получившийся в итоге скрипт:

ts-compiler.js
const ts = require('typescript');const { join, dirname } = require('path');const { exit } = require('process');const formatHost = {  getCanonicalFileName: (path) => path,  getCurrentDirectory: ts.sys.getCurrentDirectory,  getNewLine: () => ts.sys.newLine,};function on(host, functionName, before, after) {  const originalFunction = host[functionName];  host[functionName] = function () {    before && before(...arguments);    const result = originalFunction && originalFunction(...arguments);    after && after(result);    return result;  };}function clearCache() {  const cacheKeys = Object.keys(require.cache);  const paths = [    join(__dirname, 'dist'),    dirname(require.resolve('typeorm')),    dirname(require.resolve('@nestjs/typeorm')),  ];  cacheKeys    .filter((item) => paths.filter((p) => item.startsWith(p)).length > 0)    .forEach((item, index, arr) => {      delete require.cache[item];      process.stdout.clearLine(); // clear current text      process.stdout.cursorTo(0); // move cursor to beginning of line      process.stdout.write(        `Clearing cache ${Math.floor((index * 100) / arr.length + 1)}%`,      );    });  process.stdout.write(' finished.\n');}function displayFilename(originalFunc, operationName) {  let displayEnabled = false;  let counter = 0;  function displayFunction() {    const fileName = arguments[0];    if (displayEnabled) {      process.stdout.clearLine();      process.stdout.cursorTo(0);      process.stdout.write(`${operationName}: ${fileName}`);    }    counter++;    return originalFunc(...arguments);  }  displayFunction.originalFunc = originalFunc;  displayFunction.enableDisplay = () => {    counter = 0;    if (process.stdout.isTTY) {      displayEnabled = true;    }  };  displayFunction.disableDisplay = () => {    if (displayEnabled) {      displayEnabled = false;      process.stdout.clearLine();      process.stdout.cursorTo(0);    }    console.log(`${counter} times function was called`);  };  return displayFunction;}async function watchMain() {  const configPath = ts.findConfigFile(    './',    ts.sys.fileExists,    'tsconfig.json',  );  const host = ts.createWatchCompilerHost(    configPath,    {},    ts.sys,    ts.createEmitAndSemanticDiagnosticsBuilderProgram,    (diagnostic) =>      console.log(        ts.formatDiagnosticsWithColorAndContext([diagnostic], formatHost),      ),    (diagnostic) =>      console.log(        'Watch status: ',        ts.formatDiagnosticsWithColorAndContext([diagnostic], formatHost),      ),  );  host.readFile = displayFilename(host.readFile, 'Reading');  let app;  on(    host,    'createProgram',    () => {      console.log("** We're about to create the program! **");      app && app.close().then(() => (app = undefined));      host.readFile.enableDisplay();    },    () => {      host.readFile.disableDisplay();      console.log('** Program created **');    },  );  let currentProgram;  on(    host,    'afterProgramCreate',    (program) => {      console.log('** We finished making the program! Emitting... **');      currentProgram = program;    },    () => {      console.log('** Emit complete! **');      const onAppClosed = () => {        if (app) {          setTimeout(onAppClosed, 100);        } else {          clearCache();          require('./dist/bootstrap')            .bootstrap()            .then((res) => {              app = res;            });        }      };      if (currentProgram && currentProgram.getSemanticDiagnostics().length === 0) {        onAppClosed();      }    },  );  ts.createWatchProgram(host);}process.exit = (code) => {  console.log('!!!!!!!!!!!!!!!!!!!!!!! WARNING!!!!!!!!!!!!!!!!!!!!!!!!!');  console.trace(`Process try to exit with code ${code}.`);};watchMain();


Выводы


В этом туториале мы посмотрели, как можно использовать возможности Typescript Compiler API для того, чтобы гибко организовать процесс отладки и сборки кода. Как видите, разработчики Typescript дают нам достаточно мощный и удобный в использовании API, чтобы не было необходимости привлекать для этих целей сторонние библиотеки и при этом заметно повысить удобство и скорость разработки. Надеюсь, что приведенная здесь информация будет вам полезна, спасибо, что дочитали до конца!
Источник: habr.com
К списку статей
Опубликовано: 27.06.2020 02:21:47
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Javascript

Node.js

Typescript

Cli

Nest.js

Категории

Последние комментарии

© 2006-2020, personeltest.ru