Isomorphic JavaScript Applications with Catberry.js



    UPD:
    Time passed ... The framework has evolved and a lot of things from this article are already outdated.
    But no matter what, fresh material can be found here on these slides , and there is still a video for them .

    Catberry.js is a framework for developing isomorphic JavaScript applications on node.js using a modular architecture and fast rendering mechanisms. This framework allows you to write the application module once and use it both on the server for rendering pages for search robots, and in the browser for a single-page application, requesting only data for templates.

    For the first time I personally saw the term “isomorphic applications” on the Airbnb company’s blog , the translation of this article can be read on Habr, although this term began to sound a little earlier, for example, on the nodejitsu blog . Therefore, it’s a little difficult to say who invented this, but the fact is that in our time there is a whole class of web applications that are called isomorphic. On Habré, this term was mainly mentioned in articles on frameworks from gritzko and zag2art .

    What are isomorphic applications?


    Seeing this strange name, many developers are scared and try to avoid getting to know him. And in vain, I believe that it is isomorphic applications that are the future (and maybe already the present) of web applications.

    No matter how scary it may sound, in fact everything is quite simple - these are applications that can reuse the server code in the browser and behave the same on both the server and the browser. In other words, you write the code of your application once and get a server back-end that renders pages for search robots and a responsive one-page application in the browser. It, in turn, may not load any more HTML bytes from the server at all, but request only data for rendering in the browser. Almost a dream, isn't it?

    The emergence of a class of isomorphic applications is justified by the wishes of the developers, for example:
    • have a modern single-page application that works without reloading the page;
    • do not sacrifice SEO at the same time, so that for search robots it would be a regular site that is loaded from the server;
    • Do not repeat the logic of rendering pages twice for the server and browser, for this one code should be used;
    • in the case of JavaScript applications, this should be a single language environment for the developer - no need to waste time switching contexts;
    • HTML rendering on the server should occur only at the first load, and then the browser is engaged in rendering, thereby we will offload the server;
    • All this should be just for the developer.

    Personally, I want all this as much as I do web development, and, obviously, I'm not alone, because in the world of rainbow, unicorns and isomorphism, there are already many frameworks for developing isomorphic applications:

    There is also an approach such as MEAN (MongoDB + Express + Angular + node.js) that makes Angular applications isomorphic.

    Why not just take one of them?


    When I wanted to start another web project, I began to study all the existing solutions at that time and saw a number of shortcomings:
    • some solutions used front-end frameworks for rendering on the back-end. This meant virtualizing and building the DOM directly on the server, and then rendering it in HTML. This approach seemed extremely inefficient both in memory and in complexity, which would definitely lead to poor performance. For example, Rendr uses Backbone, Mojito - YUI, MEAN - Angular.
    • Another drawback is the dependence on a particular database. I have nothing against MongoDB and even used it several times, but sometimes we need reliability and transactionality, and sometimes lack of circuitry and speed. I believe that the developer should not be limited in choice. We are talking about Rendr, Meteor, Derby, which are tied tightly to MongoDB, and Derby still requires Redis.
    • Real-time data binding is, of course, a very useful feature for applications such as SCADA (ICS) or applications for working together on anything. However, if you need to implement a regular site, as developers on the RoR or the PHP framework do, this is unnecessary complexity.

    I would like to be able to develop isomorphic and simple sites so that they work quickly and easily. And since my searches were unsuccessful, I realized that you can develop a framework that will fill this niche, even for me.

    Сatberry.js


    This framework is called Catberry.js, and now it is already in version 3.0. It's not that it's old, but Semantic Versioning 2.0.0 is used for versioning , and the framework has undergone changes a couple of times without backward compatibility. Catberry is a fairly lightweight framework with its own ideology, which states "Storage and processing of data should not be part of the Catberry application, it should do a separate RESTful service."

    Familiarity with the framework should start with the SMP (Service-Module-Placeholder) approach, which replaces the usual MVC (Model-View-Controller), but, I promise, it will look familiar. Again, it is worth making a reservation, I am not against MVC, it is very good, but for a certain class of applications, where real-time data updating is just needed. That MVC, which is now used in the development of web applications, often turns out not to be what was originally invented as an MVC, but as a kind of interpretation. So I decided to name my interpretation differently so as not to confuse my colleagues.

    Service-Module-Placeholder


    As the name implies, the application is built on three components.

    Service

    Service is an external component, which is a RESTful service that our Catberry application constantly accesses. This service can be implemented on absolutely any platform: Erlang, Go, PHP, .NET, whatever. You are limited only by the HTTP 1.1 protocol.

    Module

    A module in the concept of a Catberry application is a set of logic that prepares data for the template engine and processes events from the user. In other words, if you need to render part of the page, Catberry finds the module responsible for this part of the page and asks it to prepare the data context for the template, taking into account the current state of the entire application (for example, parameters in the current URL).

    Placeholder

    At first glance, we can say that this is just a template. In fact, it can have HTML elements that link to other placeholders, and such links can appear dynamically during rendering, and this will insert another placeholder inside the current one. For example, in the content of the “paw” placeholder element of the “cat” module. Why is id used, you ask? And so that placeholders can be found very quickly in the DOM when rendering works in the browser. It really saves a lot of time.

    Placeholder Membership

    As mentioned, the placeholder is associated with the module, or rather, the module owns a certain set of placeholders, and these placeholders cannot belong to anyone else. This may raise the question: “But how to break the application into placeholders and modules?”. There are two fairly simple rules:
    1. If a section of a web page can be displayed in a single request to a RESTful service, then most likely it is a placeholder.
    2. If several placeholders depend on the same parameters in the URL and you need to update them at the same time when changing these parameters, then these placeholders should be grouped into one module.

    Differences from MVC

    Probably, one of the readers will definitely think: “What is the difference from MVC?”. If you try, you can say that Service == Model, Controller == Module and View = Placeholder. But this is exactly what I said earlier, the term MVC is very distorted nowadays, and when people interpret it in their own way, they call it MVC. I thought it necessary to indicate a different name, because:
    • Service is not part of the application, it is an external application that Module communicates with through HTTP requests, this cannot be called Model;
    • as a result, we do not have data storage and processing in the Catberry application, which means there is no Model at all;
    • according to the classic MVC description with an active model, it should notify all interested View of its changes, as is done, for example, in Meteor. There is nothing like this in Catberry.
    • there is also MVC with a passive model, but in this case, the Controller should monitor the model changes and update the View, there is nothing similar in Catberry either;
    • instead, updates only come from the user's actions when they change the URL or submit form data. Of course, no one bothers you to additionally update Placeholder by calling an update request in Module code, for example, by events or using long-polling. But the content of the placeholder will depend on the state of the application described in the URL. This is the point of the approach - the ability to completely restore the state of the application on the server and in the browser by URL in order to render identical pages.

    How it works


    The Catberry application works exactly as I previously described isomorphic applications:
    • first, the user makes a request to the server by URL;
    • the server renders the page at the specified URL;
    • along with the page, the browser version of the application is loaded into the browser;
    • and then everything works without reloading the page as a one-page application.

    For a better understanding of the picture

    Stream rendering on server


    As you can see from the description and the diagram, when rendering on the server, to render each placeholder, you usually need to make a request for a RESTful service. This can take a considerable amount of time for the user. To prevent the user from looking at a blank page with a spinning loader while we make requests for all placeholders, a stream-based rendering engine was developed. Its task is to deliver the page to the browser as soon as it is ready. In other words, as soon as the request to the service for rendering the page title is completed, the user will immediately see this title in his browser, without waiting for the entire page to be ready.

    I myself really really don’t like it when I open the page for a few seconds I see a white screen and I don’t even know if my request has reached the back-end, or now I will see the “504 Gateway Timeout”. Usually I close such sites after 3-4 seconds of a white screen.

    With streaming rendering, I will immediately see the response and the fact that the site works for me, tries and puffs, collecting data for rendering. Another nice point is that streaming does not buffer large amounts of data, which will save memory of our server with the application. Well, the most pleasant moment is that the browser, having received the HEAD element that comes from the server almost immediately, starts parsing JavaScript and CSS, as well as loading all the resources indicated in the page, all this will work in parallel, as in the diagram below.



    Parallel rendering in browser


    Stream rendering is, of course, good, but only when we are limited to sequential page loading over a TCP connection stream. When we already have a finished page in the browser, and the user clicks on the link, we need to rearrange part of the page to the new state of the application, here we are no longer limited by anything. We can fulfill requests to the RESTful service in parallel, and immediately update placeholders based on the results. And if inside there are links to others, then again in parallel to request data for them. This results in incredibly fast rendering of placeholders in the browser. In addition, if one of the requests receives a response for a very long time, then this will not affect the rendering of the rest of the placeholders.

    Tools for Isomorphism


    When we develop a web application, we often need to take actions that depend on the implementation in a particular environment, that is, they work differently in the browser and on the server. For this, Catberry has isomorphic implementations of such actions. Outwardly they work identically, have the same program interface, but internally they are implemented using the tools of the current environment. Here is a list of such implementations:
    • getting location;
    • receiving Referrer;
    • Getting User Agent
    • clearing URL fragment (hash);
    • receiving or setting a cookie;
    • Redirect
    • HTTP / HTTPS requests;
    • data cache that was used for the last rendering of each placeholder.

    It is this API that makes Catberry.js application isomorphic.

    How is the application on Catberry


    Service Locator and DI

    The architecture of the framework is based on the implementation of Service Locator and Dependency Injection patterns .

    For instance,
    var cat = catberry.create(config); // создаётся экземпляр приложения
    cat.locator.register('uhr', UHR); // можно регистрировать конструкторы по имени
    cat.locator.registerInstance('uhr', new UHR()); // или сразу экземпляры
    cat.locator.resolve('uhr'); // получить экземпляр
    cat.locator.resolveAll('uhr'); // получить все экземпляры под таким именем
    cat.locator.resolveInstance(SomeConstructorDependsOnUHR); // создать экземпляр с внедрением зависимостей
    //Зависимости внедряются достаточно просто:
    function ModuleConstructor ($uhr, someConfigSection) {
      // можно использовать зависимость $uhr
      // и даже секцию конфига someConfigSection
    }

    Such dependency injections do not break during minification, since it is done by the framework itself using UglifyJS2 .

    How the module works

    Each module is a directory with the index.js file that should export the module constructor (a module is a constructor with a declared prototype). Also, the module may have a placeholders directory in which the template placeholders of the module are located.

    Methods

    Each module can implement three groups of methods: render, handle and submit. The naming convention is used here - if your placeholder is called some-awesome-placeholder, then you must implement the renderSomeAwesomePlaceholder method if you want to prepare data for it. You may not implement it, nothing of this will break, and the template will render with an empty context, which is also quite acceptable. This convention applies to handle / submit methods that process events from the page.

    An example of the implementation of all three methods:
    ModuleConstructor.prototype.renderSome = function () {
      // получение данных
      return {some: data}; // или Promise
    };
    ModuleConstructor.prototype.handleSome = function (event) {
      // как-то обрабатываем событие
      // event.args
      // event.element
      // event.isEnding
      // event.isHashChanging
      // можно вернуть Promise
    };
    ModuleConstructor.prototype.submitSome = function (event) {
      // отправляем данные формы
      // event.values
      // event.element
    };
    

    Sometimes it is necessary to bind to DOM elements after the placeholder is rendered, after methods are provided for this, for example, for the renderSome method above:
    ModuleConstructor.prototype.afterRenderSome = function (dataContext) {
      // можно делать что угодно с отрендеренным плейсхолдером
    };
    

    You can add such methods also for handle and submit methods.
    An example implementation of the module can be found on Github .

    Promises

    As mentioned in the examples, wherever asynchronous calls are used, Catberry uses Promises (there was a great article recently ). Moreover, if any are already in the browser , the native implementation will be used, otherwise - a polyphil library from one of the authors of the specification. The Promise type, while being available globally, does not need to be connected, as if you were working with native promises.

    Where is used


    Now, on the basis of the framework, the Confettin project website has already been launched , where you can experience the performance and responsiveness of the Catberry-based application. In addition, the next version of Flamp is in full swing , which will see the light in the foreseeable future. What I personally look forward to.

    Where to begin


    If this rather cursory description of the framework interests you, then you can start with these two lines in the terminal:
    npm -g install catberry-cli
    catberry init example
    

    This will give you a working example code that works with the GitHub API and instructions on how to run it. This example demonstrates typical implementations of things that often have to be done in a web application. With the same CLI-utility, you can do many more interesting things. For example, create a new project or add a module to the project.

    If you don’t want to install anything on your computer or there is no such possibility, there is the same ready-made project on Runnable , but there you can exceed the limit of requests to the GitHub API.

    Detailed documentation and examples can be found on the official website .
    And of course, the repository page on GitHub and the catberryjs Twitter account, which is always the latest news about the framework.

    Also popular now: