Comparing version 0.5.0 to 1.0.0
@@ -7,3 +7,3 @@ /** | ||
* An exceptionally fast, thorough and tiny unused-CSS cleaner | ||
* https://github.com/leeoniya/dropcss (v0.5.0) | ||
* https://github.com/leeoniya/dropcss (v1.0.0) | ||
*/ | ||
@@ -15,15 +15,17 @@ | ||
var ATTRS = 2; | ||
var TEXT = 3; | ||
var TAG_CLOSE = 4; | ||
var TAG_CLOSE = 3; | ||
function tokenize(html, keepText) { | ||
var RE = { | ||
// TODO: handle self-closed tags <div/> ? | ||
TAG_HEAD: /\s*<([a-z0-9_-]+)(?:\s+([^>]*))?>\s*/my, | ||
TAG_TEXT: /\s*[^<]*/my, | ||
TAG_CLOSE: /\s*<\/[a-z0-9_-]+>\s*/my, | ||
}; | ||
var VOIDS = new Set("area base br col command embed hr img input keygen link meta param source track wbr".split(" ")); | ||
var RE_ATTRS = /([\w-]+)(?:="([^"]*)"|='([^']*)'|=(\S+))?/gm; | ||
// doctype, comments, meta, style, link & script tags. TODO: CDATA | ||
var NASTIES = /<!doctype[^>]*>|<!--[\s\S]*?-->|<script[^>]*>[\s\S]*?<\/script>|<style[^>]*>[\s\S]*?<\/style>|<link[^>]*>|<meta[^>]*>/gmi; | ||
var RE_ATTRS = /([\w-]+)(?:="([^"]*)"|='([^']*)'|=(\S+))?/gm; | ||
var RE = { | ||
// TODO: handle self-closed tags <div/> ? | ||
TAG_HEAD: /\s*<([a-z0-9_-]+)(?:\s+([^>]*))?>\s*/my, | ||
TEXT: /\s*[^<]*/my, | ||
TAG_CLOSE: /\s*<\/[a-z0-9_-]+>\s*/my, | ||
}; | ||
function tokenize(html) { | ||
var pos = 0, m, tokens = []; | ||
@@ -37,21 +39,2 @@ | ||
var voidTags = { | ||
area: true, | ||
base: true, | ||
br: true, | ||
col: true, | ||
command: true, | ||
embed: true, | ||
hr: true, | ||
img: true, | ||
input: true, | ||
keygen: true, | ||
link: true, | ||
meta: true, | ||
param: true, | ||
source: true, | ||
track: true, | ||
wbr: true | ||
}; | ||
function next() { | ||
@@ -83,3 +66,3 @@ m = RE.TAG_CLOSE.exec(html); | ||
if (tag in voidTags) | ||
if (VOIDS.has(tag)) | ||
{ tokens.push(TAG_CLOSE); } | ||
@@ -90,10 +73,6 @@ | ||
m = RE.TAG_TEXT.exec(html); | ||
m = RE.TEXT.exec(html); | ||
if (m != null) { | ||
syncPos(RE.TAG_TEXT); | ||
if (keepText) | ||
{ tokens.push(TEXT, m[0]); } | ||
} | ||
if (m != null) | ||
{ syncPos(RE.TEXT); } | ||
} | ||
@@ -104,2 +83,4 @@ | ||
syncPos({lastIndex: 0}); | ||
return tokens; | ||
@@ -112,13 +93,33 @@ } | ||
function node(parent, tagName, attrs) { | ||
var isText = tagName == '#'; | ||
return { | ||
tagName: tagName, | ||
attributes: attrs, | ||
classList: !isText && attrs != null && attrs.has('class') ? new Set(attrs.get('class').split(/\s+/g)) : EMPTY_SET, | ||
classList: attrs != null && attrs.has('class') ? new Set(attrs.get('class').split(/\s+/g)) : EMPTY_SET, | ||
parentNode: parent, | ||
childNodes: isText ? null : [], | ||
childNodes: [], | ||
}; | ||
} | ||
// adds ._ofTypes: {<tagName>: [...]} to parent | ||
// adds ._typeIdx to childNodes | ||
function getSibsOfType(par, tagName) { | ||
if (par != null) { | ||
var ofTypes = (par._ofTypes = par._ofTypes || {}); | ||
if (!(tagName in ofTypes)) { | ||
var typeIdx = 0; | ||
ofTypes[tagName] = par.childNodes.filter(function (n) { | ||
if (n.tagName == tagName) { | ||
n._typeIdx = typeIdx++; | ||
return true; | ||
} | ||
}); | ||
} | ||
return ofTypes[tagName]; | ||
} | ||
return null; | ||
} | ||
function build(tokens, each) { | ||
@@ -149,7 +150,2 @@ var targ = node(null, "root", EMPTY_SET), idx; | ||
break; | ||
case TEXT: | ||
var n = node(targ, '#', EMPTY_SET); | ||
targ.childNodes.push(n); | ||
each(n, targ.childNodes.length - 1); | ||
break; | ||
} | ||
@@ -185,7 +181,6 @@ } | ||
var _export_parse_ = function (html, pruneText) { | ||
// remove doctype, comments, meta, style, link & script tags. TODO: CDATA | ||
html = html.replace(/<!doctype[^>]*>|<!--[\s\S]*?-->|<script[^>]*>[\s\S]*?<\/script>|<style[^>]*>[\s\S]*?<\/style>|<link[^>]*>|<meta[^>]*>/gmi, ''); | ||
var _export_parse_ = function (html) { | ||
html = html.replace(NASTIES, ''); | ||
var tokens = tokenize(html, !pruneText); | ||
var tokens = tokenize(html); | ||
@@ -595,2 +590,4 @@ var ctx = { | ||
function _nthChild(pos, val) { | ||
var res; | ||
if (val == 'odd') | ||
@@ -646,5 +643,7 @@ { res = pos % 2 == 1; } | ||
var n = ctx.node; | ||
var tag = n.tagName; | ||
tidx = n.idx; | ||
par = n.parentNode; | ||
var len = par ? par.childNodes.length : 1; | ||
var tsibs = (void 0); | ||
@@ -655,5 +654,2 @@ switch (name) { | ||
break; | ||
case 'empty': | ||
res = n.tagName != '#' && n.childNodes.length == 0; | ||
break; | ||
case 'first-child': | ||
@@ -674,2 +670,22 @@ res = tidx == 0; | ||
break; | ||
case 'first-of-type': | ||
tsibs = getSibsOfType(par, tag); | ||
res = n._typeIdx == 0; | ||
break; | ||
case 'last-of-type': | ||
tsibs = getSibsOfType(par, tag); | ||
res = n._typeIdx == tsibs.length - 1; | ||
break; | ||
case 'only-of-type': | ||
tsibs = getSibsOfType(par, tag); | ||
res = tsibs.length == 1; | ||
break; | ||
case 'nth-of-type': | ||
tsibs = getSibsOfType(par, tag); | ||
res = _nthChild(n._typeIdx + 1, val); | ||
break; | ||
case 'nth-last-of-type': | ||
tsibs = getSibsOfType(par, tag); | ||
res = _nthChild(tsibs.length - n._typeIdx, val); | ||
break; | ||
} | ||
@@ -737,3 +753,3 @@ | ||
var pseudoAssertable = /:(?:first|last|nth|only|not|empty)\b/; // |lang | ||
var pseudoAssertable = /:(?:first|last|nth|only|not)\b/; // |lang | ||
@@ -752,4 +768,15 @@ function stripNonAssertablePseudos(sel) { | ||
function removeBackwards(css, defs, used) { | ||
for (var i = defs.length - 1; i > -1; i--) { | ||
var d = defs[i]; | ||
if (!used.has(d[2])) | ||
{ css = splice(css, d[0], d[1], ''); } | ||
} | ||
return css; | ||
} | ||
function dropKeyFrames(css) { | ||
var matches = []; | ||
var defs = []; | ||
var used = new Set(); | ||
@@ -762,3 +789,3 @@ | ||
var ch = takeUntilMatchedClosing(css, RE.lastIndex); | ||
matches.push([m.index, m[0].length + ch.length + 1, m[1]]); | ||
defs.push([m.index, m[0].length + ch.length + 1, m[1]]); | ||
} | ||
@@ -775,12 +802,31 @@ | ||
// purge backwards | ||
var css2 = css; | ||
for (var i = matches.length - 1; i > -1; i--) { | ||
var ma = matches[i]; | ||
return removeBackwards(css, defs, used); | ||
} | ||
if (!used.has(ma[2])) | ||
{ css2 = splice(css2, ma[0], ma[1], ''); } | ||
function dropFontFaces(css) { | ||
var defs = []; | ||
var used = new Set(); | ||
// defined | ||
var RE = /@font-face[\s\S]+?font-family:\s*(['"\w-]+)[^}]+\}/gm, m; | ||
while (m = RE.exec(css)) { | ||
var clean = m[1].replace(/['"]/gm, ''); | ||
defs.push([m.index, m[0].length, clean]); | ||
} | ||
return css2; | ||
// used | ||
var RE2 = /font-family:([^;!}]+)/gm; | ||
while (m = RE2.exec(css)) { | ||
var inDef = defs.some(function (d) { return m.index > d[0] && m.index < d[0] + d[1]; }); | ||
if (!inDef) { | ||
m[1].trim().split(",").forEach(function (a) { | ||
used.add(a.trim().replace(/['"]/gm, '')); | ||
}); | ||
} | ||
} | ||
return removeBackwards(css, defs, used); | ||
} | ||
@@ -897,2 +943,4 @@ | ||
out = dropFontFaces(out); | ||
// log.forEach(e => console.log(e[0], e[1])); | ||
@@ -899,0 +947,0 @@ |
@@ -7,3 +7,3 @@ /** | ||
* An exceptionally fast, thorough and tiny unused-CSS cleaner | ||
* https://github.com/leeoniya/dropcss (v0.5.0) | ||
* https://github.com/leeoniya/dropcss (v1.0.0) | ||
*/ | ||
@@ -19,15 +19,17 @@ | ||
var ATTRS = 2; | ||
var TEXT = 3; | ||
var TAG_CLOSE = 4; | ||
var TAG_CLOSE = 3; | ||
function tokenize(html, keepText) { | ||
var RE = { | ||
// TODO: handle self-closed tags <div/> ? | ||
TAG_HEAD: /\s*<([a-z0-9_-]+)(?:\s+([^>]*))?>\s*/my, | ||
TAG_TEXT: /\s*[^<]*/my, | ||
TAG_CLOSE: /\s*<\/[a-z0-9_-]+>\s*/my, | ||
}; | ||
var VOIDS = new Set("area base br col command embed hr img input keygen link meta param source track wbr".split(" ")); | ||
var RE_ATTRS = /([\w-]+)(?:="([^"]*)"|='([^']*)'|=(\S+))?/gm; | ||
// doctype, comments, meta, style, link & script tags. TODO: CDATA | ||
var NASTIES = /<!doctype[^>]*>|<!--[\s\S]*?-->|<script[^>]*>[\s\S]*?<\/script>|<style[^>]*>[\s\S]*?<\/style>|<link[^>]*>|<meta[^>]*>/gmi; | ||
var RE_ATTRS = /([\w-]+)(?:="([^"]*)"|='([^']*)'|=(\S+))?/gm; | ||
var RE = { | ||
// TODO: handle self-closed tags <div/> ? | ||
TAG_HEAD: /\s*<([a-z0-9_-]+)(?:\s+([^>]*))?>\s*/my, | ||
TEXT: /\s*[^<]*/my, | ||
TAG_CLOSE: /\s*<\/[a-z0-9_-]+>\s*/my, | ||
}; | ||
function tokenize(html) { | ||
var pos = 0, m, tokens = []; | ||
@@ -41,21 +43,2 @@ | ||
var voidTags = { | ||
area: true, | ||
base: true, | ||
br: true, | ||
col: true, | ||
command: true, | ||
embed: true, | ||
hr: true, | ||
img: true, | ||
input: true, | ||
keygen: true, | ||
link: true, | ||
meta: true, | ||
param: true, | ||
source: true, | ||
track: true, | ||
wbr: true | ||
}; | ||
function next() { | ||
@@ -87,3 +70,3 @@ m = RE.TAG_CLOSE.exec(html); | ||
if (tag in voidTags) | ||
if (VOIDS.has(tag)) | ||
{ tokens.push(TAG_CLOSE); } | ||
@@ -94,10 +77,6 @@ | ||
m = RE.TAG_TEXT.exec(html); | ||
m = RE.TEXT.exec(html); | ||
if (m != null) { | ||
syncPos(RE.TAG_TEXT); | ||
if (keepText) | ||
{ tokens.push(TEXT, m[0]); } | ||
} | ||
if (m != null) | ||
{ syncPos(RE.TEXT); } | ||
} | ||
@@ -108,2 +87,4 @@ | ||
syncPos({lastIndex: 0}); | ||
return tokens; | ||
@@ -116,13 +97,33 @@ } | ||
function node(parent, tagName, attrs) { | ||
var isText = tagName == '#'; | ||
return { | ||
tagName: tagName, | ||
attributes: attrs, | ||
classList: !isText && attrs != null && attrs.has('class') ? new Set(attrs.get('class').split(/\s+/g)) : EMPTY_SET, | ||
classList: attrs != null && attrs.has('class') ? new Set(attrs.get('class').split(/\s+/g)) : EMPTY_SET, | ||
parentNode: parent, | ||
childNodes: isText ? null : [], | ||
childNodes: [], | ||
}; | ||
} | ||
// adds ._ofTypes: {<tagName>: [...]} to parent | ||
// adds ._typeIdx to childNodes | ||
function getSibsOfType(par, tagName) { | ||
if (par != null) { | ||
var ofTypes = (par._ofTypes = par._ofTypes || {}); | ||
if (!(tagName in ofTypes)) { | ||
var typeIdx = 0; | ||
ofTypes[tagName] = par.childNodes.filter(function (n) { | ||
if (n.tagName == tagName) { | ||
n._typeIdx = typeIdx++; | ||
return true; | ||
} | ||
}); | ||
} | ||
return ofTypes[tagName]; | ||
} | ||
return null; | ||
} | ||
function build(tokens, each) { | ||
@@ -153,7 +154,2 @@ var targ = node(null, "root", EMPTY_SET), idx; | ||
break; | ||
case TEXT: | ||
var n = node(targ, '#', EMPTY_SET); | ||
targ.childNodes.push(n); | ||
each(n, targ.childNodes.length - 1); | ||
break; | ||
} | ||
@@ -189,7 +185,6 @@ } | ||
var _export_parse_ = function (html, pruneText) { | ||
// remove doctype, comments, meta, style, link & script tags. TODO: CDATA | ||
html = html.replace(/<!doctype[^>]*>|<!--[\s\S]*?-->|<script[^>]*>[\s\S]*?<\/script>|<style[^>]*>[\s\S]*?<\/style>|<link[^>]*>|<meta[^>]*>/gmi, ''); | ||
var _export_parse_ = function (html) { | ||
html = html.replace(NASTIES, ''); | ||
var tokens = tokenize(html, !pruneText); | ||
var tokens = tokenize(html); | ||
@@ -599,2 +594,4 @@ var ctx = { | ||
function _nthChild(pos, val) { | ||
var res; | ||
if (val == 'odd') | ||
@@ -650,5 +647,7 @@ { res = pos % 2 == 1; } | ||
var n = ctx.node; | ||
var tag = n.tagName; | ||
tidx = n.idx; | ||
par = n.parentNode; | ||
var len = par ? par.childNodes.length : 1; | ||
var tsibs = (void 0); | ||
@@ -659,5 +658,2 @@ switch (name) { | ||
break; | ||
case 'empty': | ||
res = n.tagName != '#' && n.childNodes.length == 0; | ||
break; | ||
case 'first-child': | ||
@@ -678,2 +674,22 @@ res = tidx == 0; | ||
break; | ||
case 'first-of-type': | ||
tsibs = getSibsOfType(par, tag); | ||
res = n._typeIdx == 0; | ||
break; | ||
case 'last-of-type': | ||
tsibs = getSibsOfType(par, tag); | ||
res = n._typeIdx == tsibs.length - 1; | ||
break; | ||
case 'only-of-type': | ||
tsibs = getSibsOfType(par, tag); | ||
res = tsibs.length == 1; | ||
break; | ||
case 'nth-of-type': | ||
tsibs = getSibsOfType(par, tag); | ||
res = _nthChild(n._typeIdx + 1, val); | ||
break; | ||
case 'nth-last-of-type': | ||
tsibs = getSibsOfType(par, tag); | ||
res = _nthChild(tsibs.length - n._typeIdx, val); | ||
break; | ||
} | ||
@@ -741,3 +757,3 @@ | ||
var pseudoAssertable = /:(?:first|last|nth|only|not|empty)\b/; // |lang | ||
var pseudoAssertable = /:(?:first|last|nth|only|not)\b/; // |lang | ||
@@ -756,4 +772,15 @@ function stripNonAssertablePseudos(sel) { | ||
function removeBackwards(css, defs, used) { | ||
for (var i = defs.length - 1; i > -1; i--) { | ||
var d = defs[i]; | ||
if (!used.has(d[2])) | ||
{ css = splice(css, d[0], d[1], ''); } | ||
} | ||
return css; | ||
} | ||
function dropKeyFrames(css) { | ||
var matches = []; | ||
var defs = []; | ||
var used = new Set(); | ||
@@ -766,3 +793,3 @@ | ||
var ch = takeUntilMatchedClosing(css, RE.lastIndex); | ||
matches.push([m.index, m[0].length + ch.length + 1, m[1]]); | ||
defs.push([m.index, m[0].length + ch.length + 1, m[1]]); | ||
} | ||
@@ -779,12 +806,31 @@ | ||
// purge backwards | ||
var css2 = css; | ||
for (var i = matches.length - 1; i > -1; i--) { | ||
var ma = matches[i]; | ||
return removeBackwards(css, defs, used); | ||
} | ||
if (!used.has(ma[2])) | ||
{ css2 = splice(css2, ma[0], ma[1], ''); } | ||
function dropFontFaces(css) { | ||
var defs = []; | ||
var used = new Set(); | ||
// defined | ||
var RE = /@font-face[\s\S]+?font-family:\s*(['"\w-]+)[^}]+\}/gm, m; | ||
while (m = RE.exec(css)) { | ||
var clean = m[1].replace(/['"]/gm, ''); | ||
defs.push([m.index, m[0].length, clean]); | ||
} | ||
return css2; | ||
// used | ||
var RE2 = /font-family:([^;!}]+)/gm; | ||
while (m = RE2.exec(css)) { | ||
var inDef = defs.some(function (d) { return m.index > d[0] && m.index < d[0] + d[1]; }); | ||
if (!inDef) { | ||
m[1].trim().split(",").forEach(function (a) { | ||
used.add(a.trim().replace(/['"]/gm, '')); | ||
}); | ||
} | ||
} | ||
return removeBackwards(css, defs, used); | ||
} | ||
@@ -901,2 +947,4 @@ | ||
out = dropFontFaces(out); | ||
// log.forEach(e => console.log(e[0], e[1])); | ||
@@ -903,0 +951,0 @@ |
@@ -1,2 +0,2 @@ | ||
/*! https://github.com/leeoniya/dropcss (v0.5.0) */ | ||
!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):(e=e||self).dropcss=r()}(this,function(){"use strict";var e=1,r=2,n=3,t=4;var a=new Set;function s(e,r,n){var t="#"==r;return{tagName:r,attributes:n,classList:!t&&null!=n&&n.has("class")?new Set(n.get("class").split(/\s+/g)):a,parentNode:e,childNodes:t?null:[]}}var i=function(i,c){var l=function(a,s){var i,c={TAG_HEAD:/\s*<([a-z0-9_-]+)(?:\s+([^>]*))?>\s*/my,TAG_TEXT:/\s*[^<]*/my,TAG_CLOSE:/\s*<\/[a-z0-9_-]+>\s*/my},l=/([\w-]+)(?:="([^"]*)"|='([^']*)'|=(\S+))?/gm,u=0,o=[];function d(e){for(var r in u=e.lastIndex,c)c[r].lastIndex=u}var f={area:!0,base:!0,br:!0,col:!0,command:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0};function h(){if(null!=(i=c.TAG_CLOSE.exec(a)))return d(c.TAG_CLOSE),void o.push(t);if(null==(i=c.TAG_HEAD.exec(a)))null!=(i=c.TAG_TEXT.exec(a))&&(d(c.TAG_TEXT),s&&o.push(n,i[0]));else{d(c.TAG_HEAD);var u=i[1];o.push(e,u);var h=i[2];if(null!=h){for(var p,v=new Map;p=l.exec(h);)v.set(p[1],(p[2]||p[3]||p[4]||"").trim());o.push(r,v)}u in f&&o.push(t)}}for(;u<a.length;)h();return o}(i=i.replace(/<!doctype[^>]*>|<!--[\s\S]*?-->|<script[^>]*>[\s\S]*?<\/script>|<style[^>]*>[\s\S]*?<\/style>|<link[^>]*>|<meta[^>]*>/gim,""),!c),u={nodes:[],tag:new Set(["*"]),class:new Set,attr:new Set};!function(i,c){for(var l,u=s(null,"root",a),o=0;o<i.length;o++)switch(i[o]){case e:var d=i[++o],f=a;i[o+1]===r&&(f=i[o+=2]),l=u.childNodes.length,u.childNodes.push(u=s(u,d,f)),c(u,l);break;case t:u=u.parentNode;break;case n:var h=s(u,"#",a);u.childNodes.push(h),c(h,u.childNodes.length-1)}}(l,function(e,r){return function(e,r,n){e.idx=r;var t=e.attributes;n.tag.add(e.tagName),e.classList.forEach(function(e){return n.class.add(e)}),t.has("id")&&n.attr.add("[id="+t.get("id")+"]"),t.has("type")&&n.attr.add("[type="+t.get("type")+"]"),n.nodes.push(e)}(e,r,u)});return u},c=/\s*\/\*[\s\S]*?\*\/\s*/gm,l=/\s*[>~+]\s*|\s+/g,u=1,o=2,d=3,f=4,h=5;function p(e,r){for(var n="",t=1;"{"==e[r]?t++:"}"==e[r]&&t--,0!=t;)n+=e[r++];return n}function v(e){var r,n={RULE_HEAD:/\s*([^{;]+?)\s*[{;]\s*/my,RULE_TAIL:/\s*([^}]*?)\s*\}/my,AT_TAIL:/\s*\}/my,RULE_FULL:/\s*([^{]*?)\{([^}]+?)\}/my},t=0,a=0,s=[];function i(e){for(var r in a=e.lastIndex,n)n[r].lastIndex=a}function c(){if(t>0&&null!=(r=n.AT_TAIL.exec(e)))return t--,s.push(o),void i(n.AT_TAIL);if(null!=(r=n.RULE_HEAD.exec(e))){var c=r[1];if(i(n.RULE_HEAD),"@"==c[0])switch(c.match(/@[a-z-]+/)[0]){case"@media":case"@supports":case"@document":t++,s.push(u,c);break;case"@import":case"@charset":case"@namespace":s.push(h,c+";");break;default:t++;var v=p(e,a);i({lastIndex:a+v.length}),s.push(u,c,h,v)}else s.push(d,(m=r[1],(x=m.split(/\s*,\s*/gm)).push(x.map(function(e){return function(e){return e.replace(/:?:[a-z-]+(?:\([^()]+\))?/gm,"")}(e).trim().replace(/(\.|#|\[)/gm," $1").replace(/\]/gm,"] ").trim().split(l)})),x)),r=n.RULE_TAIL.exec(e),s.push(f,r[1]),i(n.RULE_TAIL)}else a=e.length;var m,x}for(;a<e.length;)c();return s}function m(e,r){return r==e.tagName||"*"==r}function x(e,r,n,t){t=t||"=";var a=e.attributes;if(a.has(r)){var s=a.get(r);switch(t){case"=":return null==n||n==s;case"*=":return-1!=s.indexOf(n);case"^=":return s.startsWith(n);case"$=":return s.endsWith(n)}}return!1}function g(e,r){return e.classList.has(r)}function b(e,r){return e.some(function(e){return function e(r,n){var t,a,s,i,c,l;for(;n.idx>-1;){switch(r[n.idx]){case"_":t=r[--n.idx],l=m(n.node,t),n.idx--;break;case"#":a=r[--n.idx],l=x(n.node,"id",a,"="),n.idx--;break;case".":t=r[--n.idx],l=g(n.node,t),n.idx--;break;case"[":t=r[--n.idx],s=r[--n.idx],a=r[--n.idx],l=x(n.node,t,a,s),n.idx--;break;case":":t=r[--n.idx],a=r[--n.idx];var u=n.node;c=u.idx;var o=(i=u.parentNode)?i.childNodes.length:1;switch(t){case"not":l=!e(a,{node:n.node,idx:a.length-1});break;case"empty":l="#"!=u.tagName&&0==u.childNodes.length;break;case"first-child":l=0==c;break;case"last-child":l=c==o-1;break;case"only-child":l=1==o;break;case"nth-child":l=k(c+1,a);break;case"nth-last-child":l=k(o-c,a)}n.idx--;break;case" ":for(c=--n.idx,l=!1;!l&&null!=(i=n.node.parentNode);)n.idx=c,n.node=i,l=e(r,n);break;case">":n.idx--,null!=(i=n.node.parentNode)?(n.node=i,l=e(r,n)):l=!1;break;case"+":n.idx--,null!=(i=n.node.parentNode)&&n.node.idx>0?(n.node=i.childNodes[n.node.idx-1],l=e(r,n)):l=!1;break;case"~":if(n.idx--,l=!1,c=n.node.idx,null!=(i=n.node.parentNode)&&c>0)for(var d=0;d<c&&!l;d++)n.node=i.childNodes[d],l=e(r,n)}if(!l)break}return l}(r,{idx:r.length-1,node:e})})}var T=/^([+-]?\d*)?n([+-]\d+)?$/;function k(e,r){if("odd"==r)res=e%2==1;else if("even"==r)res=e%2==0;else if(/^\d+$/.test(r))res=e==+r;else{var n=function(e){var r=T.exec(e);if(null!=r){var n=r[1],t=r[2];return[n=null==n||"+"==n?1:"-"==n?-1:+n,t=null==t?0:+t]}return[0,0]}(r);res=function(e,r,n){if(r<0&&e<=0)return!1;if(-1===e)return n<=r;if(0===e)return n===r;if(1===e)return r<0||n>=r;var t=r%e;return t<0&&(t+=e),e>1?n>=r&&n%e===t:(e*=-1,n<=r&&n%e===t)}(n[0],n[1],e)}return res}var E=function(e,r){return b(e,Array.isArray(r)?r:function e(r){var n,t={IDENT:/([\w*-]+)/iy,ATTR:/([\w-]+)(?:(.?=)"?([^\]]*?)"?)?\]/iy,PSEUDO:/([\w-]+)(?:\(([^)]*)\))?/iy,MODE:/\s*[:.#\[]\s*/iy,COMB:/\s*[>~+]\s*|\s+/iy},a=0,s=[],i=-1;function c(e){for(var r in a=e.lastIndex,t)t[r].lastIndex=a}function l(){var l=!1;if(n=t.COMB.exec(r)){l=!0;var u=n[0].trim();""==u&&(u=" "),s.push(u),c(t.COMB),i=a}else if(n=t.MODE.exec(r)){l=!0;var o=n[0].trim();c(t.MODE),":"==o?(n=t.PSEUDO.exec(r),s.splice(i+1,0,null!=n[2]&&"not"==n[1]?e(n[2]):n[2],n[1],o),c(t.PSEUDO)):"["==o?(n=t.ATTR.exec(r),s.splice(i+1,0,n[3],n[2],n[1],o),c(t.ATTR)):(n=t.IDENT.exec(r),s.push(n[1],o),c(t.IDENT))}else(n=t.IDENT.exec(r))&&(l=!0,s.push(n[1],"_"),c(t.IDENT));return l}for(;a<r.length;)l();return s}(r))},y=/\[([\w-]+)(?:(.?=)"?([^\]]*?)"?)?\]/i,A=/:(?:first|last|nth|only|not|empty)\b/;var N=function(e){return!0};return function(e){for(var r=i(e.html,!e.keepText),n=e.shouldDrop||N,t=v(e.css.replace(c,"")),a={},s=0;s<t.length;s++)if(t[s]===d){var l=t[s+1],m=l[l.length-1];s++;for(var g=0;g<m.length;g++){var b=m[g];e:for(var T=0;T<b.length;T++){var k=b[T],w=!1,_=void 0;if(""!=k){if(k in a)w=a[k];else switch(k[0]){case"#":_=k.substr(1),a[k]=w=r.attr.has("[id="+_+"]");break;case".":_=k.substr(1),a[k]=w=r.class.has(_);break;case"[":if(k.startsWith("[type="))a[k]=w=r.attr.has(k);else{var L=k.match(y);a[k]=w=r.nodes.some(function(e){return x(e,L[1],L[3],L[2])})}break;default:a[k]=w=r.tag.has(k)}if(!w){!0===n(l[g])?l[g]=null:a[l[g]]=!0;break e}}}}}for(var I=0;I<t.length;I++)t[I]===d&&(t[++I].length,t[I]=t[I].filter(function(e){if("string"==typeof e){if(e in a)return a[e];var t=(s=e).replace(/:?:[a-z-]+/gm,function(e){return s.startsWith("::")||!A.test(e)?"":e}).replace(/:[a-z-]+\(\)/gm,"");return""==t||(t in a?a[t]:a[t]=E(r.nodes,t)||!0!==n(e))}var s;return!1}));var D=function(e){for(var r="",n=0,t=0;t<e.length;t++)switch(e[t]){case d:var a=e[++t];(n=a.length)>0&&(r+=a.join());break;case f:n>0&&(r+="{"+e[++t]+"}");break;case u:r+=e[++t]+"{";break;case o:r+="}";break;case h:r+=e[++t]}return r.replace(/@[a-z-]+\s+[^{]+\{\s*\}/gm,"")}(t);return{css:D=function(e){for(var r,n=[],t=new Set,a=/@(?:-\w+-)?keyframes\s+([\w-]+)\s*\{/gm;r=a.exec(e);){var s=p(e,a.lastIndex);n.push([r.index,r[0].length+s.length+1,r[1]])}for(var i=/animation(?:-name)?:([^;!}]+)/gm;r=i.exec(e);)r[1].trim().split(",").forEach(function(e){t.add(e.trim().match(/^[\w-]+/)[0])});for(var c,l,u,o,d=e,f=n.length-1;f>-1;f--){var h=n[f];t.has(h[2])||(c=d,l=h[0],u=h[1],o="",d=c.slice(0,l)+o+c.slice(l+u))}return d}(D)}}}); | ||
/*! https://github.com/leeoniya/dropcss (v1.0.0) */ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).dropcss=t()}(this,function(){"use strict";var e=1,t=2,r=3,n=new Set("area base br col command embed hr img input keygen link meta param source track wbr".split(" ")),a=/<!doctype[^>]*>|<!--[\s\S]*?-->|<script[^>]*>[\s\S]*?<\/script>|<style[^>]*>[\s\S]*?<\/style>|<link[^>]*>|<meta[^>]*>/gim,s=/([\w-]+)(?:="([^"]*)"|='([^']*)'|=(\S+))?/gm,i={TAG_HEAD:/\s*<([a-z0-9_-]+)(?:\s+([^>]*))?>\s*/my,TEXT:/\s*[^<]*/my,TAG_CLOSE:/\s*<\/[a-z0-9_-]+>\s*/my};var c=new Set;function o(e,t,r){return{tagName:t,attributes:r,classList:null!=r&&r.has("class")?new Set(r.get("class").split(/\s+/g)):c,parentNode:e,childNodes:[]}}function l(e,t){if(null!=e){var r=e._ofTypes=e._ofTypes||{};if(!(t in r)){var n=0;r[t]=e.childNodes.filter(function(e){if(e.tagName==t)return e._typeIdx=n++,!0})}return r[t]}return null}var u=function(l){var u=function(a){var c,o=0,l=[];function u(e){for(var t in o=e.lastIndex,i)i[t].lastIndex=o}function d(){if(null!=(c=i.TAG_CLOSE.exec(a)))return u(i.TAG_CLOSE),void l.push(r);if(null==(c=i.TAG_HEAD.exec(a)))null!=(c=i.TEXT.exec(a))&&u(i.TEXT);else{u(i.TAG_HEAD);var o=c[1];l.push(e,o);var d=c[2];if(null!=d){for(var f,h=new Map;f=s.exec(d);)h.set(f[1],(f[2]||f[3]||f[4]||"").trim());l.push(t,h)}n.has(o)&&l.push(r)}}for(;o<a.length;)d();return u({lastIndex:0}),l}(l=l.replace(a,"")),d={nodes:[],tag:new Set(["*"]),class:new Set,attr:new Set};!function(n,a){for(var s,i=o(null,"root",c),l=0;l<n.length;l++)switch(n[l]){case e:var u=n[++l],d=c;n[l+1]===t&&(d=n[l+=2]),s=i.childNodes.length,i.childNodes.push(i=o(i,u,d)),a(i,s);break;case r:i=i.parentNode}}(u,function(e,t){return function(e,t,r){e.idx=t;var n=e.attributes;r.tag.add(e.tagName),e.classList.forEach(function(e){return r.class.add(e)}),n.has("id")&&r.attr.add("[id="+n.get("id")+"]"),n.has("type")&&r.attr.add("[type="+n.get("type")+"]"),r.nodes.push(e)}(e,t,d)});return d},d=/\s*\/\*[\s\S]*?\*\/\s*/gm,f=/\s*[>~+]\s*|\s+/g,h=1,p=2,x=3,v=4,m=5;function g(e,t){for(var r="",n=1;"{"==e[t]?n++:"}"==e[t]&&n--,0!=n;)r+=e[t++];return r}function y(e){var t,r={RULE_HEAD:/\s*([^{;]+?)\s*[{;]\s*/my,RULE_TAIL:/\s*([^}]*?)\s*\}/my,AT_TAIL:/\s*\}/my,RULE_FULL:/\s*([^{]*?)\{([^}]+?)\}/my},n=0,a=0,s=[];function i(e){for(var t in a=e.lastIndex,r)r[t].lastIndex=a}function c(){if(n>0&&null!=(t=r.AT_TAIL.exec(e)))return n--,s.push(p),void i(r.AT_TAIL);if(null!=(t=r.RULE_HEAD.exec(e))){var c=t[1];if(i(r.RULE_HEAD),"@"==c[0])switch(c.match(/@[a-z-]+/)[0]){case"@media":case"@supports":case"@document":n++,s.push(h,c);break;case"@import":case"@charset":case"@namespace":s.push(m,c+";");break;default:n++;var o=g(e,a);i({lastIndex:a+o.length}),s.push(h,c,m,o)}else s.push(x,(l=t[1],(u=l.split(/\s*,\s*/gm)).push(u.map(function(e){return function(e){return e.replace(/:?:[a-z-]+(?:\([^()]+\))?/gm,"")}(e).trim().replace(/(\.|#|\[)/gm," $1").replace(/\]/gm,"] ").trim().split(f)})),u)),t=r.RULE_TAIL.exec(e),s.push(v,t[1]),i(r.RULE_TAIL)}else a=e.length;var l,u}for(;a<e.length;)c();return s}function b(e,t){return t==e.tagName||"*"==t}function k(e,t,r,n){n=n||"=";var a=e.attributes;if(a.has(t)){var s=a.get(t);switch(n){case"=":return null==r||r==s;case"*=":return-1!=s.indexOf(r);case"^=":return s.startsWith(r);case"$=":return s.endsWith(r)}}return!1}function T(e,t){return e.classList.has(t)}function E(e,t){return e.some(function(e){return function e(t,r){var n,a,s,i,c,o;for(;r.idx>-1;){switch(t[r.idx]){case"_":n=t[--r.idx],o=b(r.node,n),r.idx--;break;case"#":a=t[--r.idx],o=k(r.node,"id",a,"="),r.idx--;break;case".":n=t[--r.idx],o=T(r.node,n),r.idx--;break;case"[":n=t[--r.idx],s=t[--r.idx],a=t[--r.idx],o=k(r.node,n,a,s),r.idx--;break;case":":n=t[--r.idx],a=t[--r.idx];var u=r.node,d=u.tagName;c=u.idx;var f=(i=u.parentNode)?i.childNodes.length:1,h=void 0;switch(n){case"not":o=!e(a,{node:r.node,idx:a.length-1});break;case"first-child":o=0==c;break;case"last-child":o=c==f-1;break;case"only-child":o=1==f;break;case"nth-child":o=w(c+1,a);break;case"nth-last-child":o=w(f-c,a);break;case"first-of-type":h=l(i,d),o=0==u._typeIdx;break;case"last-of-type":h=l(i,d),o=u._typeIdx==h.length-1;break;case"only-of-type":h=l(i,d),o=1==h.length;break;case"nth-of-type":h=l(i,d),o=w(u._typeIdx+1,a);break;case"nth-last-of-type":h=l(i,d),o=w(h.length-u._typeIdx,a)}r.idx--;break;case" ":for(c=--r.idx,o=!1;!o&&null!=(i=r.node.parentNode);)r.idx=c,r.node=i,o=e(t,r);break;case">":r.idx--,null!=(i=r.node.parentNode)?(r.node=i,o=e(t,r)):o=!1;break;case"+":r.idx--,null!=(i=r.node.parentNode)&&r.node.idx>0?(r.node=i.childNodes[r.node.idx-1],o=e(t,r)):o=!1;break;case"~":if(r.idx--,o=!1,c=r.node.idx,null!=(i=r.node.parentNode)&&c>0)for(var p=0;p<c&&!o;p++)r.node=i.childNodes[p],o=e(t,r)}if(!o)break}return o}(t,{idx:t.length-1,node:e})})}var _=/^([+-]?\d*)?n([+-]\d+)?$/;function w(e,t){var r;if("odd"==t)r=e%2==1;else if("even"==t)r=e%2==0;else if(/^\d+$/.test(t))r=e==+t;else{var n=function(e){var t=_.exec(e);if(null!=t){var r=t[1],n=t[2];return[r=null==r||"+"==r?1:"-"==r?-1:+r,n=null==n?0:+n]}return[0,0]}(t);r=function(e,t,r){if(t<0&&e<=0)return!1;if(-1===e)return r<=t;if(0===e)return r===t;if(1===e)return t<0||r>=t;var n=t%e;return n<0&&(n+=e),e>1?r>=t&&r%e===n:(e*=-1,r<=t&&r%e===n)}(n[0],n[1],e)}return r}var A=function(e,t){return E(e,Array.isArray(t)?t:function e(t){var r,n={IDENT:/([\w*-]+)/iy,ATTR:/([\w-]+)(?:(.?=)"?([^\]]*?)"?)?\]/iy,PSEUDO:/([\w-]+)(?:\(([^)]*)\))?/iy,MODE:/\s*[:.#\[]\s*/iy,COMB:/\s*[>~+]\s*|\s+/iy},a=0,s=[],i=-1;function c(e){for(var t in a=e.lastIndex,n)n[t].lastIndex=a}function o(){var o=!1;if(r=n.COMB.exec(t)){o=!0;var l=r[0].trim();""==l&&(l=" "),s.push(l),c(n.COMB),i=a}else if(r=n.MODE.exec(t)){o=!0;var u=r[0].trim();c(n.MODE),":"==u?(r=n.PSEUDO.exec(t),s.splice(i+1,0,null!=r[2]&&"not"==r[1]?e(r[2]):r[2],r[1],u),c(n.PSEUDO)):"["==u?(r=n.ATTR.exec(t),s.splice(i+1,0,r[3],r[2],r[1],u),c(n.ATTR)):(r=n.IDENT.exec(t),s.push(r[1],u),c(n.IDENT))}else(r=n.IDENT.exec(t))&&(o=!0,s.push(r[1],"_"),c(n.IDENT));return o}for(;a<t.length;)o();return s}(t))},I=/\[([\w-]+)(?:(.?=)"?([^\]]*?)"?)?\]/i,N=/:(?:first|last|nth|only|not)\b/;function L(e,t,r){for(var n=t.length-1;n>-1;n--){var a=t[n];r.has(a[2])||(s=e,i=a[0],c=a[1],o="",e=s.slice(0,i)+o+s.slice(i+c))}var s,i,c,o;return e}var S=function(e){return!0};return function(e){for(var t=u(e.html,!e.keepText),r=e.shouldDrop||S,n=y(e.css.replace(d,"")),a={},s=0;s<n.length;s++)if(n[s]===x){var i=n[s+1],c=i[i.length-1];s++;for(var o=0;o<c.length;o++){var l=c[o];e:for(var f=0;f<l.length;f++){var b=l[f],T=!1,E=void 0;if(""!=b){if(b in a)T=a[b];else switch(b[0]){case"#":E=b.substr(1),a[b]=T=t.attr.has("[id="+E+"]");break;case".":E=b.substr(1),a[b]=T=t.class.has(E);break;case"[":if(b.startsWith("[type="))a[b]=T=t.attr.has(b);else{var _=b.match(I);a[b]=T=t.nodes.some(function(e){return k(e,_[1],_[3],_[2])})}break;default:a[b]=T=t.tag.has(b)}if(!T){!0===r(i[o])?i[o]=null:a[i[o]]=!0;break e}}}}}for(var w=0;w<n.length;w++)n[w]===x&&(n[++w].length,n[w]=n[w].filter(function(e){if("string"==typeof e){if(e in a)return a[e];var n=(s=e).replace(/:?:[a-z-]+/gm,function(e){return s.startsWith("::")||!N.test(e)?"":e}).replace(/:[a-z-]+\(\)/gm,"");return""==n||(n in a?a[n]:a[n]=A(t.nodes,n)||!0!==r(e))}var s;return!1}));var D=function(e){for(var t="",r=0,n=0;n<e.length;n++)switch(e[n]){case x:var a=e[++n];(r=a.length)>0&&(t+=a.join());break;case v:r>0&&(t+="{"+e[++n]+"}");break;case h:t+=e[++n]+"{";break;case p:t+="}";break;case m:t+=e[++n]}return t.replace(/@[a-z-]+\s+[^{]+\{\s*\}/gm,"")}(n);return{css:D=function(e){for(var t,r=[],n=new Set,a=/@font-face[\s\S]+?font-family:\s*(['"\w-]+)[^}]+\}/gm;t=a.exec(e);){var s=t[1].replace(/['"]/gm,"");r.push([t.index,t[0].length,s])}for(var i=/font-family:([^;!}]+)/gm;t=i.exec(e);)r.some(function(e){return t.index>e[0]&&t.index<e[0]+e[1]})||t[1].trim().split(",").forEach(function(e){n.add(e.trim().replace(/['"]/gm,""))});return L(e,r,n)}(D=function(e){for(var t,r=[],n=new Set,a=/@(?:-\w+-)?keyframes\s+([\w-]+)\s*\{/gm;t=a.exec(e);){var s=g(e,a.lastIndex);r.push([t.index,t[0].length+s.length+1,t[1]])}for(var i=/animation(?:-name)?:([^;!}]+)/gm;t=i.exec(e);)t[1].trim().split(",").forEach(function(e){n.add(e.trim().match(/^[\w-]+/)[0])});return L(e,r,n)}(D))}}}); |
{ | ||
"name": "dropcss", | ||
"version": "0.5.0", | ||
"version": "1.0.0", | ||
"description": "An exceptionally fast, thorough and tiny unused-CSS cleaner", | ||
@@ -28,3 +28,3 @@ "main": "./dist/dropcss.cjs.js", | ||
"devDependencies": { | ||
"mocha": "^6.0.2", | ||
"mocha": "^6.1.2", | ||
"nyc": "^13.3.0", | ||
@@ -31,0 +31,0 @@ "rollup": "^1.9.0", |
@@ -8,5 +8,5 @@ ## ๐ DropCSS | ||
DropCSS is an exceptionally fast, thorough and tiny ([~7.5 KB min](https://github.com/leeoniya/dropcss/tree/master/dist/dropcss.min.js)) unused-CSS cleaner; it takes your HTML and CSS as input and returns only the used CSS as output. Its custom HTML and CSS parsers are highly optimized for the 99% use case and thus avoid the overhead of handling malformed markup or stylesheets, so you must provide well-formed input. There is minimal handling for complex escaping rules, so there will always exist cases of valid input that cannot be processed by DropCSS; for these infrequent cases, please [start a discussion](https://github.com/leeoniya/dropcss/issues), use a previous, larger and slower [0.3.x version](https://github.com/leeoniya/dropcss/releases) that uses heavier but more compliant parsers, or use an alternative CSS cleaner. | ||
DropCSS is an exceptionally fast, thorough and tiny ([~8 KB min](https://github.com/leeoniya/dropcss/tree/master/dist/dropcss.min.js)) unused-CSS cleaner; it takes your HTML and CSS as input and returns only the used CSS as output. Its custom HTML and CSS parsers are highly optimized for the 99% use case and thus avoid the overhead of handling malformed markup or stylesheets, so you must provide well-formed input. There is minimal handling for complex escaping rules, so there will always exist cases of valid input that cannot be processed by DropCSS; for these infrequent cases, please [start a discussion](https://github.com/leeoniya/dropcss/issues), use a previous, larger and slower [0.3.x version](https://github.com/leeoniya/dropcss/releases) that uses heavier but more compliant parsers, or use an alternative CSS cleaner. | ||
It is recommended to also run your CSS through a structural optimizer like [clean-css](https://github.com/jakubpawlowicz/clean-css), [csso](https://github.com/css/csso), [cssnano](https://github.com/cssnano/cssnano) or [crass](https://github.com/mattbasta/crass) to group selectors, merge and remove redundant rules, purge unused keyframes, etc. | ||
As a bonus, DropCSS will also remove unused any `@keyframes` and `@font-face` blocks, despite it being somewhat out of scope for this lib (being a purely intra-CSS optimization). Speaking of which, it's a good idea to run your CSS through a structural optimizer like [clean-css](https://github.com/jakubpawlowicz/clean-css), [csso](https://github.com/css/csso), [cssnano](https://github.com/cssnano/cssnano) or [crass](https://github.com/mattbasta/crass) to re-group selectors, merge redundant rules, etc. It probably makes sense to do this after DropCSS, which can leave redundant blocks, e.g. `.foo, .bar { color: red; }; .bar { width: 50%; }` -> `.bar { color: red; }; .bar { width: 50%; }` if `.foo` is absent from your markup. | ||
@@ -16,2 +16,5 @@ A bit more on this project's backstory & discussions in [/r/javascript](https://old.reddit.com/r/javascript/comments/b3mcu8/dropcss_010_a_minimal_and_thorough_unused_css/) and on [Hacker News](https://news.ycombinator.com/item?id=19469080). | ||
--- | ||
<h3 align="center">Live Demo: <a href="https://codepen.io/leeoniya/pen/LvbRyq">https://codepen.io/leeoniya/pen/LvbRyq</a></h3> | ||
--- | ||
### Installation | ||
@@ -55,3 +58,2 @@ | ||
css, | ||
keepText: false, | ||
shouldDrop: (sel) => { | ||
@@ -72,4 +74,3 @@ if (whitelist.test(sel)) | ||
- `shouldDrop` is called for every CSS selector that could not be matched in the `html`. Return `false` to retain the selector or `true` to drop it. Additionally, this hook can be used to log all removed selectors. | ||
- `keepText` - By default, DropCSS will remove all text nodes from the HTML before processing further since very few CSS selectors can actually target text. Not having to process text nodes is a significant performance boost. However, a few uncommon pseudo-classes like `:blank` and `:empty` do assert on text nodes. If combined as e.g. `:not(:empty)`, this could result wrongful removal, or wrongful retention of selectors. Setting `keepText` to `true` will leave all text nodes in place to allow for this to work properly at the expense of performance. | ||
The `shouldDrop` hook is called for every CSS selector that could not be matched in the `html`. Return `false` to retain the selector or `true` to drop it. | ||
@@ -95,3 +96,2 @@ --- | ||
- `:not()` | ||
- `:empty` | ||
- `:first-child` | ||
@@ -102,2 +102,7 @@ - `:last-child` | ||
- `:nth-last-child()` | ||
- `:first-of-type` | ||
- `:last-of-type` | ||
- `:only-of-type` | ||
- `:nth-of-type()` | ||
- `:nth-last-of-type()` | ||
@@ -191,12 +196,4 @@ --- | ||
- All `-of-type` selectors are currently unimplemented, so will not be removed unless already disqualified by a paired selector, (e.g. `.card:first-of-type` when `.card` is absent altogether). This is pretty easy to implement and a good first issue for those interested in contributing: [Issue #4](https://github.com/leeoniya/dropcss/issues/4). | ||
- `:first-of-type` | ||
- `:last-of-type` | ||
- `:only-of-type` | ||
- `:nth-of-type()` | ||
- `:nth-last-of-type()` | ||
- `:nth-only-of-type()` | ||
- Moar tests. DropCSS is currently developed against gigantic blobs of diverse, real-world CSS and HTML. These inputs & outputs are also used for perf testing and regression detection. While not all output was verified by hand (this would be infeasible for giganitic mis-matched HTML/CSS inputs), it was loosely verified against what other cleaners remove and what they leave behind. Writing tests is additonally challenging because the way selectors are drop-tested is optimized to fast-path many cases; a complex-looking test like `.foo > ul + p:not([foo*=bar]):hover` will actually short circuit early if `.foo`, `ul` or `p` are missing from the dom, and will never continue to structural/context or negation assertions. Tests must be carefully written to ensure they hit all the desired paths; it's easy to waste a lot of time writing useless tests that add no value. Unfortunately, even 100% cumulative code coverage of the test suite would only serve as a starting point. Good tests would be a diverse set of real-world inputs and manually verified outputs. | ||
- Moar tests. Hundreds of additional, granular tests. DropCSS is currently developed against gigantic blobs of diverse, real-world CSS and HTML. These inputs & outputs are also used for perf testing and regression detection. While not all output was verified by hand (this would be infeasible for giganitic mis-matched HTML/CSS inputs), it was loosely verified against what other cleaners remove and what they leave behind. Writing tests is additonally challenging because the way selectors are drop-tested is optimized to fast-path many cases; a complex-looking test like `.foo > ul + p:not([foo*=bar]):hover` will actually short circuit early if `.foo`, `ul` or `p` are missing from the dom, and will never continue to structural/context or negation assertions. Tests must be carefully written to ensure they hit all the desired paths; it's easy to waste a lot of time writing useless tests that add no value. Unfortunately, even 100% cumulative code coverage of the test suite would only serve as a starting point. Good tests would be a diverse set of real-world inputs and manually verified outputs. | ||
--- | ||
@@ -203,0 +200,0 @@ ### Caveats |
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
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
59051
1558
0
0
203