Css-modules позволяют написать компоненты Bird и Cat, со стилями в файлах с одинаковым именем styles.css и классами .block в каждом, и эти классы будут разные для каждого из этих компонентов.
/* Bird / styles.css */
.block { }
.name { }
/* Cat / styles.css */
.block { }
.name { }
Ничего хитрого тут нет: вебпак хеширует каждый класс из всех файлов с помощью настройки "[hash:base64:8]". Все классы будут переименованы, и проставлены ссылки, чтобы понимать, какой класс откуда взяли. В базовом варианте сборки, у нас будет файл styles.css для стилей и styles.js для ссылок при работе с js.
Продолжая тестовый пример, получаем 4 независимых класса со странными именами типа k3bvEft8:
/* Bird */
.k3bvEft8 { }
.f2tp3lA9 { }
/* Cat */
.epIUQ_6W { }
.oRzvA1Gb { }
Запустим продакшн-сборку и сожмём файлы. На рабочем стенде, 300Kb css-файл стал упакован в 70Kb с помощью gzip [или 50Kb с помощью brotli]. Сжатие небольшое, потому что хеши — случайные сгенерированные строки, очень плохо сжимаются. Алгоритмы сжатия не видят последовательностей и вынуждены запоминать местоположения каждого символа, т.е. передавать содержимое этих участков как есть, без сжатия.
Что-то надо с этим делать. Но что? Во время работы, вебпак считывает дерево файлов асинхронно, и также проходит по названиям классов. Каждый раз по-разному. Единственное, за что можно зацепиться — это порядок имён внутри css — он постоянен (иначе всё сломается, в css порядок важен). Номер позиции класса в файле закодируем в однобуквенный префикс. Можно взять кодирование в 52 ([a-zA-Z]+) или в 64 ([a-zA-Z0–9_-]+) символа. Тут главное не забыть проставить защитный префикс в случаях с цифрой или дефисом.
/* Bird */
.a { }
.b { }
/* Cat */
.c { }
.d { }
Вроде выглядит неплохо — имена сжались максимально. Но загвоздка в том, что вебпак асинхронный, и каждый запуск, и особенно при параллельном запуске серверной и клиентской одновременной сборки, получает файлы в хаотичном порядке, как и имена классов. Спасибо за скорость, но тут это мешает.
/* Bird */
.c { }
.d { }
/* Cat */
.a { }
.b { }
Видите, поймали несовпадение порядка файлов.
Пофиксим это поведение, запомнив файл, откуда пришли классы, и номер их позиции.
/* Bird */
.a { }
.b { }
/* Cat */
.a { }
.b { }
Сохранили порядок внутри файлов. Но нужно как-то отличать файлы друг от друга. Избежать путанницы поможет хеш от пути файла.
/* Bird */
.a_k3bvEft8 { }
.b_k3bvEft8 { }
/* Cat */
.a_oRzvA1Gb { }
.b_oRzvA1Gb { }
('_' здесь не нужен, он только для наглядности. Хеш имеет стабильную длину, в отличие от префикса, и здесь не может быть коллизий)
У нас получились абсолютно уникальные для проекта имена классов, но содержащие повторяющиеся поледовательности.
В нашем проекте, из файлов css 50 Kb и js 47 Kb получили css 30 Kb и js 28 Kb [58 Kb суммарно, brotli].
Экономия почти 40Kb. Немного уменьшится и размер критичного css, и размер html.
Осталось написать класс для обработки данных из вебпака и прокинуть вызов в конфиге css-loader (getLocalIdent)
P.S. Можно пойти дальше и сохранять пути файлов, сортировать пути, и тоже заменять по однобуквенной стратегии, но это хуже в плане долгосрочного кеширования, плюс нужно делать несколько проходов в сборке и собирать клиент/сервер последовательно.
P.S.2 Попробовать на своём проекте можно уже сейчас, если взять код здесь
P.S.3 В продакшене сжимаем на 93% файлы *.css и *style.js. Передаём 71,6Kb от 1,1Mb распакованного файла (brotli)
Dim0v
Биты немного не так работают.
Base64 != "64-битное кодирование". А "шестнадцатеричное число" — не то же самое, что "16-битное число".
denisx Автор
Согласен, терминологию можно уточнить.