Здравствуйте, меня зовут Дмитрий Карловский и у меня… посттравматическое стрессовое расстройство после генерации сорсмапов. И сегодня, с вашей помощью, мы будем это лечить путём максимально глубокого погружения в травмирующие события.  Это — текстовая расшифровка выступления на HolyJS'21. Вы можете посмотреть видео запись, прочитать как статью, либо открыть в интерфейсе проведения презентаций. Сперва история болезни: Однажды я разработал простой формат Tree для представления абстрактных синтаксических деревьев в наиболее наглядной форме. На базе этого формата я реализовал уже несколько языков. Один из них — язык view.tree — предназначен для декларативного описания компонент и композиции их друг с другом. И именно на этом языке описаны все стандартные визуальные компоненты фреймворка $mol. Это позволяет писать короткий и наглядный код, делающий при этом много чего полезного. Сейчас вы видите законченное приложение на $mol: Оно состоит из панельки, внутри которой расположен чекбокс. И вместе они связаны двусторонней связью по заданным свойствам. В этих 5 строчках кода есть даже поддержка локализации. Эквивалентный код на JavaScript занимает в 5 раз больше места: Этот код, хоть и на более привычном многим языке, куда более сложный в понимании. Кроме того, он совсем потерял иерархию, чтобы добиться того же уровня гибкости. Плоский класс хорош тем, что от него можно отнаследоваться и переопределить любой аспект поведения компонента. Таким образом, одна из основных причин использования DSL — это возможность писать простой и лаконичный код, который легко изучить, в котором сложно накосячить, и который легко поддерживать. Другая причина внедрения DSL — это потребность давать самим пользователям расширять логику вашего приложения, используя скрипты. Например, возьмём простой скрипт автоматизации работы со списком задач, написанный обычным пользователем: Тут он говорит: назначь меня ответственным на все задачи; укажи, что они все относятся к фронтенду; если эстимейт не задан, то пропиши 1 день; и выстрой их дедлайны по очереди с учётом получившихся эстимейтов. И тут вы, возможно, спросите: почему бы просто не дать пользователю в руки JS? И тогда я с вами, внезапно, соглашусь. У меня даже есть песочница для безопасного исполнения пользовательского JS. И онлайн-песочница для песочницы: sandbox.js.hyoo.ru Можете попробовать выбраться из неё. Мой любимый пример: Function is not a function — в очень духе JS. Однако, для обычного пользователя JS — слишком сложен.   Ему было бы куда проще освоить какой-то простой язык, ориентированный на его бизнес-область, а не язык общего назначения типа JS. Ещё одна причина создавать свой DSL — это возможность написать код один раз, а исполнять его в самых разных рантаймах: В качестве иллюстрации, приведу пример из одного стартапа, который я разрабатывал. За пол года разработки мы сделали довольно много. И всё благодаря тому, что у нас был универсальный изоморфный API, который конфигурировался простеньким DSL, где описывалось, какие у нас есть сущности, какие у них есть атрибуты, какие у них типы, как связаны с другими сущностями, какие на них есть индексы и всё такое. Всего несколько десятков сущностей и под сотню связей. Простой пример — модель задачи.. Из этого декларативного описания, которое занимает несколько килобайт, генерируется уже код, работающий как на сервере, так и на клиенте, и, конечно, схема базы данных тоже автоматически обновляется. Таким образом, разработка (и особенно рефакторинг) существенно ускоряется. Достаточно поменять строчку в конфиге, и через несколько секунд мы уже можем дёргать на клиенте за новую сущность. Ну и, конечно, какой же программист не любит быстрой езды на велосипеде?  Так у нас появилось множество разных полезных инструментов: Разрабатывать их — задача не простая. Да даже чтобы плагин к любому из них написать — придётся попариться. Так что давайте порассуждаем, как всё это можно было бы упростить. Но сперва разберём проблемы, которые нас подстерегают на пути.. Допустим, пользователь написал такой вот простенький маркдаун-шаблон.. А мы сгенерировали развесистый код, который собирает DOM через JS.. Если пользователь столкнётся с ним, например, при дебаге, то ему потребуется много времени, чтобы понять, что это за лапшекод, и что он вообще делает. Совсем печально, когда код не просто раздутый, но и минифицированный с однобуквенными именами переменных и функций.. Но тут нам на помощь приходят сорсмапы. Они позволяют вместо сгенерированного кода показывать программисту тот код, что он написал.  Более того, с сорсмапами будут работать инструменты отладки: можно будет исполнять его по шагам, ставить брейкпоинты внутри строки и всё такое. Почти нативненько. Кроме того, сорсмапы используются для отображения стек-трейсов.  Браузер сперва показывает ссылки на сгенерированный код, в фоне загружая сорсмапы, после чего на лету подменяет ссылки на исходный код. Третья ипостась сорсмапов — отображение значения переменных.  В примере исходника используется имя next, но в рантайме такой переменной нет, ибо в сгенерированном коде переменная называется pipe. Однако, при наведении на next, браузер делает обратный мапинг и выводит уже значение именно переменной pipe. Интуитивно ожидается, что у сорсмапов должна быть обстоятельная спецификация, которую можно заимплементить и всё, мы в шоколаде. Этой штуке ведь уже 10 лет. Однако, всё не так радужно.. Спека насчитывает 3 версии. Первую я не нашёл, а остальные — это просто заметки в Google Docs. Вся история сорсмапов — это история о том, как программист, делающий инструменты разработчика, героически боролся за уменьшение их размеров. Суммарно они уменьшились в итоге примерно на 60%. Это мало того, что довольно смешная цифра сама по себе, так ещё борьба за размер сорсмапов — довольно бессмысленное занятие, ведь скачиваются они лишь на машине разработчика и то лишь тогда, когда он занимается отладкой. То есть мы получаем классическую беду многих программистов: оптимизация не того, что важно, а того, что интересно или проще оптимизировать. Никогда так не делайте! Если вы решитесь связаться с сорсмапами, то вам могут пригодиться следующие статьи: Далее же я расскажу вам про подводные грабли, которые обильно рассыпаны тут и там во имя уменьшения размера.. Подключать сорсмапы можно двумя способами. Можно через HTTP заголовок.. Но это довольно бестолковый вариант, так как требует специальной настройки веб-сервера. Далеко не каждый статический хостинг вообще такое позволяет. Предпочтительнее использовать другой способ — размещение ссылки в конце сгенерированного кода.. Как видите, у нас тут отдельный синтаксис для JS и отдельный для CSS. При этом второй вариант синтаксически корректен и для JS, но нет, так не заработает. Из-за этого мы не можем обойтись одной универсальной функцией для генерации кода с сорсмапами. Нам обязательно нужно отдельная функция для генерации JS кода и отдельная для CSS. Вот такое вот усложнение на ровном месте. Давайте посмотрим, что там у них внутри.. Поле sources содержит ссылки на исходники. Там могут быть любые строки, но обычно это относительные ссылки, по которым браузер будет выкачивать исходники. Но я рекомендую всегда класть эти исходники в sourcesContent — это убережёт вас от проблем с тем, что мапинги в какой-то момент у вас будут одной версии, а исходники другой, или вообще не скачаются. И тогда — счастливой отладки. Да, сорсмапы раздуваются в размере, зато это гораздо более надёжное решение, что важно во время отладки и без того глючащего кода. Получаем, что вся та борьба за размер сорсмапов была бессмысленна, так как добрая половина сорсмапа — это исходные коды. В поле names хранятся имена переменных в рантайме. Этот костыль уже не нужен, так как сейчас браузеры умеют как прямой, так и обратный мапинг. То есть они сами вытягивают имена переменных из сгенерированного кода. Ну а в поле mappings уже находятся, собственно, мапинги для сгенерированного кода. Представим мапинги для наглядности в несколько строк, чтобы понять их структуру.. Для каждой строки сгенерированного файла задаётся несколько спанов, разделённых запятыми. А в конце — точка с запятой для разделения строк. Тут у нас 3 точки с запятой, поэтому в сгенерированном файле минимум 3 строки. Важно подчеркнуть, что хотя точка с запятой и может быть висящей, а вот запятые висящими быть не могут. Ну, точнее, ФФ их скушает и не подавится, а вот Хром просто проигнорирует такие сорсмапы без какого-либо сообщения об ошибке. Спан — это некоторый набор чисел в количестве 1, 4 или 5 штук. Спан указывает на конкретное место в конкретном исходнике.  Пятое число — это номер имени переменной в списке names, который (как мы уже выяснили), не нужен, поэтому это число просто не указываем. Остальные 4 числа — это номер колонки в соответствующей строке генерированного файле, номер исходника, номер строки исходника и номер колонки в этой строке.  Имейте в виду, что номера начинаются с 0. Три последних числа можно опустить, тогда у нас будет лишь указатель на колонку в сгенерированном файле, который никуда в исходниках не мапится. Чуть позже я расскажу, зачем это надо. А пока разберём, как числа кодируются.. Наивно было бы сериализовать спаны как-нибудь так (каждая строка — один спан).. Но в сорсмапах применяется дифференциальное кодирование. То есть значения полей представлены как есть лишь для первого спана. Для остальных же сохраняется уже не абсолютное значение, а относительное — разница между текущим и предыдущим спаном.. Обратите внимание, что если к 40 от первого спана прибавить 860, то получится 900 для второго спана, а если добавить ещё 10, то 910 для третьего спана. Информации в таком представлении сохраняется столько же, но размерность чисел несколько уменьшается — они становятся ближе к 0.  Далее применяется VLQ кодирование, или кодирование с переменной длиной. Чем ближе число к 0, тем меньше оно требует байт для представления.. Как видите, каждые 5 значимых бит информации требуют по 1 дополнительному байту. Это не самый эффективный способ кодирования. Например, в WebAssembly применяется LEB128, где по байту тратится уже на каждые 7 значимых бит. Но это уже бинарный формат. А у нас тут мапинги зачем-то сделаны в формате JSON, который текстовый. В общем, формат переусложнили, а в размере не особо-то и выиграли. Ну да ладно, это ещё цветочки.. Сорсмапы мапят не диапазон байт в одном файле на диапазон в другом, как мог бы подумать наивный программист. Они мапят лишь точки. И всё, что попадёт между замапленной точкой и следующей в одном файле — оно как бы оказывается замапленным на всё, что после соответствующей точки до следующей в другом файле. И это, конечно, приводит к разным проблемам. Например, если мы добавим какой-то контент, которого нет в исходниках, и, соответственно, никуда не замапим, то он просто приклеится к предыдущему указателю..  В примере мы добавили Bar. И если не пропишем для него никакой мапинг (а мапить-то его некуда), то он приклеится к Foo. Получится, что Foo мапится на FooBar, и, например, отображение значений переменных при наведении перестаёт работать. Чтобы этого не происходило, нужно Bar замапить в никуда. Для этого как раз и нужен вариант спана с одним числом. В данном случае это будет число 3, так как Bar начинается с третьей колонки. Таким образом, мы говорим, что после данного указателя до следующего (или конца строки) контент никуда не замаплен, а Foo мапится только лишь на Foo. Есть и обратная ситуация, когда в исходнике контент есть, а в результат он не идёт. И тут тоже может быть проблема с прилипанием..  Получается, надо вырезанный контент куда-нибудь всё же замапить. Но куда? Единственное место — это куда-нибудь в конец результирующего файла. Это вполне себе рабочее решение. И всё бы хорошо, но если пайплайн наш на этом не заканчивается, и продолжается обработка, то могут быть проблемы. Например, если мы далее склеиваем несколько сгенерированных файлов вместе, то нам нужно объединить их мапинги. Устроены они таким образом, что их можно просто сконкатенировать. Однако при этом конец одного файла станет началом следующего. И всё благополучно сломается.  Можно было бы делать хитрый ремапинг при конкатенации, но тут нам на помощь приходит ещё один формат сорсмапов. Вот это твист! Их на самом деле два. Композитные сорсмапы выглядят уже следующим образом.. Тут сгенерированный файл разбивается на секции. Для каждой секции задаётся начальная позиция и либо ссылка на обычный сорсмап, либо само содержимое сорсмапа для этой секции. Причём обратите внимание на то, что задаётся начало секции в формате "строка-колонка", что крайне не удобно. Ведь для того, чтобы отмерить секцию, необходимо пересчитать все переводы строк в предыдущих секциях. Особенно весело такие приколы смотрелись бы при генерации бинарных файлов. Благо сорсмапы by design их не поддерживают. Ещё один крайний случай — макросы в том или ином виде. То есть кодогенерация на прикладном уровне. Возьмём для примера макрос log, который принимает некоторое выражение и оборачивает его в условное логирование... Таким образом мы не вычисляем потенциально тяжёлое выражение, если логирование выключено, но при этом не пишем кучу однотипного кода. Внимание, вопрос: куда мапить сгенерированный макросом код? Если мы замапим его на содержимое макроса, то получится, что, при исполнении кода по шагам, мы будем ходить внутри макроса: A-B-A-B-A-B. А в точке его применения так и не остановимся. То есть разработчик не сможет увидеть, откуда он попал в макрос, и что ему передавалось. Тогда может быть лучше мапить весь сгенерированный код на место применения макроса? Но тут получается иная беда: мы остановились на строке 1, потом снова на строке 1, потом опять… Так продолжаться может утомительно долго, смотря сколько инструкций окажется внутри макроса. Короче, теперь отладчик уже будет по несколько раз останавливаться на одном и том же месте, так и не зайдя в код макроса. Это и так-то неудобно, плюс отлаживать сами макросы таким образом просто не реально. С макросами лучше совмещать оба подхода. Сначала добавить инструкцию, которая ничего полезного не делает, но мапится на место применения макроса, а сгенерированный макросом код уже мапить на код макроса.. Таким образом, при отладке по шагам мы сначала остановимся на месте применения макроса, потом зайдём в него и пройдёмся по его коду, потом выйдем и пойдём дальше. Почти как с нативными функциями, только без возможности их перепрыгнуть, ибо рантайм ничего про наши макросы не знает. Хорошо бы в 4 версии сорсмапов добавили поддержку макросов. Эх, мечты, мечты.. Ну и касательно переменных, тут всё тоже довольно дубово. Если вы думаете, что можно выделить произвольное выражение в исходнике, и ожидать, что браузер глянет, во что оно мапится, и попробует это исполнить, то как бы не так!  И ещё один дьявол в деталях реализации. Если вы генерируете код не на сервере, а на клиенте, то, чтобы его исполнить, вам потребуется та или иная форма вызова интерпретатора. Если вы воспользуетесь для этого eval, то с мапингами будет хорошо, но работать будет медленно. Гораздо быстрее сделать функцию и её уже исполнять много раз.. Но браузер под капотом делает что-то типа: То есть добавляет в ваш код сверху две строки, отчего все мапинги сворачивают не туда. Чтобы это побороть, надо сорсмапы сдвинуть вниз, например, добавив пару точек с запятой в начало мапинга. Тогда new Function будет хорошо мапиться. Но съедет теперь уже в eval. То есть, когда вы генерируете мапинги, вы должны чётко понимать, как вы этот код будете запускать, иначе мапинги будут показывать не туда. Ну и самая главная беда сорсмапов: если вы где-то накосячите, то в большинстве случаев браузер вам ничего не скажет, а просто проигнорирует. И тогда придётся лишь гадать.. И даже Гугл тут мало чем помогает, ибо там в основном ответы на вопросы в духе "как настроить WebPack?". А там есть всего один разумный вариант настройки. Зачем пользователям раздали столько гранат — не понятно. Ладно, с сорсмапами сейчас всё довольно печально. Давайте попробуем спроектировать их сейчас с нуля. Я бы сделал для этого бинарный формат, где мапились бы не указатели, а конкретные диапазоны байт. На спан выделим константные 8 байт, то есть машинное слово. Работать с ним просто, быстро и, главное, его достаточно для наших нужд. Состоять спан будет из 3 чисел: смещение диапазона в кумулятивном исходнике (конкатенация всех исходников), длина этого диапазона, и длина диапазона в результате. Эта информация необходима и достаточна, чтобы однозначно смапить исходники на результат. Даже если результатом будет бинарник, а не текст. А даже если нам потребуется где-то что-то заремапить, то делается это простой и эффективной функцией. Но, к сожалению, приходится работать с тем, что есть сейчас.. Надеюсь, мне удалось показать, что сорсмапы — это то ещё болото, в которое лучше не лезть. В процессе трансформаций за ними нужно аккуратно следить, чтобы они не потерялись и не съехали. В сообщения об ошибках надо указывать на исходник, а в случае макросов нужно выводить трейс по исходникам. Итого: Я бы не хотел с ними связываться, но мне пришлось. Но давайте подумаем, как их всё же избежать.. Возьмём популярный инструмент, например Babel. Наверняка же там все проблемы уже решены и можно садиться да ехать! Возьмём первый попавшийся плагин.. Он трансформирует стрелочную функцию в обычную. Задача вроде бы простая, да и кода не так уж много! Однако, если присмотреться, то всё, что эта портянка делает, — это вызывает стандартный бабелевский хелпер и всё. Как-то многовато кода для такой простой задачи! Ладно, давайте заглянем в этот хелпер.. Ага, тут генерируются новые узлы AST, используя глобальные функции-фабрики. Но беда в том, что вы не контролируете, куда они будут замаплены. А чуть ранее я показывал, как важно точно контролировать, что куда мапится. Тут же этой информации нет, поэтому Бабелю ничего не остаётся, как мапить новые узлы на тот единственный узел, на который сматчился плагин (this), что далеко не всегда даёт адекватный результат. Следующая проблема — отладка трансформаций. Тут нам важно иметь возможность смотреть, какое AST было до трансформации, а какое стало после. Возьмём простенький JS код: const foo = { "bar": 123 }; Вы только гляньте, как выглядит типичное абстрактное синтаксического дерево (AST) для него.. И это лишь его половина. И это даже не бабелевское AST, а какой-то ноунейм — я просто взял самое компактное из тех, что есть на ASTExplorer. Собственно, поэтому этот инструмент вообще и появился, ибо без него смотреть на эти JSON-чики — боль и страдания. И тут нам на помощь приходит формат Tree, который я когда-то разработал специально для целей наглядного представления AST.. const foo = { "bar": 123 }; Как видите, js.tree представление уже гораздо нагляднее. И не требует никаких ASTExplorer. Хотя я и сделал для него патч для поддержки tree, который уже второй год игнорируется мейнтейнером. Это — open-source, детка! В моей реализации API для Tree ($mol_tree2) у каждого узла есть лишь 4 свойства: это имя типа, сырое значение, список дочерних узлов и спан (указатель на диапазон в исходнике).. Каждый спан содержит ссылку на исходник, само содержимое исходника, номера строки и столбца начала диапазона и длину этого диапазона.. Как видите, тут есть всё, что нужно для представления и обработки любого языка, и ничего ненужного. Генерация новых узлов происходит не глобальными функциями-фабриками, а наоборот, локальными методами-фабриками.. Каждая такая фабрика создаёт новый узел, но наследует спан от существующего узла. Таким образом, мы можем точно контролировать, на какую часть исходника будет мапиться каждый узел, даже после применения множества трансформаций AST..  На диаграмме вы видите, как мы из 2 файлов сгенерировали 1 путём 3 трансформаций, которые что-то вырезали, что-то добавили, а что-то перемешали. Но привязка к исходным кодам никуда не потерялась. Для написания трансформаций есть 4 обобщённых метода.. Каждый из них создаёт новое AST, никак не меняя существующее, что очень удобно при отладке. Они позволяют делать глубокие выборки, фильтрацию по глубокой выборке, глубокие вставки и применять хаки. Хаки — самая мощная штука, позволяющая пройтись по дереву, заменяя узлы разных типов на результат исполнения разных обработчиков. Проще всего их работу продемонстрировать на примере реализации тривиального шаблонизатора для AST. Допустим, у нас есть шаблон конфига для нашего сервера.. Распарсив его в AST, мы можем похачить наш конфиг всего в несколько строчек кода.. В результате получится, что все плейсхолдеры заменены на нужные нам значения.. Рассмотрим пример посложнее — скрипт автоматизации.. Тут у нас есть команда click. Ей передаётся идентификатор элемента, по которому должно быть произведено нажатие. Что ж, похачим этот скрипт так, чтобы на выходе получился AST яваскрипта.. Обратите внимание, что часть узлов создаются из имени команды (click), а часть из идентификатора элемента (id). То есть отладчик будет останавливаться и там, и там. А стек-трейсы ошибок будут указывать на правильные места исходника. Но можно погрузиться ещё глубже и сделать DSL для обработки DSL. Например, трансформацию скрипта автоматизации можно описать следующим образом на языке jack.tree.. Каждый hack — это макрос, который матчится на заданный тип узла, и заменяющий его на что-то иное. Это пока ещё прототип, но он уже много чего умеет. Хаки позволяют не просто дословно транслировать один язык в другой. С их помощью, можно экстрактировать из кода интересующую нас информацию. Например, у нас есть скрипт на некотором простом DSL, который что-то там выводит на английском языке.. И мы можем преобразовать его в JS так, чтобы вместо английских текстов дёргалась функция localize с нужным ключом, просто завернув его в макрос +js.. Но мы можем применить к нему другой макрос — +loc.. И тогда, наоборот, вся логика будет проигнорирована, а мы получим JSON со всеми ключами и соответствующими им текстами.. На jack.tree эти макросы описываются сравнительно не сложным кодом.. Как видите, внутри макроса можно объявлять другие макросы. То есть язык легко можно расширять средствами самого этого языка. Таким образом, можно генерировать разный код. Можно учитывать контекст, в котором находятся узлы, и матчиться только в этом контексте. Короче, техника очень простая, но мощная и при этом шустрая, так как нам не приходится ходить по дереву туда-сюда — мы только спускаемся по нему. Большая сила требует и большой ответственности. Если что-то пойдёт не так и возникнет исключение, а у нас макрос на макросе и макросом погоняет, то крайне важно выводить трейс, который поможет разобраться, кто где на что матчился по пути к месту ошибки..  Тут мы видим, что исключение возникло в точке (1), но ошибка человеком была допущена в точке (2), в которую мы пришли из точки (3). И тут вы, скорее всего, задаётесь вопросом: "Дима, зачем ещё один велосипед? Горшочек, не вари! Хватит уже велосипедов!". Я бы и рад, но давайте вкратце сравним его с альтернативами.. У Бабеля порядка 300 функций, методов и свойств. У TS там вообще какое-то запредельное complexity, ещё и почти без документации. Все они гвоздями прибиты к JS, что осложняет их использование для кастомных языков. У них мутабельное API без лаконичной сериализации AST, что существенно осложняет отладку. Наконец, AST Бабеля не является самодостаточным, то есть мы не можем из него напрямую сгенерировать и результирующий скрипт, и сорсмапы к нему — для этого надо окольными путями протягивать исходники. С сообщениями об ошибках — та же беда. У TS с этим получше, но тут уже другая крайность — он вместе с бананом даёт вам и обезьяну, и джунгли, и даже свою солнечную систему в нескольких ипостасях. Давайте глянем как выглядит типичный фронтенд-пайплайн.. Что-то тут не так… Всем этим инструментам не хватает единого языка для общения, то есть некоторого представления AST, которое было бы, с одной стороны максимально простым и абстрактным, а с другой — позволяло бы выразить всё необходимое каждому инструменту, но не завязывалось бы на него. И, на мой взгляд, формат Tree подходит для этого как нельзя лучше. Поэтому в перспективе было бы классно протолкнуть им идею перейти на этот формат. Но я для этого, к сожалению, недостаточно инфлюенсер. Так что не будем сильно раскатывать губу, но немного помечтаем.. Таким образом, основная работа проходит на уровне AST без промежуточных сериализаций. А даже если нам и потребуется временно сериализовать AST чтобы, например, передать его в другой процесс, то компактный Tree может быть сериализован и распаршен куда быстрее развесистого JSON. Ок, мы потрансформировали AST, осталось его сериализовать. Если это делать для каждого языка отдельно, то это будет слишком сложно, ведь иной язык может насчитывать десятки, а то и сотни типов узлов. И каждый нужно не только правильно сериализовать, но и правильно сформировать для него спан в мапинге. Чтобы упростить эту задачу, мы со Стефаном разработали язык text.tree, где есть всего 3 типа узлов: строки, отступы и сырой текст. Простой пример.. Любой другой язык может быть сравнительно несложно трансформирован в text.tree без плясок вокруг спанов. А дальнейшая сериализация с формированием сорсмапов — это просто применение стандартных, уже написанных функций. Ну а в дополнение к текстовой сериализации, у нас есть и сериализация бинарная. Тут всё то же самое: трансформируем любой язык в bin.tree, после чего стандартной функцией получаем из него бинарник. Например, возьмём не хитрый код на языке wasm.tree.. А теперь прогоним его через wasm.tree компилятор и получим bin.tree, который тут же преобразуем в бинарник и провалидируем WASM рантаймом.. Можно писать код как сразу на wasm.tree, так и на каком-либо своём DSL, который уже трансформировать в wasm.tree. Таким образом, вы легко можете писать под WebAssembly, не погружаясь в дебри его байт-кода. Ну… когда я этот компилятор таки допилю, конечно. Если кто-то готов помочь — присоединяйтесь. И, конечно, из bin.tree мы автоматически получаем и сорсмапы. Только вот работать они не будут. Для WASM нужно генерировать уже более взрослый формат мапинга, который используется для компилируемых языков программирования..  Но в эти дебри мне пока страшно лезть.. Пока что мы говорили лишь про генерацию кода из нашего DSL. Но для комфортной работы с ним требуется ещё много вещей.. У меня есть дикая идея — для каждой IDE сделать по одному универсальному плагину, который сможет читать декларативное описание синтаксиса языка и по нему обеспечивать базовую интеграцию с IDE: подсветку, подсказки, валидацию. Я пока что так реализовал подсветку. На $mol-канале есть трёхминутное видео процесса описания нового языка для своего проекта.. Вам не нужно ничего перезапускать, ставить девелоперские сборки среды разработки или специальные расширения к ним. Вы просто пишете код, и он в реальном времени перекрашивает ваш DSL. Справа вы видите код на языке view.tree, а слева описание этого языка. Плагин при этом ничего не знает про этот язык, но благодаря описанию знает, как его раскрашивать. Работает это просто: встретив незнакомый язык (определяемый по расширению файла), плагин сканирует воркспейс на предмет существования схемы для этого языка. Если найдёт несколько схем, то соединяет их. Есть требование и к самому языку — семантика его узлов должна задаваться синтаксически. Например, "начинается с доллара" или "имеет имя null". То есть не должно быть синтаксически неразличимых узлов, имеющих разную семантику. Это, впрочем, полезно не только для подсветки, но и для упрощения понимания языка самим пользователем. Итого, что нужно: Да, описание языка вовсе не обязательно, ведь порой хватает и дефолтной эвристики для раскраски любых tree-based языков. На этом моё повествование подходит к концу. Надеюсь, мне удалось вас заинтересовать своими изысканиями. И если так, вам могут пригодиться следующие ссылки.. Спасибо, что выслушали. Мне полегчало.  Full Stack Overflow