Warning: Undefined array key 0 in /var/www/tgoop/function.php on line 65

Warning: Trying to access array offset on value of type null in /var/www/tgoop/function.php on line 65
- Telegram Web
Telegram Web
Сижу я и думаю - а почему бы не опубликовать порцию новостей, благо они есть 😄

Я выступил на еще одном Я.Субботнике, на этот раз закрытом (но думаю, что запись появится позже). Это был мастер-класс на котором я показал как написать свое расширение для Статоскопа и собственное правило для валидации, которое это расширение использует.
Пока готовил доклад, в очередной раз порадовался тому, как классно себя показывает архитектурное решение с расширением статов. Кстати, посмотрите что добавил в плагин. Теперь вы можете писать собственные пакеты с расширениями и передавать их плагину, да и вообще, распространять как вам вздумается. А потом еще и собственные правила валидации писать, которые данные из этих расширений используют. Ну разве не классно? ☺️

А еще, по многочисленным просьбам, я добавил в cli команду query которая позволяет делать jora-запросы в статы прямо из консоли, например:
echo "compilations.time" | statoscope query -i stats.json > build-time.txt

Это может пригодиться для формирования кастомных сообщений и комментариев в ПР или тикетах или для отправки полученных данных в какое-нибудь хранилище с возможностью потом построить графики по накопленным данным.

Ну и особенно приятная для меня новость - Статоскоп теперь используется в size-limit как инструмент для анализа бандлов вместо webpack-bundle-analyzer 😌
Доброго времени суток, а давайте чуть-чуть по-оффтопим (вот прям совсем) ☺️
Расскажу вам об очень приятных воспоминаниях из профессиональной (и не только) жизни и о том, что этому предшествовало (другим каналам можно, а мне нет, чтоли? 🤣).
Абсолютно у каждого (как мне кажется) в карьере должно быть такое, знаете, особенное место работы, которое вызывает больше всего прятных воспоминаний, которое больше всего запоминается и о котором вспоминиешь с особой теплотой.
Когда-то я был фрилансером и целых 5 лет успешно набирался опыта в создании сайтов, разных веб-приложений и скриптов.
Однако, в 2013 году я решил наконец "взяться за ум" и устроиться на работу в компанию, больше общаться с людьми, делиться опытом, наконец.
Надо конечно отметить, что все это было очень непривычно - менять жизненный уклад и вот это вот всё 🙂
Любой человек, хоть раз принимавший решение переехать через полстраны поймет о чем я говорю 😅
Юго-восточная часть нашей страны не слишком изобилует возможностями для IT-специалиста, поэтому решение было принято - еду 🚀
Больше всего мне хотелось устроиться в компанию, где мне дали бы заниматься всякими архитектурными штуками, реализовывать "под капотом" свои идеи.
И вот, свершилось, я устроился работать ведущим разработчиком в TagLab - небольшую, но очень классную и уютную компанию, которая занималась разработкой веб-приложений, по большей части для внутреннего клиента, но это и хорошо, потому что тебе нужно не просто построить систему и "отвалить", а наблюдать за тем, как она развивается и отвечать за свои архитектурные решения.
ТагЛаб был замечательным местом, где я мог практически беспрепятственно экспериментировать и реализовывать любые архитектурные решения.
Порой, конечно, не очень беспрепятственно, потому что клиенты все таки ждали работающее бизнес-решение в качестве результата, а не супер-крутую и расширяемую архитектуру.
Решение, которое супер-классно работает и супер-круто расширяется - это конечно хорошо, но "выставка открывается через две недели, а у нас еще ЛК экспонентов не готовы" 😅
И чтобы вы понимали, это были не просто ЛК с какими-то там формочками, это были полноценные конструкторы форм, платформы для голосований, сложные каталоги и много всего другого интересного.
Некоторые проекты работают до сих пор (уже много лет) и без моей поддержки!
Центральной идеей всех моих архитектурных решений была расширяемость - возможность добавить новую функционльность или изменить поведение текущией - не "взрывая" при этом половину проекта (так вот откуда у расширяемости Статоскопа ноги растут 😊).
Единственным лучиком света, который понимает меня, мои интересы, амбиции, который отстаивает перед заказчиком затянутые, порой ощутимо, сроки, была Наташа - руководитель студии и одноврменно PM.
Уже на собеседовании стало понятно, что мы на одной волне и отлично дополняем друг-друга как команда.
Студия через какое-то время закрылась 😞 по независящим от нас причинам, но мы с Наташей продолжали работать над другими проектами, а по прошествии какого-то времени создали семью ❤️😊
Удивительно, конечно, устроена жизнь, никогда не знаешь что тебя ждет ☺️

PS: Люблю тебя Наташ, спасибо, что ты у меня есть ❤️

PPS: А в вашей жизни есть подобные истории?
🔥1
Совсем скоро расскажу про интеграцию Statoscope в CI 😄
В данном случае мы видим резульстат работы GitHub Action, который использует сразу две фичи statoscope cli - validate и query, формирует текст комментария и прикрепляет ссылку на отчет со статами из master-ветки и текущей. А меж тем, уже послезавтра выступаю на холи с двумя слотами про Statoscope 🚀
Возможно даже небольшую статейку напишу на эту тему
Ох, давненько не было новостей 🙂

Итак, я выступил на HolyJS с двумя слотами (доклад + воркшоп). Я рассказал про Статоскоп и показал как его раширять, как написать расширение и правило валидации. Зрителям так понравилось, что вот-вот выйдет расшифровка моего доклада и видео доклада. После этого я переведу статью на английский и буду двигаться на запад 🚀

Буквально вчера вышел выпуск подкаста Goose & Duck с моим участием. Поговорили про миграцию гусей, чихуахуа и ворованный дизайн. Очень классные и веселые ребята ☺️

А еще, я теперь руковожу технической частью платформы фронтенда Яндекс.Маркета. Приходите работать в Яндекс, тут классно!

А еще, Статоскоп обзавелся собственным твиттером, куда я буду постить релиз ноты на английском.

А еще, сам Статоскоп чуть-чуть повзрослел:

- в правиле @webpack/restricted-packages появились настройки description и alternatives, которые позволяют указать почему тот или иной пакет не стоит использовать и какие можно рассмотреть альтернативы. Спасибо @amalitsky 👍

- при сравнении чанков теперь видно какие модули были добавлены или удалены в этом чанке по сравнению с эталонным статами. Спасибо @andreygmc 👍

- я наконец закрыл свой давний гештальт и полностью переписал секцию нормализации статов и теперь полностью поддерживаю stats: { all: true }. До этого Статоскоп ломался на такой конфигурации потому что кто-то решил, что в настройки статов надо добавить опцию которая группирует модули по типу и тем самым ломает структуру списка модулей

А еще, Статоскоп теперь лучше обрабатывать concat-моудули и не дублирует имена модулей в секциях added и removed при сравнении сборок, а еще, у cli появились комады init и create (спасибо @wildOrlik), а еще... в общем гляньте changelog ☺️

