TGViewer
Сергей Мелюков Сергей Мелюков @smelukov_dev · 646 subscribers
Post #121 2.95K
Ну и, думаю, последний пост на тему 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, например.
  • 👍 6
  • ❤ 2
More from @smelukov_dev
  1. Jun 14, 2026Подумал тут про покрытие тестами кода, который написала модель Есть отдельный интересный к…
  2. May 15, 2026Кстати, если вы когда-нибудь озадачивались подсчетом ресурсов для развертывания LLM локаль…
  3. May 15, 2026В чем произошла заруба: Среди прочего, я хочу понимать какие MCP были запущены и сколько о…
  4. May 15, 2026Подумал: почему бы не писать о том, что делаю прямо сейчас и с чем сталкиваюсь 🙂 Я придер…
  5. Apr 13, 2026ИИ победил! Готовлю кое-что интересное. Интересно, что Ведьмак и Статоскоп поделили степен…
  6. Apr 10, 2026Post #126
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →