diff --git a/__tests__/purgecssDefault.test.js b/__tests__/purgecssDefault.test.js index 94bba893..5c59d708 100644 --- a/__tests__/purgecssDefault.test.js +++ b/__tests__/purgecssDefault.test.js @@ -212,12 +212,14 @@ describe('purge methods with files and default extractor', () => { }) purgecssResult = purgecss.purge()[0].css }) - it('ignores h1', () => { + it('ignores h1 h2', () => { expect(purgecssResult.includes('h1')).toBe(true) + expect(purgecssResult.includes('h3')).toBe(true) }) it('removes the comment', () => { expect(purgecssResult.includes('/* purgecss ignore */')).toBe(false) + expect(purgecssResult.includes('/* purgecss ignore current */')).toBe(false) }) }) diff --git a/__tests__/test_examples/ignore_comment/ignore_comment.css b/__tests__/test_examples/ignore_comment/ignore_comment.css index 595bfa4c..37224935 100644 --- a/__tests__/test_examples/ignore_comment/ignore_comment.css +++ b/__tests__/test_examples/ignore_comment/ignore_comment.css @@ -2,4 +2,9 @@ h1 { color: blue; -} \ No newline at end of file +} + +h3 { + /* purgecss ignore current */ + color: red; +} diff --git a/lib/purgecss.es.js b/lib/purgecss.es.js index 3cb9fdd2..9f1ddb68 100644 --- a/lib/purgecss.es.js +++ b/lib/purgecss.es.js @@ -334,6 +334,7 @@ var defaultOptions = { rejected: false }; +var IGNORE_ANNOTATION_CURRENT = 'purgecss ignore current'; var IGNORE_ANNOTATION_NEXT = 'purgecss ignore'; var IGNORE_ANNOTATION_START = 'purgecss start ignore'; var IGNORE_ANNOTATION_END = 'purgecss end ignore'; @@ -582,6 +583,16 @@ function () { } } } + /** + * Strips quotes at the end and at the end of a string + * @param {string} value the string to be stripped + */ + + }, { + key: "stripQuotes", + value: function stripQuotes(value) { + return value.replace(/(^["'])|(["']$)/g, ''); + } /** * Extract the selectors present in the passed string using a purgecss extractor * @param {array} content Array of content @@ -787,6 +798,10 @@ function () { return; } + if (this.hasIgnoreAnnotation(node)) { + return; + } + var keepSelector = true; node.selector = selectorParser(function (selectorsParsed) { selectorsParsed.walk(function (selector) { @@ -881,7 +896,30 @@ function () { if (this.options.fontFace) { if (prop === 'font-family') { - this.usedFontFaces.add(value); + var _iteratorNormalCompletion10 = true; + var _didIteratorError10 = false; + var _iteratorError10 = undefined; + + try { + for (var _iterator10 = value.split(',')[Symbol.iterator](), _step10; !(_iteratorNormalCompletion10 = (_step10 = _iterator10.next()).done); _iteratorNormalCompletion10 = true) { + var fontName = _step10.value; + var cleanedFontFace = this.stripQuotes(fontName.trim()); + this.usedFontFaces.add(cleanedFontFace); + } + } catch (err) { + _didIteratorError10 = true; + _iteratorError10 = err; + } finally { + try { + if (!_iteratorNormalCompletion10 && _iterator10.return != null) { + _iterator10.return(); + } + } finally { + if (_didIteratorError10) { + throw _iteratorError10; + } + } + } } } } @@ -920,34 +958,34 @@ function () { } if (this.options.fontFace && node.name === 'font-face') { - var _iteratorNormalCompletion10 = true; - var _didIteratorError10 = false; - var _iteratorError10 = undefined; + var _iteratorNormalCompletion11 = true; + var _didIteratorError11 = false; + var _iteratorError11 = undefined; try { - for (var _iterator10 = node.nodes[Symbol.iterator](), _step10; !(_iteratorNormalCompletion10 = (_step10 = _iterator10.next()).done); _iteratorNormalCompletion10 = true) { - var _step10$value = _step10.value, - prop = _step10$value.prop, - value = _step10$value.value; + for (var _iterator11 = node.nodes[Symbol.iterator](), _step11; !(_iteratorNormalCompletion11 = (_step11 = _iterator11.next()).done); _iteratorNormalCompletion11 = true) { + var _step11$value = _step11.value, + prop = _step11$value.prop, + value = _step11$value.value; if (prop === 'font-family') { this.atRules.fontFace.push({ - name: value, + name: this.stripQuotes(value), node: node }); } } } catch (err) { - _didIteratorError10 = true; - _iteratorError10 = err; + _didIteratorError11 = true; + _iteratorError11 = err; } finally { try { - if (!_iteratorNormalCompletion10 && _iterator10.return != null) { - _iterator10.return(); + if (!_iteratorNormalCompletion11 && _iterator11.return != null) { + _iterator11.return(); } } finally { - if (_didIteratorError10) { - throw _iteratorError10; + if (_didIteratorError11) { + throw _iteratorError11; } } } @@ -979,6 +1017,23 @@ function () { return false; } + /** + * Check if the node has a css comment to ignore current selector rule + * @param {object} rule Node of postcss abstract syntax tree + */ + + }, { + key: "hasIgnoreAnnotation", + value: function hasIgnoreAnnotation(rule) { + var found = false; + rule.walkComments(function (node) { + if (node && node.type === 'comment' && node.text.includes(IGNORE_ANNOTATION_CURRENT)) { + found = true; + node.remove(); + } + }); + return found; + } /** * Check if the node correspond to an empty css rule * @param {object} node Node of postcss abstract syntax tree @@ -1002,13 +1057,13 @@ function () { }, { key: "shouldKeepSelector", value: function shouldKeepSelector(selectorsInContent, selectorsInRule) { - var _iteratorNormalCompletion11 = true; - var _didIteratorError11 = false; - var _iteratorError11 = undefined; + var _iteratorNormalCompletion12 = true; + var _didIteratorError12 = false; + var _iteratorError12 = undefined; try { - for (var _iterator11 = selectorsInRule[Symbol.iterator](), _step11; !(_iteratorNormalCompletion11 = (_step11 = _iterator11.next()).done); _iteratorNormalCompletion11 = true) { - var selector = _step11.value; + for (var _iterator12 = selectorsInRule[Symbol.iterator](), _step12; !(_iteratorNormalCompletion12 = (_step12 = _iterator12.next()).done); _iteratorNormalCompletion12 = true) { + var selector = _step12.value; // pseudo class var unescapedSelector = selector.replace(/\\/g, ''); @@ -1027,16 +1082,16 @@ function () { } } } catch (err) { - _didIteratorError11 = true; - _iteratorError11 = err; + _didIteratorError12 = true; + _iteratorError12 = err; } finally { try { - if (!_iteratorNormalCompletion11 && _iterator11.return != null) { - _iterator11.return(); + if (!_iteratorNormalCompletion12 && _iterator12.return != null) { + _iterator12.return(); } } finally { - if (_didIteratorError11) { - throw _iteratorError11; + if (_didIteratorError12) { + throw _iteratorError12; } } } diff --git a/lib/purgecss.js b/lib/purgecss.js index 930845c0..96c69687 100644 --- a/lib/purgecss.js +++ b/lib/purgecss.js @@ -338,6 +338,7 @@ var defaultOptions = { rejected: false }; +var IGNORE_ANNOTATION_CURRENT = 'purgecss ignore current'; var IGNORE_ANNOTATION_NEXT = 'purgecss ignore'; var IGNORE_ANNOTATION_START = 'purgecss start ignore'; var IGNORE_ANNOTATION_END = 'purgecss end ignore'; @@ -586,6 +587,16 @@ function () { } } } + /** + * Strips quotes at the end and at the end of a string + * @param {string} value the string to be stripped + */ + + }, { + key: "stripQuotes", + value: function stripQuotes(value) { + return value.replace(/(^["'])|(["']$)/g, ''); + } /** * Extract the selectors present in the passed string using a purgecss extractor * @param {array} content Array of content @@ -791,6 +802,10 @@ function () { return; } + if (this.hasIgnoreAnnotation(node)) { + return; + } + var keepSelector = true; node.selector = selectorParser(function (selectorsParsed) { selectorsParsed.walk(function (selector) { @@ -885,7 +900,30 @@ function () { if (this.options.fontFace) { if (prop === 'font-family') { - this.usedFontFaces.add(value); + var _iteratorNormalCompletion10 = true; + var _didIteratorError10 = false; + var _iteratorError10 = undefined; + + try { + for (var _iterator10 = value.split(',')[Symbol.iterator](), _step10; !(_iteratorNormalCompletion10 = (_step10 = _iterator10.next()).done); _iteratorNormalCompletion10 = true) { + var fontName = _step10.value; + var cleanedFontFace = this.stripQuotes(fontName.trim()); + this.usedFontFaces.add(cleanedFontFace); + } + } catch (err) { + _didIteratorError10 = true; + _iteratorError10 = err; + } finally { + try { + if (!_iteratorNormalCompletion10 && _iterator10.return != null) { + _iterator10.return(); + } + } finally { + if (_didIteratorError10) { + throw _iteratorError10; + } + } + } } } } @@ -924,34 +962,34 @@ function () { } if (this.options.fontFace && node.name === 'font-face') { - var _iteratorNormalCompletion10 = true; - var _didIteratorError10 = false; - var _iteratorError10 = undefined; + var _iteratorNormalCompletion11 = true; + var _didIteratorError11 = false; + var _iteratorError11 = undefined; try { - for (var _iterator10 = node.nodes[Symbol.iterator](), _step10; !(_iteratorNormalCompletion10 = (_step10 = _iterator10.next()).done); _iteratorNormalCompletion10 = true) { - var _step10$value = _step10.value, - prop = _step10$value.prop, - value = _step10$value.value; + for (var _iterator11 = node.nodes[Symbol.iterator](), _step11; !(_iteratorNormalCompletion11 = (_step11 = _iterator11.next()).done); _iteratorNormalCompletion11 = true) { + var _step11$value = _step11.value, + prop = _step11$value.prop, + value = _step11$value.value; if (prop === 'font-family') { this.atRules.fontFace.push({ - name: value, + name: this.stripQuotes(value), node: node }); } } } catch (err) { - _didIteratorError10 = true; - _iteratorError10 = err; + _didIteratorError11 = true; + _iteratorError11 = err; } finally { try { - if (!_iteratorNormalCompletion10 && _iterator10.return != null) { - _iterator10.return(); + if (!_iteratorNormalCompletion11 && _iterator11.return != null) { + _iterator11.return(); } } finally { - if (_didIteratorError10) { - throw _iteratorError10; + if (_didIteratorError11) { + throw _iteratorError11; } } } @@ -983,6 +1021,23 @@ function () { return false; } + /** + * Check if the node has a css comment to ignore current selector rule + * @param {object} rule Node of postcss abstract syntax tree + */ + + }, { + key: "hasIgnoreAnnotation", + value: function hasIgnoreAnnotation(rule) { + var found = false; + rule.walkComments(function (node) { + if (node && node.type === 'comment' && node.text.includes(IGNORE_ANNOTATION_CURRENT)) { + found = true; + node.remove(); + } + }); + return found; + } /** * Check if the node correspond to an empty css rule * @param {object} node Node of postcss abstract syntax tree @@ -1006,13 +1061,13 @@ function () { }, { key: "shouldKeepSelector", value: function shouldKeepSelector(selectorsInContent, selectorsInRule) { - var _iteratorNormalCompletion11 = true; - var _didIteratorError11 = false; - var _iteratorError11 = undefined; + var _iteratorNormalCompletion12 = true; + var _didIteratorError12 = false; + var _iteratorError12 = undefined; try { - for (var _iterator11 = selectorsInRule[Symbol.iterator](), _step11; !(_iteratorNormalCompletion11 = (_step11 = _iterator11.next()).done); _iteratorNormalCompletion11 = true) { - var selector = _step11.value; + for (var _iterator12 = selectorsInRule[Symbol.iterator](), _step12; !(_iteratorNormalCompletion12 = (_step12 = _iterator12.next()).done); _iteratorNormalCompletion12 = true) { + var selector = _step12.value; // pseudo class var unescapedSelector = selector.replace(/\\/g, ''); @@ -1031,16 +1086,16 @@ function () { } } } catch (err) { - _didIteratorError11 = true; - _iteratorError11 = err; + _didIteratorError12 = true; + _iteratorError12 = err; } finally { try { - if (!_iteratorNormalCompletion11 && _iterator11.return != null) { - _iterator11.return(); + if (!_iteratorNormalCompletion12 && _iterator12.return != null) { + _iterator12.return(); } } finally { - if (_didIteratorError11) { - throw _iteratorError11; + if (_didIteratorError12) { + throw _iteratorError12; } } } diff --git a/src/constants/constants.js b/src/constants/constants.js index 55c7ff11..8e429323 100644 --- a/src/constants/constants.js +++ b/src/constants/constants.js @@ -1,3 +1,4 @@ +export const IGNORE_ANNOTATION_CURRENT = 'purgecss ignore current' export const IGNORE_ANNOTATION_NEXT = 'purgecss ignore' export const IGNORE_ANNOTATION_START = 'purgecss start ignore' export const IGNORE_ANNOTATION_END = 'purgecss end ignore' diff --git a/src/index.js b/src/index.js index 4c29a68d..906d8137 100644 --- a/src/index.js +++ b/src/index.js @@ -20,7 +20,7 @@ import { ERROR_WHITELIST_PATTERNS_TYPE, IGNORE_ANNOTATION_NEXT, IGNORE_ANNOTATION_START, - IGNORE_ANNOTATION_END + IGNORE_ANNOTATION_END, IGNORE_ANNOTATION_CURRENT } from './constants/constants' import CSS_WHITELIST from './constants/cssWhitelist' import SELECTOR_STANDARD_TYPES from './constants/selectorTypes' @@ -312,6 +312,10 @@ class Purgecss { return } + if (this.hasIgnoreAnnotation(node)) { + return + } + let keepSelector = true node.selector = selectorParser(selectorsParsed => { selectorsParsed.walk(selector => { @@ -420,6 +424,21 @@ class Purgecss { return false } + /** + * Check if the node has a css comment to ignore current selector rule + * @param {object} rule Node of postcss abstract syntax tree + */ + hasIgnoreAnnotation(rule: Object): boolean { + let found = false; + rule.walkComments(node => { + if (node && node.type === 'comment' && node.text.includes(IGNORE_ANNOTATION_CURRENT)) { + found = true + node.remove() + } + }) + return found + } + /** * Check if the node correspond to an empty css rule * @param {object} node Node of postcss abstract syntax tree