Русский
Русский
English
Статистика
Реклама

Marko.js

Marko.js фронтенд от ebay.com

23.11.2020 00:11:51 | Автор: admin
Marko.js не так популярен, как Angular, React.js, Vue.js или Svelte. Marko.js это проект ebay.com, который с 2015 года стал достоянием opensource. Собственно, именно на этой библиотеке построен фронтенд ebay.com, что позволяет сделать заключение о её практической ценности для разработчиков.

Ситуация с Marko.js, немного напоминает ситуацию с фреймворком Ember.js, который, несмотря на то что работает в качестве фронтенда нескольких высоко-нагруженных сайтов (например Linkedin) о нем мало знает среднестатистический разработчик. В случае с Marko.js можно утверждать, что совсем не знает.

Marko.js неистово быстр, особенно при серверном рендеринге. Что касается серверного рендеринга, то скорость Marko.js, скорее всего, останется недосягаемой для его неторопливых собратьев, и этому есть объективные причины. О них и поговорим в предлагаемом материале.

SSR-first фреймворк


Marko.js может стать основой для классического фронтенда (с серверным рендерингом), для одностраничного приложения (с клиентским рендерингом) и для изоморфного/универсального приложения (пример которого будет рассмотрен далее). Но все же, Marko.js можно считать SSR-first библиотекой, то есть ориентированной, в первую очередь, на серверный рендеринг. От других компонентных фреймворков, Marko.js отличает то, серверный вариант компонента не строит DOM, который потом сериализуется в строку, а реализован как поток вывода. Чтобы было ясно о чем речь, приведу листинг простого серверного компонента:

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

Идея, что серверный компонент не должен быть таким же как клиентский компонент, представляется очень естественной. И на этой основе изначально строилась библиотека Marko.js. Я могу предположить, что в случае с другими фреймворками, которые изначально строились как клиент-ориентированные, серверный рендеринг приматывался скотчем к уже существующей весьма сложной кодовой базе. Отсюда возникло это архитектурно неверное решение, когда на стороне сервера воссоздается DOM, чтобы существующий клиентский код мог быть переиспользован без изменений.

Почему это важно


Прогресс в создании одностраничных приложений, который наблюдается с широким распространением Angular, React.js, Vue.js, наряду с положительными моментами, выявил и несколько фатальных просчетов клиент-ориентированной архитектуры. В далеком 2013 году Spike Brehm из Airbnb опубликовал программную статью, в которой соответствующий раздел называется Ложка дегтя в бочке меда. При этом все отрицательные пункты бьют по бизнесу:

  • увеличивается время загрузки первой страницы;
  • контент не индексируется поисковиками;
  • проблемы с доступностью для людей с ограниченными возможностями.

Как альтернатива, наконец, были созданы фреймворки для разработки изоморфных/универсальных приложений: Next.js и Nust.js. И тут начинает вступать в игру другой фактор производительность. Всем известно, что node.js не так хорош, если его нагружать сложными расчетами. А в случае, когда мы на сервере создаем DOM, а потом запускаем его сериализацию, node.js очень быстро выдыхается. Да, мы можем поднимать бесконечное количество реплик node.js. Но может быть попробовать сделать все то же но на Marko.js?

Как начать работать с Marko.js


Для первого знакомства рекомендую начать как описано в документации с команды npx @marko/create --template lasso-express.

В результате получим основу для дальнейшей разработки проектов с настроенным сервером Express.js и компоновщиком Lasso (этот компоновщик является разработкой ebay.com и с ним проще всего интегрироваться).

Компоненты в Marko.js, как правило, располагаются в каталогах /components в файлах с расширением .marko. Код компонентов интуитивно понятен. Как сказано в документации, если Вы знаете html, то вы знаете Marko.js.

Компонент рендерится на сервере, и потом воссоздается (hydrate) на клиенте. То есть, на клиенте мы получаем не статический html, а полноценный клиентский компонент, с состоянием и событиями.

При запуске проекта в режиме разработки работает горячая перезагрузка (hot reloading).

Для построения сложного приложения, нам, скорее всего, кроме библиотеки компонентов необходимо иметь еще кое-что, например роутинг, стор, каркас для создания изоморфных/универсальных приложений. И тут, увы, проблемы те же, с которыми первые годы сталкивались разработчики React.js нет готовых решений и общеизвестных подходов. Поэтому все, что было до этого момента, можно назвать вступлением к беседе о построении приложения на основе Marko.js.

Строим изоморфное/универсальное приложение


Как я уже сказал, о Marko.js не так много статей, поэтому все нижеследующее плод моих экспериментов, отчасти основанный на работе с другими фреймворками.

Marko.js позволяет задавать и менять имя тэга или компонент динамически (то есть программно) этим и воспользуемся. Сопоставим роутам имена компонентов. Поскольку из коробки в Marko.js роутинга нет (интересно узнать как это построено на ebay.com) воспользуемся пакетом, который как раз для таких случаев 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' }) }])

Функционал пакета universal-router прост до безобразия. Он разбирает строку url, и вызывает с разобранной строкой асинхронную функцию action(req), внутри которой мы можем, например, получить доступ к разобранным параметрам строки (req.params.id). А поскольку функция action(req) вызывается асинхронно, мы можем прямо здесь инициализировать данные запросами к API.

У нас, как вы помните, в прошлом разделе был создан проект командой npx @marko/create --template lasso-express. Возьмем его как основу для нашего изоморфного/универсального приложения. Для этого немного изменим файл server.js

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

Также изменим шаблон загружаемой страницы:

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

Компонент <router/> как раз та часть которая будет отвечать за загрузку динамических компонентов, имена которых получаем из роутера в атрибуте 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`);  }}

Традиционно, Marko.js имеет состояние this.state, изменение которого вызывает изменение представления компонента, чем мы и пользуемся.

Работу с историей приходится реализовывать также самостоятельно:

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-linelet historyif (!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

Ну и, наконец, должен быть источник навигации, который перехватывает события клика по ссылке, и вызывает навигацию на странице:

import history from '../../history'<a on-click("handleClick") href=input.href><${input.renderBody}/></a>class {  handleClick(e) {    e.preventDefault()    history.navigate(this.input.href)  }}

Для удобства изучения материала я представил результаты в репозитарии.

apapacy@gmail.com
22 ноября 2020 года
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru