Marko.js - frontend do ebay.com

Marko.js não é tão popular como Angular, React.js, Vue.js ou Svelte. Marko.js é um projeto ebay.com que se tornou uma propriedade de código aberto desde 2015. Na verdade, é nesta biblioteca que se constrói o frontend ebay.com, o que nos permite tirar uma conclusão sobre seu valor prático para os desenvolvedores.



A situação com Marko.js é um pouco parecida com a situação com o framework Ember.js, que, apesar de funcionar como front-end para vários sites de alta carga (por exemplo, Linkedin), o desenvolvedor médio sabe pouco sobre isso. No caso de Marko.js, pode-se argumentar que ele não sabe nada.



Marko.js é extremamente rápido, especialmente ao renderizar do lado do servidor. Quando se trata de renderização de servidor, a velocidade do Marko.js provavelmente permanecerá fora do alcance de suas contrapartes vagarosas, e há razões objetivas para isso. Falaremos sobre eles no material proposto.



Estrutura SSR primeiro



Marko.js pode ser a base para um front-end clássico (com renderização do lado do servidor), para um aplicativo de página única (com renderização do lado do cliente) e para um aplicativo isomórfico / universal (um exemplo será discutido posteriormente). Mas ainda assim, Marko.js pode ser considerada uma biblioteca SSR primeiro, ou seja, focada principalmente na renderização do servidor. O que distingue o Marko.js de outras estruturas de componentes é que o componente do lado do servidor não constrói o DOM, que é serializado em uma string, mas é implementado como um fluxo de saída. Para deixar claro do que se trata, darei uma lista de um componente de servidor simples:



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


A ideia de que o componente servidor não deve ser igual ao componente cliente parece muito natural. E com base nisso, a biblioteca Marko.js foi originalmente construída. Posso supor que, no caso de outras estruturas que foram originalmente criadas como centradas no cliente, a renderização do lado do servidor foi gravada em uma base de código já muito complexa. Foi aí que surgiu essa decisão com falhas arquitetônicas, em que o DOM é recriado no lado do servidor para que o código do cliente existente possa ser reutilizado sem alterações.



Por que isso é importante



O progresso na criação de aplicações de página única, que se observa com o uso generalizado de Angular, React.js, Vue.js, junto com os momentos positivos, revelou vários erros fatais da arquitetura orientada ao cliente. Em 2013, Spike Brehm do Airbnb publicou um artigo programático no qual a seção relevante era intitulada "Uma mosca na sopa". Ao mesmo tempo, todos os pontos negativos atingem o negócio:



  • o tempo de carregamento da primeira página aumenta;
  • o conteúdo não é indexado por mecanismos de pesquisa;
  • problemas de acessibilidade para pessoas com deficiência.


Como alternativa, foram finalmente criados frameworks para o desenvolvimento de aplicações isomórficas / universais: Next.js e Nust.js. E então outro fator entra em jogo - o desempenho. Todo mundo sabe que node.js não é tão bom quando carregado com cálculos complexos. E no caso em que criamos o DOM no servidor e então começamos a serializá-lo, o node.js desaparece muito rapidamente. Sim, podemos içar um número infinito de réplicas do node.js. Mas talvez tente fazer a mesma coisa, mas no Marko.js?



Como começar a usar Marko.js



Para o primeiro contato, recomendo começar conforme descrito na documentação com o comando npx @marko/create --template lasso-express.



Como resultado, teremos uma base para o desenvolvimento de projetos com um servidor Express.js configurado e um vinculador Lasso (esse vinculador é desenvolvido por ebay.com e é o mais fácil de integrar).



Os componentes no Marko.js geralmente estão localizados nos diretórios / components em arquivos com a extensão .marko. O código do componente é intuitivo. Como diz a documentação, se você conhece html, conhece Marko.js.



O componente é renderizado no servidor e então hidratado no cliente. Ou seja, no cliente não recebemos html estático, mas um componente cliente completo, com estado e eventos.



Ao iniciar um projeto no modo de desenvolvimento, o recarregamento a quente funciona.



Para construir um aplicativo complexo, provavelmente, além da biblioteca de componentes, precisamos ter algo mais, por exemplo, roteamento, armazenamento, uma estrutura para criar aplicativos isomórficos / universais. E aqui, infelizmente, os problemas são os mesmos que os desenvolvedores do React.js enfrentaram nos primeiros anos - não existem soluções prontas e abordagens bem conhecidas. Portanto, tudo o que veio até este ponto pode ser chamado de uma introdução à conversa sobre a construção de um aplicativo baseado em Marko.js.



Construindo uma aplicação isomórfica / universal



Como eu disse, não existem muitos artigos sobre Marko.js, então tudo abaixo é fruto de meus experimentos, baseados em parte no trabalho com outros frameworks.



Marko.js permite que você defina e altere o nome de uma tag ou componente dinamicamente (ou seja, programaticamente) - é o que usaremos. Vamos combinar rotas - nomes de componentes. Como não há roteamento pronto para uso no Marko.js (é interessante saber como isso é construído no ebay.com), usaremos o pacote, que é apenas para esses casos - roteador universal:



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' }) }
])


A funcionalidade do pacote de roteador universal é extremamente simples. Ele analisa a string url e chama a função de ação assíncrona (req) com a string analisada, dentro da qual podemos, por exemplo, acessar os parâmetros da string analisada (req.params.id). E como a função action (req) é chamada de forma assíncrona, podemos inicializar os dados com solicitações de API aqui mesmo.



Como você deve se lembrar, na última seção um projeto foi criado por uma equipe npx @marko/create --template lasso-express. Vamos tomá-lo como base para nossa aplicação isomórfica / universal. Para fazer isso, vamos alterar ligeiramente o arquivo server.js



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


Vamos também mudar o modelo da página carregada:



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


O componente <router /> é exatamente a parte que será responsável por carregar os componentes dinâmicos, cujos nomes obtemos do roteador no atributo da 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 tem this.state, mudança que faz com que a visão do componente mude, que é o que estamos usando.



Você também deve implementar o trabalho com a história:



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


E, finalmente, deve haver uma fonte de navegação que intercepta os eventos de clique no link e chama a navegação na 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 facilitar o estudo do material, apresentei os resultados no repositório .



apapacy@gmail.com

22 de novembro de 2020



All Articles