Last active
October 12, 2021 02:53
-
-
Save chaance/5512901105daa52798e33749711a939e to your computer and use it in GitHub Desktop.
This was the result of a brainfart I had today. I wanted a PostCSS plugin that game me some *very simple* scoping capabilities, and nothing I found quite did what I wanted. Yet to thoroughly test this, but putting it here for now so I don't forget about it.
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
const DIRECTIVES = [ | |
/** | |
* If there is currently a local scope or no scope, the :root directive will | |
* revert to using only the top-level scope if one is defined. | |
* | |
* @example | |
* | |
* @simple-scope; | |
* | |
* .one {} --> .root--one {} | |
* | |
* @simple-scope title; | |
* | |
* .two {} --> .root--title--two {} | |
* .three {} --> .root--title--three {} | |
* | |
* @simple-scope :root; | |
* | |
* .four {} --> .root--four {} | |
*/ | |
":root", | |
/** | |
* The :local directive sets the scope to ignore any top-level scope and only | |
* use the given scope for the cascading styles. | |
* | |
* @example | |
* | |
* @simple-scope; | |
* | |
* .one {} --> .root--one {} | |
* | |
* @simple-scope title; | |
* | |
* .two {} --> .root--title--two {} | |
* .three {} --> .root--title--three {} | |
* | |
* @simple-scope :local copy; | |
* | |
* .four {} --> .copy--four {} | |
*/ | |
":local", | |
/** | |
* The :global directive removes all scopes and leaves the rules as-is. | |
* | |
* @example | |
* | |
* @simple-scope; | |
* | |
* .one {} --> .root--one {} | |
* | |
* @simple-scope :global; | |
* | |
* .two {} --> .two {} | |
*/ | |
":global", | |
]; | |
/** | |
* @param opts {{ rootScope?: string; separator?: string }} | |
* @returns | |
*/ | |
function scoper(opts = {}) { | |
let { rootScope = "", separator = "--" } = opts; | |
rootScope = rootScope.trim(); | |
separator = separator.trim(); | |
/** @type {string} */ | |
let currentScope; | |
let cache = {}; | |
return { | |
postcssPlugin: "postcss-simple-scope", | |
AtRule: { | |
"simple-scope": (node) => { | |
/** @type {string} */ | |
let scope; | |
/** @type {Array<string>} */ | |
let params = node.params.split(" "); | |
/** @type {string} */ | |
let directive; | |
/** @type {string} */ | |
let blockScope; | |
for (let param of params) { | |
if (DIRECTIVES.includes(param)) { | |
directive = param; | |
} else { | |
blockScope = param; | |
} | |
} | |
// TODO: some validation, maybe? | |
if (directive === ":root") { | |
scope = rootScope; | |
} else if (directive === ":global") { | |
scope = ""; | |
} else if (directive === ":local") { | |
scope = blockScope || ""; | |
} else if (blockScope) { | |
scope = rootScope | |
? [rootScope, blockScope].join(separator) | |
: blockScope; | |
} else { | |
scope = rootScope; | |
} | |
currentScope = scope; | |
// remove the scope atrule | |
node.remove(); | |
}, | |
}, | |
Rule: { | |
"*": (node) => { | |
if (!currentScope) { | |
return; | |
} | |
let newSelector = node.selector | |
.split(",") | |
.map((selector) => { | |
let scopedSelector = selector | |
.split(/\s+/) | |
.map((singleSelector) => { | |
// Note, this probably isn't very complete. I imagine there's | |
// some css parser that already reliably grabs individual class | |
// name selectors but this works well enough for now, I think. | |
if (singleSelector.startsWith(".")) { | |
singleSelector = | |
"." + currentScope + separator + singleSelector.slice(1); | |
} | |
return singleSelector; | |
}) | |
.join(" "); | |
return scopedSelector.trim(); | |
}) | |
.join(", "); | |
cache[node.selector] = newSelector; | |
}, | |
}, | |
OnceExit(root) { | |
root.walkRules((node) => { | |
if (cache[node.selector]) { | |
node.selector = cache[node.selector]; | |
} | |
}); | |
}, | |
}; | |
} | |
module.exports = scoper; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment