tgoop.com/smelukov_dev/121
Last Update:
Ну и, думаю, последний пост на тему 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, например.BY Сергей Мелюков
Share with your friend now:
tgoop.com/smelukov_dev/121