2.7Kпросмотров
22 августа 2023 г.
Score: 3.0K
Ну и, думаю, последний пост на тему 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, например.