Понемногу возвращаюсь к работе над Статоскопом, впереди много интересного 🙂
1
Привет, друзья!
Это были непростые полгода. Начиная от моего выгорания и заканчивая последними мировыми событиями.
Пробегусь по новостям :)
В конце прошлого года я в какой-то степени выгорел и перестал клепать апдейты Статоскопа с прежней частотой.
За это время в моей голове начала вырисовываться архитектура нового Статоскопа - еще более расширяемая и совершенно независимая от сборщика.
Последний пункт действительно очень важен. Новый Статоскоп делается не столько про сборку, сколько про здоровье всего проекта: файловая структура, отчеты линтеров, запусков тестов, code coverage, анализ сборки и много ещё чего.
Под капотом лежит действительно расширяемся архитектура (расширить можно почти всё - от jora-хелперов до блоков в ui) и плотная работа с графами.
Сейчас я освежаю свои знания в области графов и строю ядро практически с нуля.
Чуть позже покажу примеры расширяемости :)
А ещё, 23 июня я в третий раз выступил на HolyJS с хардкорным докладом про платформу интеграционного тестирования (слайды в закрепе)
Очень хочется чаше рассказывать обо всяких крутых штуках и идеях
Всем добра
👍31
HolyJS 2022 - Jest.pdf
6.8 MB
👍142
Сергей Мелюков
HolyJS 2022 - Jest.pdf
Привет! А вот и видео доклада.
С тех пор мы придумали как ускорить jest, написав для нашей инсталляции кастомный рантайм, который делает меньше проверок на попытке зарекваирить файл.
Казалось бы, проверки простейшие, но на тысячах рекваеров это сильно заметно.
Еще мы прикрутили inline-require plugin для бабеля и научились собирать кавередж.
Казалось бы, а какие проблемы с кавереджем? Включил опцию, запустил тесты - получил html отчёт, профит.
Да, но нет :)
Testament ведь создаёт еще один jest-рантайм в дочернем процессе, а значит надо как-то собирать и мержить кавередж с обоих рантаймов в единый отчёт (никому ведь не хочется смотреть 2 разных отчета: по клиентскому и серверному кавереджу, правда?)
Пишите что из этого больше всего интересно, расскажу. Либо расскажу обо всем постепенно ;)
👍13🔥8
А еще, я завершаю в Statoscope работу над фичей, которая еще больше сжимает html-отчет.
Сейчас отчет о сборке Я.Маркета весит ~250mb. А вчера я радостно вкрячил фичу, которая уменьшает размер отчета до 8mb!
А вообще, сырые статы Маркета занимают ~1.3gb
Только вдумайтесь, с 1.3gb до 8mb. Компрессия Statoscope = х162 (конечно, зависит от данных, но все же)
Конечно, тут не обошлось без решений безмерно уважаемого мной Романа Дворнова.
Как только всё оттестирую и допилю кое-что, обязательно расскажу. Будет какое-то количество computer science 🧪
👍23🔥8
Я, наконец, опубликовал statoscope 5.25 🎉
И знаете, что? Я очень рад. Хотя бы потому, что теперь statoscope-отчет со сравнением двух (master vs PR) клиентских сборок Яндекс Маркета весит не 494мб, а 11мб.
Нет, вам не кажется - статы пожались почти в 45 раз эффективнее 😇
Получаем экономию квоты на железо и времени пользователя, который ждет пока отчет загрузится.
Это стало возможным благодаря сжатию статов в Binary JSON.

Если очень коротко, то значения из объекта компактно записываются в виде байтов, поверх применяются всякие оптимизации типа дедупликации значений и компактного представления строк и массивов. В результате получается сплошной поток типов и данных. Никаких скобочек, кавычек и форматирования, а данные записаны компактно. Это все будет иметь заметный эффект на большом объеме данных (например, от 10мб).

Возможно, вы задаетесь вопросом: "А почему нельзя обойтись просто gzip'ом?". Binary JSON + gzip жмет в 2 раза лучше, чем просто gzip. А все потому, что сжимая JSON gzip'ом - мы сжимаем "что-то там чем-то там", а сжимая JSON инструментом, который заточен под JSON (знаем формат и специфику данных) - мы сжимаем именно JSON и делаем это эффективно. Да, многое зависит от структуры исходных данных, но я смотрю на реальные данные в виде статов Яндекс Маркета и вижу огромный профит.

