Парсим css

86
Парсим CSS Роман Дворнов Avito Екатеринбург, сентябрь 2016

Upload: roman-dvornov

Post on 07-Jan-2017

579 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: Парсим CSS

Парсим CSSРоман Дворнов

AvitoЕкатеринбург, сентябрь 2016

Page 2: Парсим CSS

Руководитель фронтенда в Avito

Основной интерес – SPA

Open source:basis.js, CSSO, component-inspector, csstree и другие

За любую движуху, кроме голодовки ;)

Page 3: Парсим CSS

Зачем парсить CSS?

Page 4: Парсим CSS

Много CSS, пишут люди, много спек, разная поддержка браузерами, все

меняется и т.д.

4

Modern CSS is hard

Page 5: Парсим CSS

Инструменты• Пре-/пост-процессинг • Форматирование • Валидация и линтирование • Подсказки • Генерация гайдбуков, тестов • Оптимизация и сжатие • …

5

Page 6: Парсим CSS

Для всего этого нужно хорошо понимать структуру CSS

6

Page 7: Парсим CSS

.rule { property: value;}

7

CSS в вакууме – все просто

👌

Page 8: Парсим CSS

Мы все еще недостаточно знаем о CSS

8

Page 9: Парсим CSS

9

Светлая сторонаВсе хорошо знают и часто используют

Знают очень опытные или копипаста

Темная сторонаТайное экспертное знание, может даже (пока) нигде не работаетНовое

CSS

Уже с нами

Page 10: Парсим CSS

CSS не так прост• at-rules @media, @supports, @font-face, …

• необязательные кавычки url(…), [name=value], …

• сложные функции calc(), var(), attr(), …

• экранирование

• bad-url, bad-string…

• …

10

Page 11: Парсим CSS

11

#\31 \ \+\ 2 { content: "hello\world"; background: url(p\(4\).jpg)}

Валидный ли CSS?

Page 12: Парсим CSS

11

#\31 \ \+\ 2 { content: "hello\world"; background: url(p\(4\).jpg)}

Валидный ли CSS?

Да, совершенно валидный! Э – экранирование

Page 13: Парсим CSS

12

#\31 \ \+\ 2 { content: "hello\world"; background: url(p\(4\).jpg);}

Экранирование

drafts.csswg.org/cssom/#the-css.escape()-method

В идентификаторах, именах классов, атрибутов и т.д.

можно использовать любые символы, если экранировать, даже придумали метод для

этого – CSS.escape()

CSS.escape('1 + 2') === '\31 \ \+\ 2'

Page 14: Парсим CSS

13

#\31 \+2 { content: "hello\world"; background: url(p\(4\).jpg);}

Экранирование

www.w3.org/TR/CSS22/grammar.html#grammar

Можно экранировать перевод строки в строках, прям как в JavaScript ;)

Page 15: Парсим CSS

14

#\31 \+2 { content: "hello\world"; background: url(p\(4\).jpg);}

Экранирование

www.w3.org/TR/CSS22/grammar.html#grammar

Можно экранировать специальные символы

в url()

Page 16: Парсим CSS

15

ЭкранированиеБраузер поймет правильно

А вот парсер инспектора стилей в Chrome DevTools написан плохо

Page 17: Парсим CSS

16

.simple { --color: green; border: 1px solid var(--color, red);}

CSS Custom Properties

www.w3.org/TR/css-variables/#using-variables

Page 18: Парсим CSS

17

.complex { --bg: url('..') no-repeat, bottom 10px right url(..); background: var(--bg, bottom url('..'), left), red;}

CSS Custom Properties

Переменная может вставить любую часть значения, даже запятые, как и fallback

www.w3.org/TR/css-variables/#using-variables

Page 19: Парсим CSS

18

@supports (display: flex) { .simple { display: flex; }}

@supports

Page 20: Парсим CSS

19

@supports (display: flex) { .simple { display: flex; }}

@supports

Декларация

Page 21: Парсим CSS

20

@supports (display: flex) { .simple { display: flex; }}

@supports

Декларация

Тоже декларация

Page 22: Парсим CSS

21

@supports (background: top calc(25% + 2px) !important) { …}

@supports

Браузер может не верно ответить на вопрос, но синтаксис разрешает все,

что можно в декларации, даже !important

www.w3.org/TR/css3-conditional/#at-supports

Page 23: Парсим CSS

Синтаксис CSS меняется• Расширяется

• Корректируется

• Добавляются новые конструкции

22

Page 24: Парсим CSS

23

:not(a.foo) { ... }:not(a):not(b) { ... }

:not()Было можно указывать только простые селекторы

www.w3.org/TR/css3-selectors/#negation

Page 25: Парсим CSS

24

:not(a, b) { ... } /* :not(a):not(b) */img:not(a *) { ... } /* изображение не внутри ссылки */

:not()CSS Selectors Level 4 разрешил список любых селекторов, уже поддерживается в Safari 9+

drafts.csswg.org/selectors-4/#negationtinyurl.com/Webkit-Complex-Selectors

Page 26: Парсим CSS

25

:nth-child(1 of p img) { ... } /* первый <img> в <p> */:nth-last-child(2n + 1 of .foo, .bar) { ... }

:nth-child()/:nth-last-child()CSS Selectors Level 4 и тут разрешил список любых селекторов, тоже поддерживается в Safari 9+

tinyurl.com/Webkit-Complex-Selectorsdrafts.csswg.org/selectors-4/#the-nth-child-pseudo

Page 27: Парсим CSS

26

.attr { width: attr(name %, var(--default-width, calc(50% + 2em))); +}

attr()CSS Values and Units Module Level 4 разрешает attr() в любом месте + тип или единица изменения + fallback значение

drafts.csswg.org/css-values/#attr-notationПока никем не поддерживается и в состоянии at risk

Page 28: Парсим CSS

Самое «вкусное»• Баги браузеров

• Хаки _property, value !ie, \9, …

• Легаси префиксы, filter, expression(), …

27

Page 29: Парсим CSS

28

_property: value; // IE6property: value !ie; // IE ≤ 7property: value\9; // IE 6-8

// Chrome ≤ 28 Safari ≤ 7.selector { (;property: value;); }.selector { [;property: value;]; }

// Chrome 22-28 Safari ≥ 7@media \\0 screen {}

browserhacks.com

Page 30: Парсим CSS

Проблема разбора CSS является комплексной и имеет множество подводных камней

29

Page 31: Парсим CSS

Написать собственный парсер CSS становится все сложнее

30

Page 32: Парсим CSS

Инструментам нужно корректное и детальное описание структуры CSS

31

Page 33: Парсим CSS

CSS парсеры

Page 34: Парсим CSS

Парсеров CSS на JavaScript достаточно много

33

Page 35: Парсим CSS

Частые проблемы• Заброшены и не развиваются

• Устарели (не поддерживают новое в CSS)

• Содержат ошибки

• Неудачная структура

• Медленные34

Page 36: Парсим CSS

Наилучшим выбором может быть парсер из PostCSS

35

postcss.org

Page 37: Парсим CSS

Плюсы PostCSS• Развивается и поддерживается

• Хорошо справляется с синтаксисом CSS и даже будущим + tolerant mode

• Сохраняет информацию о форматировании

• Удобное API для работы с AST

• Быстрый36

Page 38: Парсим CSS

Основная проблема: селекторы и значения свойств остаются не разобранными

37

Page 39: Парсим CSS

Это вынуждает разработчиков

• Использовать костыли

• Писать свои парсеры

• Использовать дополнительные парсеры:postcss-selector-parser postcss-value-parser

38

Page 40: Парсим CSS

39

// !singlequotes|!doublequotes|!url()|pixelunitvar pxRegex = /"[^"]+"|'[^']+'|url\([^\)]+\)|(\d*\.?\d+)px/ig;

css.walkDecls(function (decl, i) { if (opts.propWhiteList.indexOf(decl.prop) === -1) return; … decl.value = decl.value.replace(pxRegex, function (m, $1) { if (!$1) return m; var pixels = parseFloat($1); if (pixels < minPixelValue) return m; return toFixed((pixels / rootValue), unitPrecision) + 'rem'; });});

Кто во что горазд

postcss-pxtorem

Page 41: Парсим CSS

CSSTree

Page 42: Парсим CSS

Основные цели• Правильный разбор с учетом семантики CSS

• Детальное, корректное и удобное AST

• Удобное API для работы с AST

• Сформировать«стандарт» для CSS AST на подобии ESTree

• Быстрый и экономный41

Page 43: Парсим CSS

Немного истории

Page 44: Парсим CSS

Чуть меньше года назад я стал мейнтейнером CSSO

(минификатор CSS)

43

github.com/css/csso

Page 45: Парсим CSS

CSSO работал на основе парсера Gonzales

44

github.com/css/gonzales

Page 46: Парсим CSS

Проблемы• Не развивается с 2013

• Неудобный формат AST, местами странный

• Много ошибок

• Запутанная и сложная кодовая база

• Медленный, потребляет много памяти, GC45

Page 47: Парсим CSS

46

[ "stylesheet", [ "atrules", [ "atkeyword", [ "ident", "import" ] ], [ "s", " " ], [ "string", "'path/to/file.css'" ] ]]

Было: AST – массив массивов

Page 48: Парсим CSS

Парсер – последнее, что я собирался трогать, но…

он был полностью переписан (так получилось 😳)

47

Page 49: Парсим CSS

48

{ "type": "StyleSheet", "rules": [{ "type": "Atrule", "name": "import", "expression": { "type": "AtruleExpression", "sequence": [ ... ] }, "block": null }]}

Стало: AST – дерево объектов

Page 50: Парсим CSS

Парсер выделен в отдельный проект

github.com/csstree/csstree

49

Page 51: Парсим CSS

Скорость

Page 52: Парсим CSS

CSSO – история ускорения (в том числе про парсер)

51

tinyurl.com/csso-speedup

Page 53: Парсим CSS

После выступления разогнал парсер еще :)

52

* Вдохновленный общением с Вячеславом @mraleph Егоровым

Page 54: Парсим CSS

Хотите чтобы ваш JavaScript работал так же быстро как Си, сделайте его похожим на Си

53

Page 55: Парсим CSS

Немного из того, что сделано• Максимум string -> number • Массив объектов -> несколько TypedArray • Отказ от RegExp, toLowerCase(), … • array -> bi-directional lists • immutable objects • …

54

Page 56: Парсим CSS

55

CSSTree: 13 msMensch: 31 msCSSOM: 36 msPostCSS: 38 msRework: 81 msPostCSS Full: 100 msGonzales: 175 msStylecow: 176 msGonzales PE: 214 msParserLib: 414 ms

bootstrap.css v3.3.7 (146Kb)

github.com/postcss/benchmark

Не детальное AST

Детальное AST

PostCSS Full = PostCSS

+ postcss-selector-parser + postcss-value-parser

Page 57: Парсим CSS

56

CSSTree: 130 msPostCSS: 234 msCSSOM: 250 msMensch: 381 msRework: 507 msPostCSS Full: 571 msStylecow: 1156 msGonzales: 1202 msGonzales PE: 1646 msParserLib: 5509 ms

actiagent.ru (983Kb)

github.com/postcss/benchmark

Page 58: Парсим CSS

Разбор AST

Page 59: Парсим CSS

58

csstree.walk(function (node) { var property = this.declaration && this.declaration.property.name; if (opts.propWhiteList.indexOf(property) === -1) return;

if (node.type === 'Dimension' && node.unit === 'px') { var pixels = parseFloat(node.value); if (pixels >= minPixelValue) { node.value = toFixed((pixels / rootValue), unitPrecision); node.unit = 'rem'; } }});

Почему важно детальное ASTКак мог бы выглядеть postcss-pxtorem

Page 60: Парсим CSS

59

background: top 100% url(..) no-repeat #008800;

Identifier Url Identifier HexPercentage

При обычном разборе мы знаем

Page 61: Парсим CSS

60

background: top 100% url(..) no-repeat #008800;

background-position

background-image

background-repeat

background-color

Хотелось бы понимать больше

Page 62: Парсим CSS

Как Вы понимаете за что отвечает та или иная часть

значения?

61

Page 63: Парсим CSS

А браузер?

62

Page 64: Парсим CSS

В спецификации

63

Page 65: Парсим CSS

В спецификации

63

Page 66: Парсим CSS

Грамматика описана в

CSS Values and Units Module

похоже на RegExp, только проще

64

drafts.csswg.org/css-values/#value-defs

Page 67: Парсим CSS

Основная идея: Выдергивать из спецификаций синтаксис значений, разбирать

его и мапить на AST

65

Page 68: Парсим CSS

Выдергивать не пришлось

Mozilla Template:CSSData

66

developer.mozilla.org/en-US/docs/Template:cssdata

Page 69: Парсим CSS

Год шел к имплементации…

Запилил за несколько дней :)

67

Page 70: Парсим CSS

Еще несколько дней ушло на исправление ошибок в синтаксисах,

актуализации и дополнении

68

github.com/csstree/csstree/blob/master/data/patch.json

Получившийся патч (181 синтаксис)

Page 71: Парсим CSS

К сожалению, пока результат мапинга не тот, что ожидался…

69

Page 72: Парсим CSS

Зато получилось немало интересного

😋

70

Page 73: Парсим CSS

71

csstree.github.io/docs/syntax.html

Документация синтаксиса

Page 74: Парсим CSS

72

csstree.github.io/docs/validator.html

Валидатор синтаксиса CSS значений

Page 75: Парсим CSS

73

var csstree = require('css-tree');var syntax = csstree.syntax.defaultSyntax;var ast = csstree.parse('… your css …');

csstree.walkDeclarations(ast, function(node) { if (!syntax.match(node.property.name, node.value)) { console.log(syntax.lastMatchError); }});

Свой валидатор в 8 строк

Page 76: Парсим CSS

74

var csstree = require('css-tree');var mySyntax = csstree.syntax.сreate({ generic: true, // чтобы добавились примитивы вроде <ident>, <number> etc types: { name: '<ident>', role: 'organizer | sponsor | speaker | attendee | volunteer', person: '<name> <role>' }, properties: { FrontTalks: '<person>#' }});

mySyntax.match('fronttalks', 'Oleg organizer, Roman speaker'); // okmySyntax.match('fronttalks', 'Roman'); // Mismatch

Cобственный синтаксис

Page 77: Парсим CSS

Аналог: css-values

75

github.com/ben-eb/css-values

Page 78: Парсим CSS

css-values vs. csstree.syntax

76

с марта 2016

postcss, ES6 и т.д.

70% (254 из 361) свойств Mozilla Template:CSSData

с 4 сентября 2016 :)

csstree, ES5

100% свойств Mozilla Template:CSSData

+ 80 свойств + исправления

+ легаси + …

Page 79: Парсим CSS

Кое что еще

• csstree-validator – npm пакет + консольная команда

• stylelint-csstree-validator – плагин для stylelint

• gulp-csstree – плагин для gulp

• SublimeLinter-contrib-csstree – плагин для Sublime Text

• vscode-csstree – плагин для VS Code

More is coming…77

(запилили вчера, не шутка ;)

Page 80: Парсим CSS

Заключение

Page 81: Парсим CSS

Подробное и правильное AST – проще код инструментов, работающие в большинстве

случаев

79

Page 82: Парсим CSS

Планы: парсер• Исправить/улучшить парсинг/AST

• Возможность расширять/патчить основной синтаксис

• Возможность задавать уровень детализации

• Использовать заданный синтаксис значений при разборе (опционально)

• Оптимизировать/ускорить еще

• …80

Page 83: Парсим CSS

Планы: синтаксис• Предоставить удобный формат сопоставления

• Оптимизировать/ускорить

• Транспиляция синтаксиса в код

• Интеграции, плагины…

• …

81

Page 84: Парсим CSS

Уже есть положительный эффект• Найдены ошибки в Mozilla Template:CSSData

https://bugzilla.mozilla.org/show_bug.cgi?id=1300329

• Найдена ошибка в спекеhttps://github.com/w3c/csswg-drafts/issues/458

• Появляются вопросы к спекамhttps://github.com/w3c/csswg-drafts/issues/461

• Появляются инструменты для валидации синтаксисов, в том числе будущих

82

Page 85: Парсим CSS

github.com/csstree/csstree

83

Нужен ваш фидбек

Page 86: Парсим CSS

Роман Дворнов @rdvornov

github.com/lahmatiy [email protected]

Вопросы?

github.com/csstree/csstree