<div> I should be black </div>

<a href="#"> I should NOT be <b>italic</b></a>

<p>Should not effect these <span id="ids">IDs</span>, <span data-attribute="true">data-attributes</span>, and <span class="classes">classes</span></p>

<hr>

<div id="someBoundary">
  <div> I should be green </div>

  <a href="#"> I should be <b>italic</b></a>

  <p>Should work with these <span id="ids">IDs</span>, <span data-attribute="true">data-attributes</span>, and <span class="classes">classes</span></p>

</div>

<template id="styleOutput" style="display:block"></template>

<b>Requires:</b> Chrome/Edge 93+
body {
  padding: 1rem;
}

#styleOutput {
  display: block;
  white-space: pre;
  font-family: monospace;
  margin: 2rem 0;
  padding: 1rem;
  background: #f0f0f0;
  overflow-x: auto;
}
View Compiled
import cssModule from "https://assets.codepen.io/17/test.css" assert { type: "css" };

// Stash the rules before modification
const beforeStyles = [...cssModule.cssRules]
  .map((rule) => rule.cssText + "\n")
  .join("");

/**
 * Add hashes to an adoptable stylesheet
 * @param {CSSStyleSheet} sheet - CSS Module imported or constructed stylesheet
 * @param {NodeList} scope - A node in which to apply data-hash attributes to all children
 **/
function hashinator(sheet, scope = document.body) {
  let newSheet = "";

  // Generate a random hash
  const array = new Uint32Array(1);
  window.crypto.getRandomValues(array);
  const hash = array[0];

  // Apply hash to all elements in scope
  scope
    .querySelectorAll("*")
    .forEach((el) => el.setAttribute(`data-${hash}`, ""));

  // Loop through all the rules
  [...sheet.cssRules].forEach((rule) => {
    // Append a data-hash attribute
    // TODO: Pretty naïve rule parser
    rule.selectorText = rule.selectorText
      .split(" ")
      .map((part) =>
        /[a-zA-Z]/.test(part.charAt(0)) || // elements
        part.startsWith("#") || // ids
        part.startsWith(".") // classes
          ? part + `[data-${hash}]`
          : part.startsWith("[") // attrs
          ? `[data-${hash}]` + part
          : part
      )
      .join(" ");
    // The cssText gets automatically augmented, so we can push it into the newSheet
    newSheet += rule.cssText;
  });

  // Now we have all the udpate rules in the newSheet, update the stylesheet
  sheet.replaceSync(newSheet);
  return sheet;
}

// Adopt the new stylesheet
document.adoptedStyleSheets = [hashinator(cssModule, someBoundary)];

// HOUSEKEEPING: Demo Before & After CSS
// Render before & after
styleOutput.textContent = `BEFORE:\n${beforeStyles}\n\nAFTER:\n`;

document.adoptedStyleSheets.forEach((sheet) => {
  [...sheet.cssRules].forEach((rule) => {
    styleOutput.textContent += `${rule.cssText}\n`;
  });
});

// You could also make someStylesheet this way...

// const styleSheet = document.createElement('style')
// styleSheet.innerHTML = `
// div { color: green }
// a > b { font-style: italic }
// #ids { font-weight: bold; }
// [data-attribute]::before { content: "["; }
// [data-attribute]::after { content: "]"; }
// .classes { font-family: fantasy; }
// `
// document.head.appendChild(styleSheet)

// const styleSheet = new CSSStyleSheet();
// const styles = `
// // div { color: green }
// // a > b { font-style: italic }
// // #ids { font-weight: bold; }
// // [data-attribute]::before { content: "["; }
// // [data-attribute]::after { content: "]"; }
// // .classes { font-family: fantasy; }
// // `;
// styleSheet.replaceSync(styles);
// document.adoptedStyleSheets = [styleSheet]

// import styles from "https://assets.codepen.io/17/test.css" assert { type: "css" };

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.