purgecss 4.0.3 → 4.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -5
- package/bin/purgecss.d.ts +1 -0
- package/bin/purgecss.js +1 -91
- package/lib/purgecss.d.ts +298 -247
- package/lib/purgecss.esm.d.ts +298 -247
- package/lib/purgecss.esm.js +1 -1
- package/lib/purgecss.js +1 -1
- package/package.json +10 -6
package/README.md
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# PurgeCSS
|
|
2
2
|
|
|
3
|
-

|
|
6
|
-

|
|
7
|
-

|
|
3
|
+
[](https://www.npmjs.com/package/purgecss)
|
|
4
|
+

|
|
8
5
|

|
|
6
|
+

|
|
7
|
+
[](https://coveralls.io/github/FullHuman/purgecss?branch=main)
|
|
9
8
|
|
|
10
9
|
<p align="center">
|
|
11
10
|
<img src="https://i.imgur.com/UEiUiJ0.png" height="200" width="200" alt="PurgeCSS logo"/>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/bin/purgecss.js
CHANGED
|
@@ -1,92 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const program = require("commander");
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
const {
|
|
5
|
-
default: PurgeCSS,
|
|
6
|
-
defaultOptions,
|
|
7
|
-
setOptions,
|
|
8
|
-
standardizeSafelist,
|
|
9
|
-
} = require("../lib/purgecss");
|
|
10
|
-
|
|
11
|
-
async function writeCSSToFile(filePath, css) {
|
|
12
|
-
try {
|
|
13
|
-
await fs.promises.writeFile(filePath, css);
|
|
14
|
-
} catch (err) {
|
|
15
|
-
console.error(err.message);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
program
|
|
20
|
-
.usage("--css <css...> --content <content...> [options]")
|
|
21
|
-
.option("-con, --content <files...>", "glob of content files")
|
|
22
|
-
.option("-css, --css <files...>", "glob of css files")
|
|
23
|
-
.option("-c, --config <path>", "path to the configuration file")
|
|
24
|
-
.option(
|
|
25
|
-
"-o, --output <path>",
|
|
26
|
-
"file path directory to write purged css files to"
|
|
27
|
-
)
|
|
28
|
-
.option("-font, --font-face", "option to remove unused font-faces")
|
|
29
|
-
.option("-keyframes, --keyframes", "option to remove unused keyframes")
|
|
30
|
-
.option("-rejected, --rejected", "option to output rejected selectors")
|
|
31
|
-
.option(
|
|
32
|
-
"-s, --safelist <list...>",
|
|
33
|
-
"list of classes that should not be removed"
|
|
34
|
-
)
|
|
35
|
-
.option(
|
|
36
|
-
"-b, --blocklist <list...>",
|
|
37
|
-
"list of selectors that should be removed"
|
|
38
|
-
)
|
|
39
|
-
.option(
|
|
40
|
-
"-k, --skippedContentGlobs <list...>",
|
|
41
|
-
"list of glob patterns for folders/files that should not be scanned"
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
program.parse(process.argv);
|
|
45
|
-
|
|
46
|
-
const run = async () => {
|
|
47
|
-
// config file is not specified or the content and css are not,
|
|
48
|
-
// PurgeCSS will not run
|
|
49
|
-
if (!program.config && !(program.content && program.css)) {
|
|
50
|
-
program.help();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// if the config file is present, use it
|
|
54
|
-
// other options specified will override
|
|
55
|
-
let options = defaultOptions;
|
|
56
|
-
if (program.config) {
|
|
57
|
-
options = await setOptions(program.config);
|
|
58
|
-
}
|
|
59
|
-
if (program.content) options.content = program.content;
|
|
60
|
-
if (program.css) options.css = program.css;
|
|
61
|
-
if (program.fontFace) options.fontFace = program.fontFace;
|
|
62
|
-
if (program.keyframes) options.keyframes = program.keyframes;
|
|
63
|
-
if (program.rejected) options.rejected = program.rejected;
|
|
64
|
-
if (program.variables) options.variables = program.variables;
|
|
65
|
-
if (program.safelist) options.safelist = standardizeSafelist(program.safelist);
|
|
66
|
-
if (program.blocklist) options.blocklist = program.blocklist;
|
|
67
|
-
if (program.skippedContentGlobs) options.skippedContentGlobs = program.skippedContentGlobs;
|
|
68
|
-
|
|
69
|
-
const purged = await new PurgeCSS().purge(options);
|
|
70
|
-
const output = options.output || program.output;
|
|
71
|
-
// output results in specified directory
|
|
72
|
-
if (output) {
|
|
73
|
-
if (purged.length === 1 && output.endsWith(".css")) {
|
|
74
|
-
await writeCSSToFile(output, purged[0].css);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
for (const purgedResult of purged) {
|
|
79
|
-
const fileName = purgedResult.file.split("/").pop();
|
|
80
|
-
await writeCSSToFile(`${output}/${fileName}`, purgedResult.css);
|
|
81
|
-
}
|
|
82
|
-
} else {
|
|
83
|
-
console.log(JSON.stringify(purged));
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
run();
|
|
89
|
-
} catch (error) {
|
|
90
|
-
console.error(error.message);
|
|
91
|
-
process.exit(1);
|
|
92
|
-
}
|
|
2
|
+
"use strict";var e=require("commander"),t=require("fs"),s=require("glob"),r=require("path"),o=require("postcss"),i=require("postcss-selector-parser"),n=require("util");function a(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}function c(e){if(e&&e.__esModule)return e;var t=Object.create(null);return e&&Object.keys(e).forEach((function(s){if("default"!==s){var r=Object.getOwnPropertyDescriptor(e,s);Object.defineProperty(t,s,r.get?r:{enumerable:!0,get:function(){return e[s]}})}})),t.default=e,Object.freeze(t)}var l=c(t),u=a(t),d=a(s),f=a(r),h=c(o),p=a(i),m="4.1.2",g="Remove unused css selectors";function v(e,t){t&&t.forEach(e.add,e)}class y{constructor(e){this.undetermined=new Set,this.attrNames=new Set,this.attrValues=new Set,this.classes=new Set,this.ids=new Set,this.tags=new Set,this.merge(e)}merge(e){return Array.isArray(e)?v(this.undetermined,e):e instanceof y?(v(this.undetermined,e.undetermined),v(this.attrNames,e.attrNames),v(this.attrValues,e.attrValues),v(this.classes,e.classes),v(this.ids,e.ids),v(this.tags,e.tags)):(v(this.undetermined,e.undetermined),e.attributes&&(v(this.attrNames,e.attributes.names),v(this.attrValues,e.attributes.values)),v(this.classes,e.classes),v(this.ids,e.ids),v(this.tags,e.tags)),this}hasAttrName(e){return this.attrNames.has(e)||this.undetermined.has(e)}someAttrValue(e){for(const t of this.attrValues)if(e(t))return!0;for(const t of this.undetermined)if(e(t))return!0;return!1}hasAttrPrefix(e){return this.someAttrValue((t=>t.startsWith(e)))}hasAttrSuffix(e){return this.someAttrValue((t=>t.endsWith(e)))}hasAttrSubstr(e){return e.trim().split(" ").every((e=>this.someAttrValue((t=>t.includes(e)))))}hasAttrValue(e){return this.attrValues.has(e)||this.undetermined.has(e)}hasClass(e){return this.classes.has(e)||this.undetermined.has(e)}hasId(e){return this.ids.has(e)||this.undetermined.has(e)}hasTag(e){return this.tags.has(e)||this.undetermined.has(e)}}const b=["*",":root",":after",":before"],S={css:[],content:[],defaultExtractor:e=>e.match(/[A-Za-z0-9_-]+/g)||[],extractors:[],fontFace:!1,keyframes:!1,rejected:!1,rejectedCss:!1,stdin:!1,stdout:!1,variables:!1,safelist:{standard:[],deep:[],greedy:[],variables:[],keyframes:[]},blocklist:[],skippedContentGlobs:[],dynamicAttributes:[]};function w(e,t){const s=[];return e.replace(t,(function(){const t=arguments,r=Array.prototype.slice.call(t,0,-2);return r.input=t[t.length-1],r.index=t[t.length-2],s.push(r),e})),s}class k{constructor(e){this.nodes=[],this.isUsed=!1,this.value=e}}class F{constructor(){this.nodes=new Map,this.usedVariables=new Set,this.safelist=[]}addVariable(e){const{prop:t}=e;if(this.nodes.has(t)){const s=new k(e),r=this.nodes.get(t)||[];this.nodes.set(t,[...r,s])}else{const s=new k(e);this.nodes.set(t,[s])}}addVariableUsage(e,t){const{prop:s}=e,r=this.nodes.get(s);for(const e of t){const t=e[1];if(this.nodes.has(t)){const e=this.nodes.get(t);null==r||r.forEach((t=>{null==e||e.forEach((e=>t.nodes.push(e)))}))}}}addVariableUsageInProperties(e){for(const t of e){const e=t[1];this.usedVariables.add(e)}}setAsUsed(e){const t=this.nodes.get(e);if(t){const e=[...t];for(;0!==e.length;){const t=e.pop();t&&!t.isUsed&&(t.isUsed=!0,e.push(...t.nodes))}}}removeUnused(){for(const e of this.usedVariables){const t=this.nodes.get(e);if(t)for(const e of t){w(e.value.value,/var\((.+?)[,)]/g).forEach((e=>{this.usedVariables.has(e[1])||this.usedVariables.add(e[1])}))}}for(const e of this.usedVariables)this.setAsUsed(e);for(const[e,t]of this.nodes)for(const s of t)s.isUsed||this.isVariablesSafelisted(e)||s.value.remove()}isVariablesSafelisted(e){return this.safelist.some((t=>"string"==typeof t?t===e:t.test(e)))}}const A={access:n.promisify(l.access),readFile:n.promisify(l.readFile)};function V(e=[]){return Array.isArray(e)?{...S.safelist,standard:e}:{...S.safelist,...e}}async function j(e="purgecss.config.js"){let t;try{const s=f.default.resolve(process.cwd(),e);t=await function(e){return Promise.resolve().then((function(){return c(require(e))}))}(s)}catch(e){throw new Error(`Error loading the config file ${e.message}`)}return{...S,...t,safelist:V(t.safelist)}}async function x(e,t){return new y(await t(e))}function C(e,t){switch(t){case"next":return e.text.includes("purgecss ignore");case"start":return e.text.includes("purgecss start ignore");case"end":return e.text.includes("purgecss end ignore")}}function U(e){return e.replace(/(^["'])|(["']$)/g,"")}function R(e,t){if(!t.hasAttrName(e.attribute))return!1;if(void 0===e.value)return!0;switch(e.operator){case"$=":return t.hasAttrSuffix(e.value);case"~=":case"*=":return t.hasAttrSubstr(e.value);case"=":return t.hasAttrValue(e.value);case"|=":case"^=":return t.hasAttrPrefix(e.value);default:return!0}}function E(e,t){return t.hasId(e.value)}function N(e,t){return t.hasTag(e.value)}function P(e){return"atrule"===(null==e?void 0:e.type)}function q(e){return"rule"===(null==e?void 0:e.type)}class G{constructor(){this.ignore=!1,this.atRules={fontFace:[],keyframes:[]},this.usedAnimations=new Set,this.usedFontFaces=new Set,this.selectorsRemoved=new Set,this.removedNodes=[],this.variablesStructure=new F,this.options=S}collectDeclarationsData(e){const{prop:t,value:s}=e;if(this.options.variables){const r=w(s,/var\((.+?)[,)]/g);t.startsWith("--")?(this.variablesStructure.addVariable(e),r.length>0&&this.variablesStructure.addVariableUsage(e,r)):r.length>0&&this.variablesStructure.addVariableUsageInProperties(r)}if(!this.options.keyframes||"animation"!==t&&"animation-name"!==t)if(this.options.fontFace){if("font-family"===t)for(const e of s.split(",")){const t=U(e.trim());this.usedFontFaces.add(t)}}else;else for(const e of s.split(/[\s,]+/))this.usedAnimations.add(e)}getFileExtractor(e,t){const s=t.find((t=>t.extensions.find((t=>e.endsWith(t)))));return void 0===s?this.options.defaultExtractor:s.extractor}async extractSelectorsFromFiles(e,t){const s=new y([]);for(const r of e){let e=[];try{await A.access(r,l.constants.F_OK),e.push(r)}catch(t){e=d.default.sync(r,{nodir:!0,ignore:this.options.skippedContentGlobs})}for(const r of e){const e=await A.readFile(r,"utf-8"),o=this.getFileExtractor(r,t),i=await x(e,o);s.merge(i)}}return s}async extractSelectorsFromString(e,t){const s=new y([]);for(const{raw:r,extension:o}of e){const e=this.getFileExtractor(`.${o}`,t),i=await x(r,e);s.merge(i)}return s}evaluateAtRule(e){if(this.options.keyframes&&e.name.endsWith("keyframes"))this.atRules.keyframes.push(e);else if(this.options.fontFace&&"font-face"===e.name&&e.nodes)for(const t of e.nodes)"decl"===t.type&&"font-family"===t.prop&&this.atRules.fontFace.push({name:U(t.value),node:e})}evaluateRule(e,t){if(this.ignore)return;const s=e.prev();if(function(e){return"comment"===(null==e?void 0:e.type)}(s)&&C(s,"next"))return void s.remove();if(e.parent&&P(e.parent)&&e.parent.name.endsWith("keyframes"))return;if(!q(e))return;if(function(e){let t=!1;return e.walkComments((e=>{e&&"comment"===e.type&&e.text.includes("purgecss ignore current")&&(t=!0,e.remove())})),t}(e))return;let r=!0;const o=[];if(e.selector=p.default((e=>{e.walk((e=>{"selector"===e.type&&(r=this.shouldKeepSelector(e,t),r||(this.options.rejected&&this.selectorsRemoved.add(e.toString()),this.options.rejectedCss&&o.push(e.toString()),e.remove()))}))})).processSync(e.selector),r&&void 0!==e.nodes)for(const t of e.nodes)"decl"===t.type&&this.collectDeclarationsData(t);const i=e.parent;if(e.selector||e.remove(),function(e){return!!(q(e)&&!e.selector||(null==e?void 0:e.nodes)&&!e.nodes.length||P(e)&&(!e.nodes&&!e.params||!e.params&&e.nodes&&!e.nodes.length))}(i)&&(null==i||i.remove()),this.options.rejectedCss&&o.length>0){const t=e.clone(),s=null==i?void 0:i.clone().removeAll().append(t);t.selectors=o;const r=s||t;this.removedNodes.push(r)}}async getPurgedCSS(e,t){const s=[],r=[];for(const t of e)"string"==typeof t?r.push(...d.default.sync(t,{nodir:!0,ignore:this.options.skippedContentGlobs})):r.push(t);for(const e of r){const r="string"==typeof e?this.options.stdin?e:await A.readFile(e,"utf-8"):e.raw,o=h.parse(r);this.walkThroughCSS(o,t),this.options.fontFace&&this.removeUnusedFontFaces(),this.options.keyframes&&this.removeUnusedKeyframes(),this.options.variables&&this.removeUnusedCSSVariables();const i={css:o.toString(),file:"string"==typeof e?e:e.name};this.options.rejected&&(i.rejected=Array.from(this.selectorsRemoved),this.selectorsRemoved.clear()),this.options.rejectedCss&&(i.rejectedCss=h.root({nodes:this.removedNodes}).toString()),s.push(i)}return s}isKeyframesSafelisted(e){return this.options.safelist.keyframes.some((t=>"string"==typeof t?t===e:t.test(e)))}isSelectorBlocklisted(e){return this.options.blocklist.some((t=>"string"==typeof t?t===e:t.test(e)))}isSelectorSafelisted(e){const t=this.options.safelist.standard.some((t=>"string"==typeof t?t===e:t.test(e))),s=/^::.*/.test(e);return b.includes(e)||s||t}isSelectorSafelistedDeep(e){return this.options.safelist.deep.some((t=>t.test(e)))}isSelectorSafelistedGreedy(e){return this.options.safelist.greedy.some((t=>t.test(e)))}async purge(e){this.options="object"!=typeof e?await j(e):{...S,...e,safelist:V(e.safelist)};const{content:t,css:s,extractors:r,safelist:o}=this.options;this.options.variables&&(this.variablesStructure.safelist=o.variables||[]);const i=t.filter((e=>"string"==typeof e)),n=t.filter((e=>"object"==typeof e)),a=await this.extractSelectorsFromFiles(i,r),c=await this.extractSelectorsFromString(n,r);return this.getPurgedCSS(s,function(...e){const t=new y([]);return e.forEach(t.merge,t),t}(a,c))}removeUnusedCSSVariables(){this.variablesStructure.removeUnused()}removeUnusedFontFaces(){for(const{name:e,node:t}of this.atRules.fontFace)this.usedFontFaces.has(e)||t.remove()}removeUnusedKeyframes(){for(const e of this.atRules.keyframes)this.usedAnimations.has(e.params)||this.isKeyframesSafelisted(e.params)||e.remove()}getSelectorValue(e){return"attribute"===e.type&&e.attribute||e.value}shouldKeepSelector(e,t){if(function(e){return e.parent&&"pseudo"===e.parent.type&&e.parent.value.startsWith(":")||!1}(e))return!0;if(this.options.safelist.greedy.length>0){if(e.nodes.map(this.getSelectorValue).some((e=>e&&this.isSelectorSafelistedGreedy(e))))return!0}let s=!1;for(const o of e.nodes){const e=this.getSelectorValue(o);if(e&&this.isSelectorSafelistedDeep(e))return!0;if(e&&(b.includes(e)||this.isSelectorSafelisted(e)))s=!0;else{if(e&&this.isSelectorBlocklisted(e))return!1;switch(o.type){case"attribute":s=!![...this.options.dynamicAttributes,"value","checked","selected","open"].includes(o.attribute)||R(o,t);break;case"class":r=o,s=t.hasClass(r.value);break;case"id":s=E(o,t);break;case"tag":s=N(o,t);break;default:continue}if(!s)return!1}}var r;return s}walkThroughCSS(e,t){e.walk((e=>"rule"===e.type?this.evaluateRule(e,t):"atrule"===e.type?this.evaluateAtRule(e):void("comment"===e.type&&(C(e,"start")?(this.ignore=!0,e.remove()):C(e,"end")&&(this.ignore=!1,e.remove())))))}}async function O(e,t){try{await u.default.promises.writeFile(e,t)}catch(e){console.error(e.message)}}try{!async function(){var t;e.program.description(g).version(m).usage("--css <css...> --content <content...> [options]"),e.program.option("-con, --content <files...>","glob of content files").option("-css, --css <files...>","glob of css files").option("-c, --config <path>","path to the configuration file").option("-o, --output <path>","file path directory to write purged css files to").option("-font, --font-face","option to remove unused font-faces").option("-keyframes, --keyframes","option to remove unused keyframes").option("-v, --variables","option to remove unused variables").option("-rejected, --rejected","option to output rejected selectors").option("-rejected-css, --rejected-css","option to output rejected css").option("-s, --safelist <list...>","list of classes that should not be removed").option("-b, --blocklist <list...>","list of selectors that should be removed").option("-k, --skippedContentGlobs <list...>","list of glob patterns for folders/files that should not be scanned"),e.program.parse(process.argv);const{config:s,css:r,content:o,output:i,fontFace:n,keyframes:a,variables:c,rejected:l,rejectedCss:u,safelist:d,blocklist:f,skippedContentGlobs:h}=e.program.opts();s||o&&r||e.program.help();let p=S;s&&(p=await j(s)),o&&(p.content=o),r&&(p.css=r),n&&(p.fontFace=n),a&&(p.keyframes=a),l&&(p.rejected=l),u&&(p.rejectedCss=u),c&&(p.variables=c),d&&(p.safelist=V(d)),f&&(p.blocklist=f),h&&(p.skippedContentGlobs=h);const v=await(new G).purge(p),y=p.output||i;if(y){if(1===v.length&&y.endsWith(".css"))return void await O(y,v[0].css);for(const e of v){const s=null===(t=null==e?void 0:e.file)||void 0===t?void 0:t.split("/").pop();await O(`${y}/${s}`,e.css)}}else console.log(JSON.stringify(v))}()}catch(e){console.error(e.message),process.exit(1)}
|