Снова и снова я сталкиваюсь со следующей проблемой: CMS предоставляет нам простой HTML из поля форматированного текста для использования в нашем приложении React.

Конечно, мы можем просто отрендерить div и передать ему dangerouslySetInnerHtml, но это оставляет нам несколько предостережений:

  • Нажатие на тег a, который ведет на внутреннюю страницу, вызывает полную загрузку страницы.
    В настройках SPA это не то поведение, которое нам нужно, мы используем причудливые фреймворки (next, gatsby и т. д.), чтобы выполнять всевозможные предварительные загрузки и выборки на стороне клиента, а полная загрузка страницы сводит на нет все это.
    Это определенно ухудшит пользовательский опыт.
  • Придется иметь дело со стилем каждого тега.
    Теперь у вас есть div с форматированным текстом HTML внутри, но как насчет его дочерних элементов?
    Скорее всего, вам нужно будет так или иначе стилизовать их.
    Вы можете настроить некоторый дополнительный CSS для нацеливания на них, но поскольку это просто контент, вы, вероятно, теперь выполняете двойную работу, потому что у вас, вероятно, уже есть общие компоненты для ваших заголовков, ссылок, текста, изображений и т. д. только был способ просто повторно использовать компоненты, которые мы уже используем внутри компании?
  • Поздравляем, теперь у вас есть XSS-эксплойт!
    Вы когда-нибудь задумывались, почему dangerouslySetInnerHtml начинается с ... опасно? Это (и я цитирую) потому что легко непреднамеренно подвергнуть ваших пользователей атаке межсайтового скриптинга (XSS).
    Это означает, что если кому-то удастся захватить нашу CMS и внедрить всевозможные нежелательные теги (например, тег <script>), он получит полный доступ к HTML, который отображается на нашем сайте.
    Предоставление возможности испортить веб-сайт, украсть информацию о сеансе, перенаправить на другой веб-сайт в целом и всевозможные неприятности.
  • CMS полностью контролирует то, что ей нужно разместить на вашем сайте.
    Спустя много времени после реализации проекта вы решаете проверить сайт, только чтобы узнать, что клиент решил поменяться это шрифты для красивого Comic Sans с прекрасным ярко-синим оттенком, который абсолютно нечитаем и идет вразрез со всем, что вы отстаиваете.
    Хотя, к счастью, в наши дни я в основном работаю с гораздо большим количеством настраиваемых редакторов WYSIWYG, некоторым из нас не так повезло, и им приходится иметь дело с картами, которые нам раздают, но при этом они все еще хотят найти способ защитить клиентов от самих себя. .

Поиск чего-то более декларативного

Я столкнулся с response-html-parser, который концептуально в основном является тем, что я ищу, но он все еще был слишком функциональным и низкоуровневым в фактическом использовании. Поскольку я привык думать, реагируя, я хотел чего-то более декларативного.

Теперь рассмотрим следующее:

Все, что нам нужно для использования этого компонента, - это предоставить саму строку HTML и простое сопоставление имен тегов с нашими собственными компонентами.

  • Если мы хотим обеспечить полную передачу от одного тега HTML к компоненту, мы просто предоставляем сам компонент.
  • Если мы (например) хотим предоставить дополнительные свойства помимо атрибутов HTML, мы можем просто написать небольшой компонент-оболочку (в случаях от h1 до h5 в приведенном выше примере).
  • Мы также решили общую проблему с внутренними ссылками, написав еще один небольшой компонент-оболочку, который проверяет, начинается ли URL с /, и, если да, вместо этого отображает их через компонент Link нашего маршрутизатора.

Кроме того, если мы используем машинописный текст, мы получаем полное автозаполнение для атрибутов указанного элемента HTML.

Сочетаемость

Рассмотрим следующий пример:
У нас есть запись статьи в нашей CMS, которая имеет следующие (расширенные) поля:
поле «вступление» и поле «текст», содержащее фактический текст содержания статьи.
Абзацы вступления должны быть отображены немного более крупным шрифтом (чтобы отличать его от содержания статьи).

Теперь нам придется повозиться со стилем контейнера-оболочки вступления и попытаться настроить таргетинг на абзацы внутри него с помощью CSS, верно? Неправильно!
Мы можем просто повторно использовать сопоставление, которое мы уже используем для текста статьи, но переопределить значение для p:

Безопасность и предсказуемость

Если наш HTML-код содержал какие-либо теги, которых нет в нашем сопоставлении, они будут проигнорированы! Это то, что вы, скорее всего, предпочтете, потому что ваше приложение разработано (как с точки зрения дизайна, так и с точки зрения программного обеспечения) для поддержки только определенных тегов.
Если текстовый редактор решает добавить что-нибудь забавное (теги сценария…), лучше не обращать на это внимания.

Однако у нас могут быть некоторые теги, которые мы хотим отображать «как есть»: в этом случае вместо предоставления компонента мы можем передать null.

Теперь он больше не будет игнорироваться, но у вас по-прежнему будет белый список «разрешенных» тегов.

В качестве альтернативы, если вы хотите, чтобы это поведение применялось ко всем элементам, которых нет в нашем сопоставлении тегов, есть свойство acceptUnknown:

Возьми!