Главная / Статьи / TypeScript 7.0 переходит на компилятор Go для достижения максимальной скорости выполнения

TypeScript 7.0 переходит на компилятор Go для достижения максимальной скорости выполнения

TypeScript 7.0 переносит tsc в стек Go с параллельными проверками, новыми значениями по умолчанию для tsconfig, шаблонными литералами Unicode, более строгим анализом JS, а также временным отсутствием программной API.

1086 слов

В течение примерно четырнадцати лет компилятор TypeScript мог самостоятельно собираться. tsc представлял собой версию TypeScript, которая компилировалась в JavaScript и работала под управлением Node. По мере того как объёмы кода достигали миллионов строк, такая схема самостоятельной работы становилась узким местом.

В TypeScript 7.0 изменился язык хостинга. Компилятор и сервис обработки языка были перенесены с TypeScript на Go. Microsoft описывает этот процесс как портирование, а не полную переработку: структура проверки типов осталась прежней, как в версии 6.0, но выполнение происходит с использованием нативного кода и параллелизма на основе общей памяти. По результатам опубликованных сравнений скорость работы превышает скорость TypeScript 6.0 примерно в 10 раз.

Один из часто приводимых примеров нагрузки наглядно демонстрирует эффективность изменений. Проверка структуры проекта в VS Code — объёмом около 1,5 миллиона строк на TypeScript — заняла от примерно 77,8 секунд до около 7,5 секунд.

Если ранее для обозначения этого проекта использовалось кодовое название Project Corsa (а устаревшая версия JavaScript называлась Strada), то это издание представляет собой финальную версию данного проекта.

Почему именно Go, и почему порт?

В общественных дискуссиях часто упоминался Rust. Был выбран Go потому, что его архитектура совпадала с существующим компилятором: сложные графы, механизм сбора мусора и циклические структуры, которые трудно воссоздать с нуля. Именно это совпадение позволило осуществить порт строка за строкой.

Формат порта важнее, чем сам язык. Сохранение архитектуры позволило сохранить существующие правила. Проекты, которые уже проходят проверку типов в версии 6.0 с включенным параметром stableTypeOrdering и без параметра ignoreDeprecations, должны получать одинаковые результаты и в версии 7.0 — это будет более быстрый движок, но не другая система типов.

Сначала проводились тесты на нагрузку в производственных условиях. За более чем год до достижения этой вехи порт работал с огромными объемами данных на таких платформах, как Bloomberg, Figma, Google, Slack, Notion и Vercel.

Настоящий параллелизм

Раньше пропускная способность ограничивалась одним потоком Node. Нативные рабочие процессы устраняют это ограничение. Версия 7 распределяет задачи на анализ, проверку и генерацию результатов, а также добавляет флаги:

  • --checkers — устанавливает количество параллельных рабочих процессов для проверки типов
  • --builders — параллелизует процесс сборки проектов и стеков в монорепозиториях с использованием --checkers
  • --singleThreaded — объединяет всю работу в одном ядре для отладки и измерения базовых временных показателей

Объем работы по анализу/генерации результатов для каждого файла увеличивается с размером репозитория и степенью его модульности; у крошечных приложений с одним файлом прирост незначителен.

Режим отслеживания также был переписан. Метод опроса загружал процессор при работе с огромными деревьями файлов node_modules; использование механизма отслеживания Parcel, реализованного на Go, снижает эту нагрузку и позволяет быстрее реагировать на изменения.

Изменения в конфигурации, которые создадут проблемы командам

TypeScript 6.0 служил переходным этапом: новые параметры по умолчанию и устаревшие функции отображались как предупреждения. В версии 7.0 они превращаются в серьезные ошибки. Команды, уже прошедшие через версию 6.0, выполнили большую часть работы. Переход с версии 5.x непосредственно на 7.0 требует времени на корректировку файла tsconfig.json.

