SPA инструменты

Preview:

Citation preview

SPA инструментыРоман Дворнов

AvitoSPA Meetup, февраль 2015

О себе• Работаю в Avito • Делаю SPA • Автор basis.js

2

Все непросто…

3

Веб сегодня – большой мир возможностей...

4

Бла-бла-бла…

5

Программирование абстрактно, нужно многое держать в голове –

легко упустить детали

6

Нам нужны помощники – инструменты, которые помогут нам больше видеть и понимать

7

Слож

ность

(кол

-во бо

ли)

Размер проекта (в попугаях)

Все делаем сами

8

Слож

ность

(кол

-во бо

ли)

Размер проекта (в попугаях)

Все делаем самиИспользуем инструменты

9

Инструменты

10

Типы инструментов

• среда разработки – редаторы, IDE + плагины

• консольные – командная строка

• браузерные – in-app и плагины

11

Среда разработки• Нам "не повезло" – веб-технологии слишком гибкие

• либо базовые вещи – мало подсказок

• либо все подряд – слишком много подсказок

• обламываемся, если CommonJS, ES6 modules…

• обламываемся, если среда не знает "фреймворк"

12

Консольные инструменты• Расцвет эпохи Task Runner'ов (node.js, io.js)

• "сборка" - конкатенация, пре-/пост-процессоры, ...

• FS watch – live reload, пересборка, ... • запуск тестов, линтеры, валидаторы • …

13

Браузерные инструменты

• браузерные "Developer Tools"

• простые утилитарные плагины

• плагины фреймворков (React, Ember, Basis.js)

• runtime приложения

14

Что делать, если существующих инструментов не хватает?

15

Создать свои!

16

Лучше день потерять, потом за 5 минут долететь…

Главное видеть проблемы и стараться решать их

18

Какого типа инструменты делать?

19

В идеале – все три

20

Среда разработки

Консольные инструменты

Браузерные решения

+

+

Сборка

21

Консольный инструмент

Обычная практика• находим файлы по маске

• прогоняем через пре-/пост-процессоры

• объединяем, минизируем

• ...

• PROFIT!22

Плюсы и минусы

+ достаточно просто

– разные типы контента обрабатываются независимо

– файлы попадают в сборку, без разбора используются или нет

– каждое изменение файла – пересборка

23

А как по другому?

24

В идеале сборщик должен понять, что мы хотели

и сделать из этого оптимальную сборку

25

Сценарий• указываем html-файл (у нас же single page!)

• он находит используемые файлы

• прогоняет через пре-/пост-процессоры и т.д. • объединяет, перестраивает, перелинковывает контент

• ...

• PROFIT!26

Плюсы и минусы

+ не нужно объяснять лишнего сборщику

+ в сборку попадается только нужное

– несколько сложнее реализация сборщика

– менее надежно (не пишите непонятного ;)

27

DEMO

28

Ключевой компонент – профиль приложения

29

Профиль приложения

• граф зависимостей – в частном случае файлы и их связи

• факты о контенте – что и где используется

30

Как собрать профиль?• для этого сборщик должен быть немного

"браузером" :)

• разобрать контент "файлов"

• найти и интерпритировать конструкции

• собрать информацию воедино31

Разбор полетов

32

контента

AST: туда и обратно

Способы работы с контентом

• Как со строкой – Regular Expressions

• Структурированное представление – AST*

33

* Abstract Syntax Tree

RegExp+ дешевое решение для простых задач

– под каждую задачу свой RegExp

– ненадежно

– трудно поддерживать и расширять

– почти невозможно делать сложные вещи34

AST+ надежно + универсальное решение + гораздо проще поддерживать + возможность выполнять задачи любой сложности – более дорогое решение для простых задач – можно потерять часть информации (формат и т.д.)

35

Наш выбор – AST

36

Пример

37

ASTCSS

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

@import 'path/to/file.css';

gonzales (csso)

Пример

38

ASTJavaScript

[ "toplevel", [ [ "var", [ [ "module", [ "call", [ "name", "require" ], [ [ "string", "module" ] ] ] ] ] ]] ]

var module = require('module');

uglifyjs 1.x

Пример

39

ASTJavaScript