На скриншоте можно посмотреть сравнение json-ext с другими решениями, которые умеют в binary JSON. Сравнение проводится на примере нормализованных статов клиентской сборки Яндекс Маркета. Как видите, идея binary JSON не нова и эксперименты в этой области продолжаются, но судя по всему, остальные остановились на достигнутом и json-ext пока впереди 🤓
Думаю @gorshochekvarit еще сам расскажет подробнее про это. Хотя я и сам чуть-чуть приложил руку к энкодеру/декодеру, но после этого Роман накоммитил туда много всяких интересных оптимизаций (например, хранить массив объектов колонками, как в колончатых БД, что делает их более компактными).
🔥33👍51
Идем дальше. На скриншоте изображен итоговый пайплайн работы со статами.
Чтобы получить HTML-отчет, статы проходят несколько стадий:
- нормализация (Да, статоскоп умеет нормализовывать статы. До нормализации, статы весят 1.5гб, а после - 250мб. Подробнее описывал здесь)
- сжатие с binary JSON
- сжатие с gzip
- пилим gzip-стрим на чанки (зачем это делать подбробно описывал тут)
- энкодинг чанков в base64 (чтобы можно было хранить чанки с бинарными данными в HTML)
- запись в файл

Всё, отчет готов.
Таков путь сырых статов в 1.5гб до HTML-отчета в 5мб (и это несмотря на то, что base64 дает оверхед в ~33% от размера декодируемых данных).

Когда мы открываем такой отчет в браузере, все происходит в отбратном порядке:

- декодируем чанки из base64
- разжимаем из gzip
- разжимаем binary JSON
- денормализуем статы
- отправляем статы в UI

Итого, самая медленная часть статоскопа - это денормализация статов и их подготовка к использованию в UI. А все потому, что webpack генерит статы, не особо заворачиваясь с форматом.

И тут мы плавно переходим к Statoscope 6, над которым я работаю уже довольно давно, с переменной активностью. Вот его основные фичи:

- собственный формат статов
- расширяемость как у vscode 😇

Зачем: чтобы любой человек мог написать плагин (если его еще нет), в котором реализован UI для его любимого сборщика. Сейчас же статоскоп сильно завязан на webpack и мне это не нравится.

Будет классно, если напишете какая у вас разница в размерах статов получилась, очень интересно.

PS: Я тут заглянул в OpenCollective статоскопа и выяснилось, что там уже 413$ накапало. Это конечно не webpack с миллионным бюджетом, но все равно приятно ☺️
🔥39
А помните мой доклад про Testament - нашу внутреннюю разработку в Яндекс Маркете для интеграционных тестов на базе jest и IPC?

Так вот, все это время одной из самых бесячих проблем является скорость выполнения пака тестов (всего 7к тестов: 4к - десктоп и 3к - тач/мобильные устройства).
Мы много чего перекопали во внутренностях jest, многое о нем узнали, попробовали разные методики ускорения, от простых, до самых "упоротых" вроде форкнуть трансформер и научить его шарить кеш в CI.
Пока не будут рассказывать обо всех методиках, т.к. мы их сами еще только обкатываем, расскажу всего об одном, но очень эффективном. Сначала немного предыстории.

По результатам наших исследований, самыми медленными частями jest (не берем в расчет код внутри spec-файлов) являются: изоляция, трансформер, резолвер и сборщик кавереджа.
Вы можете возразить, мол, трансформер - это же про babel, какой смысл причислять его к внутренностям jest?
Да, но нет 🙂
У jest есть отдельный трансформер-фасад, а вот бэкендом может быть уже что угодно (babel, ts, swc, etc...). И вот этот фасад рулит кешем и всякими разными другими штуками и делает это не очень оптимально и это особенно заметно на больших паках тестов.

