|
|
@@ -24,6 +24,7 @@ this.cssMinifier = this.cssMinifier || (() => {
|
|
|
|
|
|
const REMOVED_PSEUDO_CLASSES = [":focus", ":focus-within", ":hover", ":link", ":visited", ":active"];
|
|
|
const REMOVED_PSEUDO_ELEMENTS = ["::after", "::before", "::first-line", "::first-letter", "::placeholder", "::-webkit-input-placeholder", "::selection", "::marker", "::cue", "::-webkit-progress-bar", "::-webkit-progress-value", "::-webkit-inner-spin-button", "::-webkit-outer-spin-button", "::-webkit-search-cancel-button", "::-webkit-search-cancel-button"];
|
|
|
+ const FILTERED_PSEUDO = REMOVED_PSEUDO_CLASSES.concat(REMOVED_PSEUDO_ELEMENTS);
|
|
|
const IGNORED_SELECTORS = ["::-webkit-scrollbar", "::-webkit-scrollbar-button", "::-webkit-scrollbar-thumb", "::-webkit-scrollbar-track", "::-webkit-scrollbar-track-piece", "::-webkit-scrollbar-corner", "::-webkit-resizer"];
|
|
|
|
|
|
return {
|
|
|
@@ -39,76 +40,92 @@ this.cssMinifier = this.cssMinifier || (() => {
|
|
|
} else {
|
|
|
mediaInfo = mediaAllInfo;
|
|
|
}
|
|
|
- processRules(doc, styleElement.sheet.cssRules, mediaInfo, stats);
|
|
|
- styleElement.textContent = serializeRules(styleElement.sheet.cssRules);
|
|
|
+ const cssRules = styleElement.sheet.cssRules;
|
|
|
+ stats.processed += cssRules.length;
|
|
|
+ stats.discarded += cssRules.length;
|
|
|
+ styleElement.textContent = processRules(doc, cssRules, mediaInfo);
|
|
|
+ stats.discarded -= cssRules.length;
|
|
|
}
|
|
|
});
|
|
|
doc.querySelectorAll("[style]").forEach(element => {
|
|
|
- processStyle(doc, element.style, mediaAllInfo);
|
|
|
+ let textContent = processStyleAttribute(element.style, mediaAllInfo);
|
|
|
+ if (textContent) {
|
|
|
+ element.setAttribute("style", textContent);
|
|
|
+ } else {
|
|
|
+ element.removeAttribute("style");
|
|
|
+ }
|
|
|
});
|
|
|
return stats;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- function processRules(doc, cssRules, mediaInfo, stats) {
|
|
|
- stats.processed += cssRules.length;
|
|
|
- stats.discarded += cssRules.length;
|
|
|
+ function processRules(doc, cssRules, mediaInfo) {
|
|
|
+ let sheetContent = "";
|
|
|
Array.from(cssRules).forEach(cssRule => {
|
|
|
if (cssRule.type == CSSRule.MEDIA_RULE) {
|
|
|
- processRules(doc, cssRule.cssRules, mediaInfo.medias.get(cssRule.media), stats);
|
|
|
+ sheetContent += "@media " + Array.from(cssRule.media).join(",") + "{";
|
|
|
+ sheetContent += processRules(doc, cssRule.cssRules, mediaInfo.medias.get(cssRule.media));
|
|
|
+ sheetContent += "}";
|
|
|
} else if (cssRule.type == CSSRule.STYLE_RULE) {
|
|
|
const ruleInfo = mediaInfo.rules.get(cssRule);
|
|
|
- if (ruleInfo) {
|
|
|
- const stylesInfo = parseCss.parseAListOfDeclarations(cssRule.style.cssText);
|
|
|
- const unusedStyles = stylesInfo.filter(style => !ruleInfo.style.get(style.name));
|
|
|
- if (unusedStyles.length) {
|
|
|
- unusedStyles.forEach(style => cssRule.style.removeProperty(style.name));
|
|
|
- }
|
|
|
- if (ruleInfo.matchedSelectors.size < ruleInfo.selectorsText.length) {
|
|
|
- cssRule.selectorText = ruleInfo.selectorsText.filter(selectorText => ruleInfo.matchedSelectors.has(selectorText) || testIgnoredSelector(selectorText)).join(",");
|
|
|
+ if (ruleInfo || testIgnoredSelector(cssRule.selectorText)) {
|
|
|
+ sheetContent += processRuleInfo(cssRule, ruleInfo);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ sheetContent += cssRule.cssText;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return sheetContent;
|
|
|
+ }
|
|
|
+
|
|
|
+ function processRuleInfo(cssRule, ruleInfo) {
|
|
|
+ let selectorText = "", styleCssText = "";
|
|
|
+ if (ruleInfo) {
|
|
|
+ const stylesInfo = parseCss.parseAListOfDeclarations(cssRule.style.cssText);
|
|
|
+ for (let styleIndex = 0; styleIndex < stylesInfo.length; styleIndex++) {
|
|
|
+ const style = stylesInfo[styleIndex];
|
|
|
+ if (ruleInfo.style.get(style.name)) {
|
|
|
+ if (styleCssText) {
|
|
|
+ styleCssText += ";";
|
|
|
}
|
|
|
- } else {
|
|
|
- if (!testIgnoredSelector(cssRule.selectorText)) {
|
|
|
- const parent = cssRule.parentRule || cssRule.parentStyleSheet;
|
|
|
- let indexRule = 0;
|
|
|
- while (cssRule != parent.cssRules[indexRule] && indexRule < parent.cssRules.length) {
|
|
|
- indexRule++;
|
|
|
- }
|
|
|
- if (cssRule == parent.cssRules[indexRule]) {
|
|
|
- parent.deleteRule(indexRule);
|
|
|
+ const priority = cssRule.style.getPropertyPriority(style.name);
|
|
|
+ styleCssText += style.name + ":" + cssRule.style.getPropertyValue(style.name) + (priority && ("!" + priority));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (ruleInfo.matchedSelectors.size < ruleInfo.selectorsText.length) {
|
|
|
+ for (let selectorTextIndex = 0; selectorTextIndex < ruleInfo.selectorsText.length; selectorTextIndex++) {
|
|
|
+ const ruleSelectorText = ruleInfo.selectorsText[selectorTextIndex];
|
|
|
+ if (ruleInfo.matchedSelectors.has(ruleSelectorText) || testIgnoredSelector(ruleSelectorText)) {
|
|
|
+ if (selectorText) {
|
|
|
+ selectorText += ",";
|
|
|
}
|
|
|
+ selectorText += ruleSelectorText;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- });
|
|
|
- stats.discarded -= cssRules.length;
|
|
|
+ }
|
|
|
+ return (selectorText || cssRule.selectorText) + "{" + (styleCssText || cssRule.style.cssText) + "}";
|
|
|
}
|
|
|
|
|
|
- function processStyle(doc, cssStyle, mediaInfo) {
|
|
|
+ function processStyleAttribute(cssStyle, mediaInfo) {
|
|
|
+ let styleCssText = "";
|
|
|
const styleInfo = mediaInfo.styles.get(cssStyle);
|
|
|
if (styleInfo) {
|
|
|
const stylesInfo = parseCss.parseAListOfDeclarations(cssStyle.cssText);
|
|
|
- const unusedStyles = stylesInfo.filter(style => !styleInfo.style.get(style.name));
|
|
|
- if (unusedStyles.length) {
|
|
|
- unusedStyles.forEach(style => cssStyle.removeProperty(style.name));
|
|
|
+ for (let styleIndex = 0; styleIndex < stylesInfo.length; styleIndex++) {
|
|
|
+ const style = stylesInfo[styleIndex];
|
|
|
+ if (styleInfo.style.get(style.name)) {
|
|
|
+ if (styleCssText) {
|
|
|
+ styleCssText += ";";
|
|
|
+ }
|
|
|
+ const priority = cssStyle.getPropertyPriority(style.name);
|
|
|
+ styleCssText += style.name + ":" + cssStyle.getPropertyValue(style.name) + (priority && ("!" + priority));
|
|
|
+ }
|
|
|
}
|
|
|
+ return (styleCssText || cssStyle.cssText);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- function serializeRules(rules) {
|
|
|
- let sheetContent = "";
|
|
|
- Array.from(rules).forEach(rule => {
|
|
|
- if (rule.media) {
|
|
|
- sheetContent += "@media " + Array.from(rule.media).join(",") + "{";
|
|
|
- sheetContent += serializeRules(rule.cssRules);
|
|
|
- sheetContent += "}";
|
|
|
- } else {
|
|
|
- sheetContent += rule.cssText;
|
|
|
- }
|
|
|
- });
|
|
|
- return sheetContent;
|
|
|
- }
|
|
|
-
|
|
|
function testIgnoredSelector(selectorText) {
|
|
|
let indexSelector = 0, found;
|
|
|
selectorText = selectorText.toLowerCase();
|
|
|
@@ -121,29 +138,15 @@ this.cssMinifier = this.cssMinifier || (() => {
|
|
|
if (!found) {
|
|
|
indexSelector = 0;
|
|
|
while (indexSelector < IGNORED_SELECTORS.length && !found) {
|
|
|
- found = testFilterSelector(selectorText);
|
|
|
- if (!found) {
|
|
|
- indexSelector++;
|
|
|
+ let indexPseudo = 0;
|
|
|
+ while (indexPseudo < FILTERED_PSEUDO.length && !found) {
|
|
|
+ found = selectorText.includes(FILTERED_PSEUDO[indexPseudo]);
|
|
|
+ if (!found) {
|
|
|
+ indexPseudo++;
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- }
|
|
|
- return found;
|
|
|
- }
|
|
|
-
|
|
|
- function testFilterSelector(selector) {
|
|
|
- let indexPseudoClass = 0, found;
|
|
|
- while (indexPseudoClass < REMOVED_PSEUDO_CLASSES.length && !found) {
|
|
|
- found = selector.includes(REMOVED_PSEUDO_CLASSES[indexPseudoClass]);
|
|
|
- if (!found) {
|
|
|
- indexPseudoClass++;
|
|
|
- }
|
|
|
- }
|
|
|
- if (!found) {
|
|
|
- let indexPseudoElement = 0;
|
|
|
- while (indexPseudoElement < REMOVED_PSEUDO_ELEMENTS.length && !found) {
|
|
|
- found = selector.includes(REMOVED_PSEUDO_ELEMENTS[indexPseudoElement]);
|
|
|
if (!found) {
|
|
|
- indexPseudoElement++;
|
|
|
+ indexSelector++;
|
|
|
}
|
|
|
}
|
|
|
}
|