В последние годы мы наблюдали расцвет CSS-в-JS, в основном развивавшийся сообществом React. Конечно, процесс сопровождался спорами. Многие, особенно уже хорошо знакомые с CSS, смотрели на эту инициативу с недоумением.
«Зачем кому-то писать CSS в JS?
Конечно, это ужасная идея!
Им бы просто выучить CSS!»
Если такова была ваша реакция, читайте дальше. Мы поговорим о том, почему написание своих стилей в JavaScript в конце концов не является ужасной идеей, и почему целесообразно наблюдать за быстрым развитием этой технологии.
Сообщества непонимания
React-сообщество часто не понимает CSS-сообщество, и наоборот. Меня эта ситуация интересует особенно, поскольку я оказался зажат меж двух миров. Я начал изучать HTML в конце 1990-х, и профессионально работал с CSS ещё с тёмных времён макетов на основе таблиц. Вдохновлённый CSS Zen Garden, я был в первых рядах миграции кодовых баз в сторону семантической разметки и каскадных таблиц стилей. Вскоре я стал одержим разделением работы, используя ненавязчивый JavaScript для оформления разметки, сделанной на сервере, действиями на клиенте. Вокруг этого подхода сложилось небольшое, но бурное сообщество, и мы стали первым поколением фронтенд-разработчиков, старавшихся отдать браузерной платформе заслуженное уважение.
Так что вы понимаете, что с таким опытом по разработке для веба я был рьяным критиком React-модели HTML-in-JS, которая, как казалось, шла наперекор оберегаемым нами принципам. Но фактически всё оказалось наоборот. По моему опыту, компонентная модель React в сочетании с его возможностью отрисовки на сервере наконец-то позволила нам массово создавать сложные одностраничные приложения, предоставляя пользователям быстрые, доступные и прогрессивные продукты.
Так что можете считать это знаком примирения одного сообщества с другим. Давайте вместе разберёмся, что означает тенденция объединения CSS с JS. Возможно, результат не совершенен, он может не подходить для использования в ваших продуктах. Но как минимум заслуживает вашего внимания.
Зачем нужен CSS-в-JS?
Если вы знакомы с моей недавней работой с React и CSS-модулями, то наверняка удивлены, что я защищаю CSS-в-JS.
В конце концов, CSS-модули обычно выбирают те разработчики, которые хотят получить стили в локальной области видимости (locally scoped styles) без того, чтобы поверить в CSS-в-JS. По сути, я даже не использую CSS-в-JS в своей работе. Несмотря на это, я питаю неподдельный интерес к CSS-в-JS-сообществу, внимательно слежу за нововведениями, которые они постоянно предлагают. Более того, я считаю, что это в интересах всего CSS-сообщества. Но почему? Чтобы вы поняли, почему люди предпочитают писать свои стили на JavaScript, давайте рассмотрим практические преимущества этого подхода.
Я разбил статью на пять основных частей:
- Стили в области видимости (Scoped styles)
- Критический CSS
- Более грамотные оптимизации
- Управление пакетами
- Внебраузерное применение стилей
1. Стили в области видимости (Scoped styles)
Не секрет, что чрезвычайно трудно эффективно строить большую архитектуру CSS. Когда разработчик присоединяется к уже давно существующему проекту, то для нередко труднейшей для него работой становится становится разбирательство с CSS. Чтобы снизить остроту проблемы, CSS-сообщество вложило много усилий в улучшение сопровождаемости стилей, разработав такие подход, как OOCSS и SMACSS. Но самым популярным решением сегодня является BEM, или Block Element Modifier.
По сути, BEM (применённый к чистому CSS) — всего лишь соглашение о наименованиях, ограничивающее стили классами, соблюдающими паттерн .Block__element--modifier
. В любой кодовой базе, использующей BEM, разработчики всегда должны следовать правилам этого соглашения. При строгом соблюдении BEM работает хорошо. Но почему на усмотрение соглашения оставлена такая фундаментальная вещь, как определение области видимости (scoping)? Не важно, говорят они об этом явно или нет, но большинство CSS-в-JS-библиотек следуют образу мыслей BEM, стараясь применять стили к отдельным элементам интерфейса, но реализуют это совершенно разными способами. Как это выглядит на практике? Например, в случае с glamor:
import { css } from 'glamor'
const title = css({
fontSize: '1.8em',
fontFamily: 'Comic Sans MS',
color: 'blue'
})
console.log(title)
// → 'css-1pyvz'
Как вы могли заметить, нигде в коде нет CSS-класса. Это больше не жёстко прописанная ссылка на класс, определённый где-то в системе. Теперь она автоматически генерируется для нас библиотекой. Больше не нужно беспокоиться о селекторах, конфликтующих в глобальной области видимости, а значит нам больше не нужно вручную ставить им префиксы.
Определение области видимости для этого селектора соответствует правилам определения областей видимости в окружающем коде. Если вы хотите сделать это правило доступным всему приложению, то вам нужно превратить его в JS-модуль и импортировать там, где он должен использоваться. С точки зрения сопровождаемости наших кодовых баз это чрезвычайно мощное решение, позволяющее быть уверенными, что источник каждого стиля можно легко отследить, как и любой другой код.
Переходя от простого соглашения к применению по умолчанию стилей в локальной области видимости, мы тем самым улучшаем базовое качество наших стилей. Теперь BEM является интегрированным, а не опциональным решением.
Прежде чем продолжать дальше, нужно обговорить крайне важный момент. Это генерирование настоящего CSS, а не инлайновых стилей. Большинство из ранних CSS-в-JS-библиотек прикрепляли стили напрямую к каждому элементу. Но критическим недостатком этой модели является то, что атрибуты ‘style’
не могут делать всё то, что может CSS. Многие новые библиотеки теперь сосредоточены на динамических таблицах стилей, вставляя и удаляя в ходе исполнения правила из глобального набора стилей.
В качестве примера давайте рассмотрим JSS, одну из ранних CSS-в-JS-библиотек для генерирования настоящего CSS.
При использовании JSS вы можете воспользоваться стандартными CSS-возможностями, вроде hover-стилей и медиа-запросов, которые напрямую проецируются на эквивалентные CSS-правила.
const styles = {
button: {
padding: '10px',
'&:hover': {
background: 'blue'
}
},
'@media (min-width: 1024px)': {
button: {
padding: '20px'
}
}
}
Когда вы вставляете эти стили в документ, то вам предоставляются автоматически сгенерированные классы.
const { classes } = jss.createStyleSheet(styles).attach()
Эти сгенерированные классы можно использовать вместо жёстко прописанных строк классов при генерировании разметки на JavaScript. Этот паттерн работает вне зависимости от того, используете ли вы полноценный фреймворк или что-то простое, наподобие innerHTML.
document.body.innerHTML = `
<h1 class="${classes.heading}">Hello World!</h1>
`
Такое управление стилями само по себе имеет мало значения — обычно оно используется в паре с какой-нибудь компонентной библиотекой. Поэтому в большинстве популярных библиотек встречаются биндинги. Например, JSS может с помощью react-jss легко биндиться на React-компоненты, внедряя маленькие наборы стилей в каждый компонент, тем самым управляя своим глобальным жизненным циклом.
import injectSheet from 'react-jss'
const Button = ({ classes, children }) => (
<button className={classes.button}>
<span className={classes.label}>
{children}
</span>
</button>
)
export default injectSheet(styles)(Button)
Сосредоточив наши стили вокруг компонентов, плотнее интегрируя их на уровне кода, мы эффективно приводим BEM к его логическому заключению. Настолько, что многие участники CSS-в-JS-сообщества считают, что во всём этом шаблонном коде привязки стилей была утеряна важность извлечения, именования и многократного использования компонентов.
Совершенно новый подход к этой проблеме был представлен в styled-components авторства Glen Maddern и Max Stoiber.
Вместо создания стилей, которые затем вручную привязываются к компонентам, мы создаём компоненты напрямую.
import styled from 'styled-components'
const Title = styled.h1`
font-family: Comic Sans MS;
color: blue;
`
Применяя такие стили, мы не прикрепляем класс к существующему элементу. Мы просто отрисовываем сгенерированный компонент.
<Title>Hello World!</Title>
В то время, как styled-components использует традиционный CSS-синтаксис посредством маркированных шаблонных литералов (tagged template literals), другие предпочитают работать со структурами данных. Интересной альтернативой является Glamorous.
Glamorous предлагает тот же component-first API, что и styled-components, но оперирует объектами, а не строками, благодаря чему можно не включать в библиотеку CSS-парсер — это уменьшает размер библиотеки и влияние на производительность.
import glamorous from 'glamorous'
const Title = glamorous.h1({
fontFamily: 'Comic Sans MS',
color: 'blue'
})
Какой бы синтаксис вы не выбрали для описания своих стилей, они больше не находятся в одной области видимости с компонентами — они от них неотделимы. При использовании библиотек наподобие React, компоненты являются базовыми строительными блоками, и теперь наши стили формируют основную часть архитектуры. Если мы описываем всё в нашем приложении как компоненты, то почему бы не описать так и стили?
Для закалённых ветеранов BEM всё это может выглядеть относительно мелким улучшением, учитывая важность изменения в системе. Фактически, CSS-модули позволяют добиться этого без потери комфорта от использования экосистемы CSS-инструментов. Так много проектов используют CSS-модули, поскольку те успешно решают большинство проблем с написанием больших объёмов CSS без необходимости жертвовать знакомым обычным CSS.
Но когда мы начинаем строить дальше на основе этих базовых концепций, всё становится гораздо интереснее.
Глава 2. Критический CSS
Инлайнинг критических стилей в заголовок документа относительно недавно стало лучшей методикой, уменьшающей время первичной загрузки за счёт предоставления только тех стилей, что нужны для отрисовки текущей страницы. Это резко контрастирует с тем, как мы обычно грузили стили: заставляли браузер скачивать все возможные стили для приложения до того, как на экране будет отрисован хоты бы один пиксель.
Хотя есть инструменты для извлечения и инлайнинга критического CSS, вроде critical, по сути они не меняют того факта, что критический CSS труден в сопровождении и автоматизации. Это хитрая, чисто опциональная оптимизация производительности, так что в большинстве проектов словно забывают о ней.
Совершенно другое дело CSS-в-JS. При работе с приложением, которое отрисовывается на сервере, извлечение критического CSS не просто оптимизация — CSS-в-JS на сервере априори требует, чтобы критический CSS использовался в первую очередь. Например, при использовании Aphrodite система отслеживает, какие стили используются в рамках одиночного прохода отрисовки с применением своей функции css
, которая вызывается инлайново, когда применяются классы к вашим элементам.
import { StyleSheet, css } from 'aphrodite'
const styles = StyleSheet.create({
title: { ... }
})
const Heading = ({ children }) => (
<h1 className={css(styles.heading)}>{ children }</h1>
)
Даже хотя все наши стили определены в JavaScript, вы можете легко извлечь стили для текущей страницы в статичные строки CSS, которые можно вставить в заголовок документа при отрисовке на сервере.
import { StyleSheetServer } from 'aphrodite';
const { html, css } = StyleSheetServer.renderStatic(() => {
return ReactDOMServer.renderToString(<App/>);
});
Теперь вы можете отрисовывать свои критические CSS-блоки:
const criticalCSS = `
<style data-aphrodite>
${css.content}
</style>
`;
Если вы заглядывали в модель серверной отрисовки React, то этот паттерн мог показаться вам очень знакомым. В React ваши компоненты определяют свою разметку на JavaScript, но они могут отрисованы на сервере в виде обычной HTML-строки. Если вы создаёте своё приложение с учётом прогрессивного расширения, несмотря на то, что оно написано целиком на JavaScript, то на клиенте вообще может не потребоваться JavaScript. Так или иначе, клиентская JavaScript-поставка включает в себя код, необходимый для загрузки вашего одностраничного приложения, которое неожиданно вызывается и с этого момента отрисовывается на сервере.
Поскольку отрисовка вашего HTML и CSS на сервере выполняется одновременно, библиотеки вроде Aphrodite зачастую помогают нам организовывать генерирование критического CSS и отрисовку HTML на сервере в рамках одного вызова, как мы видели в предыдущем примере. Теперь мы можем аналогичным образом отрисовывать React-компоненты в статичном HTML.
const appHtml = `
<div id="root">
${html}
</div>
`
Использование на сервере CSS-в-JS не только позволяет нашему одностраничному приложению продолжать работать без JavaScript, но может даже ускорить его отрисовку. Как и в случае с определением области видимости для наших селекторов, лучшая методика отрисовки критического CSS теперь используется не опционально, а по умолчанию.
Глава 3. Более грамотные оптимизации
Недавно мы наблюдали возникновение новых способов структурирование CSS — например, Atomic CSS и Tachyons — которые сторонятся «семантических классов» в пользу маленьких, специализированных классов. Например, при использовании Atomic CSS вы применяете классы с синтаксисом наподобие функций, которые потому могут использоваться для генерирования соответствующей таблицы стилей.
<div class="Bgc(#0280ae.5) C(#fff) P(20px)">
Atomic CSS
</div>
Цель — сохранить наш CSS-пакет как можно более компактным за счёт максимального повторного использования классов, обращаясь с классами как с инлайновыми стилями. Это идёт на пользу размеру файла, при этом влияние на кодовую базу и членов вашей команды совершенно незначительное. Сама натура этих оптимизаций подразумевает изменение одновременно CSS и разметки, что делает их более важным с точки зрения архитектуры. Как уже упоминалось, при использовании CSS-в-JS или CSS-модулей вам больше не нужно жёстко прописывать в разметке строки классов. Вместо этого используются динамические ссылки на JavaScript-значения, которые автоматически генерируются библиотекой или сборочным инструментом.
Вместо этого:
<aside className="sidebar" />
Мы пишем это:
<aside className={styles.sidebar} />
Это может выглядеть весьма поверхностным изменением, но на самом деле это монументальный сдвиг в том, как мы управляем взаимосвязями между разметкой и стилями. Предоставив CSS-инструментарию возможность преобразовывать не просто стили, но финальные классы, которые мы применяем к элементам, мы тем самым открыли совершенно новый класс оптимизаций таблиц стилей.
В предыдущем примере ‘styles.sidebar’
вычисляется в строку, но ничто не ограничивает его одним классом. Насколько известно, это может быть строка из более чем десятка классов.
<aside className={styles.sidebar} />
// Could easily resolve to this:
<aside className={'class1 class2 class3 class4'} />
Если можно оптимизировать наши стили, генерируя несколько классов для каждого набора стилей, то можно делать по-настоящему интересные вещи.
Мой любимый пример — Styletron.
Так же, как CSS-в-JS и CSS-модули автоматизируют процесс добавления в классы префиксов а-ля BEM, так и Styletron делает то же самое для Atomic CSS. Основной API заточен под одну задачу: определение конкретных CSS-правил для каждой комбинации свойства, значения и медиа-запроса, который потом возвращает автоматически сгенерированный класс.
import styletron from 'styletron';
styletron.injectDeclaration({
prop: 'color',
val: 'red',
media: '(min-width: 800px)'
});
// → 'a'
Конечно, Styletron предоставляет API более высокого уровня, такие как функция injectStyle
, которая позволяет определять одновременно несколько правил.
import { injectStyle } from 'styletron-utils';
injectStyle(styletron, {
color: 'red',
display: 'inline-block'
});
// → 'a d'
injectStyle(styletron, {
color: 'red',
fontSize: '1.6em'
});
// → 'a e'
Особо следует отметить общность между двумя наборами имен классов, сгенерированными выше. Отказавшись от низкоуровневого контроля над самими классами, определяя только желаемый набор стилей, мы позволяем библиотеке генерировать оптимальный набор атомарных (atomic) классов от нашего имени.
Оптимизации, обычно выполняемые вручную — поиск наиболее эффективного способа разделения стилей на повторно используемые классы — теперь могут быть полностью автоматизированы. Эту тенденцию вы могли здесь заметить. Atomic CSS применяется по умолчанию, а не опционально.
Глава 4. Управление пакетами
Сначала зададим себе обманчиво простой вопрос:
Как нам делиться друг с другом CSS?
Мы мигрировали от ручного скачивания CSS-файлов к специальным серверным менеджерам пакетов наподобие Bower, а затем через npm к инструментам вроде Browserify и webpack. Даже хотя некоторые из инструментов автоматизировали процесс включения CSS из внешних пакетов, фронтенд-сообщество по большей части придерживалось ручного включения CSS-зависимостей.
В то же время, есть одна вещь, для которой CSS-зависимости не слишком хороши: зависимость от других CSS-зависимостей. Как многие из вас помнят, аналогичный эффект с JavaScript-модулями мы наблюдали между Bower и npm. Bower не был связан с каким-то определённым форматом модулей, в то время как модули, публикуемые в npm, использовали формат CommonJS. Это оказало серьёзное влияние на многие пакеты, публикуемые на каждой платформе.
Комплексные деревья маленьких, вложенных (nested) зависимостей хорошо воспринимались в npm, в то время как Bower был склонен к большим, монолитным зависимостям, которых можно было иметь две или три — плюс несколько плагинов, конечно. Поскольку у зависимостей не было модульной системы, на которую можно было положиться, пакеты не могли просто использовать свои собственные зависимости, поэтому интегрировать приходилось вручную, причём самим потребителям.
В результате количество пакетов на npm росло по экспоненте, а у Bower был практически линейный рост. Конечно, на то были разные причины, но положа руку на сердце, по большей части это было следствием того, как обе платформы позволяли (или не позволяли) пакетам зависеть друг от друга в процессе выполнения.
К сожалению, для CSS-сообщества всё это выглядит очень знакомо. Мы тоже наблюдали относительно медленный рост монолитных пакетов по сравнению с JavaScript-пакетами на npm. А что если мы хотели соответствовать экспоненциальному росту npm? Что если мы хотели иметь возможность зависеть от сложных иерархий пакетов разного размера, не сосредотачиваясь на больших, всеохватывающих фреймворках? Для этого нам не только нужен был менеджер пакетов, который выполнит задачу, нам также нужен был соответствующий формат модулей.
Означает ли это, что нам нужен был менеджер пакетов, спроектированный специально для CSS? Для препроцессоров вроде Sass и Less? Действительно интересно то, что мы уже прошли через аналогичную реализацию в HTML. Если вы зададите тот же вопрос применительно к тому, как мы делимся друг с другом разметкой, то быстро заметите, что мы почти никогда не делимся напрямую чистым HTML — мы делимся HTML-в-JS. Делается это посредством jQuery-плагинов, Angular-директив и React-компонентов. Мы собираем большие компоненты из маленьких, каждый со своим собственным HTML, каждый независимо публикуется в npm. Как формат HTML для этого недостаточно силён, но встраивая HTML в полностью сформировавшийся язык программирования мы смогли легко обойти это ограничение.
А что если, как и в случае с HTML, мы бы делились CSS — и генерируемой им логикой — посредством JavaScript? Что если вместо миксинов мы применяли бы функции, возвращающие объекты и строки? Если вместо расширяющих классов мы просто объединяли объекты с Object.assign, или использовали новый оператор распределения объектов (object spread operator)?
const styles = {
...rules,
...moreRules,
fontFamily: 'Comic Sans MS',
color: 'blue'
}
Начав писать стили подобным образом, мы теперь можем комбинировать и делиться кодом стилей так же, как и любым другим кодом приложения, используя те же паттерны, те же инструменты, ту же инфраструктуру, ту же экосистему. Как это начинает окупаться, прекрасно отражено в таких библиотеках, как Polished.
Polished — это, по сути Lodash в мире CSS-в-JS. Она предоставляет сложный набор миксинов, цветовых функций, сокращений и так далее, делая процесс авторинга стилей в JavaScript гораздо более привычным для тех разработчиков, которые приходят из языков наподобие Sass. Ключевым отличием является то, что теперь этот код гораздо удобнее для комбинирования, тестирования и передачи другим; он способен использовать всю экосистему JavaScript-пакетов.
Возвращаясь к CSS, как нам достичь того же уровня opensource-активности, характерной для npm, комбинируя большие коллекции стилей из маленьких, многократно используемых opensource-пакетов? Довольно странно, но мы можем наконец к этому прийти, встраивая CSS в другой язык и полностью охватывая JavaScript-модули.
Глава 5. Внебраузерное применение стилей
Пока что все рассмотренные моменты — которые гораздо проще, когда пишешь CSS в JavaScript — это отнюдь не являются невозможными при использовании обычного CSS. Поэтому я оставил самое интересное напоследок. Что-то, не обязательно играющее огромную роль в текущем CSS-в-JS-сообществе, вполне возможно в будущем может стать фундаментом в дизайне. Что-то, влияющее не только на разработчиков, но и на дизайнеров, радикально меняющее взаимодействие этих двух дисциплин. Но сначала давайте проведём краткий экскурс в React.
Используемая в React модель завязана на компонентах, отрисовывающих немедленное представление финального результата. При работе в браузере мы вместо прямого манипулирования DOM-элементами строим сложные деревья виртуального DOM. Интересно то, что отрисовка в DOM не является частью основной React-библиотеки, она обеспечивается react-dom.
import { render } from 'react-dom'
Хотя React и была сначала создана для DOM, и до сих пор по большей части используется в этой среде, но всё же сама модель позволяет использовать React в самых разных средах просто за счёт введения новых рендереров.
JSX — это не просто виртуальный DOM, это виртуальное что угодно. Это то, что позволяет работать React Native, писать на JavaScript по-настоящему нативные приложения с помощью компонентов, которые отрисовывают виртуальные представления своих нативных двойников. Вместо div
и span
у нас теперь View
и Text
. С точки зрения CSS, самое интересное в React Native заключается в том, что он идёт с собственным StyleSheet API:
var styles = StyleSheet.create({
container: {
borderRadius: 4,
borderWidth: 0.5,
borderColor: '#d6d7da',
},
title: {
fontSize: 19,
fontWeight: 'bold',
},
activeTitle: {
color: 'red',
}
})
Здесь вы видите знакомы список стилей, в данном случае относящихся к цветам, шрифтам и границам. Эти правила довольно просты и легко применяются к большинству UI-окружений. Гораздо интереснее, когда речь заходит о нативных макетах.
var styles = StyleSheet.create({
container: {
display: 'flex'
}
})
Несмотря на то, что она расположена вне браузерного окружения, React Native поставляется с собственной нативной реализацией flexbox. Изначально она была выпущена как JavaScript-пакет css-layout, реализующего flexbox полностью на JavaScript (при поддержке соответствующего комплексного набора тестов). Теперь она мигрировала на С ради лучшей портируемости. Учитывая масштабы и важность проекта, он получил более значительное название — Yoga.
Хотя Yoga предназначен для портирования CSS-концепций в небраузерные окружения, потенциально неограниченная область его применения была ограничена только CSS-фичами. «Yoga предназначен для создания выразительной библиотеки макета, а не для реализации всего CSS». Подобные компромиссы выглядят ограничениями, но если посмотреть на историю CSS-архитектуры, то станет понятно, что работа с большими объёмами CSS связана с выбором подходящего подмножества языка.
Авторы Yoga избежали каскада в пользу стилей в области видимости, и заточили весь движок макетов под flexbox. Это лишает нас многочисленной функциональности, но открывает невероятную возможность для использования кроссплатформенных компонентов со встроенными стилями, и мы уже видели несколько известных opensource-проектов, пытающихся извлечь выгоду из этого факта.
React Native for Web предназначен для замены библиотеки react-native. При использовании бандлера (bundler) вроде Webpack можно легко использовать привязку сторонних пакетов.
module: {
alias: {
'react-native': 'react-native-web'
}
}
Использование React Native for Web позволяет компонентам React Native работать в браузерном окружении, включая браузерное портирование React Native StyleSheet API.
Аналогично, react-primitives обеспечивает кроссплатформенный набор примитивных компонентов, абстрагированных от особенностей целевой платформы, создающих рабочую основу для кроссплатформенных компонентов.
Даже Microsoft представила ReactXP, библиотеку, разработанную для упрощения передачи кода между веб- и нативными проектами, также включающую в себя их собственную платформонезависимую реализацию стилей.
Даже если вы не пишете нативные приложения, важно отметить, что наличие настоящих кроссплатформенных компонентных абстракций позволяет нам ориентироваться на практически безграничный набор сред, иногда совершенно непредсказуемым образом. Самый удивительный пример, что я встречал — react-sketchapp.
Многие из нас тратят кучу времени, пытаясь стандартизировать язык дизайна, как можно больше ограничивая количество дублирования в системах. К сожалению, поскольку мы хотели бы иметь только один источник правды, казалось бы, лучшее, на что мы могли бы надеяться, это сократить количество до двух источников — руководство для разработчиков по «живым стилям» (living style) и руководство для дизайнеров по статичным стилям. Хотя это однозначно лучше того, к чему мы привыкли исторически, всё же это подразумевает ручную синхронизацию из дизайнерских инструментов — вроде Sketch — в код и обратно. И здесь поможет react-sketchapp.
Благодаря JavaScript API, используемому в Sketch, а также возможности React подключаться к разным отрисовщикам, react-sketchapp позволяет брать наши кроссплатформенные React-компоненты и отрисовывать их в Sketch-документах.
Очевидно, что это перетряхнёт взаимодействие между дизайнерами и разработчиками. Теперь, когда при итерировании наших дизайнов мы ссылаемся на одни и те же компоненты, мы можем ссылаться и на одни и те же реализации, независимо от того, с какими инструментами мы работаем — для дизайнеров или разработчиков. С символами в Sketch и компонентами в React мы начинаем сходиться в единой абстракции, что открывает возможность плотнее взаимодействовать в работе, используя общие инструменты. Не случайно, что так много из этих новых экспериментов берут своё начало в React-сообществе и смежных с ним.
В компонентной архитектуре становится высоко приоритетной задача совмещения как можно большего количества компонентов в одном месте. Конечно, сюда входят и стили в локальной области видимости, но благодаря библиотекам вроде Relay и Apollo охватываются и более сложные сферы, вроде извлечения данных. Это открывает перед нами необъятные возможности, и мы только копнули сверху. Это оказывает огромное влияние на применение стилей в наших приложениях, и столь же большое влияние — на всё остальное в нашей архитектуре. И не без оснований.
Унифицируя нашу модель с точки зрения компонентов, написанных на едином языке, мы получаем возможность лучше разделять наши усилия — не по технологиям, а по функциональности. Сосредоточивая всё вокруг модуля из компонентов, собирая из них большие, но удобные в сопровождении системы, которые оптимизированы так, как раньше мы не помышляли, просто и быстро делясь друг с другом своей работой и комбинируя большие приложения из маленьких строительных opensource-блоков. И что ещё важнее, всё это мы можем делать без того, чтобы ломать прогрессивное расширение, без отбрасывания принципов, которые многие считают неотъемлемой частью серьёзного подхода к веб-платформе.
Больше всего меня вдохновляет возможность писать компоненты на едином языке, чтобы сформировать фундамент для нового, унифицированного языка стилей — который небывалым образом объединит фронтенд-сообщество.
К примеру, у себя в SEEK мы создаём своё руководство по «живым стилям» на основе этой компонентной модели, где семантика, взаимодействие и визуальное оформление объединены в рамках единой абстракции. В результате формируется язык дизайна, общий для разработчиков и дизайнеров. Создание страницы должно быть таким же простым, как комбинирование необходимых компонентов — это гарантирует, что наша работа соответствует бренду, но при этом мы можем обновлять наш язык дизайна гораздо позже отправки в production.
import {
PageBlock,
Card,
Text
} from 'seek-style-guide/react'
const App = () => (
<PageBlock>
<Card>
<Text heading>Hello World!</Text>
</Card>
</PageBlock>
)
Хотя наше руководство по стилям сейчас построено на React, Webpack и CSS-модулях, архитектура точно отражает всё, что вы нашли бы в любой системе, построенной с помощью CSS-в-JS. Технологии могут быть выбраны разные, но подход остаётся тем же. Однако в будущем нам скорее всего придётся как-то корректировать свой выбор, и поэтому наблюдение за тенденциями крайне важно для продолжающейся разработки нашей компонентной экосистемы. Вероятно, сегодня мы не будем использовать CSS-в-JS, но совсем не исключено, что в скором времени возникнет убедительная причина перейти на эту технологию.
CSS-в-JS эволюционировал удивительно сильно за короткое время, но важно отметить, в общей схеме эта технология находится лишь в начале своего пути. Ей ещё есть куда развиваться, постоянно появляются нововведения. По-прежнему выпускаются библиотеки для нерешённых пока задач и улучшения процесса разработки — увеличения производительности, извлечения статичного CSS при сборке, таргетирования CSS-переменных и снижения уровня входа для всех фронтенд-разработчиков. В этом направлении движется CSS-сообщество. Несмотря на существенные изменения в нашем рабочем процессе, ничто не отменяет того факта, что нам всё ещё необходимо знать CSS. Мы можем выражать это разным синтаксисом, мы можем по-разному строить архитектуру наших приложений, но фундаментальные строительные CSS-блоки никуда не делись. Наша индустрия неизбежно движется к компонентной архитектуре, и желание переосмыслить фронтенд через её призму лишь крепнет. Нам совершенно необходимо работать вместе, чтобы наши решения были широко применимы среди разработчиков с любым опытом — дизайнерским, инженерным или обоими.
И CSS-, и JS-сообщество желают улучшить фронтенд, чтобы веб-платформу воспринимали всерьёз, и улучшить наши рабочие процессы для создания следующего поколения веб-сайтов. Здесь есть масса возможностей, и хотя мы сделали очень многое, впереди ещё куча работы.
Возможно, я вас до сих пор не убедил, и это совершенно нормально. Вполне разумно, если вы считаете CSS-в-JS сегодня плохо подходящим для вашей работы, но я надеюсь, что на это есть объективные причины, а не из-за поверхностных возражений относительно синтаксиса.
В любом случае, скорее всего такой подход к разработке стилей в последующие годы будет набирать популярность, и стоит наблюдать за его быстрым развитием. Я искренне надеюсь, что вы можете присоединиться к нам и помочь сделать следующее поколение CSS-инструментария как можно более эффективным для всех фронтенд-разработчиков, посредством ли написания кода или просто активного участия в обсуждениях. Ну или по крайней мере я надеюсь, что помог вам лучше понять, почему люди так вдохновлены этой идеей, и — быть может — почему она вовсе не так нелепа.