Upload
basisjs
View
835
Download
4
Embed Size (px)
DESCRIPTION
Технический обзор фреймворка basis.js, из чего он состоит, как работают некоторые его части.
Citation preview
Basis.js
Роман ДворновИюнь, 2014
«под капотом»
basis.js – фреймворк для разработки
одностраничный веб-приложений
2
Ядро
3
Ядро• Инициализация и настройка
• Вспомогательные функции
• Модульность
• Классы
4
Функции
5
Утилитарные функции• Работа с объектами, строками, числами, массивами и функциями
• Polyfill'ы для ES5 методов и исправления некоторых методов для старых браузеров
• Обертка над консольными методами (basis.dev)
6
Специальные функции• Функции по работе с путями (basis.path)
• Выполнение кода в следующем фрейме (basis.setImmediate/clearImmediate, basis.nextTick)
• Асинхронная работа с document (basis.doc)
• basis.getter, basis.ready
7
Модульность
8
CommonJS реализация близка к node.js
9
Пример// подключаем другие модулиvar foo = basis.require('./path/to/module.js');!
// делаем что-тоvar instance = new foo.SomeClass({ ... });!
// экспортируемmodule.exports = instance;
10
Модульность основана на ресурсах
11
Ресурс – это интерфейс к файлу
12
Ресурс• Ленивый интерфейс (объявление не приводит к загрузке содержимого)
• Это функция – ее вызов или ее метода .fetch() возвращает содержимое ресурса, которое кешируется
• Повторный вызов возвращает значение из кеша
13
Пример// объявление, файл не будет загруженvar someText = basis.resource('./path/to/file.txt');!
// файл будет загружен, его содержимое// будет закешировано и возвращеноconsole.log(someText());!
// эквивалент, будет возвращено уже закешированое значениеconsole.log(someText.fetch());
14
basis.require(..) =
basis.resource(..).fetch()
15
Расширение файла определяет тип результата
16
Разные типы
17
JSON.parse(content)
compileAndRun(content)
new CssResource(content)
content
.json
.js
.css
все остальное
Расширение Результат
Можно добавить свой типvar Compiler = basis.require('./path/to/cs.js').CoffeeScript;var processJs = basis.resource.extensions['.js'];!
basis.resource.extensions['.coffee'] = function(content, url){ return processJs(Compiler.compile(content), url); };
18
* так же делается и в node.js
Относительные пути
19
JS-модули получают• __filename* – путь к файлу
• __dirname* – путь к папке файла
• resource = basis.resource(__dirname + fn)
• require = basis.require(__dirname + fn)
20
* как и в node.js
Разрешение путей
• basis.resource и basis.require разрешают пути относительно html файла
• resource и require – относительно файла модуля
21
Относительные пути упрощают реструктуризацию проекта
22
Пространства имен
23
В начале играли важную роль
24
Ресурсы уменьшили их значимость, планомерно выводим из
фреймворка
25
docs.google.com/document/d/1no1mEp3BsWa8DaXz675oKnLgapSUKcXBwYEo9LOj_DA/edit
Остаются только как сокращение относительных путей
26
Namespace → path
27
basis.ui.popup
Namespace → path
27
basis.ui.popup
Корневой неймспейс
Namespace → path
27
basis.ui.popup
Корневой неймспейсИм назначается абсолютный путь
Namespace → path
27
basis.ui.popup
Корневой неймспейсИм назначается абсолютный путь
/abs/path/to/
Namespace → path
27
basis.ui.popup
Корневой неймспейс Путь к файлуИм назначается абсолютный путь
/abs/path/to/
Namespace → path
27
basis.ui.popup
Корневой неймспейс Путь к файлуИм назначается абсолютный путь Точки заменяются на слеши и
добавляет расширение .js
/abs/path/to/
Namespace → path
27
basis.ui.popup
Корневой неймспейс Путь к файлуИм назначается абсолютный путь Точки заменяются на слеши и
добавляет расширение .js
/abs/path/to/ basis/ui/popup.js
Классы
28
Придерживаемся prototype inheritance
29
синтасический сахар лишь для создания
классов и экземпляров
30
Создание классаvar Foo = basis.Class(null, { property: 'example', init: function(value){ // конструктор this.property = value; }, method: function(){ // что-то делаем }});
31
Наследованиеvar Bar = Foo.subclass({ init: function(value){ Foo.prototype.init.call(this, value); // ... }, method: function(){ Foo.prototype.method.call(this); // ... }});
32
Наследованиеvar Bar = Foo.subclass({ init: function(value){ Foo.prototype.init.call(this, value); // ... }, method: function(){ Foo.prototype.method.call(this); // ... }});
32
Вызов переопределенных
методов
Больше сахара• Хелперы
• Авторасширение
• Расширяемые поля
33
basisjs.github.io/articles/ru-RU/basis.Class.html
Пользовательский интерфейс
34
Компонентный подход
35
36
«Component-based software engineering (CBSE) ... is a reuse-based approach
to defining, implementing and composing loosely coupled independent components
into systems...»
en.wikipedia.org/wiki/Software_component
Компонентный подход это про декомпозицию и
переиспользование
37
Обычный подход
38
list
item
item
item
item
Компонентный подход
39
list item
item
item
item
Обычный подход
40
window
form
field
field
panel button button
Компонентный подход
41
window form field
field
panel
button button
basis.ui.Node основная единица интерфейса
42
Стек функциональности
43
Функция Класс
События basis.event.Emitter
Данные basis.data.Object
DOM и патерны basis.dom.wrapper.Node
Шаблон basis.ui.Node
Наследование
Событияbasis.event.Emitter
44
Патерн Observer
45
Общение между объектами осуществляется
через события
46
Почти все классы наследники Emitter
47
Событие – специальный метод
48
Событие – специальный метод
var Foo = basis.Class(null, { emit_myEvent: basis.event.create('myEvent'), method: function(){ // выбрасываем событие this.emit_myEvent(foo, bar); }});
49
Объявляем
Используем
Работа со слушателямиvar emitter = new basis.event.Emitter();var handler = { foo: function(){ .. }, bar: function(){ .. }};!
// добавление слушателяemitter.addHandler(handler, context);// удаление слушателяemitter.removeHandler(handler, context);
50
Работа со слушателямиvar emitter = new basis.event.Emitter();var handler = { foo: function(){ .. }, bar: function(){ .. }};!
// добавление слушателяemitter.addHandler(handler, context);// удаление слушателяemitter.removeHandler(handler, context);
50
Обычно переиспользуется
Обработчики хранятся списком
51
Эффективно по памяти
52
Фреймворк 1 событие 2 события 3 события
Basis 240 240 240
Backbone 1 520 2 860 3 840
Ember 5 480 6 520 7 560
10 000 экземпляров, Кб
Эффективно по времени
53
Фреймворк 1 событие 2 события 3 события
Basis ~ 0 ~ 0 ~ 0
Backbone 20 29 38
Ember 49 68 89
10 000 экземпляров, ms
Данные basis.data.Object
54
Интерфейсный узелможет быть моделью
55
Хранить данных как ключ-значение
56
Работа c даннымиvar object = new basis.data.Object({ data: { foo: 1, bar: 2 }});!
// обновляемobject.update({ bar: 3, baz: 4 });
57
Если будут изменения, выбросит событие update с дельтой и вернет дельту.
Здесь дельта { bar: 2, baz: undefined }
Но в основном ради делегирования*
58
* об этом будет дальше
DOM basis.dom.wrapper.Node
59
Интерфейс представляет собой одно большое дерево
60
Для его организации используется модель DOM
61
Взято из DOM• свойства:
childNodes, firstChild, lastChild, nextSibling, previousSibling, parentNode
• методы:appendChild, insertBefore, removeChild, replaceChild
62
Свои дополнения• сателлиты*
• методы:setChildNodes, clear
63
* basisjs.github.io/articles/ru-RU/basis.dom.wrapper_satellite.html
DOM дает универсальность
64
Общие патерны• сортировка
• группировка (любая вложенность)
• выделение (с созданием контекста)
• disabled/enabled (с созданием контекста)
• и т.д.65
Привязка данных:автоматическая синхронизация
набор → childNodes
66
Большая часть фич в basis.js возможна именно
благодаря DOM
67
Шаблон basis.ui.Node
68
На модуле basis.ui лежит вся работа с шаблоном
69
На стороне JavaScript
70
var view = new basis.ui.Node({ template: resource('./button.tmpl'), binding: { ... }, action: { ... }});
На стороне JavaScript
70
var view = new basis.ui.Node({ template: resource('./button.tmpl'), binding: { ... }, action: { ... }});
Описание шаблона
На стороне JavaScript
70
var view = new basis.ui.Node({ template: resource('./button.tmpl'), binding: { ... }, action: { ... }});
Значения для шаблона
Описание шаблона
На стороне JavaScript
70
var view = new basis.ui.Node({ template: resource('./button.tmpl'), binding: { ... }, action: { ... }});
Значения для шаблона
Действия, которые можно вызывать из шаблона
Описание шаблона
Binding
71
var Foo = basis.ui.Node.subclass({ title: 'no title', binding: { title: function(leaf){ return leaf.title; } }, setTitle: function(newTitle){ this.title = newTitle; this.updateBind('title'); // когда надо обновить }});
binding – дополняется при наследовании
Обычно есть события
72
var Foo = basis.ui.Node.subclass({ binding: { disabled: { events: ['disable', 'enable'], getter: function(node){ return node.isDisabled(); } } }});
Action
73
var Foo = basis.ui.Node.subclass({ ... action: { select: function(event){ this.select(); }, ... }});
action – дополняется при наследовании
В шаблоне
74
<div class="foo {selected}" event-click="select"> {caption}</div>
В шаблоне
74
<div class="foo {selected}" event-click="select"> {caption}</div>
Значения из binding
В шаблоне
74
<div class="foo {selected}" event-click="select"> {caption}</div>
Значения из binding
Выполнение действия из action по событию
Разделение логики и представления
75
Объект Шаблон (DOM fragment)
binding
action
DOM операции
event listeners
DocumentJavaScript
В коде• Абстрагируемся от верстки
• Нет селекторов
• Нет указания CSS классов, имен тегов
• Нет HTML
• Нет стилей
76
В шаблонах• Не известно как и откуда берутся значения
• Нет кода
• Нет циклов
• Нет ветвлений (if)*
• Нет вычислений*
77
* вероятно появится
В большинстве случаев полное разделение
логики и представления
78
Но в любом правиле есть исключения ;)
79
Компоненты
80
В basis.js есть готовые компоненты
81
… но больше как пример и для прототипирования
82
Создавать свои компоненты достаточно просто
83
Делаем кнопку
84
Модуль с классомmodule.exports = require('basis.ui').Node.subclass({ template: resource('./button.tmpl'), binding: { caption: 'caption' }, action: { click: function(){ if (!this.isDisabled()) this.click(); } }});
85
Шаблон и стиль
<b:style src="./button.css"/><button class="my-button" event-click="click"> {caption}</button>
86
.my-button { ...}
Стиль (button.css)
Шаблон (button.tmpl)
Используемvar Button = require('./path/to/button.js');!
new Button({ caption: 'Click me!', click: function(){ alert('Hello world!'); }});
87
Данные
88
Классы basis.js
89
НаборыОбъектыСкаляры
Token Object
Entity
Dataset
Merge Автоматические наборы
Агрегатные функции
Value
Expression
Источники данныхТрансформеры
Vector
Особенности• Все данные имеют состояние
• При изменении данных создается дельта изменений
• Механизм делегирования
• Объекты взаимодействуют через изменение данных и состояния
• ...
90
Делегирование
91
Как работает
92
Данные и состояние
Данные и состояние
basis.data.Objectbasis.data.Object
Как работает
92
Данные и состояние
Данные и состояние
basis.data.Objectbasis.data.Objectdelegate
Как работает
92
Данные и состояние
basis.data.Objectbasis.data.Objectdelegate
93
С.delegate -> B D.delegate -> B B.delegate -> A
A
DC
B
Строим деревья
93
Данные и состояние
С.delegate -> B D.delegate -> B B.delegate -> A
A
DC
B
Строим деревья
На практике
94
window
panel
button button
На практике
94
window
panel
button button
new Button({ delegate: panel, handler: { stateChanged: function(){ this.setDisabled( this.state == 'processing'); } }});
На практике
94
window
panel
button button
new Button({ delegate: panel, handler: { stateChanged: function(){ this.setDisabled( this.state == 'processing'); } }});
Сработает, не важно у кого менять состояние: у кнопки, панели или окна –
данные и состояние одни
dataSource → childNodes
95
Datasetbasis.ui.Node
Object
Object
Object... ItemschildNodes
dataSource → childNodes
95
Datasetbasis.ui.NodedataSource
Object
Object
Object... ItemschildNodes
dataSource → childNodes
95
Datasetbasis.ui.NodedataSource
Object
Object
Object...
Node
Node
Node...
delegate
delegate
delegate
ItemschildNodes
Разные задачи, разные решения
96
• Произвольные поля • Строгий набор полей • Вычисляемые поля • Индекс • Нормализация значений • Defaults • Rollback • ...
basis.entity.Entitybasis.data.Objectдешево и сердито дороже, но с плюшками
97
Подробнее в докладе Не бойся, это всего лишь данные... просто их много
tinyurl.com/client-side-big-data
Наборы данных
98
Dataset (Collection)
Набор – это неупорядоченное множество объектов
99
"Автоматические" наборы• Merge – слияние множеств: объединение, разность и др
• Subtract – вычитание
• Filter – получение подмножества
• Split – разбиение на группы 1:1
• Cloud – разбиение на группы 1:М
• Slice – срез
• Extract – разворачивание
100
Наборы: пример
101
102
contacts
103
???
selected contacts
104
new basis.data.dataset.Subtract({ minuend: contacts, subtrahend: selectedContacts });
105
new basis.data.dataset.Filter({ source: new basis.data.dataset.Subtract({ minuend: contacts, subtrahend: selectedContacts }), rule: function(item){ return /ч/i.test(item.data.title); } });
Итог• Описана некоторая логическая схема связи данных и компонент
• Код работающий с contacts и selectedContacts остался прежним
• О согласованности наборов и данных заботится фреймворк
106
Шаблоны
107
DOM-based шаблонизатор
108
Известно какая будет DOM-структура и как будет
меняться
109
... еще до построения нативного DOM, на этапе
парсинга
110
111
<div class="entry"> {title}</div>
На этапе парсинга
Атрибут
Текстовый узел
Элемент
112
Описание шаблона = DOM узлы + инструкции
Знания о DOM структуре позволяют использовать
оптимизации
113
Например
• cloneNode(true) – быстрое создание DOM-фрагмента
• обработка событий через один глобальный capture-обработчик на документе для каждого уникального события
114
Шаблонизатор производит DOM-фрагменты
и обслуживает их
115
116
<div{el} class="example"> {text}</div>
{ el: <div>, text: #text, set: function(name, value){ ... }}
Описание Экземпляр
Интерфейс прилагается
Запись в DOM
117
<div{el} class="example"> <h1>{text}</h1> <ul{content}/></div>
el = fragment.firstChildtext = el.firstChild.firstChildcontent = el.lastChild
Описание Получение ссылок
Нет нужды в селекторах
(пути генерирует шаблонизатор)
Процесс
• построение DOM-фрагмента или cloneNode(true)
• создание интерфейса
• DOM-операции118
Создание Обновление
• DOM-операции
Работает быстро
119
Может проигрывать при генерации больших
фрагментов неизменяемой верстки
120
Но выигрывает на генерации повторяющихся
фрагментов
121
TodoMVC
122
100 items 1000 items
AngularJS 125 ms 1491 ms
Backbone 53 ms 510 ms
Knockout 39 ms 489 ms
vanilla 23 ms 1882 ms
jQuery 20 ms 184 ms
Backbone + basis.js templates 18 ms 202 ms
basis.js 8 ms 95 ms
2.5x быстрее
Всегда выигрывают на обновлении
123
Конструкции
125
Подключение стилей <b:style>
126
127
<b:style src="block.css"/>!
<div class="block block_{hidden}"> {caption}</div>
Подключение стилей
.block{ ...}.block_hidden{ ...}
block.cssblock.tmpl
Включение других шаблонов <b:include>
128
129
<b:style src="./example.css"/><div class="wrapper"> <b:include src="./button.tmpl"> <b:after ref="caption"> {count}</b:after> </b:include></div>
Включение шаблонов
Результат
example.tmpl
<b:style src="./button.css"/><b:style src="./example.css"/><div class="wrapper"> <button class="button"> {caption} {count} </button></div>
<b:style src="./button.css"/><button class="button"> {caption}</button>
button.tmpl
Изоляция стилей <b:isolate>
130
131
<b:style src="option.css"/>!
<div class="xo-bookings-change-status-popup-option xo-bookings-change-status-popup-option_{disabled} xo-bookings-change-status-popup-option_{hidden}"> <span class="xo-bookings-change-status-popup-option__caption xo-bookings-change-status-popup-option__caption_{selected}"> {title} </span></div>
До
132
<b:style src="option.css"/><b:isolate/>!
<div class="option option_{disabled} option_{hidden}"> <span class="caption caption_{selected}"> {title} </span></div>
После
Храним шаблоны в отдельных файлах
133
Абстрагирование +
внешние файлы =
Live update134
Live update – обновление DOM-фрагментов без перезагрузки страницы
135
136
<div class="sidebar"> ...
<ul class="list"> <li>item 1</li> <li>item 2</li> </ul> ...
</div>
<div class="list-wrapper"> <h2>Header</h2> <ul class="list"> </ul></div>
Замена DOM-фрагментаСтарый DOM Новый DOM
136
<div class="sidebar"> ...
<ul class="list"> <li>item 1</li> <li>item 2</li> </ul> ...
</div>
<div class="list-wrapper"> <h2>Header</h2> <ul class="list"> </ul></div>
insertBefore
Замена DOM-фрагментаСтарый DOM Новый DOM
136
<div class="sidebar"> ...
<ul class="list"> <li>item 1</li> <li>item 2</li> </ul> ...
</div>
<div class="list-wrapper"> <h2>Header</h2> <ul class="list"> </ul></div>
replaceChild
insertBefore
Замена DOM-фрагментаСтарый DOM Новый DOM
Live update +
Логика =
Адаптивные View
137
Live update +
Наборы шаблонов =
Темы
138
Тема = HTML + CSS
139
Без перезагрузки страницы!
140
Live update экономит время и
разгоняет разработку
141
Экземпляры шаблонов хранят мета-информацию*
142
* только в dev-режиме
Возможность определять к какому шаблону относится
DOM-узел
143
Это дает Heat map, карту показывающую
где и как часто меняется DOM
144
Анализ и выявление проблем• какие классы используются в разметке, но их нет в стилях
• какие селекторы никогда не сработают
• конфликты стилей
• и т.д.
145
Оптимальная сборка• Минимизация классов
• Удаление не используемых разметки и стилей
• и т.д.
146
Что есть еще?
147
Локализация
148
Роутер
149
Работа с сетью:ajax, jsonp, soap, upload
150
И разного всякого:crypt, dragdrop, resize,
computedStyle, UA, xml, etc.
151
Разработка
152
Инструменты153
basisjs-tools
• Консольный инструмент для разработки
• Работает под управлением node.js
• Установка:npm install -g basisjs-tools
154
В состав входят• server – dev-сервер
• extract – строит граф файлов и извлекает информацию о приложении
• build – сборка приложения
• create – кодогенерация
155
Сборка• Не требует деклараций, списков файлов, карты зависимостей и т.п.
• Рекурсивно парсит и анализирует файлы, строя граф файлов приложения
• Использует AST для анализа
• Может применять различные оптимизации
156
Достаточно выполнить
basis build
157
Google Chrome Plugin
158
(расширение для Developer Tools)
Резюме• Модульный фреймворк, решающий большую часть задач
• Большинство решений хорошо стыкуются между собой
• Полноценная экосистема (инструменты)
• Простое моделирование систем, меняющихся во времени
• Использование подходов "будущего": DOM-based шаблонизатор, анализ проекта и автоматическая его сборка, реактивное программирование
159
Ориентирован на большие долгосрочные проекты
160
Постоянное добавление нового функционала, удаление старого, переделка существующего
"Временные сложности" :)
• Только набирает известность и популярность
• Документация все еще в процессе написания
161