Приходько Артём
ЗДРАВСТВУЙТЕ
О чём доклад?
Хотим большое приложение
Как его создать ?
Что такое
приложение?
ЧАСТЬ 1
Требования
- модульным
- масштабируемым
- тестируемым
- гибким
- логическим
- декомпозируемым
- без хаоса
Технологии
ЭТАП 1
НАЧАЛО
Модульность
фундаментальный аспект всех успешно работающих крупных систем.
Bjarne Stroustrup
Песочница
Ядро
Модуль
Модуль
Модуль
События
Для чего?
очередной поиск
// Search module
...
const query = $search.value;
// :((
const tableModule = core.getModule('TableModule');
tableModule.reload(query);
...
х
// Search module
...
const query = $search.value;
core.publish('searchQueryChange.searchModule', {query});
...
// Table module
...
core.subscribe('searchQueryChange.searchModule', (data) => {
filterTable(data.query);
});
...
ЭТАП 2
ГРУБЫЙ НАБРОСОК
// index.js
import {initModules} from './Modules';
document.addEventListener('DOMContentLoaded', () => {
const core = window.AppCore;
initModules(core);
// ...
core.start({
mainBox: 'body',
pageBox: '[data-page]'
});
});
// Search module
/**
* @params - {addModule} - core.addModule: Function
*/
export default function (addModule) {
addModule('SearchModule', (sandbox) => {
const box = sandbox.getBox();
const isAdmin = sandbox.workspace.isAdmin();
...
...
});
}
// sandbox
let MainPage = {
name: 'MainPage',
templateId: 'mainPage',
modules: [
{
name: 'SearchModule',
descriptor: 'MainPageSearch',
boxId: 'searchModule'
},
...
],
...
};
export {MainPage}
ЭТАП 3
ЧЕГО НЕ ХВАТАЕТ
Добавим
- пространство пользователя
- декомпозицию модулей
// some module
const workspace = sandbox.getWorkspace();
if (workspace.isAdmin()) {
// sandbox.getDescription().modules.adminModule
// -> object:{name, descriptor}
const adminModule = sandbox.getDescription().modules.adminModule;
sandbox.initModule(adminModule.name, adminModule.description);
}
ЭТАП 4
СЛОЖНОЕ УПРОЩАЕМ
ПРОСТОЕ УСЛОЖНЯЕМ
- Sandbox
// jade
...
div.module__container(data-module='MainHeader',
data-module-desc='MainHeaderDesc')
div.module__container(data-module='LeftMenu')
div.page__content
div.module__container(data-module='PlanningPage')
+ Context
// module.index.js
export class LeftMenu {
constructor(context, opts) {
this.box = context.getBox(opts.name);
this.box.innerHTML = context.components.preloader;
context.loadTemplate(opts.templateName,
renderModule.bind(this));
}
destroy() {
this.box.innerHTML = '';
}
}
+ Service
// module.index.js
export class LeftMenu {
...
onInit() {
const MenuService = this.context
.getService('MenuService');
const WorkspaceService = this.context
.getService('WorkspaceService');
const isAdmin = WorkspaceService
&& WorkspaceService.isAdmin();
MenuService && MenuService
.load(renderModule.bind(this, isAdmin));
}
}
-
Event
+
Model
// module
const RouteService = this.context.getService('RouteService');
RouteService.subscribe(this.updateState.bind(this));
...
// service
...
subscribe = (callback) => {
return model.subscribe(callback);
}
...
// model after the change
...
listeners.slice().forEach(listener => listener(currentState));
...
ЭТАП 5
РЕЗУЛЬТАТ
// start app
...
core.start({
appRouter: appRouter,
mainBox: 'body',
pageBox: '[data-page]'
});
...
// some layout.jade
...
div(data-module='LeftMenu')
div(data-module='Content')
...
// add module
import {LeftMenu, MainHeader, Content, Footer}
from './modules';
core.addModule('LeftMenu', LeftMenu);
core.addModule('MainHeader', MainHeader);
core.addModule('Content', Content);
core.addModule('Footer', Footer);
// or
core.addModules([
{name: 'LeftMenu', constr: LeftMenu},
{name: 'MainHeader', constr: MainHeader},
{name: 'Content', constr: Content},
{name: 'Footer', constr: Footer}
]);
// Footer module
export class Footer {
constructor(context, opts) {
this.contex = context;
this.opts = opts;
this.box = this.context.getBox(opts.name);
}
onInit() {
this.box.innerHTML = 'lol';
}
destroy() {
this.box.innerHTML = '';
}
}
// service
export class Footer {
constructor(serviceContext) {
this.contex = serviceContext;
model = this.context.getModel('MainModel');
}
load() {
return this.context.trasport
.getJSON('/getList').then(res => res.json());
}
// or model
load() {
this.context.transport
.getJSON('/getList').then(res => {
const resJson = res.json();
model.set(resJson);
return resJson;
});
}
}
ЭТАП 6
ПЛЮСЫ И МИНУСЫ
+
- нет СОП
- есть состояние
- легкие модули
- сервисы
-
- нет СОП
- чрезмерная инкапсуляция
- эффективно для SPA
- много сервисов
- много писа́ть
ЭТАП 7
РАЗРЕЗ
Ядро
import {initEngines} from './engines';
let context, engine;
class Core {
constructor(opts) {
const core = initEngines(opts);
context = core.context;
engine = core.engine;
}
start() {/.../}
stop() {/.../}
getNamespace(name) {/.../}
getService(name) {/.../}
getModel(name) {/.../}
addModule(name, mObj) {/.../}
addModuleDescription(name, dObj) {/.../}
addNamespace(name, nObj) {/.../}
addService(name, sObj) {/.../}
addModel(name, sObj) {/.../}
}
Контекст
Ядро
Контекст
Ядро
Модуль
// simple module
export class SimpleModule {
constructor(context, opts) {
const box = context.getBox(opts.name);
context.render(context.getTemplate(opts.name),
box);
// or
const te = context.getService('TemplateEngine');
const components = context
.getNamespace('AppComponents');
const ListService = context
.getService('ListService');
te.render(components.List(ListService.get()),
box);
}
...
}
ЭТАП 8
ПРОСТАЯ МАГИЯ
- сервисы
- объявление модулей в html
- можно прикрутить di
ANGULAR*
- сервисы долой
- model -> store
- dispatch & action
- rename core
- shadow DOM
- module === component
REACT + flux*
* концептуально
ВЫДЫХАЕМ
ЧАСТЬ 2
Как
писать
проект?
1. Библиотеки
2. Процесс
3. Тестирование
4. Стиль
5. Учитесь
6. Улучшайте
СПАСИБО