Skip to content

Instantly share code, notes, and snippets.

@ivanzamarro
Created October 17, 2023 22:07
Show Gist options
  • Select an option

  • Save ivanzamarro/5b335b84f15bf590345dcf8720682aa0 to your computer and use it in GitHub Desktop.

Select an option

Save ivanzamarro/5b335b84f15bf590345dcf8720682aa0 to your computer and use it in GitHub Desktop.
React Style Encapsulation using Shadow DOM and Webpack

Style Encapsulation using Shadow DOM and Webpack

Objective: Ensure that styles from specific components don't bleed into the main DOM of the webpage, preserving the page's existing styles.

1. Initialization and Implementation of the Shadow DOM:

The code snippet below demonstrates how to initialize a React widget. This widget imports a stylesheet from an external library. The key here is that the styles from this external library don't affect the main page, thanks to the use of the Shadow DOM.

import React from "react";
import ReactDOM from "react-dom";
import ExternalStyles from "@library/styles/stylesheet.css";

let root;

function init({ context, rootElement }) {
    // Initiate the Shadow DOM
    const shadow = rootElement.attachShadow({ mode: "open" });    
    const renderInShadow = document.createElement("div");
    const styles = document.createElement("style");
    root = renderInShadow;
    styles.textContent = ExternalStyles.toString();
    
    rootElement.setAttribute("data-shadowed", "style-isolated-shadowed");
    shadow.appendChild(renderInShadow);
    shadow.appendChild(styles);
    
    // ... other initialization procedures ...

    // Signal that React has finished its rendering
    ReactDOM.render(
        // ... React components ...
        renderInShadow,
        () => {
            const event = new CustomEvent("ReactDOMReady", {
                detail: { message: "React component is now rendered!" },
                bubbles: true,
                cancelable: false,
            });
            window.dispatchEvent(event);
        }
    );
}

function dispose() {
    if (!root) {
        return;
    }

    ReactDOM.unmountComponentAtNode(root);
}

2. Webpack Configuration for Styling Encapsulation:

At the Webpack level, we'll design a custom loader. By leveraging the capabilities of the style-loader, we can ensure that styles are injected directly into the previously established Shadow DOM.

const shadowDOMLoader = {
    loader: "style-loader",
    options: {
        insert: function (linkTag) {
            window.addEventListener("ReactDOMReady", (evt) => {
                const parent = document.querySelector('[data-shadowed="style-isolated-shadowed"]').shadowRoot;
                parent.appendChild(linkTag);
            });
        },
    },
};

// Webpack configuration details
{
    test: /\.css$/i,
    exclude: /stylesheet\.css$/i,
    use: [shadowDOMLoader, "css-loader", /* other loaders... */],
}

Key Takeaways:

  • The initialization procedure crafts a Shadow DOM on the specified root element, ensuring the widget's content is safely encapsulated.
  • Through a tailored Webpack loader (shadowDOMLoader), styles are strategically placed inside the Shadow DOM rather than the main DOM. This insertion is prompted by the firing of the ReactDOMReady event.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment