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
Поделиться с друзьями
-->

Комментарии (38)


  1. claygod
    03.08.2016 14:38

    В вашем фреймворке реализована реактивность? Если да, то как?


    1. one_more
      03.08.2016 15:56

      Что вы имеете в виду под реактивностью?


      1. Riim
        03.08.2016 16:23
        +1

        Привет, claygod недавно интересовался изоморфным фреймворком RiftJS, который использует cellx для реактивности. Если захотите встроить cellx в свой фреймворк — спрашивайте, попробую помочь, чем смогу)) Ну и сам RiftJS стоит поковырять, там много интересных идей, я его к сожалению забросил в пользу чисто клиентского фреймворка.


        1. claygod
          03.08.2016 16:35

          Riim, именно это я и имел в виду. Однако возможно, что one_more эту парадигму программирования не применял.


          1. one_more
            03.08.2016 17:25

            Архитектура фреймворка позволяет легко добавить такой функционал, если есть необходимость, используя любую реализацию (cellx например). Нет необходимости встраивать это по умолчанию на мой взгляд (это может быть не нужно в некоторых проектах). Я сосредоточился в основном на гибкости и возможности расширения и стараюсь сохранять фреймворк максимально компактным.


      1. Alex10
        03.08.2016 16:50

        Реактивность еще очень часто упоминают в контексте meteor.js


  1. Rencom
    03.08.2016 14:46
    -5

    оперделенной привилегией
    За что Вы ее так? )


    1. one_more
      03.08.2016 15:55

      исправил)


  1. webmasterx
    03.08.2016 14:46

    как я понял это js фреймворк исключительно для генерации страниц на сервере? А что на клиенте делать?


    1. one_more
      03.08.2016 16:00

      На клиенте страницы рендерятся так же, как и на сервере. Шаблонизатор является изоморфным.


  1. sashabeep
    03.08.2016 14:58
    +1

    На демо-сайте навигация через хэшбенг и не работает


  1. Sirion
    03.08.2016 16:02

    Кажется, сайт накрыл хабраэффект. У меня ничего не работает.


  1. claygod
    03.08.2016 16:19

    Посмотрел сайт (строительный). Походил по страницам. Решил проверить, как он хранит историю. Щёлкаю подряд пункты меню сверху вниз, потом нажимаю «Назад, назад, назад… » и сайт меня кидает по страницам в каком-то странном порядке…


    1. ArtFutureDev
      01.12.2016 00:07
      +1

      Шикарная история, было интересно почитать. Большое спасибо, жду 2 часи ;)


      1. claygod
        04.08.2016 10:23
        +1

        @ one_more, вы же не будете каждому посетителю объяснять про якоря. Он будет щёлкать по меню и ожидать при щелчке на кнопку «Назад» или нажатии «Backspace», что попадёт в предыдущее состояние. Это простое юзабилити, и ничего больше.


  1. napa3um
    03.08.2016 17:06
    +4

    А нужна ли изоморфность в 2016-ом? Ведь с точки зрения современных подходов построения веб-приложений (SPA/PWA) это костыль для поисковиков, ради которого приходится корректировать всю архитектуру приложения (к слову, паук от Google уже не требует такого костыля). Проще этот костыль отдать на откуп Prerender.io, чем вносить лишние ограничения в архитектуру. ИМХО.


    1. one_more
      03.08.2016 17:28

      основная цель феймворка как раз состоит в том, чтобы реализация изоморфности не накладывала никаких ограничений на архитектуру.


      1. napa3um
        03.08.2016 17:29
        +3

        Абстракции изоморфности фреймворка рано или поздно «протекут» в виде ограничений, даже если их сразу и не было заметно.


        1. one_more
          03.08.2016 17:50

          Будет такое. Но архитектура фреймворка позволяет разбивать код на изоморфный и специфичный для каждой платформы — подобные ограничения можно обходить без использования костылей.


    1. BlessMaster
      03.08.2016 18:01
      +3

      Помимо гугла есть и другие пауки, причём, не только поисковые.
      Хотя насчёт изоморфности не могу не согласиться в том, что это протекающая абстракция.
      Речь максимум может идти о частичной изоморфности — у клиента и сервера разные задачи и разные возможности.


      1. napa3um
        03.08.2016 18:07

        > Помимо гугла есть и другие пауки, причём, не только поисковые.
        И зачем о них тревожиться?


      1. Terras
        01.12.2016 12:46

        Человек ставит паузу — и ты её снять не можешь. В итоге, у тебя просто заканчивается терпение и выходишь.


    1. Tramway
      03.08.2016 18:14

      В некоторых случаях от вас могут потребовать, чтобы first paint был в таком районе, когда еще js толком то не распарсился. Этот случай довольно редкий, но под него нужны инструменты и решения для server-side рендера.


      1. napa3um
        03.08.2016 18:19

        Я уверен, что в любые времена и при любых фреймворках останется место некорректно поставленным, но добросовестно выполненным архитектурным задачам.


    1. tenbits
      03.08.2016 18:43

      У "изоморфности" двя плюса с которыми, как по мне, сложно спорить: переиспользование кода и скорость отображения контента.


      1. napa3um
        03.08.2016 18:54
        +3

        Первый плюс, как показывает практика, касается только разделяемых валидаторов моделей (хотя в большинстве приложений таки часто получается, что серверные и клиентские модели данных отличаются), а второй плюс нивелируется кешированием скриптов и ассетов SPA/PWA на клиенте. На более-менее посещаемом ресурсе скорее клиент отрисует картинку быстрее, чем это сделает сервер. Ну а статичные страницы, независимые от логина (которые нужно отдавать анонимным пользователям, посетившим ресурс впервые) просто оформляют вне SPA, в виде так называемых лэндинг-страниц.


        1. tenbits
          03.08.2016 20:05

          Ну не скажите, а утилиты, хэлперы, сервисы? А если строить систему на интерфейсах с внедрением зависимостей? А юнит-тесты? В любом случае, валидаторами дело не заканчиваеться, поэтому зря вы ими ограничиваетесь. По второму пункту: с чего вы взяли что клиент отресует картинку быстрее? Даже если скрипты закешированы их нужно вытащить-распарсить; шаблоны закешированы? и сново достать распарсить, без этого шаблонизатор не начинает создавать dom/html; также для рендеринга нужны данные, и за ними обычно идут по http. Как бы вы не противились, если трезво оценить ситуацию, это действительно два плюса. Я не утверждаю, что это прям всем нужно и без этого никак, но от этого они не перестают быть плюсами)


          1. napa3um
            03.08.2016 20:10

            > Ну не скажите, а утилиты, хэлперы, сервисы?
            Если считать, что на сервере нет рендеринга, то они оказываются ненужными.
            > Я не утверждаю, что это прям всем нужно и без этого никак, но от этого они не перестают быть плюсами)
            Если что-то кажется вам (или вашей команде разработчиков) более привычным, чем какие-то абстрактные идеалы и сферические архитектуры в вакууме, то нет особого смысла пытаться оправдать свой выбор технологий чем-то ещё.


            1. tenbits
              03.08.2016 20:11

              Если считать, что на сервере нет рендеринга, то они оказываются ненужными.

              Всё ясно, спасибо)


              1. TimsTims
                01.12.2016 00:08

                Опечатка, сорри) поправить не могу, пока карма в минусе (уже давно, с неудачного комментария)


    1. DarthVictor
      03.08.2016 21:55
      +3

      Во-первых, почему вы думаете, что prerender.io — это меньший костыль? По факту вы запускаете браузер на лету, ждете его ответа и потом отдаете. Рендеринг JS, что на клиенте, что на сервере — все-таки документированная возможность у браузера и у Ноды. Вот еще более развернуто.
      Во-вторых, индексация сайтов с клиентским рендерингом (то есть в случае, когда от сервера приходит JSON) работает только у гугла и недавно. Даже если забыть, что в РФ у него даже не половина поискового рынка, то подгрузка содержимого нужна еще фейсбуку, вконтакту, скайпу, твиттеру и еще хреновой туче сайтов.

      Ну и в третьих


      1. napa3um
        03.08.2016 22:00
        +2

        1) Потому что в минимальной степени влияет на архитектуру приложения. 2) Да, потому о prerender и вспомнили, пока остальные поисковики не подтянутся. 3) https://github.com/prerender/prerender.


        1. one_more
          06.08.2016 04:37
          +1

          Какое-то у вас костыльно-ориентированное программирование получается — костыль для браузера, костыль для сервера; вместо того, чтобы использовать нормальный инструмент, который хорошо справляется с работой в обоих случаях


  1. gyesa
    03.08.2016 22:09

    Прошу прощения за оффтоп, ну и, конечно, это ни в коем разе не претензия, но просто когда я зашёл на гитхаб-страницу проекта и увидел там всё на русском языке, почему-то возник серьёзный диссонанс! Видимо, настолько привык к тому, что всё-всё-всё англофицируется, даже если проект делается локально, при этом разработчики с английским никак не связаны. Текста на гитхабе у вас пока не так много, думаю, вы будете расширяться, поэтому, как мне кажется, лучше бы вам уже подумать про более распространённый язык в сфере IT — тогда могут к вашему проекту появиться «глаза» и из других стран :)

    PS. Спрут у вас, конечно, милашка! Только, вот, в английском такого слова нет (там это обычно octopus), но, думаю, это вы так специально :)


    1. one_more
      03.08.2016 22:15

      Насчет sprute — специально, вы правы. А насчет английского — мне пока уровень владения не позволяет — много ошибок в письменной речи. Собираюсь переписать когда буду владеть языком на должном уровне.


      1. gyesa
        03.08.2016 22:20

        Не в моём праве вас поучать или к чему-то принуждать, но, как преподаватель, я всё-таки посоветовал бы вам перевести все имеющиеся части описания проекта на английский. Боитесь большого кол-ва ошибок? А вы не бойтесь — иначе они никогда не исчезнут. Practice makes perfect — пишите, пишите, пишите, т.е. тот самый «должный уровень» — это дело практики и времени, а не удачного стечения обстоятельств! А люди вряд ли будут критично относиться к ошибкам (английским в мире владеет такое несчётное кол-во человек, что какого только уровня он ни бывает!) — напротив, могут исправить какие-то погрешности, PR им в помощь :) Так что моё мнение — дерзайте!


  1. stepanp
    04.08.2016 04:33
    +2

    https://pbs.twimg.com/media/CFp86XJVIAAYrhd.png:large
    Вставьте кто-нибудь картинку


    1. Sirion
      04.08.2016 08:19
      +3

      image