Marko.js - interfaz de ebay.com

Marko.js no es tan popular como Angular, React.js, Vue.js o Svelte. Marko.js es un proyecto de ebay.com que se ha convertido en una propiedad de código abierto desde 2015. De hecho, es en esta biblioteca donde se construye la interfaz de ebay.com, lo que nos permite sacar una conclusión sobre su valor práctico para los desarrolladores.



La situación con Marko.js es un poco como la situación con el marco Ember.js, que, a pesar de que funciona como interfaz para varios sitios de alta carga (por ejemplo, Linkedin), el desarrollador promedio sabe poco al respecto. En el caso de Marko.js, se puede argumentar que no sabe nada.



Marko.js es tremendamente rápido, especialmente cuando se procesa en el lado del servidor. Cuando se trata de renderizado de servidores, es probable que la velocidad de Marko.js permanezca fuera del alcance de sus homólogos tranquilos, por una buena razón. Hablaremos de ellos en el material propuesto.



Primer marco SSR



Marko.js puede servir como base para un front-end clásico (con representación del lado del servidor), para una aplicación de una sola página (con representación del lado del cliente) y para una aplicación isomórfica / universal (un ejemplo de la cual se discutirá más adelante). Pero aún así, Marko.js puede considerarse una biblioteca SSR-first, es decir, centrada principalmente en la representación del servidor. Lo que distingue a Marko.js de otros marcos de componentes es que el componente del lado del servidor no construye el DOM, que luego se serializa en una cadena, sino que se implementa como un flujo de salida. Para dejar en claro de qué se trata, daré una lista de un componente de servidor 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"
  };


La idea de que el componente del servidor no debería ser el mismo que el componente del cliente parece muy natural. Y sobre esta base, se construyó originalmente la biblioteca Marko.js. Puedo suponer que en el caso de otros marcos, que originalmente se construyeron como orientados al cliente, la representación del lado del servidor se grabó en una base de código ya muy compleja. Aquí es donde surgió esta decisión arquitectónicamente defectuosa, donde el DOM se vuelve a crear en el lado del servidor para que el código de cliente existente se pueda reutilizar sin cambios.



Por qué es importante



El progreso en la creación de aplicaciones de una sola página, que se observa con el uso generalizado de Angular, React.js, Vue.js, junto con los momentos positivos, reveló varios errores fatales de la arquitectura orientada al cliente. En 2013, Spike Brehm de Airbnb publicó un artículo programático en el que la sección relevante se titula "Una mosca en el ungüento". Al mismo tiempo, todos los puntos negativos afectan al negocio:



  • aumenta el tiempo de carga de la primera página;
  • los motores de búsqueda no indexan el contenido;
  • problemas de accesibilidad para personas con discapacidad.


Como alternativa, finalmente se crearon frameworks para el desarrollo de aplicaciones isomorfas / universales: Next.js y Nust.js. Y luego entra en juego otro factor: el rendimiento. Todo el mundo sabe que node.js no es tan bueno cuando se carga con cálculos complejos. Y en el caso de que creamos un DOM en el servidor y luego comenzamos a serializarlo, node.js desaparece muy rápidamente. Sí, podemos izar un número infinito de réplicas de node.js. ¿Pero quizás intentar hacer lo mismo pero en Marko.js?



Cómo empezar con Marko.js



Para el primer conocimiento, recomiendo comenzar como se describe en la documentación con el comando npx @marko/create --template lasso-express.



Como resultado, obtendremos una base para un mayor desarrollo de proyectos con un servidor Express.js configurado y un enlazador Lasso (este enlazador es desarrollado por ebay.com y es el más fácil de integrar).



Los componentes en Marko.js generalmente se encuentran en los directorios / components en archivos con la extensión .marko. El código de los componentes es intuitivo. Como dice la documentación, si conoce html, entonces conoce Marko.js.



El componente se procesa en el servidor y luego se hidrata en el cliente. Es decir, en el cliente no recibimos HTML estático, sino un componente de cliente completo, con estado y eventos.



Al iniciar un proyecto en modo de desarrollo, funciona la recarga en caliente.



Para construir una aplicación compleja, lo más probable es que, además de la biblioteca de componentes, necesitemos algo más, por ejemplo, enrutamiento, almacenamiento, un marco para crear aplicaciones isomórficas / universales. Y aquí, por desgracia, los problemas son los mismos que enfrentaron los desarrolladores de React.js en los primeros años: no hay soluciones listas para usar ni enfoques bien conocidos. Por lo tanto, todo lo que llegó hasta este punto se puede llamar una introducción a la conversación sobre la creación de una aplicación basada en Marko.js.



Construyendo una aplicación isomorfa / universal



Como dije, no hay muchos artículos sobre Marko.js, por lo que todo lo que se muestra a continuación es fruto de mis experimentos, basados ​​en parte en trabajar con otros frameworks.



Marko.js le permite establecer y cambiar el nombre de una etiqueta o componente de forma dinámica (es decir, mediante programación); eso es lo que usaremos. Hagamos coincidir rutas: nombres de componentes. Dado que no hay un enrutamiento listo para usar en Marko.js (es interesante saber cómo se construye esto en ebay.com), usaremos el paquete, que es solo para tales casos, 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 funcionalidad del paquete de enrutador universal es increíblemente simple. Analiza la cadena de URL y llama a la función de acción asincrónica (req) con la cadena analizada, dentro de la cual podemos, por ejemplo, acceder a los parámetros de cadena analizados (req.params.id). Y dado que la función action (req) se llama de forma asincrónica, podemos inicializar los datos con solicitudes de API aquí mismo.



Como recordará, en la última sección un proyecto fue creado por un equipo npx @marko/create --template lasso-express. Tomémoslo como la base de nuestra aplicación isomórfica / universal. Para hacer esto, cambiemos ligeramente el archivo server.js



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


También cambiaremos la plantilla de la página cargada:



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


El componente <enrutador /> es exactamente la parte que se encargará de cargar los componentes dinámicos, cuyos nombres obtenemos del enrutador en el atributo de página.



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


Tradicionalmente, Marko.js tiene this.state, cambiando lo que hace que cambie la vista del componente, que es lo que usamos.



También debe implementar el trabajo con el historial usted mismo:



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


Y, finalmente, debería haber una fuente de navegación que intercepte los eventos de clic en el enlace y llame a la navegación en la página:



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

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

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


Para la conveniencia de estudiar el material, presenté los resultados en el repositorio .



apapacy@gmail.com

22 de noviembre de 2020



All Articles