Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
Жизнь в изоляции
Роман Дворнов
Avito
Санкт-Петербург 2015
Работаю в Avito
Делаю SPA
Автор basis.js
Я…
За любую движуху, 

кроме голодовки ;)
Проблема
Большие сайты и SPA
4
5
Мамонта нужно есть по частям…
Компонентный подход 

и модульность – возможность
решать большую проблему
порционно
6
Основная задача:
избежать конфликтов
7
Инкапсуляция
8
«обеспечение доступности главного … путем
помещения всего мешающего, второстепенного
в некую условную капсулу (чёрный ящик)»
ru.wikipedia.org/wiki/Инкапсуляция
Способы инкапсуляции
9
Способы инкапсуляции
• Скрипты – замыкания, модули (ES6)
9
Способы инкапсуляции
• Скрипты – замыкания, модули (ES6)
• Разметка – неймспейсы (например, SVG)
9
Способы инкапсуляции
• Скрипты – замыкания, модули (ES6)
• Разметка – неймспейсы (например, SVG)
• Стили – ???
9
Изоляция стилей
4 подхода
11
Соглашение
Подход №1
Некоторая договоренность,
описывающая принцип
именования классов
13
блок__элемент--модификатор
14
Например
Это БЭМ, но все далее так же справедливо

и для OOCSS, SMACSS, SUIT, ACSS и т.д.
Изоляция достигается путем
придумывания уникальных имен
15
«Есть только две трудные задачи в
области информатики: инвалидация
кеша и придумывание названий.»
16
– Фил Карлтон
17
Знаешь сколько верстальщики тратят на
придумывание одного имени класса?
Целый день.
Целый день, Карл!
Это не проблема, когда мало
уникальных компонент
18
Универсальный компонент
v.s.
Компонент под задачу
19
20
21
<div class="app-bookings-change-status-popup-option 

app-bookings-change-status-popup-option_{disabled} 

app-bookings-change-status-popup-option_{hidden}">	
<span class="app-bookings-change-status-popup-option__caption
app-bookings-change-status-popup-option__caption_{selected}">	
{title}	
</span>	
</div>
Проблема, когда уникальных
компонент много
Реальный пример из жизни
Другие проблемы
• Уникальность обеспечивается человеком
• Сложность автоматизированного анализа
• Нет механизмов интеграции сторонней
верстки в свою или свою в чужое окружение
• …
22
Основная проблема –
человеческий фактор
23
Если правило можно нарушить –
оно будет нарушено
24
Нельзя проверить правильно ли
вы делаете, это может сказать
только человек
25
Привет, субъективное мнение!
Плюсы
• Хоть какая-то система
• Дешево для внедрения
26
Не смотря на популярность –
так себе решение*
27
* для сложных проектов
Технология
Подход №2
Shadow DOM
29
часть Web Components
w3c.github.io/webcomponents/spec/shadow/
Shadow DOM – 

возможность инкапсулировать
DOM-фрагмент
30
31
Все созданные ранее API не
видят содержимое Shadow Tree
32
А там может быть любая разметка
и стили к ней
Любая вложенность
Любая вложенность
Shadow DOM – 

это всегда JavaScript
34
35
function init(container) {	
var shadowRoot = container.createShadowRoot();	
!
var content = document.createElement('div');	
content.innerHTML = 'some markup';	
!
var styles = document.createElement('style');	
styles.textContent = '/* some css */';	
!
shadowRoot.appendChild(styles);	
shadowRoot.appendChild(content);	
}
Создаем корень Shadow Tree
36
function init(container) {	
var shadowRoot = container.createShadowRoot();	
!
var content = document.createElement('div');	
content.innerHTML = 'some markup';	
!
var styles = document.createElement('style');	
styles.textContent = '/* some css */';	
!
shadowRoot.appendChild(styles);	
shadowRoot.appendChild(content);	
}
Создаем DOM фрагмент
37
function init(container) {	
var shadowRoot = container.createShadowRoot();	
!
var content = document.createElement('div');	
content.innerHTML = 'some markup';	
!
var styles = document.createElement('style');	
styles.textContent = '/* some css */';	
!
shadowRoot.appendChild(styles);	
shadowRoot.appendChild(content);	
}
Создаем стили
38
function init(container) {	
var shadowRoot = container.createShadowRoot();	
!
var content = document.createElement('div');	
content.innerHTML = 'some markup';	
!
var styles = document.createElement('style');	
styles.textContent = '/* some css */';	
!
shadowRoot.appendChild(styles);	
shadowRoot.appendChild(content);	
}
Вставляем все в Shadow Tree
Custom Elements позволяют
абстрагироваться от нюансов
реализации
39
www.w3.org/TR/custom-elements/
Для них также нужен JavaScript
Проблема изоляции решена?
40
Миф:
Shadow DOM обеспечивает
полную изоляцию стилей
41
На текущий момент
А Shadow DOM-то – протекает
• Часть стилей наследуется от хоста

font, line-height, color и т.д.
• Можно стилизовать извне, используя
комбинатор >>> (бывш. /deep/ и ^^)
42
dev.w3.org/csswg/css-scoping/
43
Хорошая новость:
Все производители браузеров
намерены внедрять Shadow DOM
44
встреча Google, Mozilla, Apple и Microsoft
45
Но текущий дизайн «не очень»…
Надо «все» переделать
• Будет два режима open (то что сейчас) 

и close (более закрытый)
• Отказ от комбинатора >>> (м.б. будут слоты)
• Потребуется переделать текущий API
• ...
46
tinyurl.com/oqkv5u6
Плюсы
• Честная изоляция (особенно в режиме close)
• Браузерное решение (возможный стандарт)
47
Минусы
• Сложная концепция
• Требуется JavaScript
• Трудно полифилить
• Спецификация далека от завершения, 

все еще поменяется
48
Пока рано использовать,
нужно еще подумать…
49
Инлайн-стили
Подход №3
Предложение от команды React –
пишем CSS в JavaScript
51
speakerdeck.com/vjeux/react-css-in-js
speakerdeck.com/vjeux/react-css-in-js-react-france-meetup
52
var React = require('react');	
!
var styles = {	
button: {	
color: 'white',	
backgroundColor: 'red'	
}	
};	
!
var MyButton = React.createClass({	
render: function() {	
return <button style={styles.button}>Hello, world!</button>	
}	
});
А как же каскад, 

псевдоклассы, 

медиавыражения и т.д.?
53
54
function merge(...args){	
return Object.assign({}, ...args);	
}	
!
var styles = {	
// ...	
disabled: {	
opacity: .5	
}	
};	
!
<button style={merge(	
styles.button,	
condition && styles.disabled	
)}>Hello, world!</button>	
Каскад
.button {	
...	
}	
!
.disabled {	
opacity: .5;	
}
55
.button {	
background-color: gray;	
}	
.button:hover {	
background-color: red;	
}	
!
<button 	
onMouseEnter={function(){ this.setState({ hover: true }); }}	
onMouseLeave={function(){ this.setState({ hover: false }); }} 	
style={{ background: this.state.hover ? 'red' : 'gray' }}>	
Hello, world!	
</button>	
:hover
56
.button {	
width: 600px;	
}	
@media (max-device-width: 1024px) {	
.button {	
width: 300px;	
}	
}	
!
<button style={{ width: window.innerWidth > 1024 ? 600 : 300 }}>	
Hello, world!	
</button>	
Media Queries
+ обработчик на window.resize
57
Реакция 

здорового человека
Они убили Кенни

лучшее в CSS
58
Плюсы
• Изолированные стили без селекторов
• Все возможности JavaScript для стилей
• Нет проблемы специфичности
59
Минусы
• Подходит только для решений в стиле React
• Полное слияние логики и представления
• Нельзя использовать пре- и пост-процессоры
• Нельзя применять анализ и оптимизировать
• Бесполезность браузерных Developer Tools
60
61
Шурик, Вы комсомолец?
Это же не наш метод! Где гуманизм?
Процессинг
Подход №4
Пре- и пост-процессоры:
транспиляция, трансформация,
оптимизация, минификация…
63
Скрипты Разметка Стили
«Проблема»:
процессоры рассматриваются
в рамках одной технологии
64
Но если работать с технологиями
совместно, можно получить больше
65
Рассмотрим
Basis
Ember
React
66
Basis
Подход №4.1
DOM-шаблонизаторы –

