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