Last active
December 25, 2020 18:38
-
-
Save jonathantneal/eef0894b39906bd6b83c385f3776a328 to your computer and use it in GitHub Desktop.
PostCSS using only the old CSSOM
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
/* CSSNode | |
/* ========================================================================== */ | |
function CSSNode(source) { | |
this.source = source; | |
} | |
Object.defineProperties(CSSNode.prototype, { | |
index: { | |
get() { | |
const parent = this.parent; | |
if (parent && parent.cssRules) { | |
for (let index in parent.cssRules) { | |
if (parent.cssRules[index] === this.source) { | |
return index; | |
} | |
} | |
} | |
return -1; | |
} | |
}, | |
remove: { | |
value() { | |
const index = this.index; | |
if (index !== -1) { | |
this.parent.deleteRule(index); | |
} | |
} | |
} | |
}); | |
/* CSSDeclaration | |
/* ========================================================================== */ | |
function CSSDeclaration(parent, prop) { | |
CSSNode.call(this, parent.source); | |
this.sourceProp = prop; | |
this.sourceParent = parent; | |
} | |
CSSDeclaration.prototype = Object.defineProperties(Object.create(CSSNode.prototype), { | |
prop: { | |
get() { | |
return this.sourceProp; | |
}, | |
set(newProp) { | |
const value = this.value; | |
const oldProp = this.sourceProp; | |
this.sourceProp = newProp; | |
this.source.style.removeProperty(oldProp); | |
this.source.style.setProperty(newProp, value); | |
}, | |
iterable: true | |
}, | |
parent: { | |
get() { | |
return this.sourceParent; | |
} | |
}, | |
value: { | |
get() { | |
return this.source.style.getPropertyValue(this.sourceProp); | |
}, | |
set(value) { | |
this.source.style.setProperty(this.sourceProp, value); | |
}, | |
iterable: true | |
} | |
}); | |
/* CSSContainer | |
/* ========================================================================== */ | |
function CSSContainer(source) { | |
CSSNode.call(this, source); | |
} | |
CSSContainer.prototype = Object.defineProperties(Object.create(CSSNode.prototype), { | |
each: { | |
value(cb) { | |
walk(this.source, cb, false, false) | |
} | |
}, | |
append: { | |
value() { | |
const nodes = Array.prototype.slice.call(arguments); | |
if (nodes.length) { | |
nodes. | |
this.nodes.splice(this.nodes.length, 0, ...nodes); | |
} | |
return this; | |
} | |
}, | |
nodes: { | |
get() { | |
return this.source.styleSheets | |
? Array.prototype.map.call( | |
this.source.styleSheets, | |
styleSheet => new CSSRoot(styleSheet) | |
) | |
: this.source.cssRules | |
? Array.prototype.map.call( | |
this.source.cssRules, | |
cssRule => isAtRule(cssRule) | |
? new CSSAtRule(cssRule) | |
: isRule(cssRule) | |
? new CSSRule(cssRule) | |
: new CSSContainer(cssRule) | |
) | |
: this.source.style | |
? Array.prototype.map.call( | |
this.source.style, | |
prop => new CSSDeclaration(this, prop) | |
) | |
: [] | |
}, | |
iterable: true | |
}, | |
parent: { | |
get() { | |
return this.source.parentRule || this.source.parentStyleSheet; | |
} | |
}, | |
walk: { | |
value(cb) { | |
walk(this, cb, Node, filter, true); | |
} | |
}, | |
walkDecls: { | |
value(fOrCb, oCb) { | |
const hasSelector = 1 in arguments; | |
const filter = hasSelector ? fOrCb : false; | |
const cb = hasSelector ? oCb : fOrCb; | |
walk(this, cb, CSSDeclaration, filter, true); | |
} | |
}, | |
walkAtRules: { | |
value(fOrCb, oCb) { | |
const hasSelector = 1 in arguments; | |
const filter = hasSelector ? fOrCb : false; | |
const cb = hasSelector ? oCb : fOrCb; | |
walk(this, cb, CSSAtRule, filter, true); | |
} | |
}, | |
walkRules: { | |
value(fOrCb, oCb) { | |
const hasSelector = 1 in arguments; | |
const filter = hasSelector ? fOrCb : false; | |
const cb = hasSelector ? oCb : fOrCb; | |
walk(this, cb, CSSRule, filter, true); | |
} | |
} | |
}); | |
/* CSSRoot | |
/* ========================================================================== */ | |
function CSSRoot() { | |
CSSContainer.call(this, document); | |
} | |
CSSRoot.prototype = Object.defineProperties(Object.create(CSSContainer.prototype), { | |
nodes: { | |
get() { | |
return Array.prototype.map.call( | |
this.source.styleSheets, | |
styleSheet => new CSSContainer(styleSheet) | |
); | |
} | |
} | |
}); | |
/* CSSRule | |
/* ========================================================================== */ | |
function CSSRule(source) { | |
CSSContainer.call(this, source); | |
} | |
CSSRule.prototype = Object.defineProperties(Object.create(CSSContainer.prototype), { | |
selector: { | |
get() { | |
return this.source.selectorText; | |
}, | |
set(newSelector) { | |
const index = this.index; | |
const match = this.source.cssText.match(ruleRegExp); | |
if (index !== -1 && match) { | |
const parent = this.parent; | |
parent.deleteRule(index); | |
parent.insertRule(`${newSelector}${match[2]}`, index); | |
} | |
}, | |
iterable: true | |
}, | |
}); | |
const ruleRegExp = /^\s*([^{]*?)\s*(\{[\W\w]*)$/; | |
/* CSSAtRule | |
/* ========================================================================== */ | |
function CSSAtRule(source) { | |
CSSContainer.call(this, source); | |
} | |
CSSAtRule.prototype = Object.defineProperties(Object.create(CSSContainer.prototype), { | |
name: { | |
get() { | |
const match = this.source.cssText.match(atRuleRegExp); | |
return match ? match[1] : this.source.cssText.slice(1); | |
} | |
}, | |
params: { | |
get() { | |
const match = this.source.cssText.match(atRuleRegExp); | |
return match ? match[2].trim() : this.source.cssText.slice(1).trim(); | |
} | |
} | |
}); | |
const atRuleRegExp = /^@(\S+)\s*([^{]*)/; | |
/* Walk | |
/* ========================================================================== */ | |
function walk (node, cb, Type, filter, isWalkingDescendants) { | |
if (typeof cb === 'function') { | |
Array.prototype.forEach.call(node.nodes, (child, index) => { | |
if (child instanceof Type && testWithFilter(child, Type, filter)) { | |
cb(child, index); | |
} | |
if (isWalkingDescendants && child.nodes) { | |
walk(child, cb, Type, filter, isWalkingDescendants); | |
} | |
}); | |
} | |
} | |
function testWithFilter (node, Type, filter) { | |
const string = Type === CSSDeclaration | |
? node.prop | |
: Type === CSSRule | |
? node.source.selectorText | |
: Type === CSSAtRule | |
? node.source.conditionText | |
: ''; | |
if (!filter) { | |
return true; | |
} else if (typeof filter === 'string') { | |
return string === filter; | |
} else if (filter instanceof RegExp) { | |
return filter.test(string); | |
} else if (filter instanceof Function) { | |
return filter(node); | |
} else { | |
return false; | |
} | |
} | |
function isRule (node) { | |
return 'selectorText' in node;; | |
} | |
function isAtRule (node) { | |
return Object(node.cssText)[0] === '@'; | |
} | |
/* CSS.styleSheets | |
/* ========================================================================== */ | |
if (!window.CSS) { | |
CSS = {}; | |
} | |
CSS.styleSheets = new CSSRoot(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment