Хороший случай для Eval в JavaScript
Или: Как генерировать динамические html-теги внутри шаблонов lit-html? eval
иногда путают с evil
. Мы также иногда слышим, что есть случаи, когда это необходимо. Это один из таких случаев...
Проект lit
— это фреймворк для работы с веб-компонентами. В ярких версиях 1.x и 2.x мы сильно полагались на lit
. Пока мы усердно работаем над Vivid 3.x, мы по-прежнему поддерживаем и добавляем функции в 2.x
.
Одной из таких функций было добавление тега H вокруг компонента панели расширения. Поскольку панели расширения могут использоваться в качестве заголовков (и в большинстве случаев так и есть), нам необходимо настроить их семантически как заголовки.
Мы решили, что уровень H по умолчанию равен 3 (<h3>
). Этого недостаточно, потому что панель расширения также может быть подзаголовком любого другого уровня заголовка:
<h1>Is there alien life out there?</h1>
<expansion-panel>
<h2><button>What is life?</button></h2>
</expansion-panel>
<expansion-panel>
<h2><button>What is alien?</button></h2>
</expansion-panel>
Вот видите — ставить h3
в этом случае было бы неправильно. То же самое относится и к панелям расширения под h3
, h4
и h5
.
Кто-то скажет использовать div
с aria-level
и role=header
. Хотя это сработало бы, есть цитата из MDN (и других мест):
Принимая все это во внимание, мы хотели сохранить лучшие практики MDN, но при этом разрешить использование динамических тегов.
Попытка настроить динамический тег с помощью lit-html
Как работают освещенные шаблоны?
Давайте сначала объясним, как работают шаблоны в lit
, не вдаваясь в подробности.
lit-html
— это служебная функция, которая превращает наши строки в живые шаблоны. Затем метод рендеринга lit
«волшебным образом» прослушивает изменения свойств в шаблоне и соответствующим образом обновляет представление.
Итак, у вас будет функция render
, которая выглядит примерно так:
Заголовок панели будет контентом, который мы хотели бы обозначить как заголовок.
Как я уже упоминал, мы хотели обернуть его h3
по умолчанию, поэтому он может выглядеть примерно так:
Как не менять динамически имя тега в lit-html?
Второе требование состояло в том, чтобы иметь динамику h tag
. Это означает, что мы хотели бы иметь редактируемое пользователем свойство, которое изменит уровень заголовка. Глядя на шаблон, это может показаться легкой задачей. Просто установите переменную внутри строки шаблона рядом с h
:
Как чудесно это выглядит! И как эффектно это не удается :)
Видите ли — lit-html
и система шаблонов плохо работает со статическими данными. Я избавлю вас от жутких подробностей, но ошибка, которую мы получаем, заключается в том, что он не может проанализировать шаблон - и это происходит во время выполнения! (ой - представьте, что было бы, если бы у нас не было тестов)
Пытаюсь использовать unsafeHTML
Знаете, как говорят: «Не можешь с ними бороться — используй их методы». Освещенный метод под названием unsafeHTML
обещает следующее: Renders the result as HTML, rather than text.
Так как документации не хватало, я обратился к официальной документации - модульным тестам:
Применение этого к нашему коду выглядит так:
Ок — сборка прошла. Тесты провалились (но кто смотрит на тесты, верно?). Почему они потерпели неудачу? Вот как это выглядит при использовании этого решения:
Я не буду вдаваться в подробности, почему это не сработало — достаточно сказать, что способ lit-html
рендеринга своих шаблонов не допускает «свободных» концов HTML-тегов.
Мы вернулись к нулевой точке. Мы попробовали что-то, что не сработало… пора двигаться дальше!
Пытаюсь использовать unsafe
Static
Нашей третьей попыткой было использовать другую встроенную функцию lit
под названием unsafeStatic
. Опять же, документация не слишком полезна, так что... вы знаете... модульные тесты:
Кажется, наша проблема решена. Давайте просто реализуем это в нашем коде:
И тогда… мы получаем это:
По-видимому, хотя их тесты работали с рендером unit-tests
, в «реальном мире» это не сработало. Запуск render
самостоятельно показал мне то же самое.
Итак… unsafeStatic
не работает. Что дальше?
Как использовать Eval
для создания динамических тегов в шаблонах lit-html
В нашей предыдущей неудачной попытке мы попытались динамически добавить headerLevel
следующим образом:
<h${this.headerLevel}>
${this.renderPanelHeader()}
</h${this.headerLevel}>
Попробуем решить это по-другому. Поскольку мы можем вызывать функции рендеринга, которые возвращают объекты TemplateResult
, мы можем создать функцию, которая возвращает eval
ed TemplateResult
следующим образом:
Наше решение теперь возвращает динамический шаблон с именем динамического тега для нашего заголовка:
Давайте все это свяжем
Кажется, нам удалось то, что мы хотели. У нас внутри lit-html
есть динамический тег, и мы даже использовали eval
...
Мы нашли еще одну «маленькую» проблему. При запуске наших визуальных регрессионных тестов пользовательского интерфейса (которые поставляются в комплекте с веб-пакетом) мы получили следующую ошибку:
Та же ошибка обнаружилась в нашем сборнике рассказов (снова веб-пакет…).
О, нет! Теперь наши компоненты нельзя использовать в пакетных приложениях (а это примерно… 99% приложений, использующих Vivid).
Что делать? Что делать? Я знаю — давайте впадать в депрессию и сдаваться!
Или… мы могли бы придумать другое неприятное решение!
Если мы просто установим html
в качестве локальной переменной и будем использовать локальную переменную, она должна быть доступна во время выполнения.
Таким образом, добавление const safeHTML = html;
в начало файла, а затем использование safeHTML
вместо html
решает нашу проблему.
На самом деле переменная может быть проблематичной, так как углифификатор/минификатор может изменить ее. Мы могли бы использовать статический метод для самого класса, чтобы убедиться, что он более устойчив. Это зависит от того, насколько «агрессивен» ваш углификатор/минификатор…
Не моя проблема на данный момент :)
Краткое содержание
Доступность — важный момент. Мы не хотим никого оставлять позади и делаем так, чтобы наши приложения были доступны как можно большему количеству людей.
Использование правильной семантики в вашем HTML — большая часть этого. Вот почему команда vivid
постоянно работает над добавлением специальных возможностей в компоненты нашей системы дизайна.
В нашем случае мы хотели убедиться, что expansion panels
получает правильную семантику в качестве заголовков. Поскольку мы хотели, чтобы потребители могли определять уровень заголовка, мы хотели, чтобы тег h
был динамическим.
lit-html
было не очень хорошо с этим, поэтому, попробовав несколько способов сделать это, мы обнаружили, что с помощью eval
мы можем создать динамический тег внутри lit-html
.
Затем мы обнаружили еще одну небольшую проблему: при объединении нашего компонента с другими приложениями (в нашем случае с визуальными регрессионными тестами пользовательского интерфейса и сборником рассказов) мы обнаружили, что использование eval
сложно. Он не найдет импортированный html
, поскольку выражение eval
оценивается во время выполнения, а упаковщики имеют тенденцию давать импорту свои собственные имена...
Мы решили эту проблему, создав локальное свойство в классе, и это помогло. У нас был динамический тег, который также работал с упаковщиками.
Операция прошла успешно.
Большое спасибо Rachel B. Tannenbaum и Yinon Oved за вдохновение и обзор этой статьи.