Основные изменения по умолчанию:

  • Параметр strict включен по умолчанию, если не указано иное
  • Параметр module устанавливается в значение esnext, а параметр target выбирает самую новую стабильную версию ECMAScript, меньшую по номеру, чем esnext
  • Параметр noUncheckedSideEffectImports включен по умолчанию
  • Параметр libReplacement отключен по умолчанию
  • stableTypeOrdering остаётся включённым
  • rootDir начинается с ./
  • types изначально является пустым списком
  • В документации rootDir и types называются теми аспектами, с которыми команды сталкиваются первыми, и теми, для которых решения наиболее просты.

    Если файл tsconfig.json находится выше папки src, явно укажите значение rootDir, чтобы структура вывода оставалась знакомой:

    {
      "compilerOptions": {
        "rootDir": "./src"
      },
      "include": ["./src"]
    }
    

    Поскольку types больше не автоматически импортирует всё, что находится в node_modules/@types, укажите только необходимое:

    {
      "compilerOptions": {
        "types": ["node", "jest"]
      }
    }
    

    Параметры, которые раньше лишь предупреждали, теперь приводят к серьёзным ошибкам (они перестают выполнять какую-либо полезную функцию):

    • Удалите target: es5 и downlevelIteration
    • Замените устаревшие значения moduleResolution (node, node10, classic) на nodenext или bundler
    • Устраните режимы module, такие как amd, umd, systemjs и none, в пользу esnext или preserve
    • Удалите baseUrl и указывайте paths относительно корня проекта
    • Не устанавливайте esModuleInterop / allowSyntheticDefaultImports в значение false
    • Считайте, что параметр alwaysStrict всегда включен

    Если после многих лет без проверки значение moduleResolution по-прежнему равно старому значению node, этот файл служит чек-листом для миграции.

    Unicode в типах с литеральными шаблонами

    Типы литералов шаблонов теперь определяются по код-точкам Unicode, а не по единицам кода UTF-16:

    type HeadTail<S> = S extends `${infer Head}${infer Tail}` ? [Head, Tail] : never;
    
    type Result = HeadTail<"😀abc">;
    // In 7.0:      ["😀", "abc"]
    // Previously:  ["\ud83d", "\ude00abc"]
    

    Старые компиляторы могли обрабатывать пару заместителей эмодзи по отдельности. Это соответствовало способу индексации в JS, но редко отражало намерения автора. Теперь итерация происходит в соответствии с код-точками, как это делают конструкции for...of и [...str]. Утилиты, которые намеренно учитывали единицы UTF-16, перестанут работать; все остальные получат желаемое поведение.

    Более строгий анализ JavaScript

    Раньше проверка файлов .js терпела больше идиом из эпохи JSDoc и Closure. В версии 7 это ограничение сближается с правилами для файлов .ts:

    • Места, где требуются типы, отклоняют простые значения — предпочтительнее использовать typeof someValue
    • @enum / @class теряют специальную обработку; необходимо объявлять настоящий класс или использовать @typedef
    • Простой символ ? не считается типом — предпочтительнее использовать any
  • Подтверждения с заканчивающимся символом ! не поддерживаются — необходимо явно указывать T
  • Старые форматы function(string): void заменяются на (s: string) => void
  • Пробел в программной API

    В версии 7.0 по-прежнему отсутствует стабильная программная API. Поэтому библиотеки, включающие компилятор — интеграция ESLint с TypeScript, ts-morph, ручно написанные трансформаторы, а также инструменты для редакторов в рамках шаблонов Vue, Svelte, Astro, MDX или Angular — пока не могут полностью перейти на новую версию. Microsoft считает эту проблему временной и ожидает завершения разработки API в версии 7.1 и последующих выпусках.

    Тем временем используйте версию 7.0 там, где не требуются плагины language-server. Команды, работающие с Angular, например, могут запускать tsc из версии 7.0 для быстрой проверки всего проекта с помощью CLI, сохраняя при этом версию 6.0 в редакторе. Пакет совместимости @typescript/typescript6 предоставляет бинарник tsc6 и переэкспортирует API версии 6.0, что позволяет им сосуществовать:

    {
      "devDependencies": {
        "typescript": "npm:@typescript/typescript6@^6.0.0"
      }
    }
    

    Стоит ли обновляться?

    Если вы уже используете TypeScript 6.0, миграция требует немногих изменений, но приносит значительные преимущества: достаточно скорректировать несколько параметров tsconfig, чтобы ускорить процессы CI и запуск редактора. При переходе на более старые версии могут возникнуть серьезные проблемы, но почти все из них уже были заранее сообщены.

    Установите кандидата на выпуск сегодня же:

    npm install -D typescript@rc
    

    Что касается редакторов, сегодня дополнение для VS Code поддерживает протокол LSP, а интеграция с самим VS Code постоянно развивается. Visual Studio распознает версию 7.0 в открытом рабочем пространстве без необходимости отдельной установки.

    Разработчики утверждают, что выпуск новых функций снова будет происходить в привычном многомесячном ритме, и ожидается, что версия 7.1 устранит недостатки API для встраивания кода.

    Итоговый результат: привычные правила TypeScript, работающие как нативный код.