2 lines
11 KiB
JavaScript
2 lines
11 KiB
JavaScript
import*as e from"fs";import*as t from"glob";import*as s from"path";import*as r from"postcss";import o from"postcss-selector-parser";import{promisify as i}from"util";function a(e,t){t&&t.forEach(e.add,e)}class ExtractorResultSets{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)?a(this.undetermined,e):e instanceof ExtractorResultSets?(a(this.undetermined,e.undetermined),a(this.attrNames,e.attrNames),a(this.attrValues,e.attrValues),a(this.classes,e.classes),a(this.ids,e.ids),a(this.tags,e.tags)):(a(this.undetermined,e.undetermined),e.attributes&&(a(this.attrNames,e.attributes.names),a(this.attrValues,e.attributes.values)),a(this.classes,e.classes),a(this.ids,e.ids),a(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 n=["*",":root",":after",":before"],c={css:[],content:[],defaultExtractor:e=>e.match(/[A-Za-z0-9_-]+/g)||[],extractors:[],fontFace:!1,keyframes:!1,rejected:!1,rejectedCss:!1,sourceMap:!1,stdin:!1,stdout:!1,variables:!1,safelist:{standard:[],deep:[],greedy:[],variables:[],keyframes:[]},blocklist:[],skippedContentGlobs:[],dynamicAttributes:[]};class VariableNode{constructor(e){this.nodes=[],this.isUsed=!1,this.value=e}}class VariablesStructure{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 VariableNode(e),r=this.nodes.get(t)||[];this.nodes.set(t,[...r,s])}else{const s=new VariableNode(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){const t=e.value.value.matchAll(/var\((.+?)[,)]/g);for(const e of t)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 l={access:i(e.access),readFile:i(e.readFile)};function u(e=[]){return Array.isArray(e)?{...c.safelist,standard:e}:{...c.safelist,...e}}async function d(e="purgecss.config.js"){let t;try{const r=s.resolve(process.cwd(),e);t=await import(r)}catch(e){if(e instanceof Error)throw new Error(`Error loading the config file ${e.message}`);throw new Error}return{...c,...t,safelist:u(t.safelist)}}async function h(e,t){return new ExtractorResultSets(await t(e))}function f(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 p(...e){const t=new ExtractorResultSets([]);return e.forEach(t.merge,t),t}function m(e){return e.replace(/(^["'])|(["']$)/g,"")}function g(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 S(e,t){return t.hasId(e.value)}function v(e,t){return t.hasTag(e.value)}function y(e){return"atrule"===(null==e?void 0:e.type)}function b(e){return"rule"===(null==e?void 0:e.type)}class PurgeCSS{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 VariablesStructure,this.options=c}collectDeclarationsData(e){const{prop:t,value:s}=e;if(this.options.variables){const r=s.matchAll(/var\((.+?)[,)]/g);t.startsWith("--")?(this.variablesStructure.addVariable(e),this.variablesStructure.addVariableUsage(e,r)):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=m(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(s,r){const o=new ExtractorResultSets([]),i=[];for(const r of s)try{await l.access(r,e.constants.F_OK),i.push(r)}catch(e){i.push(...t.sync(r,{nodir:!0,ignore:this.options.skippedContentGlobs}))}0===i.length&&console.warn("No files found from the passed PurgeCSS option 'content'.");for(const e of i){const t=await l.readFile(e,"utf-8"),s=this.getFileExtractor(e,r),i=await h(t,s);o.merge(i)}return o}async extractSelectorsFromString(e,t){const s=new ExtractorResultSets([]);for(const{raw:r,extension:o}of e){const e=this.getFileExtractor(`.${o}`,t),i=await h(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:m(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)&&f(s,"next"))return void s.remove();if(e.parent&&y(e.parent)&&e.parent.name.endsWith("keyframes"))return;if(!b(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 i=[];if(e.selector=o((e=>{e.walk((e=>{"selector"===e.type&&(r=this.shouldKeepSelector(e,t),r||(this.options.rejected&&this.selectorsRemoved.add(e.toString()),this.options.rejectedCss&&i.push(e.toString()),e.remove()))})),e.walk((e=>{"selector"===e.type&&e.toString()&&/(:where$)|(:is$)|(:where[^(])|(:is[^(])/.test(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 a=e.parent;if(e.selector||e.remove(),function(e){return!!(b(e)&&!e.selector||(null==e?void 0:e.nodes)&&!e.nodes.length||y(e)&&(!e.nodes&&!e.params||!e.params&&e.nodes&&!e.nodes.length))}(a)&&(null==a||a.remove()),this.options.rejectedCss&&i.length>0){const t=e.clone(),s=null==a?void 0:a.clone().removeAll().append(t);t.selectors=i;const r=s||t;this.removedNodes.push(r)}}async getPurgedCSS(e,s){var o;const i=[],a=[];for(const s of e)"string"==typeof s?a.push(...t.sync(s,{nodir:!0,ignore:this.options.skippedContentGlobs})):a.push(s);for(const e of a){const t="string"==typeof e?this.options.stdin?e:await l.readFile(e,"utf-8"):e.raw,a="string"==typeof e&&!this.options.stdin,n=r.parse(t,{from:a?e:void 0});this.walkThroughCSS(n,s),this.options.fontFace&&this.removeUnusedFontFaces(),this.options.keyframes&&this.removeUnusedKeyframes(),this.options.variables&&this.removeUnusedCSSVariables();const c=n.toResult({map:this.options.sourceMap,to:"object"==typeof this.options.sourceMap?this.options.sourceMap.to:void 0}),u={css:c.toString(),file:"string"==typeof e?e:e.name};this.options.sourceMap&&(u.sourceMap=null===(o=c.map)||void 0===o?void 0:o.toString()),this.options.rejected&&(u.rejected=Array.from(this.selectorsRemoved),this.selectorsRemoved.clear()),this.options.rejectedCss&&(u.rejectedCss=r.root({nodes:this.removedNodes}).toString()),i.push(u)}return i}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 n.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 d(e):{...c,...e,safelist:u(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)),a=t.filter((e=>"object"==typeof e)),n=await this.extractSelectorsFromFiles(i,r),l=await this.extractSelectorsFromString(a,r);return this.getPurgedCSS(s,p(n,l))}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)&&!function(e){return e.parent&&"pseudo"===e.parent.type&&(":where"===e.parent.value||":is"===e.parent.value)||!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&&(n.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)||g(o,t);break;case"class":r=o,s=t.hasClass(r.value);break;case"id":s=S(o,t);break;case"tag":s=v(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&&(f(e,"start")?(this.ignore=!0,e.remove()):f(e,"end")&&(this.ignore=!1,e.remove())))))}}export{ExtractorResultSets,PurgeCSS,VariableNode,VariablesStructure,c as defaultOptions,p as mergeExtractorSelectors,d as setOptions,u as standardizeSafelist};
|