[Разработка веб-сайтов, JavaScript, Node.JS] Marko.js — фронтенд от ebay.com
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
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-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
Ну и, наконец, должен быть источник навигации, который перехватывает события клика по ссылке, и вызывает навигацию на странице:
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 года
===========
Источник:
habr.com
===========
Похожие новости:
- [Разработка веб-сайтов, PHP, Laravel] Laravel–Дайджест (9–22 ноября 2020)
- [Разработка веб-сайтов] Устройство современного веб-браузера Chrome (часть 4/4) (перевод)
- [Ненормальное программирование, Разработка веб-сайтов, Python, Программирование] iPad для разработчика
- [Разработка веб-сайтов] Устройство современного веб-браузера Chrome (часть 3/4) (перевод)
- [Разработка веб-сайтов] Устройство современного веб-браузера Chrome (часть 2/4) (перевод)
- [JavaScript, Функциональное программирование] Для чего на самом деле нужны стрелочные функции в JavaScript
- [Разработка веб-сайтов] Устройство современного веб-браузера Chrome (перевод)
- [Разработка веб-сайтов, JavaScript, Angular, ReactJS, TypeScript] Schedulers в RxJS
- [Информационная безопасность, Разработка веб-сайтов, Облачные сервисы] Вебинар DataLine «Защита веб-приложений: как это нужно делать сегодня» 26 ноября
- [Разработка веб-сайтов, Программирование, Разработка под e-commerce, DevOps] «Kubernetes для разработчиков»: трехдневный интенсив
Теги для поиска: #_razrabotka_vebsajtov (Разработка веб-сайтов), #_javascript, #_node.js, #_javascript, #_nodejs, #_marko.js, #_razrabotka_vebsajtov (
Разработка веб-сайтов
), #_javascript, #_node.js
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 00:11
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
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-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 Ну и, наконец, должен быть источник навигации, который перехватывает события клика по ссылке, и вызывает навигацию на странице: 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 года =========== Источник: habr.com =========== Похожие новости:
Разработка веб-сайтов ), #_javascript, #_node.js |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 00:11
Часовой пояс: UTC + 5