Kaynağa Gözat

takes pseudo element contents into account when finding unused fonts

Gildas 7 yıl önce
ebeveyn
işleme
1e620dfd00
1 değiştirilmiş dosya ile 46 ekleme ve 17 silme
  1. 46 17
      lib/single-file/css-fonts-minifier.js

+ 46 - 17
lib/single-file/css-fonts-minifier.js

@@ -37,6 +37,7 @@ this.fontsMinifier = this.fontsMinifier || (() => {
 	const REGEXP_FONT_FORMAT = /\.([^.?#]+)((\?|#).*?)?$/;
 	const REGEXP_FONT_FORMAT_VALUE = /format\((.*?)\)\s*,?$/;
 	const REGEXP_FONT_SRC = /(.*?)\s*,?$/;
+	const PSEUDO_ELEMENTS = ["::after", "::before", "::first-line", "::first-letter", ":before", ":after", ":first-line", ":first-letter", "::placeholder", "::selection", "::marker", "::cue", "::slotted", "::spelling-error", "::grammar-error"];
 	const FONT_WEIGHTS = {
 		normal: 400,
 		bold: 700
@@ -68,11 +69,12 @@ this.fontsMinifier = this.fontsMinifier || (() => {
 					discarded: 0
 				}
 			};
+			const pseudoElementsContent = Array.from(doc.querySelectorAll("style")).map(style => getPseudoElementsContent(doc, style.sheet.cssRules)).join("");
 			doc.querySelectorAll("style").forEach(style => {
 				if (style.sheet) {
 					stats.rules.processed += style.sheet.cssRules.length;
 					stats.rules.discarded += style.sheet.cssRules.length;
-					processRules(doc, style.sheet.cssRules, fontsDetails, declaredFonts, usedFonts, secondPass);
+					processRules(doc, style.sheet.cssRules, fontsDetails, declaredFonts, usedFonts, pseudoElementsContent, secondPass);
 				}
 			});
 			doc.querySelectorAll("style").forEach(style => {
@@ -82,7 +84,7 @@ this.fontsMinifier = this.fontsMinifier || (() => {
 			});
 			doc.querySelectorAll("[style]").forEach(element => {
 				if (element.style.fontFamily) {
-					const fontFamilyNames = element.style.fontFamily.split(",").map(fontFamilyName => getFontFamilyName(fontFamilyName));
+					const fontFamilyNames = element.style.fontFamily.split(",").map(fontFamilyName => removeQuotes(fontFamilyName));
 					usedFonts.push(fontFamilyNames);
 				}
 			});
@@ -106,23 +108,39 @@ this.fontsMinifier = this.fontsMinifier || (() => {
 		}
 	};
 
-	function processRules(doc, rules, fontsDetails, declaredFonts, usedFonts, secondPass) {
+	function getPseudoElementsContent(doc, rules) {
+		if (rules) {
+			return Array.from(rules).map(rule => {
+				if (rule.type == CSSRule.MEDIA_RULE) {
+					return getPseudoElementsContent(doc, rule.cssRules);
+				} else if (rule.type == CSSRule.STYLE_RULE && testPseudoElements(rule.selectorText)) {
+					let content = rule.style.getPropertyValue("content");
+					content = content && removeQuotes(content);
+					return content;
+				}
+			}).join("");
+		} else {
+			return "";
+		}
+	}
+
+	function processRules(doc, rules, fontsDetails, declaredFonts, usedFonts, pseudoElementsContent, secondPass) {
 		if (rules) {
 			Array.from(rules).forEach(rule => {
 				if (rule.type == CSSRule.MEDIA_RULE) {
-					processRules(doc, rule.cssRules, fontsDetails, declaredFonts, usedFonts, secondPass);
+					processRules(doc, rule.cssRules, fontsDetails, declaredFonts, usedFonts, pseudoElementsContent, secondPass);
 				} else if (rule.type == CSSRule.STYLE_RULE) {
 					if (rule.style && rule.style.fontFamily) {
-						const fontFamilyNames = rule.style.fontFamily.split(",").map(fontFamilyName => getFontFamilyName(fontFamilyName));
+						const fontFamilyNames = rule.style.fontFamily.split(",").map(fontFamilyName => removeQuotes(fontFamilyName));
 						usedFonts.push(fontFamilyNames);
 					}
 				} else {
 					if (rule.type == CSSRule.FONT_FACE_RULE && rule.style) {
-						const fontFamilyName = getFontFamilyName(rule.style.getPropertyValue("font-family"));
+						const fontFamilyName = removeQuotes(rule.style.getPropertyValue("font-family"));
 						if (fontFamilyName) {
 							declaredFonts.add(fontFamilyName);
 						}
-						if (secondPass || testUnicodeRange(doc, rule)) {
+						if (secondPass || testUnicodeRange(doc.body.innerText + pseudoElementsContent, rule)) {
 							const fontKey = getFontKey(rule.style);
 							let fontInfo = fontsDetails.get(fontKey);
 							if (!fontInfo) {
@@ -247,7 +265,7 @@ this.fontsMinifier = this.fontsMinifier || (() => {
 					stylesheetContent += "@media " + Array.prototype.join.call(rule.media, ",") + "{";
 					stylesheetContent += deleteUnusedFonts(doc, rule.cssRules, unusedFonts);
 					stylesheetContent += "}";
-				} else if (rule.type != CSSRule.FONT_FACE_RULE || (rule.type == CSSRule.FONT_FACE_RULE && rule.style && fontFamilyName && !unusedFonts.includes(getFontFamilyName(fontFamilyName)))) {
+				} else if (rule.type != CSSRule.FONT_FACE_RULE || (rule.type == CSSRule.FONT_FACE_RULE && rule.style && fontFamilyName && !unusedFonts.includes(removeQuotes(fontFamilyName)))) {
 					stylesheetContent += rule.cssText;
 				}
 			});
@@ -255,9 +273,8 @@ this.fontsMinifier = this.fontsMinifier || (() => {
 		return stylesheetContent;
 	}
 
-	function testUnicodeRange(doc, rule) {
+	function testUnicodeRange(docContent, rule) {
 		const unicodeRange = rule.style.getPropertyValue("unicode-range");
-		const docContent = doc.body.innerText;
 		if (unicodeRange) {
 			const unicodeRanges = unicodeRange.split(REGEXP_COMMA);
 			const result = unicodeRanges.filter(rangeValue => {
@@ -286,6 +303,18 @@ this.fontsMinifier = this.fontsMinifier || (() => {
 		return true;
 	}
 
+	function testPseudoElements(selectorText) {
+		let indexSelector = 0, found;
+		selectorText = selectorText.toLowerCase();
+		while (indexSelector < PSEUDO_ELEMENTS.length && !found) {
+			found = selectorText.includes(PSEUDO_ELEMENTS[indexSelector]);
+			if (!found) {
+				indexSelector++;
+			}
+		}
+		return found;
+	}
+
 	function transformRange(range) {
 		range = range.replace(REGEXP_STARTS_U_PLUS, "");
 		while (range.length < 6) {
@@ -296,7 +325,7 @@ this.fontsMinifier = this.fontsMinifier || (() => {
 
 	function getFontKey(style) {
 		return JSON.stringify([
-			getFontFamilyName(style.getPropertyValue("font-family")),
+			removeQuotes(style.getPropertyValue("font-family")),
 			getFontWeight(style.getPropertyValue("font-weight")),
 			style.getPropertyValue("font-style"),
 			style.getPropertyValue("unicode-range"),
@@ -307,14 +336,14 @@ this.fontsMinifier = this.fontsMinifier || (() => {
 		]);
 	}
 
-	function getFontFamilyName(fontFamilyName) {
-		fontFamilyName = fontFamilyName.toLowerCase().trim();
-		if (fontFamilyName.match(REGEXP_SIMPLE_QUOTES_STRING)) {
-			fontFamilyName = fontFamilyName.replace(REGEXP_SIMPLE_QUOTES_STRING, "$1");
+	function removeQuotes(string) {
+		string = string.toLowerCase().trim();
+		if (string.match(REGEXP_SIMPLE_QUOTES_STRING)) {
+			string = string.replace(REGEXP_SIMPLE_QUOTES_STRING, "$1");
 		} else {
-			fontFamilyName = fontFamilyName.replace(REGEXP_DOUBLE_QUOTES_STRING, "$1");
+			string = string.replace(REGEXP_DOUBLE_QUOTES_STRING, "$1");
 		}
-		return fontFamilyName.trim();
+		return string.trim();
 	}
 
 	function getFontWeight(weight) {