Вы услышите много разговоров о разделении логики или разделения ответственности, но не так часто слышите о разделении стиля.

Рассмотрим следующий сценарий:

Клиент хочет, чтобы вы создали виджет, который он может продать тысячам сайтов электронной коммерции для выполнения какой-либо задачи, связанной с его бизнесом, но вот в чем загвоздка! Они не хотят, чтобы на стиль виджета влияли стили хостинга. Конечно, вашим первым подходом будет использование iframe.

Но затем они поражают вас дополнительными функциями, такими как чтение определенного значения из локального хранилища хостинг-сайта или взаимодействие с другим элементом на хостинг-сайте для бесшовной интеграции… ну, это выбрасывает iframe из окна, но как вы можете полностью защитить свой виджет из самых навязчивых правил CSS хостинговых сайтов, таких как * или div { ... } ?

Затишье перед бурей

Рассмотрим этот приятный компонент баннера/ленты, удобно размещенный внутри веб-сайта React или просто встроенный в div.

Но нас бы здесь не было, если бы хост-сайт придерживался лучших практик таблиц стилей, тщательно разделял и идентифицировал стили на основе конкретных элементов своего сайта и пространства имен.

Вместо этого они решают однажды написать следующие правила CSS.

И теперь ваш красивый баннер/лента выглядит так:

Эти правила таблицы стилей общего назначения переопределили некоторые важные части вашего компонента, и, поскольку вы не можете контролировать код на хостинговом сайте, не было бы ничего страшного в том, чтобы пытаться координировать свои действия с каждым хостинговым сайтом и объяснять им, как они должны это делать. написать CSS для своего сайта, чтобы не влиять на ваш компонент. Просто и просто этого сделать нельзя, и это вполне понятно и приемлемо.

Теперь, когда мы определили наш вариант использования, давайте посмотрим, что мы можем сделать, чтобы предотвратить такую ​​масштабную катастрофу.

Войдите в теневой стих (или дом)

Так что же такое Shadow-DOM?

Важным аспектом веб-компонентов является инкапсуляция — возможность сохранить структуру, стиль и поведение разметки скрытыми и отделенными от остального кода на странице, чтобы разные части не конфликтовали, а код оставался красивым и чистым. API Shadow DOM является ключевой частью этого, предоставляя способ присоединения скрытого отдельного DOM к элементу.

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

Процедура здесь довольно проста, мы внедряем наш основной компонент ReactJS не в обычный DOM, а в теневой DOM следующим образом:

const customRootElement = document.getElementById("custom-root");
customRootElement!.attachShadow({ mode: "open" });
const customRoot = ReactDOM.createRoot(customRootElement.shadowRoot);
customRoot.render(
 <StrictMode>
   <CustomApp />
 </StrictMode>
);

Теперь ваш баннер/лента спрятаны в тени и защищены от любых внешних изменений в правилах таблиц стилей. Хотя и не без затрат…

Ловушка

Наш код теперь может быть в целости и сохранности внутри теневого дома, однако это означает, что все стили также не учитываются и не могут получить доступ к нашему коду, даже такие библиотеки, как MUI (материальный пользовательский интерфейс), будут отображать свои компоненты полностью без стилей.

Однако, как всегда, даже для этого есть решение, есть много способов справиться с этой ситуацией, мы покажем один из них, который я считаю самым простым из всех.

При условии, что ваш CSS находится в одном или нескольких файлах, предлагаемое нами решение состоит в том, чтобы связать все правила CSS внутри файла .ts в виде переменной javascript/typescript и загрузить его в теневой дом при инициализации первого элемента.

Чтобы объединить все .css файла в один, мы использовали простой bash-скрипт.

echo export const inlineStyle = \` > ./src/style.ts
awk 1 src/*.css >> ./src/style.ts
echo \` >> ./src/style.ts

Это создаст единственную переменную, которую мы можем использовать для внедрения наших стилей внутри shadow-dom, чтобы их можно было применить к нашему компоненту.

Так

Сначала мы импортируем его import { inlineStyle } from "./style.ts"

затем мы добавляем его в теневой дом

<style>{inlineStyle}</style>

И это позаботится о большинстве правил CSS, однако есть некоторые, требующие особой обработки, такие как объявления переменных CSS и загрузка шрифтов.

const container = document.getElementById("custom-root");
const styleSlot = document.createElement("section");
styleSlot.id = "style-section";
styleSlot.innerHTML = "<style></style>";
// append the styleSlot inside the shadow
container?.shadowRoot?.appendChild(styleSlot);
const styleDom = container!.shadowRoot!.getElementById("style-section");

Что делает вышеприведенное, так это создает элемент <section> и добавляет его внутрь элемента <style>, в котором будут размещаться наши правила таблицы стилей.

Если мы также хотим включить шрифты Google и переменные CSS, мы можем сделать следующее:

if (styleDom) {
// add fonts
const family = "Rubik";
var fontFamilyStyle = ``;
let link = document.createElement('link');
link.type = "text/css";
link.rel = "stylesheet";
link.href = 'https://fonts.googleapis.com/css2?family=' + family.replace(' ', '+') + ':wght@300;400;700&display=swap';
document.head.appendChild(link);
fontFamilyStyle = `${family}`;

// add the CSS variable values
styleDom!.querySelector("style")!.innerHTML = `
:host {
 --white: #fff;
 --button-font-color: #fb703c;
 --font-title-color: #fb703c;
 --font-subtitle-color: #48bbb5;
 --font-family: ${fontFamilyStyle};
 }`;
}

Вы могли заметить, что мы использовали :host вместо :root, потому что корневой селектор не работает внутри shadow-dom.

На этом мы завершаем ту часть, где мы успешно внедрили наш компонент и его правила таблицы стилей в shadow-dom, но как насчет пользовательских компонентов, таких как Material UI.

Тень встречает MUI

В общем, MUI поддерживает теневой дом и даже имеет вспомогательное руководство на своем веб-сайте.

https://mui.com/material-ui/guides/shadow-dom/#main-content

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

Это выглядит так:

Приведенный выше скрипт создаст еще один <div> внутри теневого дома, а затем внедрит в него стили MUI по умолчанию, любая дальнейшая настройка может происходить либо непосредственно на компоненте через атрибут sx, либо через общий файл styles.ts.

Мы используем его на самом внешнем компоненте, который включает в себя все наши компоненты MUI.

<WrappedComponent>
    <OfferCardDemo />
</WrappedComponent>

История до сих пор

Используя методы, представленные в этой статье, мы смогли отделить стили пользовательского компонента React от его хостинг-сайта.

Компонент сохраняет свои исходные стили, и можно видеть, что правила CSS живут внутри теневого дома как для обычных правил стиля CSS, так и для правил стиля компонента MUI.

Вы можете просмотреть окончательный код здесь: https://github.com/7Linternational/React-shadowdom

Не стесняйтесь присылать мне любые комментарии или альтернативные способы работы с shadow-dom и ReactJS.

Если вам нужна помощь с ReactJS/Пользовательскими компонентами/ или какой-либо индивидуальной интеграцией/эффектом/анимацией, взгляните на наши решения для создания веб-сайтов по адресу«https://7linternational.com/services/website- разработка/"

Наслаждайтесь!