Улучшение Vanilla веб-компонентов
Вы уже знакомы с подключением внешних сторонних веб-компонентов в приложении Enhance? Недостатком этих внешних компонентов является то, что они не отображаются на стороне сервера, поэтому они страдают от ужасной вспышки нестандартного пользовательского элемента (FOUCE), и если что-то пойдет не так с JavaScript, они вообще не будут отображаться!
В этом посте мы покажем вам, как улучшить другой веб-компонент Vanilla, wc-icon-rule
, чтобы он был полностью развернут на клиенте, избегая использования теневого DOM.
wc-icon-rule
wc-icon-rule
создает для вас горизонтальное правило с изображением в центре, которое разбивает линию. Изображение предоставляется в качестве слота для веб-компонента. Этот компонент представляет собой чисто презентацию без какой-либо интерактивности на стороне клиента, что сделает преобразование в улучшенный компонент довольно простым.
Преобразование в Enhance компонент
Хотя wc-icon-rule
- это небольшой веб-компонент, все равно необходимо заняться преобразованием, как если бы это был более крупный и сложный веб-компонент, чтобы показать вам, как мы бы справились с этой задачей.
Шаг 1: Создайте новый компонент Enhance
Во-первых, нам понадобится новый компонент Enhance для представления wc-icon-rule
. Для этого нам нужно создать новый файл с именем app/elements/wc-icon-rule.mjs
. Содержимое файла, для начала, является:
export default function Element ({ html, state }) {
return html``
}
Это основа каждого компонента Enhance.
Шаг 2: Добавьте текущее правило wc-icon-rule
Далее мы возьмем исходный код для wc-icon-rule
и поместим его в тег script
в нашей функции рендеринга html
.
export default function Element ({ html, state }) {
return html`
<script type="module">
export class WCIconRule extends HTMLElement {
constructor () {
super()
this.__shadowRoot = this.attachShadow({ mode: 'open' })
const template = document.createElement('template')
template.innerHTML = WCIconRule.template()
this.__shadowRoot.appendChild(template.content.cloneNode(true))
}
connectedCallback () {
this.setAttribute('role', 'presentation')
for (const child of this.children) {
child.setAttribute('role', 'none')
}
}
static template () {
return \`
<style>
:host {
display: block;
overflow: hidden;
text-align: center;
}
:host:before,
:host:after {
content: "";
display: inline-block;
vertical-align: middle;
position: relative;
width: 50%;
border-top-style: var(--hr-style, solid);
border-top-width: var(--hr-width, 1px);
border-color: var(--hr-color, #000);
}
:host:before {
right: var(--space-around, 1em);
margin-left: -50%;
}
:host:after {
left: var(--space-around, 1em);
margin-right: -50%;
}
::slotted(*) {
display: inline-block;
width: var(--width, 32px);
height: var(--height, 32px);
vertical-align: middle;
}
</style>
<slot></slot>
\`
}
}
customElements.define('wc-icon-rule', WCIconRule)
</script>`
}
Сейчас мы рендерим компонент на сервере, но мы по-прежнему отправляем все это в виде тега script
, поэтому мы не исправили проблему с FOUCE.
Шаг 3: Извлеките свои стили
Тег style
для компонента отображается в функции template
. Это совершенно нормально, но с расширенной возможностью добавлять стили к тегу head
мы можем извлечь его из тега script
компонента.
Итак, давайте переместим этот тег style
над нашим тегом script
.
export default function Element ({ html, state }) {
return html`
<style>
:host {
display: block;
overflow: hidden;
text-align: center;
}
:host:before,
:host:after {
content: "";
display: inline-block;
vertical-align: middle;
position: relative;
width: 50%;
border-top-style: var(--hr-style, solid);
border-top-width: var(--hr-width, 1px);
border-color: var(--hr-color, #000);
}
:host:before {
right: var(--space-around, 1em);
margin-left: -50%;
}
:host:after {
left: var(--space-around, 1em);
margin-right: -50%;
}
::slotted(*) {
display: inline-block;
width: var(--width, 32px);
height: var(--height, 32px);
vertical-align: middle;
}
</style>
<script type="module">
export class WCIconRule extends HTMLElement {
constructor () {
super()
this.__shadowRoot = this.attachShadow({ mode: 'open' })
const template = document.createElement('template')
template.innerHTML = WCIconRule.template()
this.__shadowRoot.appendChild(template.content.cloneNode(true))
}
connectedCallback () {
this.setAttribute('role', 'presentation')
for (const child of this.children) {
child.setAttribute('role', 'none')
}
}
static template () {
return \`
<slot></slot>
\`
}
}
customElements.define('wc-icon-rule', WCIconRule)
</script>`
}
Если вы проверите свою страницу в инструментах разработки вашего браузера, вы заметите тег style
в теге head
вашей страницы. Правила CSS в теге style
компонента wc-icon-rule
были перенесены на страницы тег style
. Пользователь заметит, что Enhance слегка переписывает ваши правила CSS, так что :host
становится правилом wc-icon-rule
, чтобы правильно настроить таргетинг на все правила wc-icon-rule
на вашей странице.
Шаг 4: Удалите Shadow DOM
Как упоминалось ранее в этом посте, вам не нужен Shadow DOM для такого компонента, как этот. Давайте избавимся от нашей зависимости от теневого DOM.
Во-первых, полностью удалите функцию constructor
. Нам это не нужно. Далее давайте переместим <slot></slot>
из нашей функции template
и включим его в тег script
. Наконец, удалите остальную часть функции template
, поскольку теперь она, по сути, не работает.
Ваш код должен выглядеть следующим образом:
export default function Element ({ html, state }) {
return html`
<style>
:host {
display: block;
overflow: hidden;
text-align: center;
}
:host:before,
:host:after {
content: "";
display: inline-block;
vertical-align: middle;
position: relative;
width: 50%;
border-top-style: var(--hr-style, solid);
border-top-width: var(--hr-width, 1px);
border-color: var(--hr-color, #000);
}
:host:before {
right: var(--space-around, 1em);
margin-left: -50%;
}
:host:after {
left: var(--space-around, 1em);
margin-right: -50%;
}
::slotted(*) {
display: inline-block;
width: var(--width, 32px);
height: var(--height, 32px);
vertical-align: middle;
}
</style>
<script type="module">
export class WCIconRule extends HTMLElement {
connectedCallback () {
this.setAttribute('role', 'presentation')
for (const child of this.children) {
child.setAttribute('role', 'none')
}
}
}
customElements.define('wc-icon-rule', WCIconRule)
</script>
<slot></slot>`
}
Мы по-прежнему оставим connectedCallback на месте. Нам больше не нужен Shadow DOM, но мы все еще можем улучшить наши веб-компоненты, отображаемые на стороне сервера, с помощью JavaScript, чтобы добавить интерактивную функциональность.
Заключение
Смогли бы вы справиться со сжатием этих четырех шагов в один шаг, но хотелось бы четко объяснить, почему мы пишем компоненты так, как мы делаем с Enhance.
Хотя по сути нет ничего плохого в том, как написаны веб-компоненты Vanilla JS, изменив способ их доставки в браузер, вы можете избежать распространенных проблем с веб-компонентами, таких как FOUCE, и уменьшить общий объем JavaScript на вашей странице, что важно для производительности и доступности.