📜 ⬆️ ⬇️

Sprute.js. Another isomorphic JavaScript framework

image
Picture to attract attention

Sprute.js is a new isomorphic JS framework. With its design and implementation, the emphasis was primarily on the ease of development and the preservation of the framework itself as simple and compact as possible. First of all, it concerns isomorphism.

Why another framework?


In existing frameworks, I am not satisfied with the approach to the implementation of isomorphism - my goal was to realize isomorphism in such a way that it did not define the architecture and did not have to build the architecture around isomorphism, but to make it as transparent as possible - so that I could write the server code as I used to , and he also worked on the client. My approach can be called server side first.
')
Please do not kick much for the scarcity of the text - a detailed statement of my thoughts has always been my weak point.

Approach to isomorphism


To implement isomorphism, I decided to "emulate" node.js in the browser - isomorphic code is written on the server and works the same way on the client. To do this, I had to port the node.js require to the browser and emulate the file system. I also partially ported the node.js modules process, fs, events.

Architecture


All code is divided into 3 categories - server, client, isomorphic and divided into directories back, front, common. In the back and front directories are the base classes that are specific to the respective environment, 90% of the code is in the common directory. The functionality of the framework, such as a server, a template engine, a socket connection, is implemented as components — essentially modules. This allows you to encapsulate code with a certain area of ​​responsibility; also allows you to write isomorphic wrappers for such things as a socket connection, creating a single api on the client and server and allowing you to change the implementation of the component in the future.

Sample component:

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


Statics


Styles, scripts that implement the interactivity of the interface, etc., are collected in themes - a directory with files and a configuration object. This allows you to separate the interface logic from the rest of the logic; it also allows you to set a separate topic for each page — conveniently when redesigning the site or creating various admins, etc.

Work with data


Work with data is implemented using the data mapper pattern. The logic of saving / selecting data is in the mapper, the business logic — for example, whether the user has a certain privilege — in the model, the logic of working with a set — is in the collection. The isomorphism of the mapper is implemented as follows - the request is serialized into the object and transmitted to the server, where the request object is transmitted to the same mapper; the result is returned to the client. At the moment, the mapper is implemented using the knex library for building queries and retrieving data.

Mapper example


 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 */ }) 


How it works


The entry point is the App class. When the class is initialized, the components are initialized - the framework is complete. Then the server component takes over the work, then the routers are registered. A further scheme of work consists in processing requests by routers.

Example router:

 '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) }) } }; 


Current status


Currently there is one site running on it - bel31stroy.ru and another one is in development. The framework itself is periodically updated. Pull requests and bug reports are welcome.

Github: github.com/one-more/sprute

Source: https://habr.com/ru/post/307018/


All Articles