View
857
Download
0
Category
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