@mapbox/postcss-html-filter
Advanced tools
Comparing version 1.0.0 to 1.0.1
# Changelog | ||
## 1.0.1 | ||
- Strip all pseudo-elements and -classes from selectors before checking them. | ||
The resultant increase in CSS size should be minor (e.g. you might keep that `.foo:nth-child(8)` selector that isn't really used); but the potential for bugs in this library much, much lower. | ||
**This should not be a breaking change.** | ||
It fixes some possible bugs; and the only downside is it could possibly add some weight to your inlined CSS, if your CSS uses a *lot* of pseudo selectors. | ||
## 1.0.0 | ||
@@ -4,0 +11,0 @@ |
133
index.js
@@ -8,101 +8,56 @@ 'use strict'; | ||
const postcssDiscardUnused = require('postcss-discard-unused'); | ||
const selectorParser = require('postcss-selector-parser'); | ||
// These are the CSS 1 and 2 pseudo-elements that can be prefixed with | ||
// just one colon. | ||
const earlyPseudoElements = ['first-line', 'first-letter', 'before', 'after']; | ||
// These are CSS pseudo classes listed at | ||
// https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes | ||
// that are not supported by Cheerio (via css-select), as noted in | ||
// https://github.com/fb55/css-select#supported-selectors | ||
// These should be kept only if the selector applies without them. | ||
const pseudoClassBlacklist = [ | ||
'active', | ||
'any', | ||
'default', | ||
'dir', | ||
'first', | ||
'focus', | ||
'hover', | ||
'indeterminate', | ||
'in-range', | ||
'invalid', | ||
'lang', | ||
'left', | ||
'out-of-range', | ||
'read-only', | ||
'read-write', | ||
'right', | ||
'scope', | ||
'target', | ||
'valid', | ||
'visited', | ||
'fullscreen' | ||
]; | ||
// Always keep selectors that include these pseudo-classes. | ||
const pseudoClassesToAlwaysKeep = ['root']; | ||
const removalRegExpFragments = [ | ||
// Remove all vendor-prefixed pseudo selectors. | ||
'::?-[a-zA-Z-]+\\b', | ||
// Remove all pseudo-elements. Single colon works for early pseudo-elements. | ||
// Besides that, we can remove everything prefixed with two colons. | ||
'::[a-zA-Z-]+\\b', | ||
// Remove single-colon pseudo-elements and banished pseudo-classes. | ||
`:(${earlyPseudoElements.concat(pseudoClassBlacklist).join('|')})\\b` | ||
]; | ||
const pseudoSelectorRemovalRegExp = new RegExp( | ||
`(${removalRegExpFragments.join('|')})`, | ||
'g' | ||
); | ||
const alwaysKeepRegExp = new RegExp( | ||
`:(${pseudoClassesToAlwaysKeep.join('|')})\b` | ||
); | ||
const plugin = postcss.plugin('postcss-html-filter', options => { | ||
const $ = cheerio.load(options.html); | ||
const query = cheerio.load(options.html); | ||
return (root, result) => { | ||
root.walkRules(rule => { | ||
let cleanedSelectors = []; | ||
const transformSelector = selector => { | ||
selector.walkPseudos(pseudo => { | ||
// Keep all :root selectors. | ||
if (pseudo.value === 'root') { | ||
return; | ||
} | ||
pseudo.remove(); | ||
}); | ||
}; | ||
// Skip keyframe selectors. | ||
if (/keyframes/.test(_.get(rule, 'parent.name', ''))) return; | ||
const transformRule = rule => { | ||
let cleanedSelectors = []; | ||
rule.selectors.forEach(selector => { | ||
if (alwaysKeepRegExp.test(selector)) return; | ||
// Keep all keyframe selectors. | ||
if (/keyframes/.test(_.get(rule, 'parent.name', ''))) return; | ||
// Pseudo-selectors are red herrings in Cheerio queries. | ||
const pseudoSafeSelector = selector.replace( | ||
pseudoSelectorRemovalRegExp, | ||
'' | ||
); | ||
rule.selectors.forEach(selector => { | ||
const pseudolessSelector = selectorParser(transformSelector).processSync( | ||
selector | ||
); | ||
if (!pseudoSafeSelector) { | ||
cleanedSelectors.push(selector); | ||
return; | ||
} | ||
let matchingElements; | ||
try { | ||
matchingElements = $(pseudoSafeSelector); | ||
} catch (cheerioError) { | ||
throw new Error( | ||
`Cheerio failed to interpret the following selector:\n ${selector}` | ||
); | ||
} | ||
if (matchingElements.length !== 0) { | ||
cleanedSelectors.push(selector); | ||
} | ||
}); | ||
// Remove the rule if it has no applicable selectors | ||
if (cleanedSelectors.length === 0) { | ||
rule.remove(); | ||
} else { | ||
rule.selector = cleanedSelectors.join(', '); | ||
if (!pseudolessSelector) { | ||
cleanedSelectors.push(selector); | ||
return; | ||
} | ||
let matchingElements; | ||
try { | ||
matchingElements = query(pseudolessSelector); | ||
} catch (cheerioError) { | ||
throw new Error( | ||
`Cheerio failed to interpret the following selector:\n ${selector}` | ||
); | ||
} | ||
if (matchingElements.length !== 0) { | ||
cleanedSelectors.push(selector); | ||
} | ||
}); | ||
// Remove the rule if it no longer has applicable selectors. | ||
if (cleanedSelectors.length === 0) { | ||
rule.remove(); | ||
} else { | ||
rule.selector = cleanedSelectors.join(', '); | ||
} | ||
}; | ||
return (root, result) => { | ||
root.walkRules(transformRule); | ||
// Discard any at-rules we've emptied of rules. | ||
@@ -109,0 +64,0 @@ postcssDiscardEmpty()(root, result); |
{ | ||
"name": "@mapbox/postcss-html-filter", | ||
"version": "1.0.0", | ||
"version": "1.0.1", | ||
"description": "Filter CSS through HTML, removing selectors that do not apply to the HTML", | ||
@@ -8,3 +8,3 @@ "main": "index.js", | ||
"precommit": "lint-staged", | ||
"format": "prettier --single-quote --write '{,lib/**/,test/**/}*.js'", | ||
"format": "prettier --write '**/*.js'", | ||
"lint": "eslint .", | ||
@@ -34,15 +34,16 @@ "test-jest": "jest", | ||
"dependencies": { | ||
"cheerio": "^1.0.0-rc.1", | ||
"lodash": "^4.17.4", | ||
"cheerio": "^1.0.0-rc.2", | ||
"lodash": "^4.17.5", | ||
"postcss-discard-empty": "^2.1.0", | ||
"postcss-discard-unused": "^2.2.3" | ||
"postcss-discard-unused": "^2.2.3", | ||
"postcss-selector-parser": "^3.1.1" | ||
}, | ||
"devDependencies": { | ||
"eslint": "^4.0.0", | ||
"eslint-plugin-node": "^5.0.0", | ||
"husky": "^0.14.1", | ||
"jest": "^20.0.4", | ||
"lint-staged": "^4.0.0", | ||
"postcss": "^6.0.0", | ||
"prettier": "^1.4.4" | ||
"eslint": "^4.19.1", | ||
"eslint-plugin-node": "^6.0.1", | ||
"husky": "^0.14.3", | ||
"jest": "^22.4.3", | ||
"lint-staged": "^7.0.0", | ||
"postcss": "^6.0.21", | ||
"prettier": "^1.11.1" | ||
}, | ||
@@ -52,2 +53,5 @@ "peerDependencies": { | ||
}, | ||
"prettier": { | ||
"singleQuote": true | ||
}, | ||
"jest": { | ||
@@ -69,3 +73,3 @@ "coverageReporters": [ | ||
"eslint", | ||
"prettier --single-quote --write", | ||
"prettier --write", | ||
"git add" | ||
@@ -72,0 +76,0 @@ ] |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
16548
12
146
6
1
+ Addeddot-prop@5.3.0(transitive)
+ Addedindexes-of@1.0.1(transitive)
+ Addedis-obj@2.0.0(transitive)
+ Addedpostcss-selector-parser@3.1.2(transitive)
+ Addeduniq@1.0.1(transitive)
Updatedcheerio@^1.0.0-rc.2
Updatedlodash@^4.17.5