tgoop.com/smelukov_dev/118
Last Update:
Привет! Давненько тут ничего не было. Исправляюсь. Пока просто небольшая заметка, но чуть позже расскажу и покажу кое-что интересное ☺️
Сейчас я занимаюсь вопросом тришейкинга 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, при помощи которого парсятся значения, этого не делает
BY Сергей Мелюков

Share with your friend now:
tgoop.com/smelukov_dev/118