Last active
August 2, 2022 14:01
-
-
Save samthor/ee78b434b0f9aa525c5d235979b830aa to your computer and use it in GitHub Desktop.
CSS Modules plugin for Rollup
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
`; | |
} | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @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