В продолжение предыдущей статьи:«Углубленный Babel, часть 1: архитектура и принципы + реальный бой 🔥»Добро пожаловать в перепечатку, пусть больше людей увидит мою статью, пожалуйста, укажите источник
Эта статья не менее сухая, чем предыдущая, в ней мы подробно обсудим макросы.——Я думаю, что мы не новички в макросах, потому что первый язык многих программистовC/C++
; НемногоLisp
Диалект также поддерживает макросы (например,Clojure
,Scheme
), я слышал, что макросы у них очень элегантные, некоторые современные языки программирования тоже имеют некоторую поддержку макросов, напримерRust
,Nim
,Julia
,Elixir
, как они решают технические проблемы и реализуют лисповские макросистемы? Какую роль играют макросы в этих языках...
Если вы не читали предыдущую статью, сначала прочитайте ее, чтобы она не повлияла на ваше понимание содержания этой статьи.
Схема статьи
О макросах
Wiki
Приведенное выше определение «макро»:Макрос — это название пакетного процесса, который преобразует определенные текстовые шаблоны в соответствии с рядом предопределенных правил.解释器
или编译器
Это преобразование режима происходит автоматически при обнаружении макроса, и этот процесс преобразования называется «Расширение макроса». Для скомпилированных языков расширение макросов происходит во время компиляции, а инструменты, выполняющие расширение макросов, часто называются расширителями макросов.
Вы можете думать, что,Макрос — это код, используемый для генерации кода, он может выполнять некоторый синтаксический анализ и преобразование кода.. Макросы можно условно разделить на два типа:замена текстаирасширение синтаксиса
замена текста
Все в той или иной степени сталкивались с макросами.Первым языком многих программистов являетсяC/C++
(включая производные CObjective-C
), существуетC
Есть макроконцепция. использовать#define
Директива определяет макрос:
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
Если наша программа использует этот макрос, то при компиляции он будет расширен, например:
MIN(a + b, c + d)
будет расширен до:
((a + b) < (c + d) ? (a + b) : (c + d))
Кроме函数宏
, C
Также в对象宏
, мы обычно используем его для объявления «констант»:
#define PI 3.1214
Как показано выше,Макросы не являются изначальноC
часть языка, это состоит изC预处理器
При условии, что препроцессор обрабатывает исходный код перед компиляцией.замена текста, что дает «истинное»C
код, который затем передается компилятору.
Конечно, препроцессор C будет не только обрабатывать макросы, он также включает в себя такие операции, как введение заголовочного файла, условная компиляция, управление строкой и т.д.
Кроме,GNU m4
является более специализированным/мощным/универсальным препроцессором (расширителем макросов). Это макрорасширитель общего назначения, который можно использовать не только для C, но и для других языков и обработки текстовых файлов (Обратитесь к этой интересной статье:«Добавление поддержки оглавления в Markdown с помощью GNU m4»), оm4
можно смотретьПусть мир получит еще один учебник по GNU m4серия статей.
Макросы замены текста просты для понимания и просты в реализации, потому что они просто заменяют обычный текст, другими словами, это похоже на «текстовый редактор». Так что условно говоря,Эта форма макроса имеет ограниченные возможности, например, не проверяет синтаксис на легальность, и часто возникают проблемы с его использованием..
такПо мере того, как современные языки программирования становятся более выразительными, многие языки больше не рекомендуют использовать макросы/отсутствие макросов, а используют собственные механизмы языка (такие как функции) для решения проблем, которые безопаснее, проще для понимания и отладки. Без механизма макросов современные языки могут компенсировать недостаток метапрограммирования, вызванный отсутствием макросов, предоставляя мощные механизмы отражения или функции динамического программирования (такие как прокси-сервер Javascript, декоратор Python). Итак, выведите обратно, почемуC
Языкам нужны макросы именно потому, чтоC
Способность выражать язык слишком слаба.
расширение синтаксиса
«Настоящие» макросы происходят изLisp
Это выигрывает от некоторых особенностей самого языка Лисп:
- Его синтаксис очень прост. ТолькоS-выражение (s-выражение)(S-выражения, характеризующиеся обозначением префикса в квадратных скобках, можно рассматривать как приблизительные абстрактные синтаксические деревья Lisp (AST).)
- Данные как код. S-выражения сами по себе являются древовидными структурами данных. Кроме того, Lisp поддерживает преобразование между данными и кодом.
Из-за простой синтаксической структуры Лиспа существует очень тонкая грань между данными и программами (Модификация котировки — это данные, отсутствие котировки — программа), другими словами, программа и данные могут быть гибко преобразованы. это数据即程序、程序即数据
Концепция позволяет легко настраивать макросы в Lisp.Давайте рассмотрим пример определения макросов в Lisp:
; 使用defmacro定义一个nonsense宏, 接收一个function-name参数. 宏需要返回一个quoted
; ` 这是quote函数的简写,表示quote,即这段‘程序’是一段‘数据’, 或者说将‘程序’转换为‘数据’. quote不会被‘求值’
; defun 定义一个函数
; , 这是unquote函数的简写, 表示unquote,即将‘数据’转换为‘程序’. unquote会进行求值
; intern 将字符串转换为symbol,即标识符
(defmacro nonsense (function-name)
`(defun ,(intern (concat "nonsense-" function-name)) (input) ; 定义一个nonsense-${function-name} 方法
(print (concat ,function-name input)))) ; 输入`${function-name}${input}`
Если вы не понимаете смысл приведенной выше программы, вот реализация Javascript
Примечание. «Макросы» обычно расширяются на этапе компиляции, следующий код предназначен только для того, чтобы помочь вам понять приведенный выше код Lisp.
function nonsense(name) {
let rtn
eval(`rtn = function nonsense${name}(input) {
console.log('${name}', input)
}`)
return rtn
}
Применить расширение макроса:
(nonsense "apple") ; 展开宏,这里会创建一个nonsense-apple函数
(nonsense-apple " is good") ; 调用刚刚创建的宏
; => "apple is good"
В Лиспе макрос немного похож на функцию, за исключением того, что функция должна возвращатьquoted数据
; Когда этот макрос вызывается, Лисп будет использоватьunquote
Функция возвращает макросquoted数据
преобразовать в程序
.
Глядя на приведенный выше пример, вы вздохнете, насколько изящна и проста реализация макросов в Лиспе. Заставляет меня следоватьтитульный листузнать волнуClojure, но потом я узналElixir😂.
Гибкость макросов Лиспа выигрывает от простого синтаксиса (S-выражения могут быть эквивалентны его AST), а для языков со сложным синтаксисом (таких как Javascript) гораздо сложнее реализовать лисп-подобные макросы. Механизм макросов, предоставляемый языком, также может быть по этой причине.
Несмотря на это, многие технические трудности постепенно решаются, и многие современные языки также вводят «подобные» лисповским макромеханизмы, такие какRust,Juliaи JavascriptSweet.js
Sweet.js
Sweet.js и Rust принадлежат к одной школе, поэтому их макросинтаксис очень похож (исходный). Однако следует отметить, что:Официально Sweet.js все еще находится в экспериментальной стадии., а последнее время подачи Github осталось 2 года назад, и сообщество не видело масштабного использования. Так что не используйте его в продакшене, но это не мешает нам изучать макромеханику современного языка программирования.
мы сначала используемSweet.js
Для достижения вышеперечисленного мы передаемLisp
осуществленныйnosense
макросы, которые легче понять в сравнении:
import { unwrap, fromIdentifier, fromStringLiteral } from '@sweet-js/helpers' for syntax;
syntax nosense = function (ctx) {
let name = ctx.next().value;
let funcName = 'nonsense' + unwrap(name).value
return #`function ${fromIdentifier(name, funcName)} () {
console.log(${fromStringLiteral(name, unwrap(name).value)} + input)
}`;
};
nosense Apple
nosenseApple(" is Good") // Apple is Good
Во-первых, Sweet.js используетsyntax
ключевое слово для определения макроса с синтаксисом, подобнымconst
илиlet
.
По сути, макрос — это функция, но он выполняется на этапе компиляции., Эта функция принимаетTransformerContext
объект, вы также получаете входящее приложение макроса через этот объектСинтаксис Массив объектов, и в конечном итоге этот макрос также возвращаетмассив объектов синтаксиса.
Что такое объект грамматики? Объекты грамматики — это внутреннее представление грамматик Sweet.js, и вы можете сравнить их с приведенными выше данными Lisp.В языке со сложной грамматикой нет возможности использовать такую простую последовательность, как в кавычках, для выражения грамматики, а использование AST сложнее и труднее контролировать разработчикам. Таким образом, большинство реализаций макросов будут ссылаться на Lisp.S-表达式
, пойти на компромисс, преобразовать входящую программу в токены, а затем собрать в цитируемую структуру данных.
Например, Sweet.js будетfoo,bar('baz', 1)
Преобразование в структуру данных следующим образом:
Как видно из рисунка выше, Sweet.js разбирает входящую программу наПоследовательность вложенных токенов, эта структура и Lisp'sS-表达式
очень похожий. То есть говорится, что закрытые лексические единицы будут храниться вложенными, как в приведенном выше примере.('baz', 1)
.
Эликсир также принятАналогичный механизм кавычек/раскавычек, которые можно понять вместе
TransformerContext
реализует метод итератора, поэтому мы вызываем егоnext()
пройти, чтобы получить объект грамматики. Наконец, макрос должен возвращать массив объектов синтаксиса, Sweet.js использует что-то вроде字符串模板
изграмматика(называется语法模板
) для упрощения разработки этот шаблон в конечном итоге преобразуется в массив объектов синтаксиса.
должен быть в курсе
语法模板
Встроенное значение может быть только объектом синтаксиса, последовательностью объектов синтаксиса или TransformerContext.
используется старая версиясопоставление с образцом, похожий на синтаксис Rust, я лично предпочитаю этот, почему-то забросил
macro define {
rule { $x } => {
var $x
}
rule { $x = $expr } => {
var $x = $expr
}
}
define y;
define y = 5;
Сказав так много, похоже на Sweet.js语法对象
Дизайн является ключевым техническим моментом современных языков программирования, чтобы оставаться рядом с макросами Лиспа. я нашелElixir
,Rust
Другие языки используют аналогичный дизайн. Помимо проектирования структур данных макромеханизмы современных языков программирования включают в себя следующие особенности:
1️⃣ Гигиенический макрос (Гигиена)
Гигиенические макросы означают, что переменные, сгенерированные внутри макроса, не будут загрязнять внешнюю область, то есть, когда макрос расширяется, Sweet.js будет избегать конфликтов между переменными, определенными внутри макроса, и внешней областью.
В качестве примера создадим макрос подкачки, который меняет местами значение переменной:
syntax swap = (ctx) => {
const a = ctx.next().value
ctx.next() // 吃掉','
const b = ctx.next().value
return #`
let temp = ${a}
${a} = ${b}
${b} = temp
`;
}
swap foo,bar
Расширение будет выводиться как
let temp_10 = foo; // temp变量被重命名为temp_10
foo = bar;
bar = temp_10;
Если вы хотите ссылаться на внешние переменные, это тоже нормально. Но делать это не рекомендуется, т.Макросы не должны предполагать контекст, в котором они развернуты:
syntax swap = (ctx) => {
// ...
return #`
temp = ${a} // 不使用 let 声明
${a} = ${b}
${b} = temp
`;
}
2️⃣ Модульный
Макросы Sweet.js являются модульными:
'lang sweet.js';
// 导出宏
export syntax class = function (ctx) {
// ...
};
Импортировать:
import { class } from './es2015-macros';
class Droid {
constructor(name, color) {
this.name = name;
this.color = color;
}
rollWithIt(it) {
return this.name + " is rolling with " + it;
}
}
По сравнению с Babel (компилятором) макросы Sweet.js являются модульными/явными. Для Babel необходимо настроить различные плагины и опции в конфигурационном файле, особенно когда групповой проект строит единую спецификацию и среду, модификация скрипта сборки проекта может быть ограничена. А модульные макросы являются частью исходного кода, а не сценария сборки, что позволяет гибко использовать их, реорганизовывать и объявлять устаревшими..
описано нижеbabel-plugin-macros
Самое большое преимущество здесь, как правило,Мы надеемся, что среда сборки унифицирована и стабильна, и разработчики должны сосредоточиться на разработке кода, а не на том, как создавать программы, именно из-за вариативности кода рождаются эти решения..
должен быть в курсеМакросы раскрываются во время компиляции, поэтому пользовательский код не может быть запущен, например:
let log = msg => console.log(msg); // 用户代码, 运行时被求值,所以无法被访问
syntax m = ctx => {
// 宏函数在编译阶段被执行
log('doing some Sweet things'); // ERROR: 未找到变量log
// ...
};
Sweet.js похож на макросы на других языках, с ним вы можете:
- Добавьте синтаксический сахар (сладкий, как Sweet.js), чтобы реализовать синтаксис на свой вкус или некоторые экспериментальные функции языка.
- настроитьоператор, очень могущественный
- Удалите повторяющийся код и улучшите выразительность языка.
- ...
- не хвастайся
🤕 Извините! Sweet.js практически мертв. Так что теперь можно играть с ним как с игрушкой и не использовать в производственной среде. Даже если он не мертв, нестандартный синтаксис Sweet.js несовместим с существующей экосистемой цепочки инструментов Javascript, и его разработка и отладка (например, Typescript) будут более проблематичными.
В конце концов, Sweet.js потерпел неудачу, потому что сообщество отказалось от него. Способность выражать язык Javascript становится все сильнее и сильнее, итерация версии происходит быстро, а с такими решениями, как Babel и Typescript, действительно нет причин использовать Sweet.js.
Документы, связанные с Sweet.js, можно увидетьздесь
резюме
Этого раздела слишком много, чтобы говорить об истории и классификации макросов. Окончательное резюме — это предложение в официальном туториале по Эликсиру:Явный код лучше неявного, четкий код лучше лаконичного.
С большой властью приходит большая ответственность. Макросы мощнее и сложнее в управлении, чем обычные программы. Вам может потребоваться определенная стоимость, чтобы изучить и понять их, поэтому вы можете использовать макросы без макросов.Макросы должны быть последним магическим оружием.
Плагин Jisheng Макрос Hesheng
🤓 Это еще не конец, я проделал сразу большой путь, вернемся к делу. Теперь, когда у Babel есть плагин, почему появился еще один?babel-plugin-macros
?
Если вы еще не знакомы с Babel Macro, вы можете сначала прочитатьофициальная документация, Кроме тогоCreact-React-APPуже встроенный
Это должно быть изCreate-React-App(CRA)Кстати говоря, CRA инкапсулирует всю логику сборки проекта вreact-scripts
в сервисе.Преимущество этого в том, что разработчикам больше не нужно заботиться о деталях сборки, а апгрейд инструментов сборки стал очень удобным, а апгрейд напрямуюreact-scripts
Просто.
Если вы сами поддерживаете скрипты сборки, вам нужно обновлять множество зависимостей, а если вы хотите поддерживать кросс-проектные скрипты сборки, это будет еще более болезненно.
я здесь«Зачем использовать vue-cli3?»Это объясняет важность таких инструментов, как CRA и Vue-cli, для обслуживания командного проекта.
CRA этосильное согласиеДа, он подготовлен для вас в соответствии с лучшими практиками сообщества React.В целях защиты дивидендов, приносимых инкапсуляцией, не рекомендуется вручную настраивать Webpack, Babel... Так родился babel-plugin- макросы, вы можете увидеть этоIssue: RFC - babel-macros
Таким образом, поиск механизма «нулевой конфигурации» для Babelbabel-plugin-macros
Основная мотивация рождения.
Эта статья как раз подтверждает эту мотивацию:«Преобразование кода с нулевой конфигурацией с помощью макросов babel-plugin», в статье приводится важный момент: "Compilers are the New Frameworks"
верно,Babel играет очень важную роль в современной фронтенд-разработке.Все больше и больше фреймворков или библиотек будут создавать свои собственные плагины Babel.Они будут выполнять некоторые оптимизации на этапе компиляции, чтобы улучшить взаимодействие с пользователем, опыт разработки и время выполнения.производительность.. Например:
- babel-plugin-lodashПреобразование импорта lodash в импорт по запросу
- babel-plugin-importПлагин, упомянутый в предыдущей статье, также реализует импорт по требованию.
- babel-react-optimizeСтатически анализируйте код React и используйте определенные меры для оптимизации операционной эффективности. Например, извлечение статических реквизитов или компонентов в константы.
- root-importПереписать корневые пути импорта в относительные пути
- styled-componentsТипичное решение CSS-in-js, использующее плагины Babel для поддержки рендеринга на стороне сервера, предварительной компиляции шаблонов, сжатия стилей, очистки мертвого кода и улучшения отладки.
- prevalПредварительное выполнение кода во время компиляции
- babel-plugin-graphql-tagПредварительно скомпилированные запросы GraphQL
- ...
В сценарии плагина, указанном выше,Не все плагины универсальны, они либо привязаны к определенному фреймворку, либо используются для обработки определенных типов файлов или данных. Эти неуниверсальные плагины лучше заменить макросами..
использоватьpreval
Например, чтобы использовать форму плагина, вы сначала настраиваете плагин:
{
"plugins": ["preval"]
}
Код:
// 传递给preval的字符串会在编译阶段被执行
// preval插件会查找preval标识符,将字符串提取出来执行,在将执行的结果赋值给greeting
const greeting = preval`
const fs = require('fs')
module.exports = fs.readFileSync(require.resolve('./greeting.txt'), 'utf8')
`
Используя метод макроса:
// 首先你要显式导入
import preval from 'preval.macro'
// 和上面一样
const greeting = preval`
const fs = require('fs')
module.exports = fs.readFileSync(require.resolve('./greeting.txt'), 'utf8')
`
Эффект от них одинаков, но смысл разный. Каковы различия?
-
1️⃣Очевидно, макрос не требует настройки
.babelrc
(Конечно, база babel-plugin-macros должна быть установлена.) Это полезно для таких инструментов, как CRA, где настройка скриптов сборки устарела. -
2️⃣Преобразование из неявного в явное. В предыдущем разделе говорилось, что «явное лучше, чем неявное». Вы должны передать исходный код
导入语句
Заявите, что вы используете Macro; и способ, основанный на плагинах, вы можете не знатьpreval
Откуда взялся этот идентификатор и как он используется? Когда оно было применено? И обычно вам также необходимо взаимодействовать с другими цепочками инструментов, такими как ESlint, декларации Typescript и т. д.Макрос явно применяется кодом, мы более четко понимаем цель и сроки его применения, а исходный код минимально навязчив. Потому что есть больше
babel-plugin-macro
На этом уровне мы уменьшаем связь со средой сборки, упрощая миграцию нашего кода. -
3️⃣Макрос проще реализовать, чем плагин. поскольку он фокусируется на конкретных узлах AST, см. ниже
-
4️⃣ Кроме того, при неправильной конфигурации Macro может получать более качественные сообщения об ошибках.
Есть плюсы и минусы, и у Babel Macro определенно есть недостатки, например, по сравнению с плагинами он может толькоявное преобразование, поэтому код может быть более подробным, но лично я считаю, что в некоторых сценариях преимущества перевешивают недостатки, поэтому вы можете сделать его явным, если сможете.
Так Babel Macro тоже макрос?Каковы недостатки этих «ортодоксальных» макромеханизмов по сравнению со Sweet.js??
-
Первый макрос Babel должен быть допустимым синтаксисом Javascript. Он не поддерживает пользовательский синтаксис, и его следует обсуждать с двух сторон. Легальный синтаксис Javascript не нарушит существующую цепочку совместной работы инструментов. Если пользователям будет разрешено создавать новые синтаксисы без ограничений, в будущем со стандартными синтаксисами возникнет неоднозначность. . И наоборот, разве это не очень идиоматично и «мощно» для «макроса», который не может настроить синтаксис?
-
Поскольку это должен быть допустимый синтаксис Javascript, способность Babel Macro реализовать DSL (предметно-ориентированные языки) ослаблена.
-
Кроме того, нет существенной разницы между макросом Babel и плагином Babel., По сравнению с Sweet.js, который предоставляет явное определение и синтаксис макросов приложения, Babel Macro намного сложнее для прямого управления AST. Вам все еще нужно понимать некоторые принципы компиляции, что блокирует обычных разработчиков.
Babel может реализовать собственный синтаксис, но вам нужен Fork
@babel/parser
, чтобы преобразовать его (см. эту статью"Интенсивное чтение "Создание пользовательского синтаксиса JS с помощью Babel"). Это немного бросок, не рекомендуется
Короче говоря, макрос Babel по сути ничем не отличается от плагина Babel, он просто инкапсулирует слой поверх плагина (Сила шаблонов многоуровневой архитектуры), создание новой платформы, позволяющей разработчикам явно применять транскодирование на исходном уровне.. так,Любой сценарий, подходящий для явного деконверсии, подходит для макроса Babel.:
- Преобразование кода для конкретных фреймворков и библиотек. как
styled-components
- Генерируйте код динамически.
preval
- Обработка конкретных файлов, языков. Например
graphql-tag.macro
,yaml.macro
,svgr.macro
- ... (Проверятьawesome-babel-macros)
Как написать макрос Babel
Итак, как же работает Babel Macro?babel-plugin-macros
Требовать от разработчиков явного импорта макроса, он будет проходить все соответствующие операторы импорта,Если источник импорта соответствует/[./]macro(\.js)?$/
регулярное выражение будет думать, что вы включаете макрос. Например, все следующие операторы импорта соответствуют регулярным выражениям:
import foo from 'my.macro'
import { bar } from './bar/macro'
import { baz as _baz} from 'baz/macro.js'
// 不支持命名空间导入
Хорошо, после сопоставления с оператором импортаbabel-plugin-macros
будет импортировать указанныйmacro
модуль или пакет npm(Макрос может быть локальным файлом, общедоступным пакетом npm или вложенным путем в пакете npm).
Такmacro
Что должно быть включено в файл? следующее:
const { createMacro } = require('babel-plugin-macros')
module.exports = createMacro(({references, state, babel}) => {
// ... macro 逻辑
})
macro
Файл должен экспортировать по умолчаниюceateMacro
Созданный экземпляр может получить некоторые ключевые объекты в своем обратном вызове:
-
babel
Как и обычные плагины Babel, Macro может получитьbabel-core
объект -
state
Мы также знакомы с этим.Второй параметр метода посетителя плагина Babel — it.Мы можем использовать его для получения некоторой информации о конфигурации и сохранения некоторых пользовательских состояний. -
references
Получает все ссылки на идентификаторы экспорта макросов. В последней статье была представлена область действия, и вы, вероятно, не забыли концепции привязок и ссылок. следующее
Предположим, что пользователь использует ваш макрос следующим образом:
import foo, {bar, baz as Baz} from './my.macro' // 创建三个绑定
// 下面开始引用这些绑定
foo(1)
foo(2)
bar`by tagged Template`
;<Baz>by JSX</Baz>
тогда вы получитеreferences
Структура такая:
{
// key 为'绑定', value 为'引用数组'
default: [NodePath/*Identifier(foo)*/, NodePath/*Identifier(foo)*/], // 默认导出,即foo
bar: [NodePath/*Identifier(bar)*/],
baz: [NodePath/*JSXIdentifier(Baz)*/], // 注意key为baz,不是Baz
}
ПроверятьПодробное руководство по разработке
AST ExplorerОн также поддерживает макросы babel-plugin, вы можете играть в него.Также рекомендуется изучить следующие реальные боевые примеры.
Затем вы можете пройтиreferences
, Преобразуйте эти узлы, чтобы получить желаемую функцию макроса. Давайте начнем!
реальный бой
На этот раз мы моделируемpreval
Создаватьeval.macro
Макрос, используйте его для выполнения (оценки) некоторого кода на этапе компиляции. Например:
import evalm from 'eval.macro'
const x = evalm`
function fib(n) {
const SQRT_FIVE = Math.sqrt(5);
return Math.round(1/SQRT_FIVE * (Math.pow(0.5 + SQRT_FIVE/2, n) - Math.pow(0.5 - SQRT_FIVE/2, n)));
}
fib(20)
`
// ↓ ↓ ↓ ↓ ↓ ↓
const x = 6765
Создайте файл макроса.Согласно введению в предыдущем разделе, ① мы используемcreateMacro
создатьMacro
пример, ② и отreferences
вынуть все导出标识符
За эталонным путем 3 следует преобразование AST этих эталонных путей:
const { createMacro, MacroError } = require('babel-plugin-macros')
function myMacro({ references, state, babel }) {
// 获取默认导出的所有引用
const { default: defaultImport = [] } = references;
// 遍历引用并进行求值
defaultImport.forEach(referencePath => {
if (referencePath.parentPath.type === "TaggedTemplateExpression") {
const val = referencePath.parentPath.get("quasi").evaluate().value
const res = eval(val)
const ast = objToAst(res)
referencePath.parentPath.replaceWith(ast)
} else {
// 输出友好的报错信息
throw new MacroError('只支持标签模板字符串, 例如:evalm`1`')
}
});
}
module.exports = createMacro(myMacro);
Для краткости этот случай поддерживает только标签模板字符串
form, но строка шаблона тега может содержать интерполированные строки, например:
hello`
hello world ${foo} + ${bar + baz}
`
Его структура AST выглядит следующим образом:
нам надоTaggedTemplateExpression
Узлы преобразуются в строки. Было бы очень проблематично выполнять сплайсинг вручную, но, к счастью, объект Path каждого узла AST имеетevaluate
метод, который может "статически оценивать" узел:
t.evaluate(parse("5 + 5")) // { confident: true, value: 10 }
t.evaluate(parse("!true")) // { confident: true, value: false }
// ❌两个变量相加无法求值,因为变量值在运行时才存在,这里confident为false:
t.evaluate(parse("foo + foo")) // { confident: false, value: undefined }
Поэтому такая строка шаблона тега не может быть оценена:
evalm`1 + ${foo}` // 包含变量
evalm`1 + ${bar(1)}` // 包含函数调用
это иTypescript
изenum
, и некоторые константы скомпилированного языка одинаковы, они оцениваются во время компиляции, только некоторые примитивные значения и некоторые выражения примитивных значений поддерживаются для оценки во время компиляции.
Итак, приведенный выше код недостаточно надежен, давайте еще раз оптимизируем его, чтобы дать пользователю более подсказку в случае сбоя оценки:
defaultImport.forEach(referencePath => {
if (referencePath.parentPath.type === "TaggedTemplateExpression") {
const evaluated = referencePath.parentPath.get("quasi").evaluate();
// 转换标签模板字符串失败
if (!evaluated.confident) {
throw new MacroError("标签模板字符串内插值只支持原始值和原始值表达式");
}
try {
const res = eval(evaluated.value);
const ast = objToAst(res);
// 替换掉调用节点
referencePath.parentPath.replaceWith(ast);
} catch (err) {
throw new MacroError(`求值失败: ${err.message}`);
}
} else {
throw new MacroError("只支持标签模板字符串, 例如:evalm`1 + 1`");
}
});
Затем преобразуйте выполненное значение в AST, а затем заменитеTaggedTemplateExpression
:
function objToAst(res) {
let str = JSON.stringify(res);
if (str == null) {
str = "undefined";
}
const variableDeclarationNode = babel.template(`var x = ${str}`, {})();
// 取出初始化表达式的 AST
return variableDeclarationNode.declarations[0].init;
}
здесь@babel/template
Это удобно, он может разобрать строковый код в AST, конечно, напрямую использоватьparse
Также возможен разбор метода.
Хорошо, статья в основном здесь. В этой статье подробно рассматриваются «макросы», начиная сC
Макросы замены языкового текста на смертьSweet.js
, наконец представилbabel-plugin-macros
.
Макрос Babel по сути является подключаемым модулем Babel, за исключением того, что он модульный, и вы должны импортировать его явно, чтобы использовать его. По сравнению с "ортодоксальными" макросами, Babel Macro напрямую манипулирует AST и требует от вас освоения принципов компиляции. То, что может быть достигнуто с помощью "ортодоксальных" макросов, может достичь Babel Macro (например, гигиенические макросы). Хотя он немного упрощен по сравнению с к плагину Babel, он по-прежнему довольно многословен. Кроме того, Babel Macro не может создавать новый синтаксис, что делает его совместимым с существующей экосистемой инструментов.
Наконец! Открой свой разум 🧠, Babel Macro умеет много интересного, проверяй«Потрясающие макросы Babel». НоПомните: «Явное лучше, чем неявное, ясный код лучше, чем лаконичный код».
По состоянию на 10.10.2019 количество фанатов Наггетс перевалило за ✨2000✨, продолжайте подписываться на меня, ставить лайки и поддерживать меня.
Расширенная информация
- Zero-config code transformation with babel-plugin-macros
- RFC - babel-macros
- STOP WRITING JAVASCRIPT COMPILERS! MAKE MACROS INSTEAD
- Играем в Clojure с помощью JavaScript — макрос (1)
- Elixir Macro
- Макросы для Rust
- Глубокие мысли iOS | Определение макросов
- How does Elixir compile/execute code?
- Пусть мир получит еще одно руководство по GNU m4 (1)
- Почему макроязыки не популярны
- awesome-babel
- Какова поддержка «макро» в каждом языке программирования?
- Документы, связанные с Sweetjs