refactor: simplify style element inlining

This commit is contained in:
Mikael Sand
2019-10-20 21:37:12 +03:00
parent ee4b0d51f1
commit 3fdf621f56
+48 -60
View File
@@ -118,7 +118,8 @@ function specificity(selector) {
case 'TypeSelector': case 'TypeSelector':
// ignore universal selector // ignore universal selector
if (node.name.charAt(node.name.length - 1) !== '*') { const { name } = node;
if (name.charAt(name.length - 1) !== '*') {
C++; C++;
} }
break; break;
@@ -171,26 +172,19 @@ function flattenToSelectors(cssAst, selectors) {
* Filter selectors by Media Query. * Filter selectors by Media Query.
* *
* @param {Array} selectors to filter * @param {Array} selectors to filter
* @param {Array} useMqs Array with strings of media queries that should pass (<name> <expression>)
* @return {Array} Filtered selectors that match the passed media queries * @return {Array} Filtered selectors that match the passed media queries
*/ */
function filterByMqs(selectors, useMqs) { function filterByMqs(selectors) {
return selectors.filter(selector => { return selectors.filter(({ atrule }) => {
if (selector.atrule === null) { if (atrule === null) {
return ~useMqs.indexOf(''); return ~useMqs.indexOf('');
} }
const { name, expression } = atrule;
const mqName = selector.atrule.name; return ~useMqs.indexOf(
let mqStr = mqName; expression && expression.children.first().type === 'MediaQueryList'
if ( ? [name, csstree.generate(expression)].join(' ')
selector.atrule.expression && : name,
selector.atrule.expression.children.first().type === 'MediaQueryList' );
) {
const mqExpr = csstree.generate(selector.atrule.expression);
mqStr = [mqName, mqExpr].join(' ');
}
return ~useMqs.indexOf(mqStr);
}); });
} }
@@ -198,21 +192,20 @@ function filterByMqs(selectors, useMqs) {
* Filter selectors by the pseudo-elements and/or -classes they contain. * Filter selectors by the pseudo-elements and/or -classes they contain.
* *
* @param {Array} selectors to filter * @param {Array} selectors to filter
* @param {Array} usePseudos Array with strings of single or sequence of pseudo-elements and/or -classes that should pass
* @return {Array} Filtered selectors that match the passed pseudo-elements and/or -classes * @return {Array} Filtered selectors that match the passed pseudo-elements and/or -classes
*/ */
function filterByPseudos(selectors, usePseudos) { function filterByPseudos(selectors) {
return selectors.filter(selector => { return selectors.filter(
const pseudoSelectorsStr = csstree.generate({ ({ pseudos }) =>
type: 'Selector', ~usePseudos.indexOf(
children: new List().fromArray( csstree.generate({
selector.pseudos.map(pseudo => { type: 'Selector',
return pseudo.item.data; children: new List().fromArray(
pseudos.map(pseudo => pseudo.item.data),
),
}), }),
), ),
}); );
return ~usePseudos.indexOf(pseudoSelectorsStr);
});
} }
/** /**
@@ -222,11 +215,9 @@ function filterByPseudos(selectors, usePseudos) {
* @return {Array} Selectors without pseudo-elements and/or -classes * @return {Array} Selectors without pseudo-elements and/or -classes
*/ */
function cleanPseudos(selectors) { function cleanPseudos(selectors) {
selectors.forEach(selector => { selectors.forEach(({ pseudos }) =>
selector.pseudos.forEach(pseudo => { pseudos.forEach(pseudo => pseudo.list.remove(pseudo.item)),
pseudo.list.remove(pseudo.item); );
});
});
} }
/** /**
@@ -245,7 +236,6 @@ function compareSpecificity(aSpecificity, bSpecificity) {
return 1; return 1;
} }
} }
return 0; return 0;
} }
@@ -272,6 +262,10 @@ function sortSelectors(selectors) {
return stable(selectors, bySelectorSpecificity); return stable(selectors, bySelectorSpecificity);
} }
const declarationParseProps = {
context: 'declarationList',
parseValue: false,
};
function CSSStyleDeclaration(node) { function CSSStyleDeclaration(node) {
const style = { const style = {
style: node.props.style, style: node.props.style,
@@ -281,13 +275,9 @@ function CSSStyleDeclaration(node) {
if (!styles || styles.length === 0) { if (!styles || styles.length === 0) {
return style; return style;
} }
try { try {
csstree csstree
.parse(styles, { .parse(styles, declarationParseProps)
context: 'declarationList',
parseValue: false,
})
.children.each(({ property, value, important }) => { .children.each(({ property, value, important }) => {
try { try {
setProperty(style, property, csstree.generate(value), important); setProperty(style, property, csstree.generate(value), important);
@@ -306,7 +296,6 @@ function CSSStyleDeclaration(node) {
parseError, parseError,
); );
} }
return style; return style;
} }
@@ -341,9 +330,7 @@ function setProperty({ properties, style }, name, value, important) {
*/ */
function closestElem(node, elemName) { function closestElem(node, elemName) {
let elem = node; let elem = node;
while ((elem = elem.parent) && elem.tag !== elemName) {} while ((elem = elem.parent) && elem.tag !== elemName) {}
return elem; return elem;
} }
@@ -356,6 +343,17 @@ function initStyle(selectedEl) {
} }
} }
// useMqs Array with strings of media queries that should pass (<name> <expression>)
const useMqs = ['', 'screen'];
// usePseudos Array with strings of single or sequence of pseudo-elements and/or -classes that should pass
const usePseudos = [''];
const parseProps = {
parseValue: false,
parseCustomProperty: false,
};
/** /**
* Moves + merges styles from style elements to element styles * Moves + merges styles from style elements to element styles
* *
@@ -369,14 +367,10 @@ function initStyle(selectedEl) {
* empty string element for all non-pseudo-classes and/or -elements * empty string element for all non-pseudo-classes and/or -elements
* *
* @param {Object} document document element * @param {Object} document document element
* @param {Object} opts plugin params
* *
* @author strarsis <strarsis@gmail.com> * @author strarsis <strarsis@gmail.com>
* @author modified by: msand <msand@abo.fim>
*/ */
const opts = {
useMqs: ['', 'screen'],
usePseudos: [''],
};
export function inlineStyles(document) { export function inlineStyles(document) {
// collect <style/>s // collect <style/>s
const styleElements = querySelectorAll(document, 'style'); const styleElements = querySelectorAll(document, 'style');
@@ -397,13 +391,7 @@ export function inlineStyles(document) {
// collect <style/>s and their css ast // collect <style/>s and their css ast
try { try {
flattenToSelectors( flattenToSelectors(csstree.parse(children, parseProps), selectors);
csstree.parse(children, {
parseValue: false,
parseCustomProperty: false,
}),
selectors,
);
} catch (parseError) { } catch (parseError) {
console.warn( console.warn(
'Warning: Parse error of styles of <style/> element, skipped. Error details: ' + 'Warning: Parse error of styles of <style/> element, skipped. Error details: ' +
@@ -413,10 +401,10 @@ export function inlineStyles(document) {
} }
// filter for mediaqueries to be used or without any mediaquery // filter for mediaqueries to be used or without any mediaquery
const selectorsMq = filterByMqs(selectors, opts.useMqs); const selectorsMq = filterByMqs(selectors);
// filter for pseudo elements to be used // filter for pseudo elements to be used
const selectorsPseudo = filterByPseudos(selectorsMq, opts.usePseudos); const selectorsPseudo = filterByPseudos(selectorsMq);
// remove PseudoClass from its SimpleSelector for proper matching // remove PseudoClass from its SimpleSelector for proper matching
cleanPseudos(selectorsPseudo); cleanPseudos(selectorsPseudo);
@@ -425,18 +413,18 @@ export function inlineStyles(document) {
const sortedSelectors = sortSelectors(selectorsPseudo).reverse(); const sortedSelectors = sortSelectors(selectorsPseudo).reverse();
// match selectors // match selectors
for (let selector of sortedSelectors) { for (let { rule, item } of sortedSelectors) {
if (selector.rule === null) { if (rule === null) {
continue; continue;
} }
const selectorStr = csstree.generate(selector.item.data); const selectorStr = csstree.generate(item.data);
try { try {
// apply <style/> to matched elements // apply <style/> to matched elements
for (let element of querySelectorAll(document, selectorStr)) { for (let element of querySelectorAll(document, selectorStr)) {
initStyle(element); initStyle(element);
const { style } = element; const { style } = element;
const { properties } = style; const { properties } = style;
csstree.walk(selector.rule, { csstree.walk(rule, {
visit: 'Declaration', visit: 'Declaration',
enter({ property, value, important }) { enter({ property, value, important }) {
// existing inline styles have higher priority // existing inline styles have higher priority