Прототипирование с веб-компонентами: создание RSS Reader.
Приготовьтесь отправиться в путешествие по изучению прототипирования приложения с помощью веб-компонентов, модулей es6, event target, bit cli и т. д. Вместе мы узнаем, как использовать веб-компоненты и изучим несколько дополнительных особенностей.
В этой статье мы выполним прототипирование RSS reader с использованием веб-компонентов. Конечный результат будет выглядеть следующим образом:
Код можно найти на GitHub. Почему веб-компоненты??
Поговорим о
Прототипирование с веб-компонентами: создание RSS Reader...
Приготовьтесь отправиться в путешествие по изучению прототипирования приложения с помощью веб-компонентов, модулей es6, event target, bit cli и т. д. Вместе мы узнаем, как использовать веб-компоненты и изучим несколько дополнительных особенностей.
В этой статье мы выполним прототипирование RSS reader с использованием веб-компонентов. Конечный результат будет выглядеть следующим образом:
Код можно найти на GitHub.
Почему веб-компоненты??
Поговорим о том, почему стоит выбрать веб-компоненты для UI-стратегии. Есть несколько причин:
- Техническая перспективность. Веб-компоненты представляют собой стандарт и поддерживаются браузером. Краткая история сети показывает, что выбор стандарта приносит пользу.
- Framework-agnostic. Когда несколько команд работают над одним приложением с несколькими библиотеками, такими как Vue и React, достижение одной функциональности между этими библиотеками представляет трудную задачу. Необходима стандартизация!
- Повторно используемая система дизайна. Еще один способ применения framework-agnostic компонентов проявляется при необходимости создания системы дизайна для команды.
- Размер bundle. Несмотря на то, что React является более завершенным с точки зрения использования API и поддержки библиотек, порой размер действительно имеет значение.
Что такое веб-компоненты?
Благодаря веб-компонентам можно развивать инкапсулированный от основного документа компонент. Веб-компоненты предлагают:
- Пользовательский элемент — это API Javascript, с помощью которого можно определить новый тип html-тега в соответствии с коллекцией компонентов.
- Шаблоны HTML — представляют собой теги
<template>
и<slot>
, с помощью которых можно определить layout шаблона. - Shadow DOM — специфичный для компонента. Это своего рода изолированное окружение для DOM компонента, отделенное от основного документа.
Вместе эти три API позволяют инкапсулировать функциональность компонента и с легкостью отделить его от основного APP. По сути, с его помощью можно расширить api DOM с дополнительными тегами.
Как работает lit?
Lit — это абстракция над оригинальным api, предоставляющая две основные возможности:
Lit-html — библиотека для созданияповторно используемых html-шаблонов в контексте javascript.
Эта библиотека использует функцию под названием теговые шаблоны вместе с es6, которые выглядят следующим образом:
tag `some ${boilerPlate} in my string`
С помощью этой функции можно выполнить парсинг строки с пользовательским элементом. Это ядро lit-html, сочетающее шаблоны в javascript прямо в браузере. В случае с lit, функция render внутри элемента lit может содержать следующее выражение:
// bind function to click // bind text to button body html`<button @click=${myFunc}>${buttonText}</button>` // it can also be composable and conditional // here we see internal lit components rendered according to a condition. html`<div>${isReady ? html`<main-app>`: html`<app-load/>` }</div>`
Документацию можно посмотреть здесь.
lit-element — базовый класс для компонентов. Он предоставляет способ определения props, привязку к жизненному циклу компонента и унифицированный интерфейс компонента.
Чтобы разобраться подробнее, рассмотрим компонент nav-bar:
// imports are all es6 modules import { html, LitElement } from 'https://unpkg.com/lit-element?module' import { createEvent, eventChangeCurrent } from '../rss/events.js' // we extend the LitElement component to have a common life cycle. export class NavBar extends LitElement { // lit uses this static function to track the properties the component needs to receive // every time these properties change it will re-render. static get properties () { return { list: { type: Array }, // notice we use the built in Array class as type emitter: { type: Object } } } constructor () { super() this.list = [] // we need to initialize our properties this.emitter = {} this.changeCurrent = this.changeCurrent.bind(this) // bind changeCurrent before passing it to lit // you should always bind once. } // click handler. changeCurrent (e) { const { url, name } = e // like in our rss client, all communication is done via event emitters this.emitter.dispatchEvent(createEvent(eventChangeCurrent, { name, url })) } render () { // we return an html function call to parse our html template return html` <nav> <!-- inner lit components are composed with another template --> ${this.list.map((listItem) => html` <!-- all custom element must use a '-' in the name, so the browser will ignore them on initial rendering--> <nav-item <!-- syntax for binding an event handler --> @click=${this.changeCurrent} <!-- pass in properties as attributes on the --> changeCurrent=${this.changeCurrent} url=${listItem.url} name=${listItem.name}> </nav-item>`)} </nav> ` } } // in most code examples you would see `window.customElements.define`call to // register the component in the browser. I see this as breaking encapsulation. // This is not lit faults, it's part of the spec which can improve.
Создание RSS-Reader!
Для тех, кто не знаком с RSS, это протокол синдикации, созданный на рубеже веков, который предоставляет пользователям и приложениям доступ к обновлениям онлайн содержимого.
Исходный код проекта можно найти в этом репозитории.
Основные ограничения по дизайну:
- Lit-element. В этом проекте используются lit-html и lit-element от команды разработчиков polymer. Это отличная библиотека для работы над стандартами веб-компонентов, устраняющая множество проблем при работе с шаблонами, однако стоит отметить, что создание lit было вдохновлено библиотекой hyper, которую также стоит изучить.
- Отсутствие Bundle (практически). В попытке изучить несколько новых функций сети, этот проект использует модули es6. Однако это лишь исключение из правила, парсер RSS от Bobby Brennan — это “обычный” пакет браузера.
- Подходит только для браузера. У этого проекта отсутствует компонент backend.
- Все модули доступны на платформе компонентов bit.devдля повторного использования. bit cli и платформа — это одни из лучших способов обмена компонентами JS и в особенности веб-компонентами.
- В этом проекте используются timers и
eventTarget
вместо workers. Workers не работают с модулями es6. - Этот репозиторий находится на стадии прототипирования, поэтому не содержит тестирования.
Рассмотрим основные entry points приложения. index.html
<html> <head> <title>RSS Reader</title> <style> main { display: flex; } nav-bar { margin-right: 20px } </style> </head> <body> <main id="main-app"> <!-- two main components one for the left panel and one for rendering the rss items --> <nav-bar id="side-bar"></nav-bar> <item-list id="main-list"></item-list> </main> <!-- non es module to have RSS parser--> <script src="https://unpkg.com/[email protected]/dist/rss-parser.js"></script> <script type="module"> import { main } from '/source/reader.js' main() </script> </body> </html>
Главная функция в файле reader.js
:
export function main () { defineElements(elements) // define all elements in one place const store = createStore() // create main store to update for new channels hookUpEvents(store) // define main events from the ui to the store. topLevelRender(store.getSideBarList(), '#side-bar', store.emitter) // render side-bar. }
Суть заключается в том, что все части соединяются посредством событий, а каждый компонент приложения независим. Чтобы увидеть остальную часть приложения, загляните в репозиторий.
Общие
index.html
— в качестве главного layout проекта.reader.js
— главный файл javascript проекта, устанавливающий event emitters.
Папка Elements — веб-компоненты lit-element.
item-list.js
— список элементов feed, отображающий текущий выбранный feed.nav-bar.js
— редактирование и использование feed.rss-item.js/nav-item.js
— представляет фрагмент внутри соответствующих списков.
Папка RSS. Хранилище и возможности rss
events.js
— содержит все названия событий и функцию создания событий.feed-key.js
— функция для создания уникального ключа feed в хранилище.rss-client.js
— получает и парсирует rss feed.rss-store
— главное состояние приложения.
Папка Utils
defer-function.js
используется для отправки асинхронных событий.define-elements.js
по возможности избегает глобальных веб-компонентов.
Следует отметить, что модульность заложена в структуре приложения. Все папки проекта содержат компоненты различных типов.
bit CLI — это основной движок для возможности повторного использования. С помощью инструмента Bit можно написать модульный код, а также управлять исходным кодом и зависимостями.
Рассмотрим еще один компонент. Фрагмент кода для компонента rss client.
//imports include a .js file because these are es6 modules (detailed later) import { eventRssItems, createEvent } from './events.js' import { createFeedKey } from './feed-key.js' // creates a client object with the startFeed, stopFeed, and poll functions // emitter - communication medium to dispatch events // data - array of url:string and name:string to identify channels export function createClient (emitter, data) { const feeds = {} const client = { // Receives a data object with name and url and starts to track it over time startFeed: function ({ name, url }) { const key = createFeedKey(name, url) // createFeedKey is the primary key for channels in the app. feeds[createFeedKey(name, url)] = feeds[key] || start(name, url, emitter) return feeds[key] }, // Receives a data object with name and url and stops tracking it. stopFeed: function ({ name, url }) { const key = createFeedKey(name, url) if (feeds[key]) { clearTimeout(feeds[key].timer) delete feeds[key] } }, // poll a feed one time poll: async function ({ name, url }) { return pollFeed(name, url, emitter) } } // start Object.values(data).map((value) => client.startFeed(value)) return client } // creates a timer for a feed which runs every fixedTimer milliseconds async function start (name, url, emitter) { const fixedTimer = 10 * 1000 // 10 seconds async function timerHandler () { await pollFeed(name, url, emitter) startData.timer = setTimeout(timerHandler, fixedTimer) } const startData = { name, url, timer: setTimeout(timerHandler, 0) } return startData } // using the RSSParser package parsed a specific feed. async function pollFeed (name, url, emitter) { const feedUrl = `https://cors-anywhere.herokuapp.com/${url}` // hack to avoid cors problem const parser = new window.RSSParser() const feed = await parser.parseURL(feedUrl) feed.name = name feed.url = url emitter.dispatchEvent(createEvent(eventRssItems, feed)) return feed }
Обратите внимание, что в этом компоненте присутствует инверсия контроля, а главные зависимости клиента получены в функции factory. Также используется функция setTimeout, которая вызывает себя в качестве основного timer для опроса feed. Это действие выполняется каждые 10 секунд для упрощения отладки.
Некоторые проблемы этого проекта:
- `customElements.define` является глобальным. Как было сказано выше, компоненты определены глобально. Помимо этого, во всех рассмотренных мной примерах метод define вызывается внутри модуля, что приводит к инкапсуляции и коллизии имен при росте базы кода компонента в приложении. В попытке переместить все это в одно место я создал компонент define-element.
- Не так легко использовать повторно. Чтобы повторно использовать в React компонент, нужно упаковать в него веб-компонент. Это необходимо для контроля над распространением событий и props.
- При работе с модулями es6 и выходе из node разрешение модуля трудно определить интуитивно. Можно ожидать, что папка преобразуется в index.js, если рассматривать ее как систему модулей. Однако если представить ее в качестве веб-сервера, возвращающего ассеты, ситуация приобретает смысл. Также добавление этих
.js
портит внешний вид кода.
Что мы рассмотрели?
Мы изучили прототип приложения RSS reader, а также его структурирование для повышения модульности. Мы узнали, почему стоит использовать веб-компоненты, а также как интегрировать их в приложение. И наконец, мы рассмотрели несколько проблем, связанных с использованием веб-компонентов.
Читайте также:
- Микросервисы. Руководство для начинающих
- Как создать инструмент командной строки в NodeJS
- Лучшие генераторы статических сайтов для React в 2019 году
Перевод статьи Doron Tsur: Prototyping with Web Components: Build an RSS Reader