Резолвер - отдельная история. Подробнее смотрите в вышеупомянутом докладе. А если коротко, то jest-резолвер нам не подходит, т.к. мы собираем статику вебпаком и используем различные настройки webapack-резолвера. Поведение некоторых из них просто невозможно воспроизвести в jest-резолвере. Поэтому мы начали использовать родной резолвер вебпака.
Это замедлило нам тесты, потому что у webpack очень медленный резолвер и он активно работает с ФС, несмотря на встроенный кеш, а еще, он генерит очень большой callstack, но об этом позже. Мы решили выйти из положения при помощи memfs сэмулировав ФС в памяти и scanfs для сканирования реальной ФС и помещения структуры ФС в memfs. Таким образом, мы сделали нечто похожее на haste-map, который в jest-резолвере делает +- то же самое, и отыграли скорость, потерянную от переезда на enhanced-resolve.
Время шло, кодовая база росла, количество тестов росло, время прогона пака увеличивалось
Отдельно отмечу, что профайлинг такой конфигурации - занятие специфическое, потому что enhanced-resolve медленный и генерит оооооооочень глубокий стектрейс. Дамп прогона одного spec-файла часто достигал более 500мб и v8 просто отказывался мне его отдавать, потому что максимальная длина строки в v8 - 512мб. Да и дамп таких размеров - то еще удовольствие. А добавьте туда еще глубокую рутину внутри memfs, который тоже так себе написан и получите чудовищных размером cpu-профиль.

В какой-то момент я подумал, что "хватит это терпеть" и решил набросать собственный резолвер, который умел бы больше, чем jest-резолвер, но не был бы таким перегруженным и медленным, как enhanced-resolve. Так появился Revelation - быстрый резолвер модулей для node.js с поддержкой свойства mainFiles и возможностью модифицировать package.json налету. Резолвер обмазан всевозможными кешами, в том числе кешированием ближайшей node_modules (или другой директории с модулями, в зависимостями от опции). А со всеми этими приседаниями с кешем, memfs оказался уже не нужен, потому что revalation < enhanced-resolve + memfs > enhanced-resolve.

В итоге, мы получили следующее ускорение прогона тестов (основано на графиках нашего CI с 4к тестов в десктопе и 3к тестов в таче):
90 перцентиль:
- время уменьшилось на 55% в десктопе
- время уменьшилось на 40% в таче

95 перцентиль:
- время уменьшилось на 60% в десктопе
- время уменьшилось на 55% в таче

В качестве бонуса, получили возможность снимать cpu-профайлы и локально отлаживать большие тесты, потому что профиль похудел с более чем 500мб до 52мб (в 10 раз!).
Очень хороший профит как по скорости, так и по DX.
Из того, что еще хотелось бы реализовать - это поддержка свойств exports и imports в package.json.
🔥212👍1
Теперь о том, как можно использовать revelation у себя, в качестве jest-резолвера:

rev-resolver.js:
const Revelation = require('revelation-resolver').default;
const rev = new Revelation(options);
module.exports = (request, options) => rev.resolve(options.basedir, request);

jest.config.js:
module.exports = {
resolver: './path/to/rev-resolver'
};

И всё.

Поделитесь пожалуйста своими циферками, если вдруг будете пробовать Rev у себя, очень интересно. Возможно, по сравнению с дефолтным jest-резолвером, такой разницы и не будет. Просто у нас нет большого количества чистых jest-тестов, чтобы провести корректный замер. А может, на вашем примере я пойму что еще можно улучшить в резолвере, чтобы он стал лучше и быстрее и так мы нанесем сокрушительную и непоправимую пользу другим 🚀

PS: нет, использовать Rev в качестве резолвера для webpack не получится, т.к. множество плагинов и внутрянка webpack завязаны на родной резолвер 😉
👍8
🎙️ Поболтали с Сергеем Бережным про разное: про то, как я пришел в профессию, про то, как я программирую. Если понравится формат, то на канале есть и другие видео ;)

https://youtu.be/QHqXE-bo67c
👍12🔥31🤡1👨‍💻1
Привет! Давненько тут ничего не было. Исправляюсь. Пока просто небольшая заметка, но чуть позже расскажу и покажу кое-что интересное ☺️
Сейчас я занимаюсь вопросом тришейкинга CSS в бандле Яндекс Маркета: пытаюсь определить CSS-классы, которые точно не используются и вырезать весь CSS, связанный с этими классами. Задачка оказалась чуть сложнее, чем казалось изначально, но все движется к завершению, о чем и расскажу позже.
Чтобы вырезать CSS неиспользуемых классов я беру CSSO, передаю ему CSS нашего бандла и список всех классов, которые точно используются, а CSSO отдает CSS без "мертвых" (неиспользуемых) стилей. Как такие стили могли попасть в бандл - другой вопрос, расскажу позже. Эта заметка о другом.

Исторически, для минификации CSS, мы используем cssnano.
Но теперь, в пайплайн обработки CSS добавился еще и CSSO. Казалось бы, все должно быть хорошо, но не тут-то было.
В нашем CSS есть вполне безобидная конструкция:
body {
font: .8em 'YS Text', Arial, Helvetica, sans-serif;
}


Если обработать ее отдельно через разные минификаторы, то получатся вполне валидные конструкции:
cssnano: body{font:.8em YS Text,Arial,Helvetica,sans-serif}
CSSO: body{font:.8em"YS Text",Arial,Helvetica,sans-serif}

А если обработать ее сначала через CSSO, а потом через cssnano, то получаем невалидный css:
body{font:.8em"YS Text"Arial,Helvetica,sans-serif}

Проблема вот здесь: .8em"YS Text"Arial
cssnano не вставил запятую перед Arial и получилась невалидное значение.
То есть вот такой вариант .8em "YS Text" cssnano минифицирует нормально, а вот такой .8em"YS Text" уже нет, хотя это валидное значение.
Пошел смотреть исходники cssnano, порадовало, что все разделено на пакеты. Это, по сути, набор плагинов для postcss.
Корень проблемы оказался вот здесь, в пакете postcss-minify-font-values. Тут определяется токен, на котором заканчивается описание внешнего вида (текст, размре и т.д.) и начинается описание font-family, и работает это по такой логике: "мамой клянусь - через 2 токена будет описание font-family" 😄
Например, для значения .8em "YS Text",Arial будет вот так:
- { type: 'word', value: '.8em' } <- здесь заканчивается описание внешнего вида и через 2 токена отсюда будет font-family
- { type: "space", value: " " } <- раз
- { type: 'string', quote: '"', value: 'YS Text' } <- два, а вот и font-family
- { type: 'div', value: ',', before: '', after: ' ' }
- { type: 'word', value: 'Arial' }

Но перед cssnano стоит CSSO и превращает эту конструкцию в .8em"YS Text",Arial и это абсолютно валидное значение, но логика той части cssnano, которая отвечает за работу со значениями font-свойств перестает работать:
- { type: 'word', value: '.8em' } <- через 2 токена должен быть font-family
- { type: 'string', quote: '"', value: 'YS Text' } <- раз
- { type: 'div', value: ',', before: '', after: ' ' } <- два... ой 😳
- { type: 'word', value: 'Arial' }

В результате, cssnano просто игнорирует этот токен и на выходе получаем .8em"YS Text"Arial

Недолго думая, занес в cssnano ПР с фиксом.
Там просто учтен кейс, в котором перед font-family можен не быть пробела и конструкция .8em"YS Text",Arial превратится в совершенно валидную .8em YS Text,Arial 🎉

Но проблема, на самом деле, глубже. Заключается она в том, что работа с CSS в cssnano и плагинах postcss (поверх которого работает cssnano) происходит на уровне токенов, а не на уровне детального AST, вот и получается, что разбор значений происходит не по спеке, а на "честном слове". Таким способом проблематично учесть всю сложность синтаксиса CSS (а он действительно сложный!). Вот пример еще одного issue из cssnano на тему странностей парсинга и трансформации.
css-tree (поверх которого работает CSSO) разбирает CSS по спеке и строит детальное AST.
Здесь можно посмотреть внутренности разбора любого значения.

У Романа Дворнова (автора css-tree и мейнтейнера CSSO) есть целый доклад на эту тему.

P.S.: возможно, в экосистеме postcss есть плагин, который генерит детальный AST по спеке, но в данном случае, postcss-minify-font-values , а точнее postcss-value-parser, при помощи которого парсятся значения, этого не делает
🔥247👍4
Мой ПР, из поста выше, пока не посмотрели, а проблему решать как-то надо.
Начал перебирать варианты:
- заменить минификатор
- поменять csso и cssnano местами
- отключить у csso все оптимизации кроме вырезания неиспользуемых классов

Первый вариант слишком рискованный
Второй выглядит как так себе, потому что cssnano вышит в конфиг сборки, а csso просто используется в плагине, на этапе optimizeChunkAssets
Третий вариант тоже не подходит, потому что дело, как оказалось, не в csso, а в css-tree. Это css-tree убирает пробел:

const {parse, generate} = require("css-tree");

const ast = parse('.foo {font: 1em "Arial"}');
const source = generate(ast);

console.log(source); // .foo{font:1em"Arial"}


Делает это css-tree совершенно законно и генерит валидный CSS. У css-tree нет задачи восстановить CSS из AST в первозданном виде.
Но проблему, все такие надо как-то решать, например добавить пробелы перед строками везде, где их нет. Звучит как костыль, но пока мой ПР в cssnano не вмержили - ок.

Оказалось, что генератор в css-tree можно кастомизировать. Вот так можно вставить пробелы вообще перед всеми токенами:


const {parse, generate} = require("css-tree");
const {tokenTypes} = require("css-tree/tokenizer");

const ast = parse('.foo {font: 1em "Arial"}');
const source = generate(ast, {
decorator(handlers) {
return {
...handlers,
tokenBefore() {
this.emit(' ');
return tokenTypes.WhiteSpace;
}
}
}
});
console.log(source); // . foo { font : 1em "Arial" }


Хендлер tokenBefore используется для расстановки пробелов между токенами во время генерации из AST, потому что само AST не содержит информации о пробелах (whitespaces). Например, если парсить border: 1px solid red в AST и потом обратно в CSS, то именно tokenBefore даст понять генератору, что 1px, solid и red должны быть разделены пробелами.

Предыдущий результат - это, конечно, не то, что нам нужно, поэтому немного перепишем:


const {parse, generate} = require("css-tree");
const {tokenTypes} = require("css-tree/tokenizer");

const ast = parse('.foo {font: 1em "Arial"}');
const source = generate(ast, {
decorator(handlers) {
return {
...handlers,
tokenBefore(prev, current, value) {
if (prev !== tokenTypes.WhiteSpace && current === tokenTypes.String) {
this.emit(' ');
return tokenTypes.WhiteSpace;
}

return handlers.tokenBefore(prev, current, value);
}
}
}
});
console.log(source); // .foo{font:1em "Arial"}


Здесь мы добавляем пробелы только перед строковыми токенами и только в тех случаях, когда перед ними еще нет пробела.

Но мы же работаем не с css-tree напрямую, а с csso. Как здесь быть?
Очень просто. Если раньше мы делали так:


const CSSO = require('csso');

const compressedCSS = CSSO.minify(input.source, {
sourceMap: false,
restructure: false,
usage: {
classes: usedClasses
}
});


То теперь будет так:


const {parse, compress, generate} = require('csso/syntax');

const ast = parse(source);
const compressedAST = compress(ast, {
restructure: false,
usage: {
classes: usedClasses
}
}).ast;
const compressedCSS = generate(compressedAST, {
sourceMap: false,
decorator(handlers) {
// ...
}
});


Вот и всё, так я обхожу баг в cssnano
👍18
Ну и, думаю, последний пост на тему css-парсинга в этом цикле )
Возможно, у вас в голове крутится что-то вроде:
"Сергей, в предыдущем посте ты плевался от работы с исходником через токены, а в генераторе сам этим грешишь!".
Все так, и в случае, когда нужно просто добавить пробелов - это ок. Более того, css-tree делает намного более детальную токенизацию, нежели postcss-value-parser
Но! Если все таки хочется заморочиться с контекстом и не вставлять пробелы только перед именем шрифта и только в свойстве font, то так тоже можно.
Решение делится на несколько этапов:
- найти все декларации у которых имя свойства равно font
- найти в них ноды со значением типа family-name
- вставить пробелы в генераторе только для этих нод

