Написание настраиваемого преобразователя AST TypeScript (часть 3)

Это продолжение Части 2 и более глубокое погружение в парочку продвинутых приемов, которые я использовал при написании своих трансформеров.

Импорт без TS

Импорт без TS позволяет мне объявлять явные зависимости от статических ресурсов, таких как CSS / png / svg, других файлов, отличных от JS, и даже внешней инструментальной цепочки сборки. Это важно при работе с большой кодовой базой, так как легче отследить цепочку зависимостей и провести рефакторинг, когда что-либо изменяется в графе деп.

Это основная причина, по которой я написал https://github.com/longlho/ts-transform-css-modules

Процесс довольно прост: всякий раз, когда мы встречаем узел import с moduleSpecifier, заканчивающимся на .css, мы пытаемся преобразовать этот узел в хэш const foo = {className: '1nahjk'}.

Интересно то, что это позволяет вам запускать конвейер сборки PostCSS в оперативном режиме, пока он синхронный.

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

Макрос компилятора

Макрос компилятора - довольно распространенная концепция в традиционном компиляторе. В мире babel babel-plugin-macros неплохо представляет эту концепцию. Это позволяет вам делать такие вещи, как тегирование определенного фрагмента кода для извлечения, встроенные скрипты pre-eval, встраивание переменных времени компиляции и т. Д.

С моей точки зрения, import можно рассматривать как своего рода макрос компилятора, в котором импортируемые ресурсы могут обрабатываться по-разному. Другой распространенный пример - извлечение строки для i18n, подобное https://github.com/longlho/ts-transform-react-intl

Каждый раз, когда я встречаю символ _, импортированный из ts-transform-react-intl, я помечаю параметр как MessageDescriptor и извлекаю их для перевода. Пост-преобразованный код становится:

Во время этого процесса мы также можем запустить линтер синтаксиса ICU, объявленного в формате, чтобы отловить такие вещи, как недопустимый синтаксис, эмодзи, ограничения поставщика и т. Д.

Использование TypeChecker

Проверка _ в операторе import может быть неточной, особенно когда задействованы псевдонимы: import {_ as _d} from 'ts-transform-react-intl'

Однако API TypeChecker позволяет разрешать псевдонимы typeChecker.getAliasedSymbol и различные другие API, которые становятся очень удобными при соединении символов и типов вместе. Это определенно намного мощнее, чем парсер на основе AST, такой как estree.

Модификация объема / инъекция

Хотя встроенный мод очень удобен, есть случаи, когда вы можете оптимизировать свой код, например, подняв переменную за пределы области видимости. Хотя это можно сделать вручную в исходном коде, это определенно требует умственных затрат во время разработки и проверки кода, в то время как такие вещи, как извлечение элементов const React, могут выполняться автоматически: https://github.com/dropbox/ts-transform-react-constant -элементы

Общий поток

Найдите правильное лексическое окружение (объем)

ctx.startLexicalEnvironment: добавить лексическое окружение в стек для хранения объявления переменных / функций локали

ctx.suspendLexicalEnvironment и ctx.resumeLexicalEnvironment: обычно при посещении списка параметров, предотвращая создание новой области.

ctx.endLexicalEnvironment: вытащить стек.

ctx.hoistVariableDeclaration: записать переменную в текущем лексическом окружении в стеке.

Однако часто создается новая область видимости, поэтому меньше контроля над тем, где она поднимается (например, ввод тела функции создает область видимости, а подъем только записывает в текущую область видимости в стеке, поэтому нет контроля для подъема).

В нашем случае инъекция в верхнюю часть вполне подойдет.

Найдите верхний узел, чтобы ввести эту переменную

#!/env/node: ниже shebang (не в спецификации и может вылететь в Esprima)

"use strict";: ниже директивы пролога (например, «используйте строгий»)

// ts-lint:disable: ниже комментариев верхнего уровня

import * as React from 'react';: ниже import React

Найдите постоянные элементы

Поскольку мы не можем просматривать резервные копии, нам пришлось бы выполнить полный проход по дереву, отфильтровать узлы с неизменяемыми атрибутами и собрать их.

Преобразовать

После сбора этих узлов мы должны преобразовать их в заранее объявленные имена переменных, используя ts.createUniqueName(prefix) для каждого подъемного узла.

После этого мы можем добавить после верхнего узла импорта React, добавив[reactNode, …hoistedVarStatements]. Наконец, сделайте еще один обход и замените эти ReactElement переменными, созданными ранее.

Цепочка трубопроводов

Вы даже можете связать эти трансформаторы AST, как показано ниже:

Надеюсь, вы, ребята, узнаете что-то новое! Я, безусловно, многому научился в ходе этого процесса, включая чтение спецификации и погружение в другие конвейеры сборки в экосистеме.