136
Парсим CSS performance tips & tricks Роман Дворнов Avito Москва, сентябрь 2016

Парсим CSS: performance tips & tricks

Embed Size (px)

Citation preview

Page 1: Парсим CSS: performance tips & tricks

Парсим CSS performance tips & tricks

Роман Дворнов Avito

Москва, сентябрь 2016

Page 2: Парсим CSS: performance tips & tricks

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

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

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

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

Page 3: Парсим CSS: performance tips & tricks

Парсим CSS (зачем? почему? как дальше жить?)

3

tinyurl.com/csstree-intro

Начало истории (доклад)

Page 4: Парсим CSS: performance tips & tricks

CSSTree

Page 5: Парсим CSS: performance tips & tricks

CSSTree – самый быстрый и детальный парсер CSS

5

Page 6: Парсим CSS: performance tips & tricks

Как я до этого докатился?

Page 7: Парсим CSS: performance tips & tricks

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

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

7

github.com/css/csso

Page 8: Парсим CSS: performance tips & tricks

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

8

github.com/css/gonzales

Page 9: Парсим CSS: performance tips & tricks

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

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

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

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

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

Page 10: Парсим CSS: performance tips & tricks

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

10

Page 11: Парсим CSS: performance tips & tricks

Альтернатива?

Page 12: Парсим CSS: performance tips & tricks

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

12

Page 13: Парсим CSS: performance tips & tricks

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

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

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

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

• Медленные13

Page 14: Парсим CSS: performance tips & tricks

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

14

postcss.org

Page 15: Парсим CSS: performance tips & tricks

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

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

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

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

• Быстрый15

Page 16: Парсим CSS: performance tips & tricks

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

(хранятся в виде строки)

16

Page 17: Парсим CSS: performance tips & tricks

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

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

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

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

17

Page 18: Парсим CSS: performance tips & tricks

Переход на PostCSS означал написание собственных парсеров селекторов и свойств, что не сильно отличается от

написания парсера целиком

18

Page 19: Парсим CSS: performance tips & tricks

Регулярный рефакторинг приводит к тому, что парсер может быть полностью переписан

(это норма 😳)

19

Page 20: Парсим CSS: performance tips & tricks

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

github.com/csstree/csstree

20

Page 21: Парсим CSS: performance tips & tricks

Скорость

Page 22: Парсим CSS: performance tips & tricks

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

22

tinyurl.com/csso-speedup

В предыдущих сериях (доклад)

Page 23: Парсим CSS: performance tips & tricks

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

23

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

Page 24: Парсим CSS: performance tips & tricks

24

CSSTree: 24 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-selector-parser

+ postcss-value-parser

Page 25: Парсим CSS: performance tips & tricks

Epic fail как выяснилось позже, я вынес

не ту версию парсера

25

😱github.com/csstree/csstree/commit/57568c758195153e337f6154874c3bc42dd04450

Page 26: Парсим CSS: performance tips & tricks

26

CSSTree: 24 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

На FrontTalks был показан результат

до разгона

13 ms

Page 27: Парсим CSS: performance tips & tricks

Парсеры: курс молодого бойца

Page 28: Парсим CSS: performance tips & tricks

Основные шаги

• Токенизация

• Построение дерева (лексер)

28

Page 29: Парсим CSS: performance tips & tricks

Токенизация

Page 30: Парсим CSS: performance tips & tricks

30

