Совместимый CSS

CSS это множество вещей. Вещей, пугающих новичков, и крайне важных (даже восхитительных) для тех из нас, кто использует их изо дня в день. Он настолько переплетается с объектной моделью браузера, что иногда сложно понять, где CSS, а где браузер. И, как большинство из нас знает по собственному опыту, из этого очень легко построить какое-то неподдерживаемое месиво.
Что бы вы ни думали о CSS, очевидно, что он недостаточно определен. Пробелы в самом языке привели к появлению массы препроцессоров, полизаполнений и обходных путей, а недостаток какой-либо истинной изоляции времени прогона привел к огромному ряду допущений и методик, позволяющих сделать код поддерживаемым. Пока происходит формирование спецификаций (переменные CSS, функции цвета, стандартные контрольные точки, Shadow DOM и пр.), пустой тип наполняется какофонией идей и предположений, часто с очень редким совпадением.
Этот значительный дефицит консенсуса является настоящей проблемой. Как говорится в самом великом из австралийских фильмов: \"Я не знаю,что представляет собой альтернатива консенсусу, но ты ее добился\"
Мы и вправду добились альтернативы консенсусу, и мы все дальше отходим от консенсуса каждый раз, когда обсуждаем новые идеи, как если бы старые были полностью неработоспособны, а также когда мы действительно представляем поэтапное движение вперед. Поэтому, так как эта статья в основном имеет дело с новыми идеями, позвольте сказать, что, как бы вы ни писали свой CSS на данный момент, он в самом деле хорош. В частности, мы считаем, что сочетание Sass, BEM и Gulp это на данный момент лучший выбор для большинства реальных проектов. Они позволяют быстро выполнять большинство задач, не заставляя вас сталкиваться с огромным количеством технических долгов, а также содержат массу примеров и уроков, по которым можно научиться, и толпу опытных специалистов, которым можно задавать вопросы.
Цель
На данный момент происходит немало дискуссий о том, стоит ли весь наш код или его часть переводить на JS. Такие проекты, как react-style, jss, radium и jsxstyle предлагают конкурентоспособные способы, как это сделать, но они сталкиваются с проблемами из-за неправильного направления. Кейт Грант отлично высказался по этому вопросу:
Перестаньте притворяться, что DOM и JavaScript это разные проблемы. Вместо этого отделите проблемы, которые действительно отличаются: выпадающее меню отличается от списка объектов; модальное диалоговое окно отличается от нижнего колонтитула страницы. Какого черта вы пихаете их в один HTML-документ?
Отношения между CSS и JavaScript отличаются. С помощью HTML невозможно действительно разделить проблемы разметки и соответствующих элементов JS. С помощью CSS такое разделение реально и даже необходимо для чистой организации кода.
Кейт Грант
Отношения между CSS и JavaScript отличаются. С помощью HTML невозможно действительно разделить проблемы разметки и соответствующих элементов JS. С помощью CSS такое разделение реально и даже необходимо для чистой организации кода.
Существует три важных вопроса, которые следует задать относительно потенциальных работ по оформлению, чтобы оценить их эффективность:
- Как быстро вы можете построить и оформить новый компонент?
- Сколько раз вы можете использовать стили повторно в процессе работы?
- Насколько сложно изменить что-либо впоследствии, чтобы оно выглядело визуально уникальным?
- Сколько раз вы можете использовать стили повторно в процессе работы?
Именно на это стоит ориентироваться в работе. Мы хотим скорости, многократного использования, но не обязательно одновременно, а современные рабочие процессы CSS (например, Sass & BEM) могут связывать все три понятия вместе. Все подходы создания стиля в JS я воспринимаю как преодоление первых двух препятствий, поэтому, несмотря на то, что рендеринг JS предоставляет нам некоторые новые возможности, это не стоит того, чтобы отступать на шаг назад в отношении эффективности. (Стоит отметить, что проблема с всеохватывающими структурами CSS, такими как Bootstrap, заключаются в том, что они могут привести вас к неудаче на финишной прямой.)
Sass & BEM требуют огромного количества условных обозначений и алгоритмов, не позволяющих всему выйти из-под контроля. Возможно, потом, в будущем, CSS станет удерживать текучесть, при этом автоматизируя условные обозначения. Чем-то, что будет сочетать в себе лучшие качества CSS и JS.
Взаимосвязи между языками
Первым, кто это сделал, был RequireJS, но позже Webpack & JSPM популяризовали принцип require (или import, ведь уже 2015 год) из не JS-файла, от которого зависит ваш JS. Например:
import url from './logo.png';
import './main-nav.css';
export default class MainNav extends React.Component {
render() {
return <nav className=\"MainNav\">
<img src={url} className=\"MainNav_Logo\"/>
...
</nav>
}
}
Только определенный загрузчик решает, что будет делать каждый import, но поняв взаимосвязь между JS и файлами активов, мы получили новую возможность. Например, мы не хотим вручную вводить то, как будет выглядеть URL файла изображения, мы можем просто указать его локально. Фактически, загрузчик, отвечающий за файл PNG, может предварительно обработать изображение, прогнав его через оптимизатор, после чего поставить на нем метку о проверке, переместить в директорию активов или в CDN, и вернуть URL. И в самом деле, именно эти функции сочетают в себе image-loader и file-loader от Webpack, и это вполне себе неплохо работает.
Но как насчет CSS в предыдущем примере? Мы полагаемся на правило, что main-nav.css определяет класс MainNav и MainNav_Logo, так же само, как полагались на правило для URL в отношении PNG. Но мы можем сделать лучше.
А что если бы файл CSS мог экспортировать переменные?
В вышеприведенном примере есть очевидный кандидат названия классов, которые определяет файл CSS:
import url from './logo.png';
import styles from './main-nav.css';
export default class MainNav extends React.Component {
render() {
return <nav className={styles.Nav}>
<img src={url} className={styles.Logo}/>
...
</nav>
}
}
Этот подход в последние несколько месяцев был замечен во многих местах. Около десяти недель назад Джулиан Виерек опубликовал на Medium статью под названием \"Modularise CSS the React Way\". Тремя неделями позже Тобиас Копперс (автор из Webpack) добавил концепцию заполнителей в свой загрузчик CSS, а Гай Бедфорд (автор из JSPM) предположил, что мы видим нечто похожее на JSPM. Через месяц после этого Марк Дэлгляйш опубликовал статью \"The End of Global CSS\", которая показала многим людям, какой потенциал скрывается в этом подходе.
Новый синтаксис
Эта идея получила свое развитие в проекте под названием \"CSS Modules\", но сначала нужно было сделать так, чтобы информация пересекла границу CSS-JS стандартным способом. Все исследования были направлены на JSPM и Webpack. Но в этом не было ничего, что было бы неотъемлемо связано с загрузчиком, поэтому разработчики решили объединить усилия. Вот к чему они пришли:
ICSS: Совместимый CSS
ICSS это обычный CSS с парой небольших дополнений. Он похож на объектный файл в совместимых языках результаты компилятора, которые могут быть позже связаны для формирования полноценной программы. Вот первая характеристика ICSS:
Он разработан в качестве объекта компилирования, а не для использования человеком.
Компилирования из чего? Ну, для начала, модулей CSS, но, по сути, сюда может входить что угодно. Единственное требование: Каждый файл должен компилироваться отдельно, а потом привязываться к загрузчику.
Элементы вроде @import в Sass или CSS эффективно связывают исходный код из нескольких файлов таким образом, чтобы вы могли передавать переменные или примеси по кругу, и многие плагины PostCSS, считают, что вы делаете нечто подобное. Фактически, большинство операций обработки в CSS выполняются глобально, так как CSS всегда был глобальным.
JavaScript тоже когда-то был таким. До появления CommonJS не было стандарта для a.js, чтобы требовать b.js, они оба просто запускались в одном и том же контексте глобального браузера и при необходимости могли использовать друг друга. С введением require() и module.exports, JavaScript стал языком программирования с соответствующей системой взаимосвязей и соответствующим локальным определением. ICSS разработан с той же целью.
:export
Первая задача позволить символам экспортироваться из файла в JS или другой файл CSS. Это блок псевдоселектора :export:
:export {
Nav: _nav_nav_afd97dfs867;
Logo: _nav_logo_97fd867fsfg;
}
._nav_nav_afd97dfs867 { /* nav styles */ }
._nav_logo_97fd867fsfg { /* logo styles */ }
Символы просто экспортируются как простой объект JS:
import styles from './nav.css';
// styles: { Nav: \"_nav_nav_afd97dfs867\", Logo: \"_nav_logo_97fd867fsfg\" }
CSS Modules используют это для определения всех селекторов класса по умолчанию, но внутри они генерируют глобально-уникальные названия классов и используют :export для того, чтобы предоставить их в JS.
:import
Еще одно дополнение синтаксиса это псевдоселектор :import. Он позволяет файлу CSS назначать зависимости от другого элемента и определять, какие символы он хочет импортировать. Это последняя способность, которая действительно показывает, на что способен CSS.
:import(\"./utils.css\") {
i__util_class_1: HorizontalNav;
i__util_var_1: SharedUtilVar;
}
Здесь путь к файлу CSS представлен в виде аргумента. После этого загрузчик пойдет и вызовет файл, и свяжет его с этим файлом. Далее идет объявление локальных временных псевдонимов и экспортированных символов из взаимозависимости, которую они представляют. Этот блок будет соответствовать следующему блоку :export для utils.css:
/* utils.css */
:export {
HorizontalNav: _utils_horizontalnav_c7ab86431;
SharedUtilVar: rgb(200, 100, 0);
}
Здесь мы экспортируем название класса как HorizontalNav и переменную в виде SharedUtilVar, но на самом деле они воспринимаются как простые строки.
Использование импорта
Как только файл ICSS будет загружен и связан с импортом, символы будут переданы, а блок :import удален. В продолжение к вышеприведенному примеру:
/* nav.css */
:import(\"./utils.css\") {
i__util_class_1: HorizontalNav;
i__util_var_1: SharedUtilVar;
}
:export {
Nav: _nav_nav_afd97dfs867 i__util_class_1;
}
._nav_nav_afd97dfs867 {
color: i__util_var_1;
}
После того, как :import будет обработан в соответствии с блоком :export в предыдущем разделе, файл примет следующий вид:
/* nav.css */
:export {
Nav: _nav_nav_afd97dfs867 _utils_horizontalnav_c7ab86431;
}
._nav_nav_afd97dfs867 {
color: rgb(200, 100, 0);
}
Когда этот файл будет импортирован, :export превратится в объект JS, а остаток файла будет внедрен в DOM при помощи загрузчика.
Высокоуровневый пример
ICSS разработан для того, чтобы компилироваться, а не писаться от руки, поэтому я решил продемонстрировать, как выполняется одна из функций CSS Modules. Давайте рассмотрим формулировку, которая похожа на концепцию @extend из Sass:
/* my-component.css */
.outer {
composes: flex-centered from \"../utils.css\";
background: rgba(0,0,0,0.8);
}
.inner {
composes: white-bg black-shadow from \"./utils.css\";
border-radius: 4px;
}
Это означает, что .outer включает все стили из .flex-centered, а .inner включает .white-bg и .black-shadow, где все классы использования определяются в utils.css:
/* utils.css */
.flex-centered {
display: flex;
justify-content: center;
align-items: center;
}
.white-bg {
background-color: #eee;
}
.black-shadow {
box-shadow: 0 0 0 1px black, 0 0 8px -2px rgba(0,0,0,0.8);
}
Эти стили используются в JS за счет их импорта:
/* my-component.js */
import styles from \"./my-component.css\"
export default class MyComponent extends React.Component {
render() {
return <div className={styles.outer}>
<div className={styles.inner}>
/* content */
</div>
</div>
}
}
Проектная цель CSS Modules заключается в написании чего-либо, что выглядит глобальным, но компилируется для локализации. Как и в случае со всеми препроцессорами, определяющими ICSS, такая компиляция выполняется пофайлово. Вот скомпилированный результат ICSS:
/* my-component.css (interoperable) */
:import(\"./utils.css\") {
i__util_class_1: flex-centered;
i__util_class_2: white-bg;
i__util_class_3: black-shadow;
}
:export {
outer: _mycomponent_outer_ab24c761 i__util_class_1;
inner: _mycomponent_inner_145bfed2 i__util_class_2 i__util_class_3;
}
._mycomponent_outer_ab24c761 {
background: rgba(0,0,0,0.8);
}
._mycomponent_inner_145bfed2 {
border-radius: 4px;
}
/* utils.css (interoperable) */
:export {
flex-centered: _util_flexcentered_be5fd72ac;
white-bg: _util_whitebg_6dc31abb;
black-shadow: _util_blackshadow_9cd82af23;
}
._util_flexcentered_be5fd72ac {
display: flex;
justify-content: center;
align-items: center;
}
._util_whitebg_6dc31abb {
background-color: #eee;
}
._util_blackshadow_9cd82af23 {
box-shadow: 0 0 0 1px black, 0 0 8px -2px rgba(0,0,0,0.8);
}
Когда эти файлы загружаются, получается следующий объект JS:
import styles from \"./my-component.css\"
// styles: {
// outer: \"_mycomponent_outer_ab24c761 _util_flexcentered_be5fd72ac\",
// inner: \"_mycomponent_inner_145bfed2 _util_whitebg_6dc31abb _util_blackshadow_9cd82af23\"
// }
Это показывает некоторые преимущества, которые предоставляет CSS Modules:
- Каждый файл может обрабатываться отдельно (используется параллельный и пошаговый порядок);
- Стили могут использоваться повторно за счет экспортирования нескольких классов для одного компонента вместо попыток изменить CSS;
- Все стили безопасны с глобальной точки зрения, так как совмещают в себе удобочитаемую для человека часть и гарантированно уникальную часть для разработки. При выпуске эти классы можно сделать значительно меньше, при этом сохраняя их уникальность.
- Стили могут использоваться повторно за счет экспортирования нескольких классов для одного компонента вместо попыток изменить CSS;
Если CSS Modules категоричен, то ICSS таковым не является. Это подталкивает меня к формулировке окончательной характеристики дизайна: ICSS спроектирован для предоставления CSS возможности загружаться и связываться вместе без поиска лучшего способа, как это сделать.