Isomorphe JavaScript-Anwendungen mit Catberry.js



    UPD: Die
    Zeit ist vergangen ... Das Framework hat sich weiterentwickelt und viele Dinge aus diesem Artikel sind bereits veraltet.
    Aber egal was passiert , auf diesen Folien ist frisches Material zu finden , und es gibt immer noch ein Video für sie .

    Catberry.js ist ein Framework für die Entwicklung isomorpher JavaScript-Anwendungen auf node.js unter Verwendung einer modularen Architektur und schneller Rendering-Mechanismen. Mit diesem Framework können Sie ein Anwendungsmodul einmal schreiben und es sowohl auf dem Server zum Rendern von Seiten für Suchroboter als auch in einem Browser für eine einseitige Anwendung verwenden, wobei nur Daten für Vorlagen angefordert werden.

    Zum ersten Mal habe ich den Begriff "isomorphe Anwendungen" auf dem Airbnb-Unternehmensblog gesehen . Die Übersetzung dieses Artikels kann auf Habr nachgelesen werden, obwohl dieser Begriff zum Beispiel im Blog von nodejitsu etwas früher zu klingen begann . Daher ist es ein wenig schwierig zu sagen, wer es erfunden hat. Fakt ist jedoch, dass es in unserer Zeit eine ganze Klasse von Webanwendungen gibt, die als isomorph bezeichnet werden. Auf Habré wurde dieser Begriff hauptsächlich in Artikeln über Frameworks von gritzko und zag2art erwähnt .

    Was sind isomorphe Anwendungen?


    Angesichts dieses seltsamen Namens haben viele Entwickler Angst und versuchen, ihn nicht kennenzulernen. Und vergebens glaube ich, dass es isomorphe Anwendungen sind, die die Zukunft (und möglicherweise bereits die Gegenwart) von Webanwendungen darstellen.

    Egal wie beängstigend es auch klingen mag, eigentlich ist alles ganz einfach - dies sind Anwendungen, die den Servercode im Browser wiederverwenden und sich sowohl auf dem Server als auch im Browser gleich verhalten können. Mit anderen Worten, Sie schreiben Ihren Anwendungscode einmal und erhalten ein Server-Back-End, das Seiten für Suchroboter und eine reaktionsfähige Anwendung mit nur einer Seite im Browser rendert. Es kann seinerseits nicht einmal mehr HTML-Bytes vom Server laden, sondern nur Daten zum Rendern im Browser anfordern. Fast ein Traum, nicht wahr?

    Die Entstehung einer Klasse isomorpher Anwendungen wird durch die Wünsche der Entwickler gerechtfertigt, zum Beispiel:
    • eine moderne einseitige Anwendung haben, die funktioniert, ohne die Seite neu zu laden;
    • opfern Sie nicht gleichzeitig SEO, sodass es sich bei Suchrobotern um eine reguläre Site handelt, die vom Server geladen wird.
    • Wiederholen Sie die Logik des Renderns von Seiten für den Server und den Browser nicht zweimal, da dieser Code verwendet werden sollte.
    • Im Fall von JavaScript-Anwendungen sollte dies für den Entwickler eine einsprachige Umgebung sein - Sie müssen keine Zeit damit verschwenden, Kontexte zu wechseln.
    • HTML-Rendering auf dem Server sollte nur beim ersten Laden erfolgen, und der Browser rendert dann. Dadurch wird der Server entladen.
    • All dies sollte nur für den Entwickler sein.

    Ich persönlich möchte das alles genauso wie die Webentwicklung, und natürlich bin ich nicht allein, denn in der Welt des Regenbogens, der Einhörner und des Isomorphismus gibt es bereits viele Rahmenbedingungen für die Entwicklung isomorpher Anwendungen:

    Es gibt auch einen Ansatz wie MEAN (MongoDB + Express + Angular + node.js), der Angular-Anwendungen isomorph macht.

    Warum nicht einfach einen von ihnen nehmen?


    Als ich ein anderes Webprojekt starten wollte, begann ich, alle zu dieser Zeit vorhandenen Lösungen zu studieren und sah eine Reihe von Mängeln:
    • Einige Lösungen verwendeten Front-End-Frameworks für das Rendern im Back-End. Dies bedeutete, dass das DOM direkt auf dem Server virtualisiert und erstellt und anschließend in HTML gerendert wurde. Dieser Ansatz schien sowohl im Gedächtnis als auch in der Komplexität äußerst ineffektiv zu sein, was definitiv zu einer schlechten Leistung führen würde. Rendr verwendet beispielsweise Backbone, Mojito - YUI, MEAN - Angular.
    • Ein weiterer Nachteil ist die Abhängigkeit von einer bestimmten Datenbank. Ich habe nichts gegen MongoDB und habe es sogar mehrere Male selbst benutzt, aber manchmal brauchen wir Zuverlässigkeit und Transaktionalität und manchmal mangelnde Schaltkreise und Geschwindigkeit. Ich glaube, dass der Entwickler in der Auswahl nicht eingeschränkt werden sollte. Wir sprechen von Rendr, Meteor, Derby, die eng mit MongoDB verbunden sind, und Derby benötigt immer noch Redis.
    • Die Datenbindung in Echtzeit ist natürlich eine sehr nützliche Funktion für Anwendungen wie SCADA (ICS) oder Anwendungen für die Zusammenarbeit an beliebigen Objekten. Wenn Sie jedoch eine reguläre Site implementieren müssen, wie dies Entwickler im RoR- oder PHP-Framework tun, ist dies unnötig komplex.

    Ich möchte in der Lage sein, isomorphe und einfache Sites zu entwickeln, damit sie schnell und einfach funktionieren. Und da meine Suche nicht erfolgreich war, wurde mir klar, dass Sie ein Framework entwickeln können, das diese Nische auch für mich ausfüllt.

    Сatberry.js


    Dieses Framework heißt Catberry.js und ist bereits in Version 3.0 enthalten. Es ist nicht so, dass es alt ist, aber für die Versionierung wird Semantic Versioning 2.0.0 verwendet , und das Framework wurde einige Male ohne Abwärtskompatibilität geändert. Catberry ist ein relativ leichtes Framework mit einer eigenen Ideologie, die besagt: "Speicherung und Verarbeitung von Daten sollten nicht Teil der Catberry-Anwendung sein, sondern einen separaten RESTful-Service."

    Die Vertrautheit mit dem Framework sollte mit dem SMP-Ansatz (Service-Module-Placeholder) beginnen, der den üblichen MVC (Model-View-Controller) ersetzt, aber ich verspreche, er wird vertraut aussehen. Auch hier lohnt es sich, eine Reservierung zu machen, ich bin nicht gegen MVC, es ist sehr gut, aber für eine bestimmte Klasse von Anwendungen, bei denen nur eine Aktualisierung der Echtzeitdaten erforderlich ist. Die MVC, die heute bei der Entwicklung von Webanwendungen verwendet wird, stellt sich häufig nicht als eine MVC heraus, sondern als eine Art Interpretation. Deshalb habe ich beschlossen, meine Interpretation anders zu benennen, um meine Kollegen nicht zu verwirren.

    Service-Modul-Platzhalter


    Wie der Name schon sagt, besteht die Anwendung aus drei Komponenten.

    Service

    Service ist eine externe Komponente, ein REST-Service, auf den unsere Catberry-Anwendung ständig zugreift. Dieser Service kann auf jeder Plattform implementiert werden: Erlang, Go, PHP, .NET, was auch immer. Sie sind nur durch das HTTP 1.1-Protokoll eingeschränkt.

    Modul

    Ein Modul im Konzept einer Catberry-Anwendung ist eine Reihe von Logikfunktionen, die Daten für die Template-Engine vorbereiten und Ereignisse vom Benutzer verarbeiten. Mit anderen Worten, wenn Sie einen Teil der Seite rendern müssen, findet Catberry das Modul, das für diesen Teil der Seite verantwortlich ist, und fordert es auf, den Datenkontext für die Vorlage unter Berücksichtigung des aktuellen Status der gesamten Anwendung vorzubereiten (z. B. Parameter in der aktuellen URL).

    Platzhalter

    Auf den ersten Blick können wir sagen, dass dies nur eine Vorlage ist. Tatsächlich kann es HTML-Elemente geben, die auf andere Platzhalter verweisen, und solche Links können während des Renderns dynamisch angezeigt werden. Dadurch wird ein weiterer Platzhalter in den aktuellen eingefügt. Zum Beispiel im Inhalt des Platzhalterelements "paw" des Moduls "cat". Warum wird ID verwendet, fragen Sie? Und damit Platzhalter beim Rendern im Browser sehr schnell im DOM gefunden werden können. Das spart wirklich viel Zeit.

    Platzhalter-Mitgliedschaft

    Wie bereits erwähnt, ist der Platzhalter dem Modul zugeordnet, oder vielmehr besitzt das Modul einen bestimmten Satz von Platzhaltern, und diese Platzhalter können keinem anderen gehören. Dies kann die Frage aufwerfen: "Aber wie kann die Anwendung in Platzhalter und Module unterteilt werden?". Es gibt zwei ziemlich einfache Regeln:
    1. Wenn ein Abschnitt einer Webseite in einer einzelnen Anforderung an einen RESTful-Service angezeigt werden kann, handelt es sich höchstwahrscheinlich um einen Platzhalter.
    2. Wenn mehrere Platzhalter von denselben Parametern in der URL abhängen und Sie diese beim Ändern dieser Parameter gleichzeitig aktualisieren müssen, sollten diese Platzhalter in einem Modul zusammengefasst werden.

    Unterschiede zu MVC

    Wahrscheinlich wird einer der Leser auf jeden Fall denken: „Was ist der Unterschied zu MVC?“. Wenn Sie es versuchen, können Sie sagen, dass Service == Model, Controller == Module und View = Placeholder. Aber genau das habe ich vorhin gesagt, der Begriff MVC ist heutzutage sehr verzerrt, und wenn die Leute ihn auf ihre eigene Weise interpretieren, nennen sie ihn MVC. Ich hielt es für notwendig, einen anderen Namen anzugeben, weil:
    • Der Dienst ist nicht Teil der Anwendung. Es handelt sich um eine externe Anwendung, mit der das Modul über HTTP-Anforderungen kommuniziert. Dies kann nicht als Modell bezeichnet werden.
    • Infolgedessen haben wir keine Datenspeicherung und -verarbeitung in der Catberry-Anwendung, was bedeutet, dass es überhaupt kein Modell gibt.
    • Gemäß der klassischen MVC-Beschreibung mit einem aktiven Modell sollte es alle interessierten View über seine Änderungen informieren, wie dies beispielsweise in Meteor der Fall ist. Catberry hat nichts dergleichen.
    • Es gibt auch MVC mit einem passiven Modell, aber in diesem Fall sollte der Controller die Modelländerungen überwachen und die Ansicht aktualisieren. In Catberry gibt es auch nichts Vergleichbares.
    • Stattdessen erfolgen Aktualisierungen nur aufgrund der Benutzeraktion, wenn sie die URL ändern oder Formulardaten senden. Natürlich hindert Sie niemand daran, Placeholder zusätzlich zu aktualisieren, indem Sie eine Aktualisierungsanforderung im Modulcode aufrufen, z. B. durch Ereignisse oder durch langes Abrufen. Der Inhalt des Platzhalters hängt jedoch vom Status der in der URL beschriebenen Anwendung ab. Dies ist der Punkt des Ansatzes - die Möglichkeit, den Status der Anwendung auf dem Server und im Browser über die URL vollständig wiederherzustellen, um identische Seiten zu rendern.

    Wie funktioniert das?


    Die Catberry-Anwendung funktioniert genau wie die zuvor beschriebenen isomorphen Anwendungen:
    • Zunächst fordert der Benutzer den Server über die URL an.
    • Der Server rendert die Seite unter der angegebenen URL.
    • zusammen mit der Seite wird die Browserversion der Anwendung in den Browser geladen;
    • und dann funktioniert alles, ohne die Seite als einseitige Anwendung neu zu laden.

    Zum besseren Verständnis des Bildes

    Stream-Rendering auf dem Server


    Wie Sie der Beschreibung und dem Diagramm entnehmen können, müssen Sie beim Rendern auf dem Server normalerweise eine Anforderung für einen RESTful-Service stellen, um jeden Platzhalter zu rendern. Dies kann für den Benutzer viel Zeit in Anspruch nehmen. Um zu verhindern, dass der Benutzer mit einem sich drehenden Loader auf eine leere Seite blickt, während alle Platzhalter angefordert werden, wurde eine streambasierte Rendering-Engine entwickelt. Ihre Aufgabe ist es, die Seite dem Browser zuzustellen, sobald sie fertig ist. Mit anderen Worten, sobald die Anforderung an den Dienst zum Rendern des Seitentitels abgeschlossen ist, wird der Benutzer diesen Titel sofort in seinem Browser sehen, ohne darauf zu warten, dass die gesamte Seite fertig ist.

    Es gefällt mir nicht, wenn ich die Seite für ein paar Sekunden öffne. Ich sehe einen weißen Bildschirm und weiß nicht einmal, ob meine Anfrage das Back-End erreicht hat, oder jetzt wird "504 Gateway Timeout" angezeigt. Normalerweise schließe ich solche Seiten nach 3-4 Sekunden eines weißen Bildschirms.

    Mit Streaming-Rendering sehe ich sofort die Reaktion und die Tatsache, dass die Site für mich funktioniert, versucht und blättert und sammelt Daten zum Rendern. Ein weiterer Vorteil ist, dass beim Streaming keine großen Datenmengen zwischengespeichert werden, wodurch Speicherplatz auf unserem Server mit der Anwendung gespart wird. Nun, der angenehmste Moment ist, dass der Browser, der das HEAD-Element, das vom Server kommt, fast sofort erhalten hat, mit dem Parsen von JavaScript und CSS beginnt und alle auf der Seite angegebenen Ressourcen lädt.



    Paralleles Rendern im Browser


    Stream-Rendering ist natürlich gut, aber nur dann, wenn eine Seite nur nacheinander über einen TCP-Verbindungsstream geladen werden kann. Wenn wir bereits eine fertige Seite im Browser haben und der Benutzer auf den Link klickt, müssen wir einen Teil der Seite auf den neuen Status der Anwendung zurücksetzen. Hier sind wir nicht mehr durch irgendetwas eingeschränkt. Wir können Anfragen an den RESTful-Service parallel erfüllen und Platzhalter basierend auf den Ergebnissen sofort aktualisieren. Und wenn es im Inneren Links zu anderen gibt, dann wieder parallel, um Daten für sie anzufordern. Dies führt zu einem unglaublich schnellen Rendern von Platzhaltern im Browser. Wenn eine der Anforderungen über einen sehr langen Zeitraum eine Antwort erhält, hat dies keine Auswirkungen auf die Darstellung der übrigen Platzhalter.

    Werkzeuge für den Isomorphismus


    Wenn wir eine Webanwendung entwickeln, müssen wir häufig Maßnahmen ergreifen, die von der Implementierung in einer bestimmten Umgebung abhängen, dh, sie funktionieren im Browser und auf dem Server unterschiedlich. Dafür hat Catberry isomorphe Implementierungen solcher Aktionen. Äußerlich arbeiten sie identisch, haben dieselbe Softwareschnittstelle, werden jedoch intern mit den Tools der aktuellen Umgebung implementiert. Hier ist eine Liste solcher Implementierungen:
    • Standort ermitteln;
    • Empfangen von Referrer;
    • User Agent erhalten
    • URL-Fragment (Hash) löschen;
    • Empfangen oder Setzen eines Cookies;
    • Umleiten
    • HTTP / HTTPS-Anforderungen;
    • Datencache, der für das letzte Rendern jedes Platzhalters verwendet wurde.

    Es ist diese API, die Catberry.js Anwendung isomorph macht.

    Wie ist die Anwendung auf Catberry


    Service Locator und DI

    Die Architektur des Frameworks basiert auf der Implementierung von Service Locator- und Dependency Injection- Mustern .

    Zum Beispiel
    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
    }

    Solche Abhängigkeitsinjektionen brechen während der Minifizierung nicht ab, da sie vom Framework selbst mithilfe von UglifyJS2 ausgeführt werden .

    Wie das Modul funktioniert

    Jedes Modul ist ein Verzeichnis mit der Datei index.js, die den Modulkonstruktor exportieren soll (ein Modul ist ein Konstruktor mit einem deklarierten Prototyp). Das Modul verfügt möglicherweise auch über ein Platzhalterverzeichnis, in dem sich die Vorlagenplatzhalter des Moduls befinden.

    Methoden

    Jedes Modul kann drei Gruppen von Methoden implementieren: Rendern, Behandeln und Senden. Hier wird die Benennungskonvention verwendet. Wenn Ihr Platzhalter als "some-awesome-placeholder" bezeichnet wird, müssen Sie die renderSomeAwesomePlaceholder-Methode implementieren, wenn Sie Daten dafür vorbereiten möchten. Sie können es möglicherweise nicht implementieren, nichts davon wird beschädigt, und die Vorlage wird mit einem leeren Kontext gerendert, was ebenfalls akzeptabel ist. Diese Konvention gilt für Handle / Submit-Methoden, die Ereignisse von der Seite verarbeiten.

    Ein Beispiel für die Implementierung aller drei Methoden:
    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
    };
    

    Manchmal ist es notwendig, nach dem Rendern des Platzhalters an DOM-Elemente zu binden, nachdem Methoden dafür bereitgestellt wurden, z. B. für die obige renderSome-Methode:
    ModuleConstructor.prototype.afterRenderSome = function (dataContext) {
      // можно делать что угодно с отрендеренным плейсхолдером
    };
    

    Sie können solche Methoden auch für Handle- und Submit-Methoden hinzufügen.
    Eine Beispielimplementierung des Moduls finden Sie auf Github .

    Versprechen

    Wie in den Beispielen erwähnt, verwendet Catberry überall dort, wo asynchrone Aufrufe verwendet werden, Promises (es gab kürzlich einen großartigen Artikel ). Darüber hinaus wird, falls bereits vorhanden , eine native Implementierung verwendet, andernfalls eine Polyphil-Bibliothek von einem der Autoren der Spezifikation. Der Promise-Typ ist zwar global verfügbar, muss jedoch nicht verbunden sein, als würden Sie mit nativen Versprechungen arbeiten.

    Wo wird verwendet


    Auf der Grundlage des Frameworks wurde bereits die Confettin- Projektwebsite gestartet , auf der Sie die Leistung und Reaktionsfähigkeit der Catberry-basierten Anwendung erleben können. Darüber hinaus ist die nächste Version von Flamp in vollem Gange , die in absehbarer Zeit das Licht erblicken wird. Worauf ich mich persönlich freue.

    Wo soll ich anfangen?


    Wenn Sie diese eher flüchtige Beschreibung des Frameworks interessiert, können Sie mit diesen beiden Zeilen im Terminal beginnen:
    npm -g install catberry-cli
    catberry init example
    

    Auf diese Weise erhalten Sie einen funktionsfähigen Beispielcode, der mit der GitHub-API zusammenarbeitet, und Anweisungen zu dessen Ausführung. Dieses Beispiel zeigt typische Implementierungen von Dingen, die häufig in einer Webanwendung ausgeführt werden müssen. Das gleiche CLI-Dienstprogramm kann viel interessantere Dinge tun. Erstellen Sie beispielsweise ein neues Projekt oder fügen Sie dem Projekt ein Modul hinzu.

    Wenn Sie nichts auf Ihrem Computer installieren möchten oder es keine solche Möglichkeit gibt, gibt es dasselbe fertige Projekt auf Runnable , aber dort können Sie das Anforderungslimit an die GitHub-API überschreiten.

    Detaillierte Dokumentationen und Beispiele finden Sie auf der offiziellen Website .
    Nun und natürlich die Repository-Seite auf GitHub und catberryjs Twitter-Account, das ist immer die neuesten Nachrichten über das Framework.

    Jetzt auch beliebt: