Marko.js - interface de ebay.com

Marko.js n'est pas aussi populaire que Angular, React.js, Vue.js ou Svelte. Marko.js est un projet ebay.com qui est devenu une propriété open source depuis 2015. En fait, c'est sur cette bibliothèque que le front-end ebay.com est construit, ce qui nous permet de tirer une conclusion sur sa valeur pratique pour les développeurs.



La situation avec Marko.js est un peu comme la situation avec le framework Ember.js, qui, malgré le fait qu'il fonctionne comme une interface pour plusieurs sites à forte charge (par exemple, Linkedin), le développeur moyen en sait peu. Dans le cas de Marko.js, on peut soutenir qu'il ne sait pas du tout.



Marko.js est extrêmement rapide, en particulier lors du rendu côté serveur. En ce qui concerne le rendu côté serveur, la vitesse de Marko.js restera probablement hors de portée pour ses homologues tranquilles, pour une bonne raison. Nous en parlerons dans le matériel proposé.



Cadre SSR-first



Marko.js peut servir de base à un frontal classique (avec rendu côté serveur), à une application monopage (avec rendu côté client) et à une application isomorphe / universelle (dont un exemple sera discuté plus tard). Mais quand même, Marko.js peut être considéré comme une bibliothèque SSR d'abord, c'est-à-dire principalement axée sur le rendu serveur. Ce qui distingue Marko.js des autres frameworks de composants est que le composant côté serveur ne construit pas le DOM, qui est ensuite sérialisé dans une chaîne, mais est implémenté en tant que flux de sortie. Pour clarifier de quoi il s'agit, je vais donner une liste d'un composant serveur simple:



// 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"
  };


L'idée que le composant serveur ne doit pas être le même que le composant client semble très naturelle. Et sur cette base, la bibliothèque Marko.js a été construite à l'origine. Je peux supposer que dans le cas d'autres frameworks, qui étaient à l'origine construits comme orientés client, le rendu côté serveur a été enregistré sur une base de code déjà très complexe. C'est là que cette décision défectueuse sur le plan architectural est survenue, où le DOM est recréé côté serveur afin que le code client existant puisse être réutilisé tel quel.



Pourquoi c'est important



Les progrès dans la création d'applications à page unique, observés avec l'utilisation généralisée d'Angular, React.js, Vue.js, ainsi que les moments positifs, ont révélé plusieurs erreurs fatales de l'architecture orientée client. En 2013, Spike Brehm d'Airbnb a publié un article programmatique dans lequel la section pertinente est intitulée «Une mouche dans la pommade». Dans le même temps, tous les points négatifs touchent l'entreprise:



  • le temps de chargement de la première page augmente;
  • le contenu n'est pas indexé par les moteurs de recherche;
  • problèmes d'accessibilité pour les personnes handicapées.


Comme alternative, des frameworks pour le développement d'applications isomorphes / universelles ont finalement été créés: Next.js et Nust.js. Et puis un autre facteur entre en jeu - la performance. Tout le monde sait que node.js n'est pas si bon lorsqu'il est chargé de calculs complexes. Et dans le cas où nous créons le DOM sur le serveur et que nous commençons à le sérialiser, node.js s'éteint très rapidement. Oui, nous pouvons hisser un nombre infini de répliques node.js. Mais peut-être essayer de faire la même chose mais dans Marko.js?



Comment démarrer avec Marko.js



Pour la première connaissance, je recommande de commencer comme décrit dans la documentation avec la commande npx @marko/create --template lasso-express.



En conséquence, nous obtiendrons une base pour le développement ultérieur de projets avec un serveur Express.js configuré et un lieur Lasso (ce lieur est développé par ebay.com et est le plus facile à intégrer).



Les composants de Marko.js sont généralement situés dans les répertoires / components dans des fichiers avec l'extension .marko. Le code composant est intuitif. Comme le dit la documentation, si vous connaissez le html, vous connaissez Marko.js.



Le composant est rendu sur le serveur puis hydraté sur le client. Autrement dit, sur le client, nous ne recevons pas du code HTML statique, mais un composant client à part entière, avec l'état et les événements.



Lors du démarrage d'un projet en mode développement, le rechargement à chaud fonctionne.



Pour construire une application complexe, nous avons très probablement besoin d'autre chose que la bibliothèque de composants, par exemple, le routage, le stockage, un cadre pour créer des applications isomorphes / universelles. Et ici, hélas, les problèmes sont les mêmes que ceux auxquels les développeurs de React.js ont été confrontés dans les premières années - il n'y a pas de solutions toutes faites et d'approches bien connues. Par conséquent, tout ce qui est arrivé jusqu'à présent peut être appelé une introduction à la conversation sur la création d'une application basée sur Marko.js.



Construire une application isomorphe / universelle



Comme je l'ai dit, il n'y a pas beaucoup d'articles sur Marko.js, donc tout ce qui suit est le fruit de mes expériences, basées en partie sur le travail avec d'autres frameworks.



Marko.js vous permet de définir et de modifier le nom d'une balise ou d'un composant de manière dynamique (c'est-à-dire par programme) - c'est ce que nous utiliserons. Associons les routes - les noms des composants. Puisqu'il n'y a pas de routage prêt à l'emploi dans Marko.js (il est intéressant de savoir comment cela est construit sur ebay.com), nous utiliserons le package, qui est juste pour de tels cas - 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' }) }
])


La fonctionnalité du package de routeur universel est extrêmement simple. Il analyse la chaîne d'url et appelle l'action de fonction asynchrone (req) avec la chaîne analysée, à l'intérieur de laquelle nous pouvons, par exemple, accéder aux paramètres de la chaîne analysée (req.params.id). Et comme la fonction action (req) est appelée de manière asynchrone, nous pouvons initialiser les données avec des requêtes API ici.



Comme vous vous en souvenez, dans la dernière section, un projet a été créé par une équipe npx @marko/create --template lasso-express. Prenons-le comme base de notre application isomorphe / universelle. Pour ce faire, modifions légèrement le fichier server.js



app.get('/*', async function(req, res) {
    const { page, data } = await router.resolve(req.originalUrl);
    res.marko(indexTemplate, {
            page,
            data,
        });
});


Nous allons également changer le modèle de la page chargée:



<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>


Le composant <router /> est exactement la partie qui sera responsable du chargement des composants dynamiques, dont nous obtenons les noms du routeur dans l'attribut de page.



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


Traditionnellement, Marko.js a this.state, qui change, ce qui fait changer la vue du composant, ce que nous utilisons.



Vous devez également implémenter vous-même le travail avec l'histoire:



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


Et enfin, il devrait y avoir une source de navigation qui intercepte les événements de clic sur le lien et appelle la navigation sur la page:



import history from '../../history'

<a on-click("handleClick") href=input.href><${input.renderBody}/></a>

class {
  handleClick(e) {
    e.preventDefault()
    history.navigate(this.input.href)
  }
}


Pour faciliter l'étude du matériel, j'ai présenté les résultats dans le référentiel .



apapacy@gmail.com

22 novembre 2020



All Articles