TGViewer
Сергей Мелюков Сергей Мелюков @smelukov_dev · 646 subscribers
Post #119 1.42K
Мой ПР, из поста выше, пока не посмотрели, а проблему решать как-то надо.
Начал перебирать варианты:
- заменить минификатор
- поменять csso и cssnano местами
- отключить у csso все оптимизации кроме вырезания неиспользуемых классов

Первый вариант слишком рискованный
Второй выглядит как так себе, потому что cssnano вышит в конфиг сборки, а csso просто используется в плагине, на этапе optimizeChunkAssets
Третий вариант тоже не подходит, потому что дело, как оказалось, не в csso, а в css-tree. Это css-tree убирает пробел:

const {parse, generate} = require("css-tree");

const ast = parse('.foo {font: 1em "Arial"}');
const source = generate(ast);

console.log(source); // .foo{font:1em"Arial"}


Делает это css-tree совершенно законно и генерит валидный CSS. У css-tree нет задачи восстановить CSS из AST в первозданном виде.
Но проблему, все такие надо как-то решать, например добавить пробелы перед строками везде, где их нет. Звучит как костыль, но пока мой ПР в cssnano не вмержили - ок.

Оказалось, что генератор в css-tree можно кастомизировать. Вот так можно вставить пробелы вообще перед всеми токенами:


const {parse, generate} = require("css-tree");
const {tokenTypes} = require("css-tree/tokenizer");

const ast = parse('.foo {font: 1em "Arial"}');
const source = generate(ast, {
decorator(handlers) {
return {
...handlers,
tokenBefore() {
this.emit(' ');
return tokenTypes.WhiteSpace;
}
}
}
});
console.log(source); // . foo { font : 1em "Arial" }


Хендлер tokenBefore используется для расстановки пробелов между токенами во время генерации из AST, потому что само AST не содержит информации о пробелах (whitespaces). Например, если парсить border: 1px solid red в AST и потом обратно в CSS, то именно tokenBefore даст понять генератору, что 1px, solid и red должны быть разделены пробелами.

Предыдущий результат - это, конечно, не то, что нам нужно, поэтому немного перепишем:


const {parse, generate} = require("css-tree");
const {tokenTypes} = require("css-tree/tokenizer");

const ast = parse('.foo {font: 1em "Arial"}');
const source = generate(ast, {
decorator(handlers) {
return {
...handlers,
tokenBefore(prev, current, value) {
if (prev !== tokenTypes.WhiteSpace && current === tokenTypes.String) {
this.emit(' ');
return tokenTypes.WhiteSpace;
}

return handlers.tokenBefore(prev, current, value);
}
}
}
});
console.log(source); // .foo{font:1em "Arial"}


Здесь мы добавляем пробелы только перед строковыми токенами и только в тех случаях, когда перед ними еще нет пробела.

Но мы же работаем не с css-tree напрямую, а с csso. Как здесь быть?
Очень просто. Если раньше мы делали так:


const CSSO = require('csso');

const compressedCSS = CSSO.minify(input.source, {
sourceMap: false,
restructure: false,
usage: {
classes: usedClasses
}
});


То теперь будет так:


const {parse, compress, generate} = require('csso/syntax');

const ast = parse(source);
const compressedAST = compress(ast, {
restructure: false,
usage: {
classes: usedClasses
}
}).ast;
const compressedCSS = generate(compressedAST, {
sourceMap: false,
decorator(handlers) {
// ...
}
});


Вот и всё, так я обхожу баг в cssnano
  • 👍 18
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 →