Этап 1: собираем все декларация типа font:

Для этого воспользуемся волкером и обойдем AST:


const {walk} = require('css-tree');

walk(compressedAST, {
enter(node) {
if (node.type === 'Declaration' && node.property === 'font') {
// нашли!
}
}
});


Этап 2: ищем в этих декларация ноды со значением типа family-name

Все не так просто. Так, например, здесь:


.foo {
color: red;
animation-name: blue;
}


только один цвет - red, а blue, хоть и валидное имя цвета, но используется как название анимации.
AST не знает какой тип значения (цвет, размер, имя шрифта и тп) хранится в ноде. Для этого, нам нужно подняться на уровень лексического разбора и найти нужные нам лексемы. Для этого у css-tree есть лексер. Вот его мы и используем чтобы в декларациях типа font найти все имена шрифтов:


const {walk, lexer} = require('css-tree');

const allFamilyNameNodes = new WeakSet();

walk(compressedAST, {
enter(node) {
if (node.type === 'Declaration' && node.property === 'font') {
const familyNames = lexer.findAllFragments(node, 'Type', 'family-name');

for (const item of familyNames) {
for (const node of item.nodes) {
targetNodes.add(node);
}
}
}
}
});


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

Этап 3: вставляем пробелы только перед собранными нодами

Здесь берем за основу уже знакомый нам код декоратора и чуть-чуть меняем его так, чтобы он срабатывал только для нод, которые мы собрали


const css = generate(compressedAST, {
decorator(handlers) {
return {
...handlers,
node(node) {
this.currentNode = node;
handlers.node(node);
},
tokenBefore(prev, current, value) {
if (
prev !== tokenTypes.WhiteSpace &&
current === tokenTypes.String &&
allFamilyNameNodes.has(this.currentNode)
) {
this.emit(' ');
return tokenTypes.WhiteSpace;
}
return handlers.tokenBefore(prev, current, value);
}
};
}
});


Всё.

Да, здесь можно было сразу найти все family-name, не обходя декларация типа font:

const familyNames = lexer.findAllFragments(compressedAST, 'Type', 'family-name');


Но в таком случае мы бы нашли вообще все family-name и в других свойствах. Тем не менее, вполне рабочий вариант, нечто среднее между первым и вторым, но мне захотелось показать более комплексный пример, да и такие вот комплексные штуки как раз используеются в разного рода плагинах к IDE, например.
👍62
Я покидаю Яндекс 😀👋☺️
Это были хорошие 4 года: чему-то научился сам, чему-то научил других - было круто.
С благодарностью к коллегам за совместную работу ❤️

Сейчас у меня неделя отдыха, а после начинаю неистово работать над другим проектом, в другой компании 🤫
В результате недельного отдыха планирую значительно продвинуться с новой архитектурой статоскопа. За эти выходные набросал прототип, чтобы проверить накопившиеся идеи, полет отличный. Новая архитектура позволила избавиться от того, от чего давно хотел избавиться, упростить то, что давно хотел упростить, добавить фичи, которые давно хотел добавить… в общем, должно быть огонь!
Please open Telegram to view this post
VIEW IN TELEGRAM
52👍12💔5
Привет, VK! 💬👋☺️
С удовольствием встретил здесь бывших коллег и старых знакомых ☺️
Буду трудиться в роли ведущего эксперта/архитектора над новой архитектурой фронтенда https://cloud.vk.com/
Впереди действительно много работы и один большооооой вызов 🚀
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥108👍75💩32😢30👎52😁1
Please open Telegram to view this post
VIEW IN TELEGRAM
2025/10/12 19:54:04
Back to Top
HTML Embed Code: