Парсим css

Post on 07-Jan-2017

579 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

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

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

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

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

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

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

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

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

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

4

Modern CSS is hard

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

5

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

6

.rule { property: value;}

7

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

👌

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

8

9

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

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

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

CSS

Уже с нами

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

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

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

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

• bad-url, bad-string…

• …

10

11

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

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

11

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

Валидный ли 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'

13

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

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

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

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

14

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

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

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

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

в url()

15

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

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

16

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

CSS Custom Properties

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

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

18

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

@supports

19

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

@supports

Декларация

20

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

@supports

Декларация

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

21

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

@supports

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

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

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

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

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

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

22

23

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

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

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

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

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

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

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

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

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

27

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

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

29

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

30

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

31

CSS парсеры

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

33

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

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

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

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

• Медленные34

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

35

postcss.org

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

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

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

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

• Быстрый36

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

37

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

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

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

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

38

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

CSSTree

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

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

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

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

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

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

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

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

43

github.com/css/csso

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

44

github.com/css/gonzales

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

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

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

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

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

46

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

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

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

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

47

48

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

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

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

github.com/csstree/csstree

49

Скорость

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

51

tinyurl.com/csso-speedup

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

52

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

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

53

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

54

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

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

Разбор AST

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

59

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

Identifier Url Identifier HexPercentage

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

60

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

background-position

background-image

background-repeat

background-color

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

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

значения?

61

А браузер?

62

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

63

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

63

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

CSS Values and Units Module

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

64

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

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

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

65

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

Mozilla Template:CSSData

66

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

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

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

67

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

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

68

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

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

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

69

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

😋

70

71

csstree.github.io/docs/syntax.html

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

72

csstree.github.io/docs/validator.html

Валидатор синтаксиса 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 строк

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обственный синтаксис

Аналог: css-values

75

github.com/ben-eb/css-values

css-values vs. csstree.syntax

76

с марта 2016

postcss, ES6 и т.д.

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

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

csstree, ES5

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

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

+ легаси + …

Кое что еще

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

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

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

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

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

More is coming…77

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

Заключение

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

случаев

79

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

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

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

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

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

• …80

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

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

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

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

• …

81

Уже есть положительный эффект• Найдены ошибки в 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

github.com/csstree/csstree

83

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

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

github.com/lahmatiy rdvornov@gmail.com

Вопросы?

github.com/csstree/csstree

top related