Sprute.js. Ещё один изоморфный JavaScript фреймворк

в 11:32, , рубрики: framework, javascript, node.js, sprute.js

image
Картинка для привлечения внимания

Sprute.js — новый изоморфный JS фреймворк. При его проектировании и реализации упор делался в первую очередь на удобство разработки и сохранение самого фреймворка максимально простым и компактным. В первую очередь это касается изоморфности.

Зачем еще один фреймворк?

В существующих фреймворках меня не устраивает подход к реализации изоморфности — моей целью было реализовать изоморфность таким образом, чтобы это не определяло архитектуру и не приходилось строить архитектуру вокруг изоморфности, а сделать её максимально прозрачной — чтобы я мог писать серверный код так, как я это привык, и он так же работал на клиенте. Мой подход можно назвать server side first.

Прошу сильно не пинать за скудность текста — развернутое изложение своих мыслей всегда было моим слабым местом.

Подход к изоморфности

Для реализации изоморфности я решил «эмулировать» node.js в браузере — изоморфный код пишется на сервере и работает так же на клиенте. Для этого пришлось портировать node.js'ный require в браузер и эмулировать файловую систему. Так же я частично портировал node.js'ные модули process, fs, events.

Архитектура

Весь код разделяется на 3 категории — серверный, клиентский, изоморфный и разделен по директориям back, front, common. В директориях back и front находятся базовые классы, специфичные для соответствующего окружения, 90% кода находится в директории common. Функционал фреймворка, такой как сервер, шаблонизатор, сокетное соединение реализован в виде компонентов — по сути модулей. Это позволяет инкапсулировать код c определенной зоной ответственности; так же позволяет писать изоморфные обертки для таких вещей, как сокетное соединение, создавая единый api на клиенте и сервере и позволяя менять реализацию компонента в дальнейшем.

Пример компонента:

'use strict';

module.exports = {
    init() {
        let module;
        app.clientSide(() => {
            module = require('./lib/client')
        });
        app.serverSide(() => {
            module = require('./lib/server')
        });
        return module.init()
    }
};

Статика

Стили, скрипты, реализующие интерактивность интерфейса и т.п., собираются в темы — директория с файлами и объект конфигурации. Это позволяет отделить логику интерфейса от остальной логики; так же это позволяет для каждой страницы задавать отдельную тему — удобно при редизайне сайта или создании различных админок и т.п.

Работа с данными

Работа с данными реализована с использованием паттерна data mapper. Логика сохранения/выбора данных находится в маппере, бизнес логика — например, обладает ли пользователь оперделенной привилегией — в модели, логика работы с набором — в коллекции. Изоморфность работы маппера реализуется следующим образом — запрос сериализуется в объект и передается на сервер, там объект запроса передается тому же мапперу; результат возвращается на клиент. На данный момент реализован маппер, использующий библиотеку knex для построения запросов и выборки данных.

Пример маппера

const BaseMapper = require(app.get('commonPath')+'/mappers/knex-mapper');

class CoolModel {}

class CoolMapper extends BaseMapper {
    constructor() {
        let connections;
        app.serverSide(() => {
            connections = require(process.cwd()+'/configuration/connections')
        });
        app.clientSide(() => {
            connections = {};
        });
        super({
            client: 'mysql',
            connection: connections.mysql
        })
    }
    get tableName() {
        return 'cool'
    }

    get model() {
        return CoolModel
    }

    beforeCreateTable(table) {
        table.comment('very cool table')
    }

    addColumns(table) {
        table.increments('id').primary();
        table.string('field1');
        table.integer('field2');
        table.string('field3')
    }

    get validator() {
        if(!this._validator) {
            let vE = app.get('validationEngine');
            this._validator = new vE({
                id: 'integer',
                field1: 'not_empty',
                field2: 'integer',
                field3: 'not_empty'
            })
        }
        return this._validator
    }

    validateModel(model) {
        return this.validator.validate(model)
    }
}

const mapper = new CoolMapper();
mapper.find().limit(10).offset(5).then(collection => { /* code here */ });
mapper.findOne().where({id:2}).then(model => { /* code here */ })

Как это работает

Точкой входа является класс App. При инициализации класса производится инициализация компонентов — работа фреймворка здесь завершена. Дальше работу на себя берет компонент сервер, затем регистрируются роутеры. Дальнейшая схема работы состоит в обработке запросов роутерами.

Пример роутера:

'use strict';

const BaseRouter = require(app.get('classPath')+'/routers/base'),
    process = require('process'),
    theme = require(process.cwd()+'/configuration/theme-light');

module.exports = class extends BaseRouter {
    constructor(params, DomDocument) {
        super(params);
        this.DomDocument = DomDocument || require(app.get('classPath')+'/classes/dom-document')
    }

    index(req, res) {
        const view = new (require('../views/main-page'))(theme),
            DomDocument = new this.DomDocument(theme);
        view.render().then(html => {
            DomDocument.setBlock('main', html);
            this.loadPage(DomDocument, res)
        })
    }
};

Состояние на данный момент

В данный момент на нем работает один сайт — bel31stroy.ru и еще один находится в разработке. Сам фреймворк периодически подвергается доработкам. Pull реквесты и баг репорты приветствуются.

Github: github.com/one-more/sprute

Автор: one_more

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js