Основные моменты использования Feast

Вопросы

Вступление

			// Пример создания блока
			const btn = new UIButton({
				name: "make",
				icon: "wow",
				value: "Сделать",
			});
			#!+ // Рендер
			#!- btn.renderTo(document.body);
			#!+ // Меняем аттрибуты
			#!- btn.set({value: "Сделать хорошо!"});
		

button/button.html

			<button type="{attrs.type}">
				<!-- Важно: корневой класс добавляется автоматически -->
				#! <bem:mod name="with_icon" test="attrs.icon"/>
				#!+ <span
					bem:elem="icon"
					**fn:if="attrs.icon"**
				>
					#!+ <!-- Использование блока `icon` -->
					#!- <b:icon name="{attrs.icon}"/>
				#!- </span>
				#!+ <span bem:elem="value">
					**{attrs.value}** <!-- аналог <fn:value>attrs.value </fn:value> -->
				#!- </span>
			</button>
		

button/button.css

			.button {
				// Кнопка с иконкой
				&_with_icon { /*... */ }

				// Иконка
				&__icon { /*... */ }

				// Текст/Значение
				&__value { /*... */ }
			}
		
			import feast from "feast";
			#!+ // Связанне ресурсы
			import template from "feast-tpl!./button.html";
			import styleSheet from "feast-css!./button.css";
			#!-
			#!+ // Используемые блоки
			import UIIcon from "blocks/icon/icon";
			#!-
			#!+ /** @class UIButton */
			export default feast.Block.extend(/** @lends UIButton# */{
				#! name: "button", // Название блока, оно же корневой css-класс
				#! template, // Шаблон
				#! styleSheet, // Стили
				#!+ blocks: { // Используемые блоки
					icon: UIIcon, // "название" => UI-Блок
				#!- },
				#!+ defaults: {
					icon: null,
					type: "submit",
					value: "Submit",
				#!- }
			#!- });
		

Вопросы?

Датабиндинг

Датабиндинг

Можно сказать его нет, данные должны спускаться сверхувниз:
			app:render()
			    ⬇
			  event
			    ⬇
			app:render()
		

Датабиндинг

Датабиндинг: Модели и Блоки

На данный момент в feast зарезервировано два свойства, видя которые он пытается подписаться на фиксированный список событий:
			<!-- Блок «Список писем» -->
			<div>
				<!-- Используем абстрактный UIDataset -->
				<b:dataset
					models="{attrs.models}"
					compact="{attrs.compact}"
				>
					<!-- Определяем итерируемую часть -->
					<fn:match name="dataset-item" args="model">
						<b:letter-list-item
							key="{model.id}"
							model="{model}"
						/>
					</fn:match>
				</b:dataset>
			</div>
		

Датабиндинг: Модели и Блоки

			#!+ const datasetLetter = new UIDatasetLetters({
				models: mailbox.getLetters(),
			});
			#!- datasetLetter.renderTo(document.body);
			#!+ // Где-то в коде добавляются модели
			#!- letters.set([/* список моделей */]);
			#!+ // Ещё где-то меняется статус письма
			#!- letters[0].set("flags.unread", false);
		

Вопросы?

Адаптивность

Адаптивность

Адаптивность: «Ручная»

			// У элементов есть набор аттрибутов,
			// которые отвчеают за его внещний вид и размеры
			<b:button
				ico="delete"
				size="m"
				short
				borderless
			/>
		

Адаптивность: «Ручная»

			<b:layout use:mediator="layout-manager">
				#!+ <fn:match name="left-column" args="layout">
					#! <b:button short="{layout.leftColumn <= 4}"/>
					#!+ <fn:choose>
						<fn:when test="layout.is('xxs')">
							<!-- ... -->
						</fn:when>

						#!+ <fn:when test="layout.isLessThen('xl') || layout.isLessThen('m')">
							<!-- ... -->
						#!- </fn:when>
					#!- </fn:choose>
				</fn:match>

				<!-- И так далее -->
				#!- <fn:match name="main-frame" args="layout"/>
			</b:layout>
		

Медиаторы

			import feast from "feast";
			export default feast.Mediator.create("layout-manager", {
				components: {
					#!+ "layout": { // название для обращения внутри медиатора
						#! "class": UILayout,
						#!+ "attrs": { // Список аттрибутов, которые будет изменять медиатор
							#!+ "size": (mediator) => mediator.size,
							"leftColumn": (mediator) => mediator.columnLeft,
							"rightColumn": (mediator) => mediator.columnRight,
							#!- "support3pane": (mediator) => mediator.support3pane
						#!- },
					#!- },
					#!+ "portal-menu": {
						"class": UIPortalMenu,
						"attrs": { /* ... */ },
						#!+ // события, на которые подписывается медиатор
						#!- "events": ["layout:2pane", "layout:3pane"],
					#!- },
					#!+ "headline": {
						"class": UIHeadline,
						"attrs": { /* ... */ },
					#!- },
				},
				handleEvent(evt) { /* ... */ }
			});
		
			import feast from "feast";
			export default feast.Mediator.create("layout-manager", {
				components: { /* ... */ },

				// Обработчик событий от компонентов по умолчанию
				// (можно указать свой для каждого компонента)
				handleEvent(evt) {
					const type = evt.type;
					switch (type) {
						case "layout:2pane":
						case "layout:3pane":
							// Обновляем свойство медиатора
							this.type = type.split(":")[1];
							break;
					}
					// После этого все связанные блоки будут обновлены!
				}
			});
		

Вопросы?

Feast + Toolkit

Feast + Toolkit

Для этого мы создали пакет feast-mailru-toolkit, который добавляет поддержку namespace <toolkit:{name} /> в feast.

Feast + Toolkit

			<!-- Feast Style -->
			<toolkit:btn
				text="Click me!"
				remit:click="tap"
			/>
			#!+ <!-- Toolkit Style -->
			<toolkit:btn><![CDATA[{
				"text": "Click me!",
				"remit:click": "tap"
			#!- }]]></toolkit:btn>
		

Вопросы?

Роутер

Pilot 2

			// Создаем роутер на основе карты сайта (всех маршрутов)
			const app = Pilot.create(sitemap);
			#!+ // Функция для получения данные для приложения
			const getViewAttrs = () => ({
				folder: app.model.folder,
				folders: app.model.folders,
				letters: app.model.letters,
				letter: app.model.letter,
				router: app,
				route: app.route,
				request: app.request,
			#!- });
			#!+ // Создаём view-приложения
			#!- app.view = new UIApplication(getViewAttrs());
			#!+ // Обновляем приложение на каждый routeend
			#!- app.on("routeend", () => app.view.set(getViewAttrs()));
			#!+ // Рендер приложения c удалением содержимого
			#!- app.view.renderTo(document.body, {replace: true});
		

Пример sitemap

			export default {
				model: { // модели доступные всем маршрутам
					folders: () => Folder.find(),
				},

				"#index": {
					url: { // описание маски и правил валидации
						pattern: "/:folder",
					},
					model: {
						threads: (req) => Thread.find({folder: req.params.folder})
					}
				},

				"#other-page-id": { ... }
			};
		

Производительность

Производительность

В прошлом мирко-докладе «React, где скорость?», я уже проводил сравнение с React, цифры которого примерно такие-же, а то и медленней, чем у нашего даталиста в старой Почте.

Так что не буду приводить новых цифр, а просто попытаюсь объяснить, за счет чего мы выжимаем скорость.

Производительность

Как я уже говорил, за render vdom отвечает citojs, это один самых быстрых движков, но и у него есть предел.

Всё дело в самой сути vdom, это:

Создание фрагмента

Основная проблема в fest/toolkit была в том, что там нет встроеных механизмов по работе аттрибутами и BEM.
			#!+ // Тут же мы имеем такие штуки как
			<fn:attr name="..." value="..." test="..."/>
			#!- <fn:add-class name="..." test="..."/>
			#!+ // А главное
			<bem:mod name="..." test="..."/>
			<bem:mod name="..." value="..." test="..."/>
			#!- // и другие...
		

Рекурсивное обновление

Основная проблема в том, что именно обновлений не так много и основное время уходит на рекурсию.

Поэтому основная задача с водится к «Как минимизировать количество операций сравнений?»

Рекурсивное обновление

Для этого в feast есть несколько механизмов

Инструменты

«Пипетка»

DevTools/Timeline

Click

Render

Octavius/Timeline

The End