не только «быстро»
68
tinyurl.com/domtemplates
Год назад в докладе
Тогда была только идея, 

теперь есть реализация и опыт
69
Предусловия
• В JavaScript не используем CSS-классы

(абстрагируемся от верстки)
• В шаблонах явно указываются используемые
файлы стилей (нет глобальных)
70
71
<b:style src="block.css"/>	
<div class="block block_{hidden}">	
{caption}	
</div>
.block { … }	
.block_hidden { … }
block.css
block.tmpl
Вот так
Инструкция шаблонизатору 

о необходимости
подключить файл стилей
Префикс b: (сокр. от basis)
стандартный механизм
неймспейсов в XML
(для избежания конфликта
имен)
Взяв любой шаблон, 

мы «знаем» всю его разметку 

и используемые им стили
72
Изоляция включается
инструкцией шаблонизатору
<b:isolate/>
73
Изоляция достигается путем
добавления уникального
префикса всем именам классов
74
75
<b:style src="block.css"/>	
<b:isolate/>	
<div class="block block_{hidden}">	
{caption}	
</div>
.block { … }	
.block_hidden { … }
block.css
block.tmpl
Добавляем инструкцию
76
<b:style src="block.css"/>	
<b:isolate/>	
<div class="block block_{hidden}">	
{caption}	
</div>
.block { … }	
.block_hidden { … }
block.css
block.tmpl
Отдаем шаблонизатору
!
!
Шаблонизатор
Пре-процессор
77
Шаблонизатор преобразует разметку и стили
<b:style src="block.css"/>	
<b:isolate/>	
<div class="block block_{hidden}">	
{caption}	
</div>
.block { … }	
.block_hidden { … }
block.css
block.tmpl
!
!
Шаблонизатор
Пре-процессор
77
Шаблонизатор преобразует разметку и стили
<link href="block.css?prefix=jc1hsy_">	
!
<div class="jc1hsy_block jc1hsy_block_{hidden}">	
{caption}	
</div>
.jc1hsy_block { … }	
.jc1hsy_block_hidden { … }
block.css?prefix=jc1hsy_
Разметка
!
!
Шаблонизатор
Пре-процессор
Префиксы генерируются
случайным образом, 

вид зависит от задачи
78
Еще вернемся к этому
Непредсказуемость префикса
лишает возможности «хакать»
верстку извне
79
Честный АНБ – без вариантов
80
<div class="app-bookings-change-status-popup-option 

app-bookings-change-status-popup-option_{disabled} 

app-bookings-change-status-popup-option_{hidden}">	
<span class="app-bookings-change-status-popup-option__caption
app-bookings-change-status-popup-option__caption_{selected}">	
{title}	
</span>	
</div>
Помните пример?
81
<b:style src="option.css"/>	
<b:isolate/>	
!
<div class="option option_{disabled} option_{hidden}">	
<span class="caption caption_{selected}">	
{title}	
</span>	
</div>
То же с изоляцией
Фишка не только в
префиксах…
82
Переопределение стилей
вместо добавления 

новых классов
83
84
Безопасное дополнение стилей
<b:style src="button.css"/>	
<button class="button">	
click me	
</button>
button.tmpl
<b:isolate/>	
<b:style>	
.button { background: green; }	
</b:style>	
<b:include src="./button.tmpl"/>
ok-button.tmpl
<b:isolate/>	
<b:style>	
.button { background: red; }	
</b:style>	
<b:include src="./button.tmpl"/>
cancel-button.tmpl
85
Безопасное дополнение стилей
<b:isolate/>	
<b:style>	
.button { background: green; }	
</b:style>	
<b:include src="./button.tmpl"/>
ok-button.tmpl
<b:isolate/>	
<b:style>	
.button { background: red; }	
</b:style>	
<b:include src="./button.tmpl"/>
cancel-button.tmpl
<b:style src="button.css"/>	
<button class="button">	
click me	
</button>
button.tmpl
85
Безопасное дополнение стилей
<b:isolate/>	
<b:style>	
.button { background: green; }	
</b:style>	
<b:include src="./button.tmpl"/>
ok-button.tmpl
<b:isolate/>	
<b:style>	
.button { background: red; }	
</b:style>	
<b:include src="./button.tmpl"/>
cancel-button.tmpl
<style>	
.jkc83s_button { … }	
.jkc83s_button { background: green; }	
</style>	
<button class="jkc83s_button">	
click me	
</button>
<style>	
.h9sg2n_button { … }	
.h9sg2n_button { background: green; }	
</style>	
<button class="h9sg2n_button">	
click me	
</button>
!
Стили раздельно для примера, 

в сборке все стили
объединяются и сжимаются
86
Масштабируем подход:
изоляция включаемой верстки
87
88
<b:isolate/>	
<b:style>	
.example-foo { color: red; }	
</b:style>	
<div class="panel">	
<b:include src="foo.tmpl" isolate="example-"/>	
<b:include src="foo.tmpl" isolate>	
<b:style src="nested.css"/>	
</b:include>	
</div>
example.tmpl
<b:style src="foo.css"/>	
<div class="foo">	
example	
</div>
foo.tmpl
Можно указывать конкретный префикс для
обращения вне включения, или не указывать 

и добавлять стили внутри инструкции включения
Изолируем включения
89
<link href="foo.css?prefix=hs83shyf_example-">	
<link href="foo.css?prefix=hs83shyf_y0dk7x_">	
<link href="nested.css?prefix=hs83shyf_y0dk7x_">	
<style>	
.hs83shyf_example-foo { color: red; }	
</style>	
<div class="hs83shyf_panel">	
<div class="hs83shyf_example-foo">	
example	
</div>	
<div class="hs83shyf_y0dk7x_foo">	
example	
</div>	
</div>
90
<link href="foo.css?prefix=hs83shyf_example-">	
<link href="foo.css?prefix=hs83shyf_y0dk7x_">	
<link href="nested.css?prefix=hs83shyf_y0dk7x_">	
<style>	
.hs83shyf_example-foo { color: red; }	
</style>	
<div class="hs83shyf_panel">	
<div class="hs83shyf_example-foo">	
example	
</div>	
<div class="hs83shyf_y0dk7x_foo">	
example	
</div>	
</div>
3 изолированные области имен
в одной разметке!
CSS неймспейсы:
изоляция файлов стилей
91
92
<b:style src="bootstrap.css" ns="bt"/>	
<b:style src="icons.css" ns="icon"/>	
<b:style src="style.css"/>	
!
<div class="active">	
<span class="icon icon:active"></span>	
<button class="bt:btn bt:active">	
Button	
</button>	
</div>
Решаем проблему конфликта имен
93
<link href="bootstrap.css?prefix=iv7z2b_">	
<link href="icons.css?prefix=jasdhb_">	
<link href="style.css">	
!
<div class="active">	
<span class="icon jasdhb_active"></span>	
<button class="iv7z2b_btn iv7z2b_active">	
Button	
</button>	
</div>
Имена из разных файлов стилей не пересекаются
94
<link href="bootstrap.css?prefix=iv7z2b_">	
<link href="icons.css?prefix=jasdhb_">	
<link href="style.css">	
!
<div class="active">	
<span class="icon jasdhb_active"></span>	
<button class="iv7z2b_btn iv7z2b_active">	
Button	
</button>	
</div>
Знаем какой класс из какого файла
Кое что еще
95
Есть же еще есть 

пост-процессинг!
96
97
PostCSS
множество плагинов
csso
структурная оптимизация
…
Например
Оптимизации
98
99
.jkc83s_button {	
width: 100px;	
color: green;	
}
.jkc83s_button {	
color: red;	
width: 100px;	
}	
.jkc83s_button {	
color: green;	
}
csso
csso умеет объединять блоки
с одинаковым селектором
100
.foo {	
color: red;	
width: 100px;	
}	
.bar {	
color: green;	
}
Но в таком случае бессилен
селекторы разные
Понимание границ верстки дает
возможность для больших
оптимизаций
101
Границы обеспечивает изоляция стилей
102
<b:style src="style.css"/>	
<b:isolate/>	
!
<div class="foo bar">	
...	
</div>
.foo {	
color: red;	
width: 100px;	
}	
.bar {	
color: green;	
}
example.tmpl style.css
У нас есть все информация об использовании
foo и bar встречаются только 

на одном элементе
103
<b:style src="style.css"/>	
<b:isolate/>	
!
<div class="s82jhs">	
...	
</div>
.s82jhs {	
color: red;	
width: 100px;	
}	
.s82jhs {	
color: green;	
}
example.tmpl style.css
Можно склеить
.foo + .bar
104
.s82jhs {	
width: 100px;	
color: green;	
}
example.tmpl style.css
<b:style src="style.css"/>	
<b:isolate/>	
!
<div class="s82jhs">	
...	
</div>
Дальше за дело снова берется csso
Поиск ошибок и
ликвидация мертвого кода
105
106
<b:style src="example.css"/>	
!
<span class="foo never-used">	
...	
</span>	
.foo {	
/* ... */	
}	
.dead-style {	
/* ... */	
}	
example.tmpl example.css
Если класс используется в разметке, но
не используется в стилях или наоборот…
Не используемые имена классов
107
Инструменты нам скажут об этом
Проблемы отладки
108
109
Длинные префиксы,
трудно искать исходник
Нет ссылки на
оригинальный файл стилей
Префиксы
110
Мы определяем вид префикса,
в зависимости от окружения
111
В режиме разработки
используются префиксы вида
iNNN__
112
Где NNN – номер шаблона, часто совпадает
между перезагрузками страницы
113
Короткие префиксы, 

легко отличать друг от друга
В боевой среде

используются base36 хеши
h5fjy1bkfb4zcskh__
114
115
function generatePrefix(){	
function base36(num){	
return Math.round(num).toString(36);	
}	
!
// префикс должен начинаться с буквы	
var result = base36(10 + 25 * Math.random());	
!
while (result.length < 16)	
result += base36(new Date * Math.random());	
!
return result.substr(0, 16);	
}
Вероятность пересечения

1 / 5 747 921 912 739 067 000 000 000
Функция генерации префикса
Трансформация имен классов
116
Трансформация имен классов
• В шаблоне:

.example
116
Трансформация имен классов
• В шаблоне:

.example
• В документе – режим разработки:

.i123__example
116
Трансформация имен классов
• В шаблоне:

.example
• В документе – режим разработки:

.i123__example
• В документе – боевая среда:

.h5fjy1bkfb4zcskh__example
116
Трансформация имен классов
• В шаблоне:

.example
• В документе – режим разработки:

.i123__example
• В документе – боевая среда:

.h5fjy1bkfb4zcskh__example
• В документе – боевая среда + сжатие имен:

.Gh
116
117
Наша верстка в боевой среде
Поиск и ссылка на источник
118
Видео
119
Инструменты могут решить проблему поиска
Подробнее в докладе «SPA инструменты»
Видео
119
Инструменты могут решить проблему поиска
Подробнее в докладе «SPA инструменты»
120
Но как быть с информацией
в Developer Tools?
121
.example {	
/* … */	
}	
!
style.css
Исходный файл стилей
122
.jdf9gd__example {	
/* … */	
}	
!
!
style.css?prefix=jdf9gd__
Подставляем префикс – получаем новый файл
123
.jdf9gd__example {	
/* … */	
}	
!
/*# sourceURL=style.css?prefix=jdf9gd__ */	
!
style.css?prefix=jdf9gd__
Добавляем sourceURL
Имя сгенерированного
файла
124
.jdf9gd__example {	
/* … */	
}	
!
/*# sourceURL=style.css?prefix=jdf9gd__ */	
/*# sourceMappingURL=data:application/json;base64,... */	
style.css?prefix=jdf9gd__
Добавляем карту кода
Содержимое

карты кода в base64
125
btoa(	
JSON.stringify({	
"version": 3,	
"sources": ["style.css"],	
"mappings":	
"AAAA" + ";AACA".repeat(css.split('n').length) 	
})	
)
Генерация карты с построчным соответствием
Оригинальное имя файла
без префикса
Повторяем ";AACA" столько раз, сколько строк
126
Не забудьте включить карты кода для CSS
127
Ссылка на оригинальный файл стилей
с правильным номером строкиPROFIT!
Не так важна реализация,
главное – идея
128
Можете повторить у себя дома
Ember
Подход №4.2
130
Более простая реализация
github.com/ebryn/ember-component-css
Осторожно – эксперимент!
Ember 2.0
131
Изоляция достигается путем
уникального класса 

на корневом элементе компонента
132
& {	
padding: 2px;	
}	
.foo {	
color: red;	
}
example.hbs example.css
<div class="example-a34fba">	
<span class="foo">	
hello world	
</div>	
</div>
133
.example-a34fba {	
padding: 2px;	
}	
.example-a34fba .foo {	
color: red;	
}
<div class="example-a34fba">	
<span class="foo">	
hello world	
</div>	
</div>
example.hbs example.css
Проблемы
• Пока на уровне прототипа
• Конфликты при вложении компонент
• Нет изоляции включений или стилей
• Под вопросом возможности отладки
134
В самом начале пути, 

но в правильном направлении
135
React
Подход №4.3
137
+ +
React Webpack PostCSS
(local-scope plugin)
tinyurl.com/m9xoefq
138
Изоляция достигается путем
замены имен классов и id

и предоставления карты замен
139
!
Webpack
local-scope
.foo { background: red; }	
#bar { background: green; }
Оригинальный файл стилей
139
!
Webpack
local-scope
.foo { background: red; }	
#bar { background: green; }
Оригинальный файл стилей
139
!
Webpack
local-scope
.foo { background: red; }	
#bar { background: green; }
.ze2420…e8b7 { background: red; }	
#zdf120…a66d { background: green; }
Оригинальный файл стилей
Преобразованный
файл стилей
139
exports.locals = {	
foo: "ze2420…e8b7",	
bar: "zdf120…a66d"	
}
!
Webpack
local-scope
.foo { background: red; }	
#bar { background: green; }
.ze2420…e8b7 { background: red; }	
#zdf120…a66d { background: green; }
Оригинальный файл стилей
Преобразованный
файл стилей
Карта замен
140
loader: 'css?localIdentName=' + (	
process.env.NODE_ENV === 'development' ?	
'[name]__[local]___[hash:base64:5]' :	
'[hash:base64:20]'	
)
Можно управлять видом замены
В режиме разработки:
MyComponent__foo___1rJwx
В боевом окружении:
rJwx92gmbvaLiDdzgXiJ
141
import React from 'react';	
import styles from './MyComponent.css';	
!
class MyComponent extends React.Component {	
render() {	
return (	
<div className={styles.foo}>	
<div className={styles.bar}>	
Local scope!	
</div>	
</div>	
);	
}	
};
Используем оригинальные имена,
не зная их текущего вида
В самом начале пути, 

но в правильном направлении
142
Hot! Появилось всего два месяца назад
Все идет к тому, что это будет
стандартной функцией
загрузчика CSS в Webpack
143
Эпилог
Пока нет идеального способа
изоляции стилей
145
Разработчики браузеров
думают над проблемой
146
Возможно решением будет
Shadow DOM
147
Пока же лучшая альтернатива
пре- и пост-процессинг
148
Надежнее и больше
возможностей
149
Вопросы?
150
Роман Дворнов
@rdvornov
rdvornov@gmail.com
basis.js
basisjs.com
github.com/basisjs

More Related Content

Жизнь в изоляции