TGViewer
Сергей Мелюков Сергей Мелюков @smelukov_dev · 646 subscribers
Post #118 1.15K
Привет! Давненько тут ничего не было. Исправляюсь. Пока просто небольшая заметка, но чуть позже расскажу и покажу кое-что интересное ☺️
Сейчас я занимаюсь вопросом тришейкинга 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, при помощи которого парсятся значения, этого не делает
GitHub GitHub - css/csso: CSS minifier with structural optimizations CSS minifier with structural optimizations. Contribute to css/csso development by creating an account on GitHub.
  • 🔥 24
  • ❤ 7
  • 👍 4
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 →