Basis.js – «под капотом»

1,677 views

Published on

Технический обзор фреймворка basis.js, из чего он состоит, как работают некоторые его части.

Published in: Technology
0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,677
On SlideShare
0
From Embeds
0
Number of Embeds
49
Actions
Shares
0
Downloads
6
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

Basis.js – «под капотом»

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

×