Приходько Артём

ЗДРАВСТВУЙТЕ

О чём доклад?

Хотим большое приложение

Как его создать ?

Что такое

приложение?

ЧАСТЬ 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. Улучшайте

СПАСИБО

Made with Slides.com