И так начнем с ТЗ. Это делается, во первых, для себя, во вторых, для тех кто еще не понял, что ту происходит, но каким-то образом попал на эту страницу. Лично я люблю писать большие и
Например:
.header {
position: fixed;
top: 0;
...
}
.header a {
display: block;
float: right;
...
}
.sidebar {
float:right;
max-width: 30%;
...
}
.sidebar a {
font-size: 16px;
....
}
Но можно же сократить header до h, a sidebar до s, таким образом сэкономив не мало байт не только в CSS, но и JS файлах, т.к. скорее всего ваш JS будет содержать селекторы по именам классов.
Однако от такого сокращения будет страдать читаемость кода, а как следствие и скорость разработки. По этому надо создать инструмент для проведения данной замены автоматически.
Небольшое пояснение как это работает в Closure
Google Closure состоит из нескольких инструментов, один из которых Google Closure Stylesheets, который является и препроцессором и постпроцессором для таблиц стилей.
Как препроцессор он аналогичен всем своим собратьям, но больше всего он похож на SCSS/SASS.
Как постпроцессор он парсит имена классов создавая словарь замен и заменяет все имена классов на их короткие обозначения.
На пример, код выше станет:
.a {
position: fixed;
top: 0;
...
}
.a a {
display: block;
float: right;
...
}
.b {
float:right;
max-width: 30%;
...
}
.b a {
font-size: 16px;
....
}
А словарь замен будет:
{
"header": "a",
"sidebar": "b"
}
На самом деле функционала там намного больше, но статья не о том. Есть еще Closure Templates, неплохой шаблонизатор, в котором надо отметить все имена классов специальной директивой, что бы потом применить словарь замен к шаблонам.
Например:
{namespace test}
/**
* Test template
*
*/
{template .test}
<div class="{css header}">Header<a href="#">Home</a><a href="#">About</a><header>
...
<div class="{css sidebar}">Sidebar<header>
Так же не надо забывать, что у нас есть JS в котором тоже надо «доработать» имена всех CSS классов:
var header = goog.dom.getElementByClass(goog.getCssName('header'));
var sidebar = goog.dom.getElementByClass(goog.getCssName('sidebar'));
И только когда мы исправим все исходники и отправим их на компиляцию, вместе со словарем замен, то все заработает.
Главный недостаток этого метода, что словарь собран по CSS, т.е. если у вас есть класс который используется только для выборки DOM элемента из JS, то в словарь он может не попасть (а может и попасть, но оговорюсь, что данная статья не обзор Closure Tools).
Вернемся к плагину
Раскидывать везде функции, мне показалось не очень удобно, по этому я решил задавать имена классов по маске ___<%className%>__.
Таким образом стили придут к виду:
.___header__ {
position: fixed;
top: 0;
...
}
.___header__ a {
display: block;
float: right;
...
}
.___sidebar__ {
float:right;
max-width: 30%;
...
}
.___sidebar__ a {
font-size: 16px;
....
}
А работа с DOM в JS, на примере jQuery:
var header = $('.___header__');
var sidebar = $('.___sidebar__');
На примере React:
function Header(props) {
return (
<div className="___header__">
{props.children}
</div>
);
}
На примере Backbone:
module.exports = Backbone.View.extend({
tagName: 'div',
className: '___header__'
});
UPD:
Для Angular пример получился толстым.
Оговорюсь сразу, что конструкции типа:
var genClassName = function(v) {
return '___' + v + '__';
}
module.exports = Backbone.View.extend({
tagName: 'div',
className: genClassName('header')
});
работать не будут. Также как и стили:
[class*="bold"] {
font-weight:bolder;
}
Первые шаги
Установив пакет:
npm install --save cssrename-webpack-plugin
И немного доработав webpack.config.js:
const CssRenameWebpackPlugin = require('cssrename-webpack-plugin');
...
module.exports = {
...
plugins: [
CssRenameWebpackPluginConfig,
...
]
};
В процессе сборки появится строчка:
«Profit: 355»
Которая будет сообщать о том сколько байт было сэкономлено.
Неудобства и их решения
Но если в
В простейшем виде это будет одно регулярное выражение. По этому почему бы не добавить подобный loader который избавит нас от добавления нижних подчеркиваний хотя бы к CSS.
npm install --save cssrename-loader
Очередные мутации webpack.config.js:
module.exports = {
module: {
loaders: [
{
test: /\.css$/,
loader: "style-loader!css-loader!cssrename-loader"
}
]
}
};
То что получилось с тестовым проектом
Update:
Писать я хотел не об этом, но видимо надо добавить. Далее рассмотрены только стили, выигрыш по JS и шаблонам так просто не посчитать. Для Google Closure Stylesheets:
Сайт/Файл | Оригинальный объем/zip | Полученный объем/zip | Процент экономии(zip) |
---|---|---|---|
acss.io bundle.488facb7.css | 13.8KB/4.4KB | 13.3KB/4.0KB | ?9% |
getbem.com/introduction getbem.com.1.0.0.css | 13.3KB/3.3KB | 12KB/3.1KB | ?6% |
Bootstrap | 121.2KB/18.7KB | 96.9KB/16.7KB | ?10% |
habrahabr.ru global_main.css | 212.3KB/30.6KB | 155.2KB/26.9KB | ?13% |
Пруф
Что «не пролезло» в Google Closure Stylesheets:
Для Atomic пришлось сделать две замены по регулярным выражениям:
\\\(((?:(?!\\\)).)*?)\\\) => --$1--
\\. => --
Для BEM «не пролезло»:
@supports (display: -moz-box) {
[class*=LineClamp] {
display: block
}
}
@-webkit-keyframes bounce {
0% {
-webkit-transform: translateY(-100%);
transform: translateY(-100%);
-webkit-filter: blur(5px);
filter: blur(5px)
}
100%, 40% {
-webkit-transform: translateY(0);
transform: translateY(0)
}
60% {
-webkit-transform: translateY(-10%);
transform: translateY(-10%)
}
}
@keyframes bounce {
0% {
-webkit-transform: translateY(-100%);
transform: translateY(-100%);
-webkit-filter: blur(5px);
filter: blur(5px)
}
100%, 40% {
-webkit-transform: translateY(0);
transform: translateY(0)
}
60% {
-webkit-transform: translateY(-10%);
transform: translateY(-10%)
}
}
Из Bootstrap «не пролезло»:
border-top: 4px solid \9;
@media all and (transform-3d),(-webkit-transform-3d)
@-ms-viewport {
width: device-width
}
Из Хабра «не пролезло»:
@charset "UTF-8";
@-moz-document url-prefix() {
.search-field__select {
text-indent: .01px;
text-overflow: ''
}
}
Комментарии (21)
lexich
30.11.2016 00:04+1Ну можно использовать css-modules и правильно настроить генерацию имен классов, но как уже обсудили выше — это неуловимый Джо.
Akuma
30.11.2016 00:58+3Для проекта с посещаемостью меньше Google, Facebook и аналогичных, это бессмысленная трата времени и сил разработчика, а так же большие шансы напороться на ошибку в JS.
Когда-то уже обсуждалось, что гугл таким образом может существенно экономить трафик, т.к. 1 Кб экономии при миллиарде просмотров экономит почти 1 Тб трафика, что довольно неплохо. Но вот «обычному» проекту с посещаемостью пусть даже 100000 в сутки — это экономия на спичках.
3axap4eHko
30.11.2016 03:37а как вы решаете вопрос при манипуляции классами из js? Например я через
classList.toggle
переключаю класс илиclassList.add
добавляю его. учитывает ли это оптимизатор?edejin
30.11.2016 03:53ну например у вас было:
elem.classList.toggle("you");
А теперь вы должны написать:
elem.classList.toggle("___you__");
А в результате получилось:
elem.classList.toggle("a");
Т.е. все что вам нужно, вне зависимости от фрейворка — просто обносить классы подчеркиваниями.
Если я конечно, с просони, правильно понял ваш вопрос.
jbubsk
30.11.2016 08:51Боюсь представить, что бы мне ответила команда, если бы я предложил им: «коллеги, а может попробуем это?»
ruzhovt
30.11.2016 09:49+1gzip + кеш браузера решают 99% проблем с трафиком.
webpack создавался не для экономии трафика ;)
а идея с переименованиями классов — вообще мрак.knotri
30.11.2016 12:20Да почему мрак? Если пользоватся БЕМ-ом — там огромные именна классов (пусть и которые хорошо жмутся gzip).
ruzhovt
30.11.2016 20:30потому что поломается половина javascript кода. имена классов часто используются для ссылания на конкретный дом элемент из кода. и да спасибо за минус в карму, вам того же и вдвойне :)
edejin
30.11.2016 20:47Простите, а можно пример поломки javascript? По возможности кусок сорца.
ruzhovt
01.12.2016 09:39<a href="#" ng-class="{selected: ctx.counter > 5}"/>
вот.edejin
01.12.2016 15:16А у меня для вашего примера получилось.
Вот так:
<a href="#" ng-class="{___selected__: ctx.counter > 5}"/>
Я три часа как начал читать, что такое Angular, и видимо что-то делаю не правильно…
Добавил в сталью пример для Angular.
vtrushin
08.12.2016 14:10-1Вы предлагаете обернуть, почему-то несбалансированным количеством андерскоров (слева 3, справа 2), все классы только для того, чтоб сэкономить копейки кода? Взамен получаете трудночитаемые стили (учитывая еще, что в бэме тоже используется __ и _) и очень большую вероятность ошибки в количестве андерскоров. Ну и конечно же трудночитаемость коснется и хтмла
kafeman
edejin
Не скажите.
Я тут прошелся Google Closure Stylesheets по
https://habracdn.net/habr/styles/1480427855/_build/global_main.css
И получил 155KB против 207KB и это только стили без скриптов и объемов респонса, т.к. хабр не SPA.
А вот для SPA это был бы отличный профит.
kafeman
А теперь пройдитесь еще раз, только в этот раз учтя компрессию. Выигрыш будет незначительным, если будет вообще.
edejin
Вы правы, не спорю.
С зипом профит только по CSS 5KB.
edejin
И 3KB (в зипе) на HTML конкретно этой страницы.
Сколько там у хабра заходов в день…
vintage
Что капля в море, что стакан в океане — погоды они не делают.
kafeman
Стоит ли париться из-за этих 8 Кб, 5 из которых будут закешированны? Всякие картинки весят значительно больше, вот с них и стоит начать оптимизизацию.
Хотел провести эксперимент сам, но closure-compiler ругается на 79 ошибок Хабра. Вы, вероятно, его с какими-то особыми флагами запускаете, чтобы успокоить?
edejin
Как-то так:
java -jar ~/Загрузки/closure-stylesheets.jar --output-renaming-map-format CLOSURE_UNCOMPILED --rename CLOSURE --allow-unrecognized-properties --allow-unrecognized-functions --output-renaming-map renaming_map.js ~/Загрузки/global_main.css -o ./out.css
Советую отпритепринтить.
И вот это он точно не схавает.
charset removed in .../global_main.css at line 1 column 1:
charset «UTF-8»;
^
unknown @ rule in .../global_main.css at line 2 378 column 1:
@-moz-document url-prefix() {
^
GSS constant not defined: MS in .../global_main.css at line 8 314 column 28:
font-family: Trebuchet MS
^
3 error(s), 0 warning(s)