{ "type": "Program", "body": [ { "type": "VariableDeclaration", "declarations": [ { "type": "VariableDeclarator", "id": { "type": "Identifier", "name": "module" }, ...

var module = require('module');

ESTree (Esprima, uglifyjs 2.x, …)

Все уже придумано до нас

• JavaScript – Esprima, acorn, uglifyjs, …

• CSS – gonzales (csso), postCSS, …

• (X)HTML, XML – htmlparser2, sax, …

40

Для многих форматов есть парсеры, производящие AST

Работаем с AST• walker – делает рекурсивный обход дерева

• анализ = walker + сбор фактов

• трансформер (transformer) = walker + модифицикация дерева

• транслятор (translator) = walker + генерация кода (строка)

41

Пример walker'а

42

function gonzalesAstWalker(ast, handlers){ function walk(token){ for (var i = 2, child; child = token[i]; i++) { var handler = handlers[child[1]]; if (typeof handler == 'function') handler(child); walk(child); } }!

walk([{}, '', ast]);};

для AST производимого gonzales

Используем

43

var ast = gonzales.parse( '.foo { background: url("foo.jpg"); }' + '.bar { background: url("bar.png"); }', 'stylesheet', true);!

gonzalesAstWalker(ast, { 'uri': function(token){ console.log(token[2][2]); }});

находим все url(…)

Вывод в консоли: "foo.jpg""bar.png"

Поиск Священного Грааля

44

связей

HTML• <script src="{url}"></script>• <script>{inlineScript}</script>• <link rel="stylesheet" href="{url}">• <style>{inlineStyle}</style>• <img src="{url}">• <element style="{inlineStyleBlock}">• ...

45

CSS

• @import {url}

• url({url})

46

CSS

• @import {url}

• url({url})

46

Вы уже знаете как это сделать ;)

JavaScript

47

Все несколько сложнее…

Нужно несколько проходов и больше телодвижений

JavaScript проход №1• определяем области видимости • для определенных значений добавляем

обработчики:

48

var fnToken = someScope.get('require');fnToken.run = function(token, args){ // интерпритация функции для сборщика};

JavaScript проход №2• делаем базовую интерпритацию

• объявления, присвоения • вызов функций • return • стараемся понять, что можем понять

49

Примерно вот так

50

jsAstWalker(ast, { 'call': function(token, scope){ var expr = token[1]; var args = token[2]; var fn = scope.resolve(expr);!

if (fn && fn[0] == 'function' && fn.run) fn.run(token, args.map(function(a){ return scope.resolve(a) || a; })); }, ...});

обработка вызовов функций

Успешно понимаем

51

var foo = basis.require;foo('./path/to/file.ext');// и даже такvar prefix = './path/to';var bar = foo;bar(prefix + '/foo.ext');

на примере basisjs-tools

сборщик правильно распознает вызов

basis.require и значения аргументов

Так просто не обманешь

52

function example(){ var basis = { require: function(){ … } }; basis.require('module');}

на примере basisjs-tools

Этот вызов будет проигнорирован, т.к. функция не оригинальная

Да, понимает не все

53

var require = basis.require;var modules = ['foo', 'bar'];modules.forEach(function(name){ require(name);});

на примере basisjs-tools

сборщик распознает вызов basis.require, но не сможет разрешить названия модулей

Можно упороться и научить сборщик понимать более

сложные патерны

54

С другой стороны это "фича", которая останавливает от

написания "странного" кода ;)

55

Профиль приложения полезен не только для сборки

56

Профиль приложения

• Профиль + трансформации = сборка

• Профиль + проверки = линтер

• Профиль + … = …

57

Главное: знаем все связи и можно работать со всеми

видами контента одновременно

58

DEMO

59

Матчинг CSS классов• Сохраняем карту – какие классы используются в HTML (шаблонах)

• Сохраняем карту – какие классы используются в селекторах

• Есть в HTML, но нет в CSS –> варнинг

• Есть в CSS, но нет в HTML –> варнинг60

Делаем свой линтер

61

Делаем свой линтер

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

• Интегрируем с редактором

62

Это очень важно

Интеграция с Sublime Text

63

from SublimeLinter.lint import NodeLinter, util!

class Basisjs(NodeLinter): syntax = ('javascript', 'javascriptnext', 'html', 'css', 'json') cmd = ('basis', 'lint', '@', '-r', 'checkstyle') regex = ( r'^\s+?<error line="(?P<line>\d+)" ' r'column="(?P<col>\d+)" ' r'severity="(?P<warning>error)" ' r'message="(?P<message>.+?)"' ) multiline = True tempfile_suffix = '-'

плагин для SublimeLinter

Вызываем консольную

утилиту

PROFIT!

64

Стыкуем все вместе

65

DEMO

66

67

Плагин

Браузер Dev-сервер Среда разработки

Web Socket (socket.io) Файловая система

DOM события

Немного деталей

• Сервер слушает файловую систему, и отправляет браузеру уведомления об изменениях

• Браузер отправляет серверу команды и слушает уведомления

68

Открытие файла в редакторе

• Браузер отправляет команду dev-серверу openFile('path/to/file.ext:10:20')

• dev-сервер выполняет консольную команду subl path/to/file.ext:10:20

69

Кое-что еще…

70

DEMO

71

Под капотом все то же: парсер, AST, walker'ы, …

72

И еще немного магии• Dev-режим

• Компонентный подход

• DOM шаблонизация

• Разделение логики и представления

• Модульность

• Sandbox

• …73

В заключении

74

Если чего-то не хватает – делайте это сами!

75

Наша эффективность зависит от инструментов, не меньше чем от фреймворков и нашего опыта

76

Главное видеть проблемы и стараться решать их

77

Спасибо!

78

Роман Дворнов @rdvornov rdvornov@gmail.com

basis.js basisjs.com

github.com/basisjs

Recommended