Skip to content

Instantly share code, notes, and snippets.

@samthor
Last active August 2, 2022 14:01
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samthor/ee78b434b0f9aa525c5d235979b830aa to your computer and use it in GitHub Desktop.
Save samthor/ee78b434b0f9aa525c5d235979b830aa to your computer and use it in GitHub Desktop.
CSS Modules plugin for Rollup
import fs from 'fs';
// as per https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/master/CSSModules/v1Explainer.md
export default function cssModules() {
return {
name: 'css-modules',
async load(id) {
if (!id.endsWith('.css')) {
return;
}
const raw = await fs.promises.readFile(id, 'utf-8');
const encoded = JSON.stringify(raw);
return `
const sheet = new CSSStyleSheet();
sheet.replaceSync(${encoded});
export default sheet;
`;
}
};
}
/**
* @fileoverview Polyfill for adoptedStyleSheets and friends on ShadowRoot only.
*
* This ensures that <style> nodes are prepended to a ShadowRoot for corresponding CSSStyleSheet
* instances set under adoptedStyleSheets. It doesn't hide these <style> nodes from introspection
* in any way (i.e., patching DOM methods), but is just useful when .innerHTML is updated.
*/
const attachedTo = Symbol('attachedTo'); // on CSSStyleSheet, set of live nodes
const styleNode = Symbol('styleNode'); // backing <style> for CSSStyleSheet
const adoptedStyleSheets = Symbol('adopted'); // backing for array
const styleSheetOwner = Symbol('sso'); // which CSSStyleSheet owns <style>
const instantiatedAdoptedStyleSheets = Symbol('real'); // sheets created for this node
const adoptedStyleSheetsObserver = Symbol('observer');
let hasConstructableStyleSheets = false;
try {
new CSSStyleSheet();
hasConstructableStyleSheets = true;
} catch (e) {
const realCSSStyleSheet = window.CSSStyleSheet;
// This isn't a constructor, just wraps to return a real CSSStyleSheet.
window.CSSStyleSheet = function interceptCSSStyleSheet(css) {
const style = document.createElement('style');
style.textContent = css;
document.documentElement.appendChild(style);
const sheet = style.sheet;
sheet[attachedTo] = new Set();
sheet[styleNode] = style;
style.remove();
return sheet;
};
// Polyfill methods on real CSSStyleSheet.
realCSSStyleSheet.prototype.replaceSync = function(css) {
this[styleNode].textContent = css;
this[attachedTo].forEach((node) => {
node.textContent = css; // update all live instances
});
};
// TODO: we'd need .replace() and friends too
}
if (!('adoptedStyleSheets' in ShadowRoot.prototype)) {
function rectifyAdoptedStyleSheets() {
const expected = this[adoptedStyleSheets];
// Check that nodes [0,n] point back to the expected CSSStyleSheet.
// nb. this doesn't guard against users making local changes to textContent
let ok = true;
for (let i = 0; i < expected.length; ++i) {
const check = this.childNodes[i];
if (!check) {
ok = false;
break;
}
if (check[styleSheetOwner] !== expected[i]) {
ok = false;
break;
}
}
if (ok) {
return;
}
// Nuke all previous instantiated sheets.
const previousAdopted = this[instantiatedAdoptedStyleSheets] || [];
previousAdopted.forEach((node) => {
const sheet = node[styleSheetOwner];
sheet[attachedTo].delete(node);
node.remove();
});
// Prepare clones of target CSSStyleSheet instances.
const toPrepend = expected.map((sheet) => {
const node = sheet[styleNode].cloneNode(true);
sheet[attachedTo].add(node);
node[styleSheetOwner] = sheet;
return node;
});
// Save for next update, prepend to Node.
this[instantiatedAdoptedStyleSheets] = toPrepend;
this.prepend(...toPrepend);
}
Object.defineProperty(ShadowRoot.prototype, 'adoptedStyleSheets', {
get() {
return this[adoptedStyleSheets] || [];
},
set(v) {
this[adoptedStyleSheets] = Object.freeze((v || []).slice());
let observer = this[adoptedStyleSheetsObserver];
if (v && v.length) {
// insert MutationObserver if required, to detect .innerHTML changes etc
if (!observer) {
observer = new MutationObserver(rectifyAdoptedStyleSheets.bind(this));
observer.observe(this, {childList: true});
this[adoptedStyleSheetsObserver] = observer;
}
} else {
// clear MutationObserver if no longer required
if (observer) {
observer.disconnect();
this[adoptedStyleSheetsObserver] = null;
}
}
rectifyAdoptedStyleSheets.call(this);
}
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment