Skip to content

Instantly share code, notes, and snippets.

@jonathantneal
Last active December 25, 2020 18:38
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonathantneal/eef0894b39906bd6b83c385f3776a328 to your computer and use it in GitHub Desktop.
Save jonathantneal/eef0894b39906bd6b83c385f3776a328 to your computer and use it in GitHub Desktop.
PostCSS using only the old CSSOM
/* 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