<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" };
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.