Die Situation mit Marko.js ist ein bisschen ähnlich wie mit dem Ember.js-Framework, das trotz der Tatsache, dass es als Frontend für mehrere hoch geladene Sites (z. B. Linkedin) fungiert, der durchschnittliche Entwickler wenig darüber weiß. Im Fall von Marko.js kann argumentiert werden, dass er es überhaupt nicht weiß.
Marko.js ist extrem schnell, insbesondere beim Rendern auf der Serverseite. Wenn es um das Rendern von Servern geht, bleibt die Geschwindigkeit von Marko.js für seine gemächlichen Kollegen wahrscheinlich unerreichbar, und dafür gibt es objektive Gründe. Wir werden im vorgeschlagenen Material darüber sprechen.
SSR-First-Framework
Marko.js kann die Basis für ein klassisches Front-End (mit serverseitigem Rendering), für eine einseitige Anwendung (mit clientseitigem Rendering) und für eine isomorphe / universelle Anwendung sein (ein Beispiel dafür wird später erläutert). Dennoch kann Marko.js als SSR-First-Bibliothek betrachtet werden, die sich hauptsächlich auf das Rendern von Servern konzentriert. Was Marko.js von anderen Komponenten-Frameworks unterscheidet, ist, dass die serverseitige Komponente nicht das DOM erstellt, das dann in eine Zeichenfolge serialisiert, sondern als Ausgabestream implementiert wird. Um zu verdeutlichen, worum es geht, werde ich eine einfache Serverkomponente auflisten:
// Compiled using marko@4.23.9 - DO NOT EDIT
"use strict";
var marko_template = module.exports = require("marko/src/html").t(__filename),
marko_componentType = "/marko-lasso-ex$1.0.0/src/components/notFound/index.marko",
marko_renderer = require("marko/src/runtime/components/renderer");
function render(input, out, __component, component, state) {
var data = input;
out.w("<p>Not found</p>");
}
marko_template._ = marko_renderer(render, {
___implicit: true,
___type: marko_componentType
});
marko_template.meta = {
id: "/marko-lasso-ex$1.0.0/src/components/notFound/index.marko"
};
Die Idee, dass die Serverkomponente nicht mit der Clientkomponente identisch sein muss, scheint sehr natürlich. Auf dieser Basis wurde ursprünglich die Marko.js-Bibliothek erstellt. Ich kann davon ausgehen, dass bei anderen Frameworks, die ursprünglich als clientorientiertes Framework erstellt wurden, das serverseitige Rendering auf eine bereits sehr komplexe Codebasis übertragen wurde. Hier entstand diese architektonisch fehlerhafte Entscheidung, bei der das DOM auf der Serverseite neu erstellt wird, damit vorhandener Clientcode unverändert wiederverwendet werden kann.
Warum ist es wichtig?
Die Fortschritte bei der Erstellung von Anwendungen mit nur einer Seite, die bei der weit verbreiteten Verwendung von Angular, React.js, Vue.js und den positiven Momenten beobachtet wurden, zeigten mehrere schwerwiegende Fehler der kundenorientierten Architektur. Bereits 2013 veröffentlichte Spike Brehm von Airbnb einen programmatischen Artikel, in dem der entsprechende Abschnitt den Titel "Eine Fliege in der Salbe" trägt. Gleichzeitig haben alle negativen Punkte das Geschäft getroffen:
- Die Ladezeit der ersten Seite erhöht sich.
- Inhalte werden von Suchmaschinen nicht indiziert.
- Zugänglichkeitsprobleme für Menschen mit Behinderungen.
Als Alternative wurden schließlich Frameworks für die Entwicklung isomorpher / universeller Anwendungen erstellt: Next.js und Nust.js. Und dann kommt noch ein weiterer Faktor ins Spiel - die Leistung. Jeder weiß, dass node.js nicht so gut ist, wenn es mit komplexen Berechnungen geladen wird. Und wenn wir das DOM auf dem Server erstellen und dann mit der Serialisierung beginnen, wird node.js sehr schnell ausgeblendet. Ja, wir können unendlich viele Replikate von node.js hochziehen. Aber vielleicht versuchen Sie das Gleiche, aber in Marko.js?
Erste Schritte mit Marko.js
Für die erste Bekanntschaft empfehle ich, wie in der Dokumentation beschrieben mit dem Befehl zu beginnen
npx @marko/create --template lasso-express.
Als Ergebnis erhalten wir eine Grundlage für die Weiterentwicklung von Projekten mit einem konfigurierten Express.js-Server und einem Lasso-Linker (dieser Linker wurde von ebay.com entwickelt und ist am einfachsten zu integrieren).
Komponenten in Marko.js befinden sich normalerweise in den Verzeichnissen / components in Dateien mit der Erweiterung .marko. Der Komponentencode ist intuitiv. Wie in der Dokumentation angegeben, kennen Sie Marko.js, wenn Sie HTML kennen.
Die Komponente wird auf dem Server gerendert und dann auf dem Client hydratisiert. Das heißt, auf dem Client erhalten wir kein statisches HTML, sondern eine vollwertige Clientkomponente mit Status und Ereignissen.
Wenn Sie ein Projekt im Entwicklungsmodus starten, funktioniert das Hot-Reloading.
Um eine komplexe Anwendung zu erstellen, benötigen wir höchstwahrscheinlich neben der Komponentenbibliothek noch etwas anderes, z. B. Routing, Speichern und ein Framework zum Erstellen isomorpher / universeller Anwendungen. Und hier sind leider die Probleme dieselben, mit denen die Entwickler von React.js in den ersten Jahren konfrontiert waren - es gibt keine vorgefertigten Lösungen und bekannten Ansätze. Daher kann alles, was bis zu diesem Punkt kam, als Einführung in die Konversation über das Erstellen einer auf Marko.js basierenden Anwendung bezeichnet werden.
Aufbau einer isomorphen / universellen Anwendung
Wie gesagt, es gibt nicht viele Artikel über Marko.js, daher ist alles unten das Ergebnis meiner Experimente, die teilweise auf der Arbeit mit anderen Frameworks beruhen.
Mit Marko.js können Sie den Namen eines Tags oder einer Komponente dynamisch (dh programmgesteuert) festlegen und ändern - das werden wir verwenden. Lassen Sie uns Routen abgleichen - Namen von Komponenten. Da es in Marko.js kein sofort einsatzbereites Routing gibt (es ist interessant zu wissen, wie dies auf ebay.com aufgebaut ist), werden wir das Paket verwenden, das nur für solche Fälle gedacht ist - Universal-Router:
const axios = require('axios');
const UniversalRouter = require('universal-router');
module.exports = new UniversalRouter([
{ path: '/home', action: (req) => ({ page: 'home' }) },
{
path: '/user-list',
action: async (req) => {
const {data: users} = await axios.get('http://localhost:8080/api/users');
return { page: 'user-list', data: { users } };
}
},
{
path: '/users/:id',
action: async (req) => {
const {data: user} = await axios.get(`http://localhost:8080/api/users/${req.params.id}`);
return { page: 'user', data: { req, user } };
}
},
{ path: '(.*)', action: () => ({ page: 'notFound' }) }
])
Die Funktionalität des Universal-Router-Pakets ist unglaublich einfach. Es analysiert die URL-Zeichenfolge und ruft die Funktion asynchronous action (req) mit der analysierten Zeichenfolge auf, in der wir beispielsweise auf die Parameter der analysierten Zeichenfolge (req.params.id) zugreifen können. Und da die action (req) -Funktion asynchron aufgerufen wird, können wir die Daten hier mit API-Anforderungen initialisieren.
Wie Sie sich erinnern, wurde im letzten Abschnitt ein Projekt von einem Team erstellt
npx @marko/create --template lasso-express. Nehmen wir es als Grundlage für unsere isomorphe / universelle Anwendung. Ändern Sie dazu die Datei server.js geringfügig
app.get('/*', async function(req, res) {
const { page, data } = await router.resolve(req.originalUrl);
res.marko(indexTemplate, {
page,
data,
});
});
Wir werden auch die Vorlage der geladenen Seite ändern:
<lasso-page/>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Marko | Lasso + Express</title>
<lasso-head/>
<style>
.container{
margin-left: auto;
margin-right: auto;
width: 800px;
}
</style>
</head>
<body>
<sample-header title="Lasso + Express"/>
<div class="container">
<router page=input.page data=input.data/>
</div>
<lasso-body/>
<!--
Page will automatically refresh any time a template is modified
if launched using the browser-refresh Node.js process launcher:
https://github.com/patrick-steele-idem/browser-refresh
-->
<browser-refresh/>
</body>
</html>
Die <router /> -Komponente ist genau der Teil, der für das Laden dynamischer Komponenten verantwortlich ist, deren Namen wir vom Router im Seitenattribut erhalten.
<layout page=input.page>
<${state.component} data=state.data/>
</layout>
import history from '../../history'
import router from '../../router'
class {
onCreate({ page, data }) {
this.state = {
component: require(`../${page}/index.marko`),
data
}
history.listen(this.handle.bind(this))
}
async handle({location}) {
const route = await router.resolve(location);
this.state.data = route.data;
this.state.component = require(`../${route.page}/index.marko`);
}
}
Traditionell hat Marko.js diesen Status, der sich ändert, wodurch sich die Ansicht der Komponente ändert, was wir verwenden.
Sie müssen die Arbeit mit der Geschichte auch selbst implementieren:
const { createBrowserHistory } = require('history')
const parse = require('url-parse')
const deepEqual = require('deep-equal')
const isNode = new Function('try {return !!process.env;}catch(e){return false;}') //eslint-disable-line
let history
if (!isNode()) {
history = createBrowserHistory()
history.navigate = function (path, state) {
const parsedPath = parse(path)
const location = history.location
if (parsedPath.pathname === location.pathname &&
parsedPath.query === location.search &&
parsedPath.hash === location.hash &&
deepEqual(state, location.state)) {
return
}
const args = Array.from(arguments)
args.splice(0, 2)
return history.push(...[path, state, ...args])
}
} else {
history = {}
history.navigate = function () {}
history.listen = function () {}
}
module.exports = history
Und schließlich sollte es eine Navigationsquelle geben, die die Klickereignisse auf dem Link abfängt und die Navigation auf der Seite aufruft:
import history from '../../history'
<a on-click("handleClick") href=input.href><${input.renderBody}/></a>
class {
handleClick(e) {
e.preventDefault()
history.navigate(this.input.href)
}
}
Um das Material leichter studieren zu können, habe ich die Ergebnisse im Repository präsentiert .
apapacy@gmail.com
22. November 2020