diff --git a/__tests__/purgecssDefault.test.js b/__tests__/purgecssDefault.test.js index 8e3f4c80..76bc448c 100644 --- a/__tests__/purgecssDefault.test.js +++ b/__tests__/purgecssDefault.test.js @@ -288,6 +288,26 @@ describe('purge methods with files and default extractor', () => { expect(purgecssResult.includes('@keyframes rotateAni')).toBe(true) }) }) + + // Font Face + describe('purge unused font-face', () => { + let purgecssResult + beforeAll(() => { + purgecssResult = new Purgecss({ + content: [`${root}font_face/font_face.html`], + css: [`${root}font_face/font_face.css`], + fontFace: true + }).purge()[0].css + }) + it("keep @font-face 'Cerebri Sans'", () => { + expect(purgecssResult.includes(`src: url('../fonts/CerebriSans-Regular.eot?')`)).toBe( + true + ) + }) + it("remove @font-face 'OtherFont'", () => { + expect(purgecssResult.includes(`src: url('xxx')`)).toBe(false) + }) + }) }) describe('purge methods with raw content and default extractor', () => { diff --git a/__tests__/test_examples/font_face/font_face.css b/__tests__/test_examples/font_face/font_face.css index 31a29a46..16bd9b35 100644 --- a/__tests__/test_examples/font_face/font_face.css +++ b/__tests__/test_examples/font_face/font_face.css @@ -5,10 +5,18 @@ src: url('../fonts/CerebriSans-Regular.eot?') format('eot'), url('../fonts/CerebriSans-Regular.otf') format('opentype'), url('../fonts/CerebriSans-Regular.svg#Cerebri_Sans') format('svg'), url('../fonts/CerebriSans-Regular.ttf') format('truetype'), url('../fonts/CerebriSans-Regular.woff') format('woff'); } +@font-face { + font-family: 'OtherFont'; + font-weight: 400; + font-style: normal; + src: url('xxx') +} + .unused { color: black; } used { color: red; -} \ No newline at end of file + font-family: 'Cerebri Sans'; +} diff --git a/__tests__/test_examples/keyframes/index.css b/__tests__/test_examples/keyframes/index.css index 3b787be7..8166b694 100644 --- a/__tests__/test_examples/keyframes/index.css +++ b/__tests__/test_examples/keyframes/index.css @@ -1,3 +1,11 @@ +@-webkit-keyframes rotateAni { + from { + transform: rotate(0deg); + } + to { + transform: rotate(359deg); + } +} @keyframes rotateAni { from { transform: rotate(0deg); @@ -11,6 +19,16 @@ animation: rotateAni 200ms ease-out both; } +@-webkit-keyframes flashAni +{ + from, 50%, to { + opacity: 1; + } + + 25%, 75% { + opacity: 0; + } +} @keyframes flashAni { from, 50%, to { diff --git a/__tests__/test_examples/keyframes/keyframes.css b/__tests__/test_examples/keyframes/keyframes.css index 08f19f1b..19dced24 100644 --- a/__tests__/test_examples/keyframes/keyframes.css +++ b/__tests__/test_examples/keyframes/keyframes.css @@ -20,4 +20,8 @@ 25%, 75% { opacity: 0.5; } +} + +.flash { + animation: flash } \ No newline at end of file diff --git a/flow-typed/index.js b/flow-typed/index.js index 3f275145..44249393 100644 --- a/flow-typed/index.js +++ b/flow-typed/index.js @@ -17,7 +17,8 @@ declare type Options = { info?: boolean, rejected?: boolean, legacy?: boolean, - keyframes?: boolean + keyframes?: boolean, + fontFace?: boolean } declare type ExtractorsObj = { @@ -29,3 +30,8 @@ declare type ResultPurge = { file: ?string, css: string } + +declare type AtRules = { + keyframes: Array, + fontFace: Array +} \ No newline at end of file diff --git a/lib/purgecss.es.js b/lib/purgecss.es.js index d9e39ebc..859a217e 100644 --- a/lib/purgecss.es.js +++ b/lib/purgecss.es.js @@ -1 +1 @@ -import fs from"fs";import glob from"glob";import postcss from"postcss";import selectorParser from"postcss-selector-parser";function normalizeArray(t,e){for(var r=0,o=t.length-1;o>=0;o--){var n=t[o];"."===n?t.splice(o,1):".."===n?(t.splice(o,1),r++):r&&(t.splice(o,1),r--)}if(e)for(;r--;r)t.unshift("..");return t}var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/,splitPath=function(t){return splitPathRe.exec(t).slice(1)};function resolve(){for(var t="",e=!1,r=arguments.length-1;r>=-1&&!e;r--){var o=r>=0?arguments[r]:"/";if("string"!=typeof o)throw new TypeError("Arguments to path.resolve must be strings");o&&(t=o+"/"+t,e="/"===o.charAt(0))}return t=normalizeArray(filter(t.split("/"),function(t){return!!t}),!e).join("/"),(e?"/":"")+t||"."}function normalize(t){var e=isAbsolute(t),r="/"===substr(t,-1);return(t=normalizeArray(filter(t.split("/"),function(t){return!!t}),!e).join("/"))||e||(t="."),t&&r&&(t+="/"),(e?"/":"")+t}function isAbsolute(t){return"/"===t.charAt(0)}function join(){return normalize(filter(Array.prototype.slice.call(arguments,0),function(t,e){if("string"!=typeof t)throw new TypeError("Arguments to path.join must be strings");return t}).join("/"))}function relative(t,e){function r(t){for(var e=0;e=0&&""===t[r];r--);return e>r?[]:t.slice(e,r-e+1)}t=resolve(t).substr(1),e=resolve(e).substr(1);for(var o=r(t.split("/")),n=r(e.split("/")),i=Math.min(o.length,n.length),a=i,s=0;s1&&void 0!==arguments[1]?arguments[1]:[];return e.length?e.find(function(e){return e.extensions.find(function(e){return t.endsWith(e)})}).extractor:!0===this.options.legacy?LegacyExtractor:DefaultExtractor}},{key:"extractSelectors",value:function(t,e){var r=new Set,o=e.extract(t);if(null===o)throw new Error(ERROR_EXTRACTER_FAILED);return o.forEach(function(t){r.add(t)}),r.delete(""),r}},{key:"getSelectorsCss",value:function(t){var e=this;this.root.walkRules(function(r){var o=r.prev();if(!e.isIgnoreAnnotation(o)){r.selector=selectorParser(function(r){r.walk(function(r){var o=[];if("selector"===r.type){if(r.parent&&":not"===r.parent.value&&"pseudo"===r.parent.type)return;var n=!0,i=!1,a=void 0;try{for(var s,l=r.nodes[Symbol.iterator]();!(n=(s=l.next()).done);n=!0){var u=s.value,c=u.type,f=u.value;SELECTOR_STANDARD_TYPES.includes(c)&&void 0!==f?o.push(f):"tag"!==c||/[+]|(even)|(odd)|^from$|^to$|^\d/.test(f)||o.push(f)}}catch(t){i=!0,a=t}finally{try{!n&&l.return&&l.return()}finally{if(i)throw a}}e.shouldKeepSelector(t,o)||r.remove()}})}).processSync(r.selector);var n=r.parent;"atrule"===n.type&&e.options.keyframes&&"keyframes"===n.name&&(e.atRules.keyframes[n.params]=n),r.selector||r.remove(),e.isRuleEmpty(n)&&n.remove()}})}},{key:"isIgnoreAnnotation",value:function(t){return!(!t||"comment"!==t.type)&&t.text.includes(IGNORE_ANNOTATION)}},{key:"isRuleEmpty",value:function(t){return!!("decl"===t.type&&!t.value||"rule"===t.type&&!t.selector||t.nodes&&!t.nodes.length||"atrule"===t.type&&(!t.nodes&&!t.params||!t.params&&!t.nodes.length))}},{key:"shouldKeepSelector",value:function(t,e){var r=!0,o=!1,n=void 0;try{for(var i,a=e[Symbol.iterator]();!(r=(i=a.next()).done);r=!0){var s=i.value;if(this.options.legacy){var l=s.split(/[^a-z]/g),u=!1,c=!0,f=!1,y=void 0;try{for(var p,h=l[Symbol.iterator]();!(c=(p=h.next()).done);c=!0){var v=p.value;if(v){if(!t.has(v))break;u=!0}}}catch(t){f=!0,y=t}finally{try{!c&&h.return&&h.return()}finally{if(f)throw y}}if(u)return!0;if(t.has(s)||CSS_WHITELIST.includes(s)||this.isSelectorWhitelisted(s))return!0}else{var E=s.replace(/\\/g,"");if(E.startsWith(":"))continue;if(!(t.has(E)||CSS_WHITELIST.includes(E)||this.isSelectorWhitelisted(E)))return!1}}}catch(t){o=!0,n=t}finally{try{!r&&a.return&&a.return()}finally{if(o)throw n}}return!this.options.legacy}},{key:"isSelectorWhitelisted",value:function(t){return!!(CSS_WHITELIST.includes(t)||this.options.whitelist&&this.options.whitelist.some(function(e){return e===t})||this.options.whitelistPatterns&&this.options.whitelistPatterns.some(function(e){return e.test(t)}))}}]),t}();export default Purgecss; +import fs from"fs";import glob from"glob";import postcss from"postcss";import selectorParser from"postcss-selector-parser";function normalizeArray(e,t){for(var r=0,n=e.length-1;n>=0;n--){var o=e[n];"."===o?e.splice(n,1):".."===o?(e.splice(n,1),r++):r&&(e.splice(n,1),r--)}if(t)for(;r--;r)e.unshift("..");return e}var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/,splitPath=function(e){return splitPathRe.exec(e).slice(1)};function resolve(){for(var e="",t=!1,r=arguments.length-1;r>=-1&&!t;r--){var n=r>=0?arguments[r]:"/";if("string"!=typeof n)throw new TypeError("Arguments to path.resolve must be strings");n&&(e=n+"/"+e,t="/"===n.charAt(0))}return e=normalizeArray(filter(e.split("/"),function(e){return!!e}),!t).join("/"),(t?"/":"")+e||"."}function normalize(e){var t=isAbsolute(e),r="/"===substr(e,-1);return(e=normalizeArray(filter(e.split("/"),function(e){return!!e}),!t).join("/"))||t||(e="."),e&&r&&(e+="/"),(t?"/":"")+e}function isAbsolute(e){return"/"===e.charAt(0)}function join(){return normalize(filter(Array.prototype.slice.call(arguments,0),function(e,t){if("string"!=typeof e)throw new TypeError("Arguments to path.join must be strings");return e}).join("/"))}function relative(e,t){function r(e){for(var t=0;t=0&&""===e[r];r--);return t>r?[]:e.slice(t,r-t+1)}e=resolve(e).substr(1),t=resolve(t).substr(1);for(var n=r(e.split("/")),o=r(t.split("/")),i=Math.min(n.length,o.length),a=i,s=0;s1&&void 0!==arguments[1]?arguments[1]:[];return t.length?t.find(function(t){return t.extensions.find(function(t){return e.endsWith(t)})}).extractor:!0===this.options.legacy?LegacyExtractor:DefaultExtractor}},{key:"extractSelectors",value:function(e,t){var r=new Set,n=t.extract(e);if(null===n)throw new Error(ERROR_EXTRACTER_FAILED);return n.forEach(function(e){r.add(e)}),r.delete(""),r}},{key:"getSelectorsCss",value:function(e){var t=this;this.root.walk(function(r){return"rule"===r.type?t.evaluateRule(r,e):"atrule"===r.type?t.evaluateAtRule(r):void 0})}},{key:"evaluateRule",value:function(e,t){var r=this,n=e.prev();if(!this.isIgnoreAnnotation(n)){var o=!0;if(e.selector=selectorParser(function(e){e.walk(function(e){var n=[];if("selector"===e.type){if(e.parent&&":not"===e.parent.value&&"pseudo"===e.parent.type)return;var i=!0,a=!1,s=void 0;try{for(var l,u=e.nodes[Symbol.iterator]();!(i=(l=u.next()).done);i=!0){var c=l.value,f=c.type,y=c.value;SELECTOR_STANDARD_TYPES.includes(f)&&void 0!==y?n.push(y):"tag"!==f||/[+]|(even)|(odd)|^from$|^to$|^\d/.test(y)||n.push(y)}}catch(e){a=!0,s=e}finally{try{!i&&u.return&&u.return()}finally{if(a)throw s}}(o=r.shouldKeepSelector(t,n))||e.remove()}})}).processSync(e.selector),o){var i=!0,a=!1,s=void 0;try{for(var l,u=e.nodes[Symbol.iterator]();!(i=(l=u.next()).done);i=!0){var c=l.value,f=c.prop,y=c.value;if(this.options.keyframes&&("animation"===f||"animation-name"===f)){var h=!0,p=!1,v=void 0;try{for(var d,E=y.split(" ")[Symbol.iterator]();!(h=(d=E.next()).done);h=!0){var R=d.value;this.usedAnimations.add(R)}}catch(e){p=!0,v=e}finally{try{!h&&E.return&&E.return()}finally{if(p)throw v}}}this.options.fontFace&&"font-family"===f&&this.usedFontFaces.add(y)}}catch(e){a=!0,s=e}finally{try{!i&&u.return&&u.return()}finally{if(a)throw s}}}var m=e.parent;e.selector||e.remove(),this.isRuleEmpty(m)&&m.remove()}}},{key:"evaluateAtRule",value:function(e){if(this.options.keyframes&&e.name.endsWith("keyframes"))this.atRules.keyframes.push(e);else if(this.options.fontFace&&"font-face"===e.name){var t=!0,r=!1,n=void 0;try{for(var o,i=e.nodes[Symbol.iterator]();!(t=(o=i.next()).done);t=!0){var a=o.value,s=a.prop,l=a.value;"font-family"===s&&this.atRules.fontFace.push({name:l,node:e})}}catch(e){r=!0,n=e}finally{try{!t&&i.return&&i.return()}finally{if(r)throw n}}}else;}},{key:"isIgnoreAnnotation",value:function(e){return!(!e||"comment"!==e.type)&&e.text.includes(IGNORE_ANNOTATION)}},{key:"isRuleEmpty",value:function(e){return!!("decl"===e.type&&!e.value||"rule"===e.type&&!e.selector||e.nodes&&!e.nodes.length||"atrule"===e.type&&(!e.nodes&&!e.params||!e.params&&!e.nodes.length))}},{key:"shouldKeepSelector",value:function(e,t){var r=!0,n=!1,o=void 0;try{for(var i,a=t[Symbol.iterator]();!(r=(i=a.next()).done);r=!0){var s=i.value;if(this.options.legacy){var l=s.split(/[^a-z]/g),u=!1,c=!0,f=!1,y=void 0;try{for(var h,p=l[Symbol.iterator]();!(c=(h=p.next()).done);c=!0){var v=h.value;if(v){if(!e.has(v))break;u=!0}}}catch(e){f=!0,y=e}finally{try{!c&&p.return&&p.return()}finally{if(f)throw y}}if(u)return!0;if(e.has(s)||CSS_WHITELIST.includes(s)||this.isSelectorWhitelisted(s))return!0}else{var d=s.replace(/\\/g,"");if(d.startsWith(":"))continue;if(!(e.has(d)||CSS_WHITELIST.includes(d)||this.isSelectorWhitelisted(d)))return!1}}}catch(e){n=!0,o=e}finally{try{!r&&a.return&&a.return()}finally{if(n)throw o}}return!this.options.legacy}},{key:"isSelectorWhitelisted",value:function(e){return!!(CSS_WHITELIST.includes(e)||this.options.whitelist&&this.options.whitelist.some(function(t){return t===e})||this.options.whitelistPatterns&&this.options.whitelistPatterns.some(function(t){return t.test(e)}))}}]),e}();export default Purgecss; diff --git a/lib/purgecss.js b/lib/purgecss.js index cc7bd43f..abc70c7a 100644 --- a/lib/purgecss.js +++ b/lib/purgecss.js @@ -1 +1 @@ -"use strict";function _interopDefault(e){return e&&"object"==typeof e&&"default"in e?e.default:e}var fs=_interopDefault(require("fs")),glob=_interopDefault(require("glob")),postcss=_interopDefault(require("postcss")),selectorParser=_interopDefault(require("postcss-selector-parser"));function normalizeArray(e,t){for(var r=0,n=e.length-1;n>=0;n--){var o=e[n];"."===o?e.splice(n,1):".."===o?(e.splice(n,1),r++):r&&(e.splice(n,1),r--)}if(t)for(;r--;r)e.unshift("..");return e}var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/,splitPath=function(e){return splitPathRe.exec(e).slice(1)};function resolve(){for(var e="",t=!1,r=arguments.length-1;r>=-1&&!t;r--){var n=r>=0?arguments[r]:"/";if("string"!=typeof n)throw new TypeError("Arguments to path.resolve must be strings");n&&(e=n+"/"+e,t="/"===n.charAt(0))}return e=normalizeArray(filter(e.split("/"),function(e){return!!e}),!t).join("/"),(t?"/":"")+e||"."}function normalize(e){var t=isAbsolute(e),r="/"===substr(e,-1);return(e=normalizeArray(filter(e.split("/"),function(e){return!!e}),!t).join("/"))||t||(e="."),e&&r&&(e+="/"),(t?"/":"")+e}function isAbsolute(e){return"/"===e.charAt(0)}function join(){return normalize(filter(Array.prototype.slice.call(arguments,0),function(e,t){if("string"!=typeof e)throw new TypeError("Arguments to path.join must be strings");return e}).join("/"))}function relative(e,t){function r(e){for(var t=0;t=0&&""===e[r];r--);return t>r?[]:e.slice(t,r-t+1)}e=resolve(e).substr(1),t=resolve(t).substr(1);for(var n=r(e.split("/")),o=r(t.split("/")),i=Math.min(n.length,o.length),a=i,s=0;s1&&void 0!==arguments[1]?arguments[1]:[];return t.length?t.find(function(t){return t.extensions.find(function(t){return e.endsWith(t)})}).extractor:!0===this.options.legacy?LegacyExtractor:DefaultExtractor}},{key:"extractSelectors",value:function(e,t){var r=new Set,n=t.extract(e);if(null===n)throw new Error(ERROR_EXTRACTER_FAILED);return n.forEach(function(e){r.add(e)}),r.delete(""),r}},{key:"getSelectorsCss",value:function(e){var t=this;this.root.walkRules(function(r){var n=r.prev();if(!t.isIgnoreAnnotation(n)){r.selector=selectorParser(function(r){r.walk(function(r){var n=[];if("selector"===r.type){if(r.parent&&":not"===r.parent.value&&"pseudo"===r.parent.type)return;var o=!0,i=!1,a=void 0;try{for(var s,l=r.nodes[Symbol.iterator]();!(o=(s=l.next()).done);o=!0){var u=s.value,c=u.type,f=u.value;SELECTOR_STANDARD_TYPES.includes(c)&&void 0!==f?n.push(f):"tag"!==c||/[+]|(even)|(odd)|^from$|^to$|^\d/.test(f)||n.push(f)}}catch(e){i=!0,a=e}finally{try{!o&&l.return&&l.return()}finally{if(i)throw a}}t.shouldKeepSelector(e,n)||r.remove()}})}).processSync(r.selector);var o=r.parent;"atrule"===o.type&&t.options.keyframes&&"keyframes"===o.name&&(t.atRules.keyframes[o.params]=o),r.selector||r.remove(),t.isRuleEmpty(o)&&o.remove()}})}},{key:"isIgnoreAnnotation",value:function(e){return!(!e||"comment"!==e.type)&&e.text.includes(IGNORE_ANNOTATION)}},{key:"isRuleEmpty",value:function(e){return!!("decl"===e.type&&!e.value||"rule"===e.type&&!e.selector||e.nodes&&!e.nodes.length||"atrule"===e.type&&(!e.nodes&&!e.params||!e.params&&!e.nodes.length))}},{key:"shouldKeepSelector",value:function(e,t){var r=!0,n=!1,o=void 0;try{for(var i,a=t[Symbol.iterator]();!(r=(i=a.next()).done);r=!0){var s=i.value;if(this.options.legacy){var l=s.split(/[^a-z]/g),u=!1,c=!0,f=!1,y=void 0;try{for(var p,h=l[Symbol.iterator]();!(c=(p=h.next()).done);c=!0){var v=p.value;if(v){if(!e.has(v))break;u=!0}}}catch(e){f=!0,y=e}finally{try{!c&&h.return&&h.return()}finally{if(f)throw y}}if(u)return!0;if(e.has(s)||CSS_WHITELIST.includes(s)||this.isSelectorWhitelisted(s))return!0}else{var E=s.replace(/\\/g,"");if(E.startsWith(":"))continue;if(!(e.has(E)||CSS_WHITELIST.includes(E)||this.isSelectorWhitelisted(E)))return!1}}}catch(e){n=!0,o=e}finally{try{!r&&a.return&&a.return()}finally{if(n)throw o}}return!this.options.legacy}},{key:"isSelectorWhitelisted",value:function(e){return!!(CSS_WHITELIST.includes(e)||this.options.whitelist&&this.options.whitelist.some(function(t){return t===e})||this.options.whitelistPatterns&&this.options.whitelistPatterns.some(function(t){return t.test(e)}))}}]),e}();module.exports=Purgecss; +"use strict";function _interopDefault(e){return e&&"object"==typeof e&&"default"in e?e.default:e}var fs=_interopDefault(require("fs")),glob=_interopDefault(require("glob")),postcss=_interopDefault(require("postcss")),selectorParser=_interopDefault(require("postcss-selector-parser"));function normalizeArray(e,t){for(var r=0,n=e.length-1;n>=0;n--){var o=e[n];"."===o?e.splice(n,1):".."===o?(e.splice(n,1),r++):r&&(e.splice(n,1),r--)}if(t)for(;r--;r)e.unshift("..");return e}var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/,splitPath=function(e){return splitPathRe.exec(e).slice(1)};function resolve(){for(var e="",t=!1,r=arguments.length-1;r>=-1&&!t;r--){var n=r>=0?arguments[r]:"/";if("string"!=typeof n)throw new TypeError("Arguments to path.resolve must be strings");n&&(e=n+"/"+e,t="/"===n.charAt(0))}return e=normalizeArray(filter(e.split("/"),function(e){return!!e}),!t).join("/"),(t?"/":"")+e||"."}function normalize(e){var t=isAbsolute(e),r="/"===substr(e,-1);return(e=normalizeArray(filter(e.split("/"),function(e){return!!e}),!t).join("/"))||t||(e="."),e&&r&&(e+="/"),(t?"/":"")+e}function isAbsolute(e){return"/"===e.charAt(0)}function join(){return normalize(filter(Array.prototype.slice.call(arguments,0),function(e,t){if("string"!=typeof e)throw new TypeError("Arguments to path.join must be strings");return e}).join("/"))}function relative(e,t){function r(e){for(var t=0;t=0&&""===e[r];r--);return t>r?[]:e.slice(t,r-t+1)}e=resolve(e).substr(1),t=resolve(t).substr(1);for(var n=r(e.split("/")),o=r(t.split("/")),i=Math.min(n.length,o.length),a=i,s=0;s1&&void 0!==arguments[1]?arguments[1]:[];return t.length?t.find(function(t){return t.extensions.find(function(t){return e.endsWith(t)})}).extractor:!0===this.options.legacy?LegacyExtractor:DefaultExtractor}},{key:"extractSelectors",value:function(e,t){var r=new Set,n=t.extract(e);if(null===n)throw new Error(ERROR_EXTRACTER_FAILED);return n.forEach(function(e){r.add(e)}),r.delete(""),r}},{key:"getSelectorsCss",value:function(e){var t=this;this.root.walk(function(r){return"rule"===r.type?t.evaluateRule(r,e):"atrule"===r.type?t.evaluateAtRule(r):void 0})}},{key:"evaluateRule",value:function(e,t){var r=this,n=e.prev();if(!this.isIgnoreAnnotation(n)){var o=!0;if(e.selector=selectorParser(function(e){e.walk(function(e){var n=[];if("selector"===e.type){if(e.parent&&":not"===e.parent.value&&"pseudo"===e.parent.type)return;var i=!0,a=!1,s=void 0;try{for(var l,u=e.nodes[Symbol.iterator]();!(i=(l=u.next()).done);i=!0){var c=l.value,f=c.type,y=c.value;SELECTOR_STANDARD_TYPES.includes(f)&&void 0!==y?n.push(y):"tag"!==f||/[+]|(even)|(odd)|^from$|^to$|^\d/.test(y)||n.push(y)}}catch(e){a=!0,s=e}finally{try{!i&&u.return&&u.return()}finally{if(a)throw s}}(o=r.shouldKeepSelector(t,n))||e.remove()}})}).processSync(e.selector),o){var i=!0,a=!1,s=void 0;try{for(var l,u=e.nodes[Symbol.iterator]();!(i=(l=u.next()).done);i=!0){var c=l.value,f=c.prop,y=c.value;if(this.options.keyframes&&("animation"===f||"animation-name"===f)){var h=!0,p=!1,v=void 0;try{for(var d,E=y.split(" ")[Symbol.iterator]();!(h=(d=E.next()).done);h=!0){var R=d.value;this.usedAnimations.add(R)}}catch(e){p=!0,v=e}finally{try{!h&&E.return&&E.return()}finally{if(p)throw v}}}this.options.fontFace&&"font-family"===f&&this.usedFontFaces.add(y)}}catch(e){a=!0,s=e}finally{try{!i&&u.return&&u.return()}finally{if(a)throw s}}}var m=e.parent;e.selector||e.remove(),this.isRuleEmpty(m)&&m.remove()}}},{key:"evaluateAtRule",value:function(e){if(this.options.keyframes&&e.name.endsWith("keyframes"))this.atRules.keyframes.push(e);else if(this.options.fontFace&&"font-face"===e.name){var t=!0,r=!1,n=void 0;try{for(var o,i=e.nodes[Symbol.iterator]();!(t=(o=i.next()).done);t=!0){var a=o.value,s=a.prop,l=a.value;"font-family"===s&&this.atRules.fontFace.push({name:l,node:e})}}catch(e){r=!0,n=e}finally{try{!t&&i.return&&i.return()}finally{if(r)throw n}}}else;}},{key:"isIgnoreAnnotation",value:function(e){return!(!e||"comment"!==e.type)&&e.text.includes(IGNORE_ANNOTATION)}},{key:"isRuleEmpty",value:function(e){return!!("decl"===e.type&&!e.value||"rule"===e.type&&!e.selector||e.nodes&&!e.nodes.length||"atrule"===e.type&&(!e.nodes&&!e.params||!e.params&&!e.nodes.length))}},{key:"shouldKeepSelector",value:function(e,t){var r=!0,n=!1,o=void 0;try{for(var i,a=t[Symbol.iterator]();!(r=(i=a.next()).done);r=!0){var s=i.value;if(this.options.legacy){var l=s.split(/[^a-z]/g),u=!1,c=!0,f=!1,y=void 0;try{for(var h,p=l[Symbol.iterator]();!(c=(h=p.next()).done);c=!0){var v=h.value;if(v){if(!e.has(v))break;u=!0}}}catch(e){f=!0,y=e}finally{try{!c&&p.return&&p.return()}finally{if(f)throw y}}if(u)return!0;if(e.has(s)||CSS_WHITELIST.includes(s)||this.isSelectorWhitelisted(s))return!0}else{var d=s.replace(/\\/g,"");if(d.startsWith(":"))continue;if(!(e.has(d)||CSS_WHITELIST.includes(d)||this.isSelectorWhitelisted(d)))return!1}}}catch(e){n=!0,o=e}finally{try{!r&&a.return&&a.return()}finally{if(n)throw o}}return!this.options.legacy}},{key:"isSelectorWhitelisted",value:function(e){return!!(CSS_WHITELIST.includes(e)||this.options.whitelist&&this.options.whitelist.some(function(t){return t===e})||this.options.whitelistPatterns&&this.options.whitelistPatterns.some(function(t){return t.test(e)}))}}]),e}();module.exports=Purgecss; diff --git a/src/constants/defaultOptions.js b/src/constants/defaultOptions.js index 52027de8..ed9623a7 100644 --- a/src/constants/defaultOptions.js +++ b/src/constants/defaultOptions.js @@ -10,7 +10,8 @@ const defaultOptions: Options = { info: false, rejected: false, legacy: false, - keyframes: false + keyframes: false, + fontFace: false } export default defaultOptions diff --git a/src/index.js b/src/index.js index 08ba21bc..2107f9a3 100644 --- a/src/index.js +++ b/src/index.js @@ -31,9 +31,13 @@ import LegacyExtractor from './Extractors/LegacyExtractor' class Purgecss { options: Options root: Object - atRules: Object = { - keyframes: {} + atRules: AtRules = { + keyframes: [], + fontFace: [] } + usedAnimations: Set = new Set() + usedFontFaces: Set = new Set() + selectorsRemoved: Set = new Set() constructor(options: Options | string) { if (typeof options === 'string' || typeof options === 'undefined') @@ -128,6 +132,9 @@ class Purgecss { // purge keyframes if (this.options.keyframes) this.removeUnusedKeyframes() + // purge font face + if (this.options.fontFace) this.removeUnusedFontFaces() + sources.push({ file, css: this.root.toString() @@ -138,24 +145,28 @@ class Purgecss { } /** - * Remove Keyframes that are never used + * Remove Keyframes that were never used */ removeUnusedKeyframes() { - const usedAnimations = new Set() + for (const node of this.atRules.keyframes) { + const nodeName = node.params + const used = this.usedAnimations.has(nodeName) - // list all used animations - this.root.walkDecls(/animation/, decl => { - for (const word of decl.value.split(' ')) { - usedAnimations.add(word) + if (!used) { + node.remove() } - }) + } + } - // remove unused keyframes - for (const nodeName in this.atRules.keyframes) { - const keyframeUsed = usedAnimations.has(nodeName) + /** + * Remove Font-Faces that were never used + */ + removeUnusedFontFaces() { + for (const { node, name } of this.atRules.fontFace) { + const used = this.usedFontFaces.has(name) - if (!keyframeUsed) { - this.atRules.keyframes[nodeName].remove() + if (!used) { + node.remove() } } } @@ -237,59 +248,108 @@ class Purgecss { * @param {*} selectors selectors used in content files */ getSelectorsCss(selectors: Set) { - this.root.walkRules(node => { - const annotation = node.prev() - if (this.isIgnoreAnnotation(annotation)) return - node.selector = selectorParser(selectorsParsed => { - selectorsParsed.walk(selector => { - const selectorsInRule = [] - if (selector.type === 'selector') { - // if inside :not pseudo class, ignore + this.root.walk(node => { + if (node.type === 'rule') { + return this.evaluateRule(node, selectors) + } + if (node.type === 'atrule') { + return this.evaluateAtRule(node) + } + }) + } + + /** + * Evaluate css selector and decide if it should be removed or not + * @param {AST} node postcss ast node + * @param {Set} selectors selectors used in content files + */ + evaluateRule(node: Object, selectors: Set) { + const annotation = node.prev() + if (this.isIgnoreAnnotation(annotation)) return + + let keepSelector = true + node.selector = selectorParser(selectorsParsed => { + selectorsParsed.walk(selector => { + const selectorsInRule = [] + if (selector.type === 'selector') { + // if inside :not pseudo class, ignore + if ( + selector.parent && + selector.parent.value === ':not' && + selector.parent.type === 'pseudo' + ) { + return + } + for (const { type, value } of selector.nodes) { if ( - selector.parent && - selector.parent.value === ':not' && - selector.parent.type === 'pseudo' + SELECTOR_STANDARD_TYPES.includes(type) && + typeof value !== 'undefined' ) { - return - } - for (const { type, value } of selector.nodes) { - if ( - SELECTOR_STANDARD_TYPES.includes(type) && - typeof value !== 'undefined' - ) { - selectorsInRule.push(value) - } else if ( - type === 'tag' && - !/[+]|(even)|(odd)|^from$|^to$|^\d/.test(value) - ) { - // test if we do not have a pseudo class parameter (e.g. 2n in :nth-child(2n)) - selectorsInRule.push(value) - } + selectorsInRule.push(value) + } else if ( + type === 'tag' && + !/[+]|(even)|(odd)|^from$|^to$|^\d/.test(value) + ) { + // test if we do not have a pseudo class parameter (e.g. 2n in :nth-child(2n)) + selectorsInRule.push(value) } + } - let keepSelector = this.shouldKeepSelector(selectors, selectorsInRule) + keepSelector = this.shouldKeepSelector(selectors, selectorsInRule) - if (!keepSelector) { - selector.remove() + if (!keepSelector) { + selector.remove() + } + } + }) + }).processSync(node.selector) + + // loop declarations + if (keepSelector) { + for (const { prop, value } of node.nodes) { + if (this.options.keyframes) { + if (prop === 'animation' || prop === 'animation-name') { + for (const word of value.split(' ')) { + this.usedAnimations.add(word) } } - }) - }).processSync(node.selector) + } + if (this.options.fontFace) { + if (prop === 'font-family') { + this.usedFontFaces.add(value) + } + } + } + } - const parent = node.parent + const parent = node.parent - // register atrules to purgecss - if ( - parent.type === 'atrule' && - (this.options.keyframes && parent.name === 'keyframes') - ) { - this.atRules.keyframes[parent.params] = parent - } + // Remove empty rules + if (!node.selector) node.remove() + if (this.isRuleEmpty(parent)) parent.remove() + } - // Remove empty rules - if (!node.selector) node.remove() - if (this.isRuleEmpty(parent)) parent.remove() - }) + /** + * Evaluate at-rule and register it for future reference + * @param {AST} node postcss ast node + */ + evaluateAtRule(node: Object) { + if (this.options.keyframes && node.name.endsWith('keyframes')) { + this.atRules.keyframes.push(node) + return + } + + if (this.options.fontFace && node.name === 'font-face') { + for (const { prop, value } of node.nodes) { + if (prop === 'font-family') { + this.atRules.fontFace.push({ + name: value, + node + }) + } + } + return + } } /**