• whitespaces – [ \n\r\t\f]+ • keyword – [a-zA-aZ…]+ • number – [0-9]+ • string – "string" или 'string' • comment – /* comment */ • punctuation – [;,.#\{\}\[\]\(\)…]

Разбиение текста на токены

Page 31: Парсим CSS: performance tips & tricks

31

.foo { width: 10px;}

[ '.', 'foo', ' ', '{', '\n ', 'width', ':', ' ', '10', 'px', ';', '\n', '}']

Page 32: Парсим CSS: performance tips & tricks

Нужна дополнительная информация о токене: тип и локация

32

На этапе токенизации мы знаем тип и позицию,

считать их после – дорого

Page 33: Парсим CSS: performance tips & tricks

33

.foo { width: 10px;}

[ { type: 'FullStop', value: '.', offset: 0, line: 1, column: 1 }, …]

Page 34: Парсим CSS: performance tips & tricks

Сборка

Page 35: Парсим CSS: performance tips & tricks

35

function getSelector() { var selector = { type: 'Selector', sequence: [] };

// main loop

return selector;}

Сборка

Page 36: Парсим CSS: performance tips & tricks

36

for (;currentToken < tokenCount; currentToken++) { switch (tokens[currentToken]) { case TokenType.Hash: // # selector.sequence.push(getId()); break; case TokenType.FullStop: // . selector.sequence.push(getClass()); break; … }

Main loop

Page 37: Парсим CSS: performance tips & tricks

37

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

Результат

Page 38: Парсим CSS: performance tips & tricks

История ускорения #2

Page 39: Парсим CSS: performance tips & tricks

39

[ { type: 'FullStop', value: '.', offset: 0, line: 1, column: 1 }, …]

Стоимость токена: 24 + 5 * 4 + массив = min 50 bytes per token

В нашем проекте ~1Mb CSS 254 062 токена

= min 12.7 Mb

Page 40: Парсим CSS: performance tips & tricks

Прелюдия: меняем подход

Page 41: Парсим CSS: performance tips & tricks

Посчитать все токены, а потом из них собирать AST – проще,

но ведет к лишним затратам памяти и медленней

41

Page 42: Парсим CSS: performance tips & tricks

Scanner (ленивый токенайзер)

42

Page 43: Парсим CSS: performance tips & tricks

43

scanner.token // текущий токен или nullscanner.next() // переход к следующему токенуscanner.lookup(N) // заглядывание вперед, возвращает // токен на N-ой позиции от текущей

Основное API

Page 44: Парсим CSS: performance tips & tricks

44

• lookup(N) заполняет буфер токенов до позиции N, если еще не заполнен, возвращает N-1 токен из буфера

• next()делает shift из lookup буфера, если он не пустой, либо читает новый токен

Page 45: Парсим CSS: performance tips & tricks

Создается столько же токенов, но нужно меньше памяти в один

момент времени

45

Page 46: Парсим CSS: performance tips & tricks

Проблема: заставляем GC плакать работать

46

Page 47: Парсим CSS: performance tips & tricks

Уменьшаем стоимость токенов: «многоходовочка»

Page 48: Парсим CSS: performance tips & tricks

48

[ { type: 'FullStop', value: '.', offset: 0, line: 1, column: 1 }, …]

Строковые обозначения удобны при отладке, но они не выходят за рамки

сканера и можно заменить на числа

Page 49: Парсим CSS: performance tips & tricks

49

[ { type: FULLSTOP, value: '.', offset: 0, line: 1, column: 1 }, …]

…// '.'.charCodeAt(0)var FULLSTOP = 46;…

Page 50: Парсим CSS: performance tips & tricks

50

[ { type: 46, value: '.', offset: 0, line: 1, column: 1 }, …]

Page 51: Парсим CSS: performance tips & tricks

51

[ { type: 46, value: '.', offset: 0, line: 1, column: 1 }, …]

Можно не хранить подстроку – это особенно

расточительно для одиночных символов;

к тому же многие многие конструкции собираются из нескольких токенов – эффективнее брать одну

подстроку вместо конкатенации нескольких

Page 52: Парсим CSS: performance tips & tricks

52

[ { type: 46, value: '.', offset: 0, line: 1, column: 1 }, …]

[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]

Page 53: Парсим CSS: performance tips & tricks

53

[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]

Look, Ma! No strings just numbers!

Page 54: Парсим CSS: performance tips & tricks

54

Да не просто Array, а TypedArray

Массив объектов

Массивы чисел

Page 55: Парсим CSS: performance tips & tricks

Array vs. TypedArray• Не могут содержать дырок

• В теории быстрее (т.к. меньше проверок)

• Хранятся вне heap (если достаточно большие)

• Предзаполнены нулями

55

Page 56: Парсим CSS: performance tips & tricks

56

[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]

Uint8Array Uint32Array Uint32Array Uint32Array Uint32Array

1 4 4 4 4

17 per token(кол-во токенов) 254 062 x 17 = 4.3Mb

Page 57: Парсим CSS: performance tips & tricks

4.3Mb vs. 12.7Mb (min)

57

Page 58: Парсим CSS: performance tips & tricks

Хьюстон, у нас проблемы: TypedArray фиксированной длины,

а мы не знаем сколько токенов будет

58

Page 59: Парсим CSS: performance tips & tricks

59

[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]

Uint8Array Uint32Array Uint32Array Uint32Array Uint32Array

1 4 4 4 4

17 per token(кол-во символов) 983 085 x 17 = 16.7Mb

Page 60: Парсим CSS: performance tips & tricks

16.7Mb vs. 12.7Mb (min)

60

Page 61: Парсим CSS: performance tips & tricks

16.7Mb vs. 12.7Mb (min)

60

Не повод сдаваться, давайте немного

подумаем…

Page 62: Парсим CSS: performance tips & tricks

61

start = [ 0, 5, 6, 7, 9, 11, …, 35 ]

end = [ 5, 6, 7, 9, 11, 12, …, 36 ]

Page 63: Парсим CSS: performance tips & tricks

61

start = [ 0, 5, 6, 7, 9, 11, …, 35 ]

end = [ 5, 6, 7, 9, 11, 12, …, 36 ]

Page 64: Парсим CSS: performance tips & tricks

62

start = [ 0, 5, 6, 7, 9, 11, …, 35 ]

end = [ 5, 6, 7, 9, 11, 12, …, 36 ]

offset = [ 0, 5, 6, 7, 9, 11, …, 35, 36 ] start = offset[i] end = offset[i + 1]

+

=

Page 65: Парсим CSS: performance tips & tricks

63

[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]

Uint8Array Uint32Array Uint32Array Uint32Array Uint32Array

1 4 4 4 4

13 per token983 085 x 13 = 12.7Mb

Page 66: Парсим CSS: performance tips & tricks

64

a { top: 0;}

lines = [ 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3]

columns = [ 1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1]

lines & columns

Page 67: Парсим CSS: performance tips & tricks

64

a { top: 0;}

lines = [ 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3]

columns = [ 1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1]

lines & columns

Page 68: Парсим CSS: performance tips & tricks

65

line = lines[offset];

column = offset - lines.lastIndexOf(line - 1, offset);

lines & columns

Page 69: Парсим CSS: performance tips & tricks

65

line = lines[offset];

column = offset - lines.lastIndexOf(line - 1, offset);

lines & columns

Ок для коротких строк, нужно кешировать для

длинных

Page 70: Парсим CSS: performance tips & tricks

66

[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]

Uint8Array Uint32Array Uint32Array Uint32Array Uint32Array

1 4 4 4 4

9 per token983 085 x 9 = 8.8Mb

Page 71: Парсим CSS: performance tips & tricks

67

8.8Mb vs. 12.7Mb (min)

Page 72: Парсим CSS: performance tips & tricks

Меньше операций со строками

Page 73: Парсим CSS: performance tips & tricks

«Убийцы» производительности*• RegExp • Конкатенация строк • toLowerCase/toUpperCase • substr/substring • …

69

* Засоряют GC и он все портит

Page 74: Парсим CSS: performance tips & tricks

«Убийцы» производительности*• RegExp • Конкатенация строк • toLowerCase/toUpperCase • substr/substring • …

70

Без этого никак, но от остального можно избавиться

* Засоряют GC и он все портит

Page 75: Парсим CSS: performance tips & tricks

71

var start = scanner.tokenStart;

scanner.next();

scanner.next();

return source.substr(start, scanner.tokenEnd);

Нет конкатенации!

Page 76: Парсим CSS: performance tips & tricks

72

function cmpStr(source, start, end, str) { if (end - start !== str.length) { return false; }

for (var i = start; i < end; i++) { var sourceCode = source.charCodeAt(i); var strCode = str.charCodeAt(i - start);

if (sourceCode !== strCode) { return false; } }

return true;}

Сравнение строк

Page 77: Парсим CSS: performance tips & tricks

73

function cmpStr(source, start, end, str) { if (end - start !== str.length) { return false; }

for (var i = start; i < end; i++) { var sourceCode = source.charCodeAt(i); var strCode = str.charCodeAt(i - start);

if (sourceCode !== strCode) { return false; } }

return true;}

Сравнение строк

Быстрое отсечение по длине

Page 78: Парсим CSS: performance tips & tricks

74

function cmpStr(source, start, end, str) { if (end - start !== str.length) { return false; }

for (var i = start; i < end; i++) { var sourceCode = source.charCodeAt(i); var strCode = str.charCodeAt(i - start);

if (sourceCode !== strCode) { return false; } }

return true;}

Сравнение строк

Сравниваем код за кодом

Page 79: Парсим CSS: performance tips & tricks

Как сравнивать без учета регистра*?

75

* То есть без toLowerCase/toUpperCase

Page 80: Парсим CSS: performance tips & tricks

Эвристика• Сравниваем с заранее известными строками (str)

• Заранее заданные строки всегда в нижнем регистре и содержат только латинские буквы

• Читал я как то в твиттере…

76

Page 81: Парсим CSS: performance tips & tricks

Чтобы перевести из верхнего регистра в нижний, нужно выставить 6-й бит в 1

(работает только для латинских букв)

'A' = 01000001'a' = 01100001

'A'.charCodeAt(0) | 32 === 'a'.charCodeAt(0)

77

Page 82: Парсим CSS: performance tips & tricks

78

function cmpStr(source, start, end, str) { … for (var i = start; i < end; i++) { … // source[i].toLowerCase() if (sourceCode >= 65 && sourceCode <= 90) { // 'A' .. 'Z' sourceCode = sourceCode | 32; }

if (sourceCode !== strCode) { return false; } } …}

Сравнение строк без учета регистра

Page 83: Парсим CSS: performance tips & tricks

Бенефиты• Часто срабатывает быстрое отсечение

• Нет получения подстрок (не давим на GC)

• Нет получения временных строк (результат toLowerCase/toUpperCase)

• Операция сравнения не производит мусор

79

Page 84: Парсим CSS: performance tips & tricks

Результаты• RegExp • Конкатенация строк • toLowerCase/toUpperCase • substr/substring

80

Page 85: Парсим CSS: performance tips & tricks

Отказываемся от массивов (от слова совсем)

Page 86: Парсим CSS: performance tips & tricks

Что не так с массивами• Если растить массив, то происходит копирование памяти + нагрузка на GC

• Мы не можем заранее знать размер массива

82

Page 87: Парсим CSS: performance tips & tricks

Решение?

83

Page 88: Парсим CSS: performance tips & tricks

Двусвязные списки

84

Page 89: Парсим CSS: performance tips & tricks

85

Page 90: Парсим CSS: performance tips & tricks

85

AST node AST node AST node AST node

Page 91: Парсим CSS: performance tips & tricks

Требует немного больше памяти чем массивы, но…

86

Page 92: Парсим CSS: performance tips & tricks

Плюсы• Не вызывает копирование памяти

• Не засоряет GC при построении AST

• Мы получаем next/prev

• Дешевая вставка/удаление

• Лучше для мономорфности87

Page 93: Парсим CSS: performance tips & tricks

Всё это и многое другое позволило уменьшить потребление памяти,

нагрузку на GC и ускорить вдвое

88

Page 94: Парсим CSS: performance tips & tricks

Но это еще не конец 😋

89

Page 95: Парсим CSS: performance tips & tricks

История ускорения #3 неделя после FrontTalks

Page 96: Парсим CSS: performance tips & tricks

Общие моменты

• Упрощение структуры AST

• Меньше потребление памяти, переиспользование

• list.map().join() -> цикл + конкатенация

• и по мелочи…

91

Page 97: Парсим CSS: performance tips & tricks

И снова про стоимость токенов

Page 98: Парсим CSS: performance tips & tricks

93

[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]

Uint8Array Uint32Array Uint32Array Uint32Array Uint32Array

1 types 4 offsets 4 4 lines 4

9 per token983 085 x 9 = 8.8Mb

Page 99: Парсим CSS: performance tips & tricks

lines можно считать не всегда и лениво

94

Page 100: Парсим CSS: performance tips & tricks

95

[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]

Uint8Array Uint32Array Uint32Array Uint32Array Uint32Array

1 types 4 offsets 4 4 lines 4

5 per token983 085 x 5 = 4.9Mb

Page 101: Парсим CSS: performance tips & tricks

Действительно ли для offsets нужно 32 бита?

Эвристика: вряд ли кто-то будет парсить CSS больше 16Mb

96

Page 102: Парсим CSS: performance tips & tricks

97

offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]

type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]

Page 103: Парсим CSS: performance tips & tricks

98

offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]

type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]

offsetAndType[i] = type[i] << 24 | offset[i]

+

=

Page 104: Парсим CSS: performance tips & tricks

99

offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]

type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]

offsetAndType[i] = type[i] << 24 | offset[i]offsetAndType = [ 16777216, 788529157, … ]

+

=

Page 105: Парсим CSS: performance tips & tricks

100

offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]

type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]

offsetAndType[i] = type[i] << 24 | offset[i]offsetAndType = [ 16777216, 788529157, … ]start = offsetAndType[i] & 0xFFFFFF;type = offsetAndType[i] >> 24;

+

=

Page 106: Парсим CSS: performance tips & tricks

101

[ { type: 46, start: 0, end: 1, line: 1, column: 1 }, …]

Uint8Array Uint32Array Uint32Array Uint32Array Uint32Array

1 types 4 offsets 4 4 lines 4

4 per token983 085 x 4 = 3.9Mb

Page 107: Парсим CSS: performance tips & tricks

3.9-7.8 Mb vs. 12.7 Mb (min)

102

Page 108: Парсим CSS: performance tips & tricks

103

class Scanner { ... next() { var next = this.currentToken + 1;

this.currentToken = next; this.tokenStart = this.tokenEnd; this.tokenEnd = this.offsetAndType[next + 1] & 0xFFFFFF; this.tokenType = this.offsetAndType[next] >> 24; }}

Нужно всего 2 чтения для 3 значений, т.к. конец становится началом

Page 109: Парсим CSS: performance tips & tricks

104

class Scanner { ... next() { var next = this.currentToken + 1;

this.currentToken = next; this.tokenStart = this.tokenEnd; this.tokenEnd = this.offsetAndType[next + 1] & 0xFFFFFF; this.tokenType = this.offsetAndType[next] >> 24; }}

Два чтения из массива – как то не круто…

Page 110: Парсим CSS: performance tips & tricks

105

offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]

type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]

offsetAndType[i] = type[i] << 24 | offset[i]start = endend = offsetAndType[i + 1] & 0xFFFFFF;type = offsetAndType[i] >> 24;

Page 111: Парсим CSS: performance tips & tricks

105

offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]

type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]

offsetAndType[i] = type[i] << 24 | offset[i]start = endend = offsetAndType[i + 1] & 0xFFFFFF;type = offsetAndType[i] >> 24;

Page 112: Парсим CSS: performance tips & tricks

106

offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]

type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]

Первое смещение всегда ноль

Page 113: Парсим CSS: performance tips & tricks

107

offset = [ 0, 5, 6, 7, 9, 11, 11, …, 1234 ]

type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]

Сдвигаем влево

Page 114: Парсим CSS: performance tips & tricks

108

offset = [ 5, 6, 7, 9, 11, 11, …, 1234 ]

type = [ 1, 47, 47, 4, 4, 47, 5, …, 3 ]

offsetAndType[i] = type[i] << 24 | offset[i + 1]offsetAndType[i] = type[i] << 24 | offset[i]start = endend = offsetAndType[i] & 0xFFFFFF;type = offsetAndType[i] >> 24;

Page 115: Парсим CSS: performance tips & tricks

109

class Scanner { ... next() { var next = this.currentToken + 1;

this.currentToken = next; this.tokenStart = this.tokenEnd; this.tokenEnd = this.offsetAndType[next] & 0xFFFFFF; this.tokenType = this.offsetAndType[next] >> 24; }}

Теперь можно в одно чтение

Page 116: Парсим CSS: performance tips & tricks

110

class Scanner { ... next() { var next = this.currentToken + 1;

this.currentToken = next; this.tokenStart = this.tokenEnd; next = this.offsetAndType[next]; this.tokenEnd = next & 0xFFFFFF; this.tokenType = next >> 24; }}

-50% чтений (~250k)

👌

Page 117: Парсим CSS: performance tips & tricks

Переиспользование

Page 118: Парсим CSS: performance tips & tricks

Сканер каждый раз создавал новые массивы на каждый

разбор

112

Page 119: Парсим CSS: performance tips & tricks

Сканер каждый раз создавал новые массивы на каждый

разбор

112

Page 120: Парсим CSS: performance tips & tricks

Новая стратегия• По дефолту создается буфер в 16Kb

• Создается новый буфер, только если он мал для разбираемого CSS

• Значительный прирост скорости, особенно в сценариях разбора малых фрагментов CSS

113

Page 121: Парсим CSS: performance tips & tricks

114

CSSTree: 24 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

13 ms 7 ms

Текущий результат

Page 122: Парсим CSS: performance tips & tricks

И это еще не конец… 😋

115

Page 123: Парсим CSS: performance tips & tricks

Минутка «рекламы»

Page 124: Парсим CSS: performance tips & tricks

CSSTree – не только про скорость

117

Page 125: Парсим CSS: performance tips & tricks

Новая фича*: Разбор и матчинг синтаксиса

CSS значений

118

* Пока уникальная среди CSS парсеров

Page 126: Парсим CSS: performance tips & tricks

Пример

119

Page 127: Парсим CSS: performance tips & tricks

120

csstree.github.io/docs/syntax.html

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

Page 128: Парсим CSS: performance tips & tricks

121

csstree.github.io/docs/validator.html

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

Page 129: Парсим CSS: performance tips & tricks

122

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 130: Парсим CSS: performance tips & tricks

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

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

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

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

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

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

More is coming…123

Page 131: Парсим CSS: performance tips & tricks

Заключение

Page 132: Парсим CSS: performance tips & tricks

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

125

Page 133: Парсим CSS: performance tips & tricks

Изучайте алгоритмы, структуры данных, как работают JS-движки и GC –

у вас будет больше вариантов для оптимизаций

126

– К.О.

Page 134: Парсим CSS: performance tips & tricks

Доклады по теме• CSSO – история ускорения

tinyurl.com/csso-speedup

• Парсим CSStinyurl.com/csstree-intro

127

Page 135: Парсим CSS: performance tips & tricks

github.com/csstree/csstree

128

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

Page 136: Парсим CSS: performance tips & tricks

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

github.com/lahmatiy [email protected]

Вопросы?

github.com/csstree/csstree