Web-компоненты и hugo
И вот что в итоге получилось
Да это я веб-компонент, без поддержки маркдауна и прочего...
Хотя бы вложенный html есть и то хорошо.
При этом все по спецификации. Ссылки внутри слот компонента стилизированы через CSS от темы, пример: lit-element
Если вы взглянули под спойлер, то уже увидели, что все грустно. Если нет - поехали смотреть на код, он простой, разбирать его и думать, что же пошло не так.
Написали мы значит на коленке такой вот простой код с применением библиотеки lit-element:
import { LitElement, property, customElement, html, css, internalProperty } from 'lit-element';
import { classMap } from 'lit-html/directives/class-map';
@customElement('nitridan-spoiler')
export class Spoiler extends LitElement {
@internalProperty()
private collapsed = true;
@property()
public hideText: string = 'Hide';
@property()
public showText: string = 'Show';
private expand(e: MouseEvent): void {
e.preventDefault();
e.stopPropagation();
this.collapsed = false;
}
private collapse(e: MouseEvent): void {
e.preventDefault();
e.stopPropagation();
this.collapsed = true;
}
public render() {
return html`
<div>
${this.collapsed
? html`<a href='#' @click='${this.expand}'>${this.showText}</a>`
: html`<a href='#' @click='${this.collapse}'>${this.hideText}</a>`}
<div class='${classMap({content: true, hidden: this.collapsed, visible: !this.collapsed})}'>
<slot></slot>
</div>
</div>
`;
}
static get styles() {
return css`
a {
text-decoration: none;
}
.content {
overflow: hidden;
transition: max-height 0.5s;
}
.content.hidden {
max-height: 0px;
}
.content.visible {
max-height: var(--nitridan-max-spoiler-height, 500px);
}
`;
}
}
Пойдем с хорошего, а его много:
- lit-element это очень простая библиотека, под ECMAScript + TypeScript, которая работает как с декораторами так и без них, не тяжелый фреймворк.
- Учитывая, что веб компоненты в здравом уме в ИЕ тащить никто не будет (пожалуйста, не надо, они там тормозят) бандл из билиотеки + 1-2 компонента всегда будет небольшим. Без gz ~20-30кб.
- Производительность уже меряли, там все не так уж хорошо, гуглите и читайте сами, меряйте тоже сами, но она достаточная если мы не говорим о гриде на тысячи ячеек с кастомными веб-компонентами.
- Я встроил таки веб-компонент в блог на движке hugo. То есть получилось, что все работает из коробки на базовых стандартах. И мне даже не пришлось включать какой-то бандлинг. Я просто собрал веб-компонент вебпаком. Закинул как статический файл в блог и подключил.
- Инкапсуляция на саму разметку компонента работает (забудем о шрифтах). Ссылки на странице не красятся в зависимости от того ходили по ним или нет, а ссылки веб-компонента выглядят убого и перекрашиваются.
- Мы теоретически даже можем встраивать scalajs в сайт написанный на classic asp + vbscript😵
А теперь поговорим о спорном:
- Во-первых половинчатость инкапсуляции. О ней читать нужно обязательно. Шрифт у нас не дефолтный какой-то. Элементы в слотах отрисованы стилями парент пейджи.
- Во-вторых вот этот shadowRoot это конечно хорошо и если представить, что мы прям вылизали компонент, описали все переменные для последующей кастомизации и дали его людям в руки это круто. Но скажите честно. Где вы видели third-party работающее без напильника и массы изменений? Где-то shadowRoot помогает нам, где-то нет. Но если мы верстаем страницу целиком сами наверное брать чужой компонент с недовыставленными переменными (как пример выше) это скорее плохо.
- Интеграция с другими фреймворками далека от идеала, я не смог нормально завести markdown внутри слота компонента с hugo. Зато с html проблем нет. Но не хочу я писать html😡
Ну и собственно как включается эта штука в hugo
<script src="/js/components.js"></script>
<nitridan-spoiler showText="{{ .Get `showText` }}" hideText="{{ .Get `hideText` }}">
{{ .Inner }}
</nitridan-spoiler>
Рзбираем таки код компонента и lit-element
- @customElement(‘nitridan-spoiler’) - этот декоратор просто превращается в customElements.define, читать тут: https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements
- @property() помечает свойства как “реактивные”. То есть когда оно меняется - мы делаем перерисовку разметки методом render.
- @internalProperty() - смотри выше, но для того, что не нужно выставлять наружу(чертова инкапсуляция, зачем она вообще нужна? Все public и не паримся). То есть нужно для хранения внутреннего состояния.
- Внутри render мы используем библиотеку lit-html. Авторы говорят, что она очень быстро все отрисовывает без всякого виртуал дома опираясь на современные API новых браузеров. Короче не запускайте с тонной полифилов в IE. Умрет.
- static get styles() - авторы библиотеки явно верят в CSS in JS. и предлагают описывать стили помещаемые в shadowRoot прямо в TS/JS файл. Вроде идея неплохая, но подсветка кода и отступы в NVIM у меня так и не завелись. Короче эти строчные литералы в коде боль.
- И того получается, что каждый раз, когда мы меняем ивент хендлерами свойство внутреннего сотояния происходит перерисовка компонента. Браузер вычисляет, что поменять нужно только ссылку и классы контейнера контента. Уже скомпилированный stylesheet применяется бразуером при смене стилей. Как-то в теории так это работает.
Так когда это надо?
Я знаю, что есть масса людей верящих в веб-компоненты. Но когда мы пишем сайты/приложения, мы пытаемся сформировать консистентную тему. Написанные кем-то компоненты тяжело и больно в это вписываются. Писать на них целиком проект тоже выходит больно. Наилучшее применение на мой взгляд для них:
- Расширение легаси кода вроде asp.net webforms и древнее. Мы можем легко написать современный компонент и внедрить в древний сайт.
- Для платформ вроде SalesForce где нужно расширять чужую верстку своими дополнениями. И эта чужая верстка может часто как-то изменяться.
- Быстрое прототипирование. Когда дизайн далек от окончания и не совсем понятно как этим в итоге будут пользоваться.
Буду ли я дальше экспериментировать с веб-компонентами и искать им практическое применение?
Однозначно, да. На данный момент у нас есть масса языков, которые можно скомпилировать под бразуер. Есть масса никак нормально несовместимых фреймворков. Web-компоненты выглядят как путь к упорядочиванию этого всего и реальный шанс постепенно оживлять древние страницы написаные еще в далекие 90-е. Да сейчас это все не развито. Но развивается то где есть энтузиасты. Собственно потому я лично буду смотреть в эту сторону в отличие от реально непрактичных мертвых технологий.