diff --git a/jest.config.js b/jest.config.js index 6156b82e..39ee4c09 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,6 +6,7 @@ module.exports = { moduleFileExtensions: ["ts", "tsx", "js", "json"], moduleNameMapper: { "^purgecss$": "/packages/purgecss/src", + "^@fullhuman/purgecss-from-html$": "/packages/purgecss-from-html/src", }, rootDir: __dirname, testMatch: ["/packages/**/__tests__/**/*test.ts"], diff --git a/lerna.json b/lerna.json index 78296607..cf094c1f 100644 --- a/lerna.json +++ b/lerna.json @@ -2,5 +2,5 @@ "packages": [ "packages/*" ], - "version": "4.1.1" + "version": "4.1.2" } diff --git a/packages/grunt-purgecss/package-lock.json b/packages/grunt-purgecss/package-lock.json index 5026a272..8f8590d2 100644 --- a/packages/grunt-purgecss/package-lock.json +++ b/packages/grunt-purgecss/package-lock.json @@ -1,6 +1,6 @@ { "name": "grunt-purgecss", - "version": "4.1.1", + "version": "4.1.2", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/packages/grunt-purgecss/package.json b/packages/grunt-purgecss/package.json index 68b31df0..2ec5d1ed 100644 --- a/packages/grunt-purgecss/package.json +++ b/packages/grunt-purgecss/package.json @@ -1,6 +1,6 @@ { "name": "grunt-purgecss", - "version": "4.1.1", + "version": "4.1.2", "description": "Grunt plugin for PurgeCSS", "author": "Ffloriel", "homepage": "https://purgecss.com", @@ -30,7 +30,7 @@ "test": "echo \"Error: run tests from root\" && exit 1" }, "dependencies": { - "purgecss": "^4.1.1" + "purgecss": "^4.1.2" }, "devDependencies": { "@types/grunt": "^0.4.25", diff --git a/packages/gulp-purgecss/package-lock.json b/packages/gulp-purgecss/package-lock.json index 2198b075..54f8c17f 100644 --- a/packages/gulp-purgecss/package-lock.json +++ b/packages/gulp-purgecss/package-lock.json @@ -1,6 +1,6 @@ { "name": "gulp-purgecss", - "version": "4.1.1", + "version": "4.1.2", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/packages/gulp-purgecss/package.json b/packages/gulp-purgecss/package.json index 798f3a3a..6ceb6e3e 100644 --- a/packages/gulp-purgecss/package.json +++ b/packages/gulp-purgecss/package.json @@ -1,6 +1,6 @@ { "name": "gulp-purgecss", - "version": "4.1.1", + "version": "4.1.2", "description": "Gulp plugin for purgecss", "author": "Ffloriel", "homepage": "https://purgecss.com", @@ -37,7 +37,7 @@ "dependencies": { "glob": "^7.1.6", "plugin-error": "^1.0.1", - "purgecss": "^4.1.1", + "purgecss": "^4.1.2", "through2": "^4.0.1" }, "devDependencies": { diff --git a/packages/gulp-purgecss/src/index.ts b/packages/gulp-purgecss/src/index.ts index 51880f43..17d76da9 100644 --- a/packages/gulp-purgecss/src/index.ts +++ b/packages/gulp-purgecss/src/index.ts @@ -8,7 +8,7 @@ import { UserDefinedOptions } from "./types"; const PLUGIN_NAME = "gulp-purgecss"; -export function getFiles(contentArray: string[]): string[] { +function getFiles(contentArray: string[]): string[] { return contentArray.reduce((acc: string[], content) => { return [...acc, ...glob.sync(content)]; }, []); diff --git a/packages/postcss-purgecss/package-lock.json b/packages/postcss-purgecss/package-lock.json index 399a5c9f..ebec66f3 100644 --- a/packages/postcss-purgecss/package-lock.json +++ b/packages/postcss-purgecss/package-lock.json @@ -1,6 +1,6 @@ { "name": "@fullhuman/postcss-purgecss", - "version": "4.1.1", + "version": "4.1.2", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/packages/postcss-purgecss/package.json b/packages/postcss-purgecss/package.json index eb43c539..f005ec19 100644 --- a/packages/postcss-purgecss/package.json +++ b/packages/postcss-purgecss/package.json @@ -1,6 +1,6 @@ { "name": "@fullhuman/postcss-purgecss", - "version": "4.1.1", + "version": "4.1.2", "description": "PostCSS plugin for PurgeCSS", "author": "FoundrySH ", "homepage": "https://purgecss.com", @@ -26,7 +26,7 @@ "url": "https://github.com/FullHuman/purgecss/issues" }, "dependencies": { - "purgecss": "^4.1.1" + "purgecss": "^4.1.2" }, "devDependencies": { "postcss": "^8.3.0" diff --git a/packages/purgecss-webpack-plugin/package-lock.json b/packages/purgecss-webpack-plugin/package-lock.json index 8c0ac0c7..8fefc6eb 100644 --- a/packages/purgecss-webpack-plugin/package-lock.json +++ b/packages/purgecss-webpack-plugin/package-lock.json @@ -1,6 +1,6 @@ { "name": "purgecss-webpack-plugin", - "version": "4.1.1", + "version": "4.1.2", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/packages/purgecss-webpack-plugin/package.json b/packages/purgecss-webpack-plugin/package.json index aae55f11..0b6adefd 100644 --- a/packages/purgecss-webpack-plugin/package.json +++ b/packages/purgecss-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "purgecss-webpack-plugin", - "version": "4.1.1", + "version": "4.1.2", "description": "PurgeCSS plugin for webpack - Remove unused css", "author": "Ffloriel", "homepage": "https://purgecss.com", @@ -36,7 +36,7 @@ "test": "echo \"Error: run tests from root\" && exit 1" }, "dependencies": { - "purgecss": "^4.1.1", + "purgecss": "^4.1.2", "webpack": "^5.4.0", "webpack-sources": "^3.2.0" }, diff --git a/packages/purgecss/__tests__/index.test.ts b/packages/purgecss/__tests__/index.test.ts index f3056376..8ef9a472 100644 --- a/packages/purgecss/__tests__/index.test.ts +++ b/packages/purgecss/__tests__/index.test.ts @@ -1,7 +1,7 @@ -import PurgeCSS from "./../src/index"; +import purgecssFromHtml from "@fullhuman/purgecss-from-html"; import { ExtractorResult } from "../src/types"; - -import { ROOT_TEST_EXAMPLES } from "./utils"; +import PurgeCSS from "./../src/index"; +import { notFindInCSS, ROOT_TEST_EXAMPLES } from "./utils"; describe("purgecss with config file", () => { it("initialize without error with a config file specified", () => { @@ -71,3 +71,27 @@ describe("special characters, with custom Extractor", () => { expect(purgedCSS.includes("\\32 -panel")).toBe(false); }); }); + +describe("PurgeCSS with detailed extractor for html", () => { + let purgedCSS: string; + beforeAll(async () => { + const resultsPurge = await new PurgeCSS().purge({ + content: [`${ROOT_TEST_EXAMPLES}chaining-rules/index.html`], + css: [`${ROOT_TEST_EXAMPLES}chaining-rules/index.css`], + extractors: [ + { + extensions: ["html"], + extractor: purgecssFromHtml, + }, + ], + }); + purgedCSS = resultsPurge[0].css; + }); + + it("keeps parent1 selector", () => { + expect(purgedCSS.includes("parent1")).toBe(true); + }); + it("removes parent3, d33ef1, .parent2", () => { + notFindInCSS(expect, ["parent3", "d33ef1", "parent2"], purgedCSS); + }); +}); diff --git a/packages/purgecss/__tests__/pseudo-elements.test.ts b/packages/purgecss/__tests__/pseudo-elements.test.ts new file mode 100644 index 00000000..d6cadbd7 --- /dev/null +++ b/packages/purgecss/__tests__/pseudo-elements.test.ts @@ -0,0 +1,30 @@ +import PurgeCSS from "./../src/index"; +import { ROOT_TEST_EXAMPLES } from "./utils"; + +describe("pseudo elements", () => { + let purgedCSS: string; + beforeAll(async () => { + const resultsPurge = await new PurgeCSS().purge({ + content: [`${ROOT_TEST_EXAMPLES}pseudo-elements/pseudo-elements.html`], + css: [`${ROOT_TEST_EXAMPLES}pseudo-elements/pseudo-elements.css`], + }); + purgedCSS = resultsPurge[0].css; + }); + it("finds root pseudo-elements", () => { + expect(purgedCSS.includes("::-webkit-file-upload-button")).toBe(true); + expect(purgedCSS.includes("::grammar-error")).toBe(true); + expect(purgedCSS.includes("::-webkit-datetime-edit-fields-wrapper")).toBe( + true + ); + expect(purgedCSS.includes("::-moz-focus-inner")).toBe(true); + expect(purgedCSS.includes("::file-selector-button")).toBe(true); + }); + + it("finds pseudo-elements on used class", () => { + expect(purgedCSS.includes(".used::grammar-error")).toBe(true); + }); + + it("removes pseudo-elements on unused class", () => { + expect(purgedCSS.includes(".unused::grammar-error")).toBe(false); + }); +}); diff --git a/packages/purgecss/__tests__/rejected.test.ts b/packages/purgecss/__tests__/rejected.test.ts index f6cc757b..50b8fd3f 100644 --- a/packages/purgecss/__tests__/rejected.test.ts +++ b/packages/purgecss/__tests__/rejected.test.ts @@ -53,6 +53,7 @@ describe("rejected", () => { ".parent.d33ef1", ".parent2.def", ".parent3.def1", + "[href^='#']", ]); }); }); diff --git a/packages/purgecss/__tests__/test_examples/chaining-rules/index.css b/packages/purgecss/__tests__/test_examples/chaining-rules/index.css index ecba436d..7f29826a 100644 --- a/packages/purgecss/__tests__/test_examples/chaining-rules/index.css +++ b/packages/purgecss/__tests__/test_examples/chaining-rules/index.css @@ -36,4 +36,8 @@ } .parent3.def1{ color:red; +} + +[href^='#'] { + color: green; } \ No newline at end of file diff --git a/packages/purgecss/__tests__/test_examples/css-variables/variables.css b/packages/purgecss/__tests__/test_examples/css-variables/variables.css index 696ae7d0..c5d0cce5 100644 --- a/packages/purgecss/__tests__/test_examples/css-variables/variables.css +++ b/packages/purgecss/__tests__/test_examples/css-variables/variables.css @@ -23,4 +23,33 @@ background-color: var(--accent-color); color: var(--primary-color); border-color: var(--color-first); +} + +@media (min-width: 1024px) { + :root { + --color-first: var(--wrong-order); + --primary-color: blue; + --secondary-color: indigo; + --tertiary-color: aqua; + --unused-color: violet; + --used-color: rebeccapurple; + --accent-color: orange; + --wrong-order: yellow; + --random: var(--not-existing); + } + + .button { + --button-color: var(--tertiary-color); + --border-color: linear-gradient(to top, var(--secondary-color), var(--used-color, white)); + + background-color: var(--primary-color); + color: var(--accent-color); + border-color: var(--border-color); + } + + .button:focus { + background-color: var(--accent-color); + color: var(--primary-color); + border-color: var(--color-first); + } } \ No newline at end of file diff --git a/packages/purgecss/__tests__/test_examples/pseudo-elements/pseudo-elements.css b/packages/purgecss/__tests__/test_examples/pseudo-elements/pseudo-elements.css new file mode 100644 index 00000000..7f08cf8d --- /dev/null +++ b/packages/purgecss/__tests__/test_examples/pseudo-elements/pseudo-elements.css @@ -0,0 +1,67 @@ +/* Remove inner border and padding from Firefox, but don't restore the outline like Normalize. */ + +::-moz-focus-inner { + padding: 0; + border-style: none; +} + + +/* Fix height of inputs with a type of datetime-local, date, month, week, or time +See https://github.com/twbs/bootstrap/issues/18842 */ + +::-webkit-datetime-edit-fields-wrapper, +::-webkit-datetime-edit-text, +::-webkit-datetime-edit-minute, +::-webkit-datetime-edit-hour-field, +::-webkit-datetime-edit-day-field, +::-webkit-datetime-edit-month-field, +::-webkit-datetime-edit-year-field { + padding: 0; +} + +::-webkit-inner-spin-button { + height: auto; +} + + +/* Remove the inner padding in Chrome and Safari on macOS. */ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* Remove padding around color pickers in webkit browsers */ + +::-webkit-color-swatch-wrapper { + padding: 0; +} + + +/* Inherit font family and line height for file input buttons */ + +::file-selector-button { + font: inherit; +} + +/* 1. Change font properties to `inherit` +2. Correct the inability to style clickable types in iOS and Safari. */ + +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button; +} + +::grammar-error { + text-decoration: underline green; + color: green; +} + +.used::grammar-error { + text-decoration: underline blue; + color: blue; +} + +.unused::grammar-error { + text-decoration: underline red; + color: red; +} diff --git a/packages/purgecss/__tests__/test_examples/pseudo-elements/pseudo-elements.html b/packages/purgecss/__tests__/test_examples/pseudo-elements/pseudo-elements.html new file mode 100644 index 00000000..9c6548fa --- /dev/null +++ b/packages/purgecss/__tests__/test_examples/pseudo-elements/pseudo-elements.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/packages/purgecss/bin/purgecss.js b/packages/purgecss/bin/purgecss.js index 787ef5e9..5b72287e 100755 --- a/packages/purgecss/bin/purgecss.js +++ b/packages/purgecss/bin/purgecss.js @@ -1,2 +1,2 @@ #!/usr/bin/env node -"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.0",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=["*","::-webkit-scrollbar","::selection",":root","::before","::after"],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);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.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)];for(;0!==t.length;){const e=t.pop();e&&!e.isUsed&&(e.isUsed=!0,t.push(...e.nodes))}}removeUnused(){for(const e of this.usedVariables){const t=this.nodes.get(e);if(t){w(t.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)t.isUsed||this.isVariablesSafelisted(e)||t.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)));return b.includes(e)||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 --content [options]"),e.program.option("-con, --content ","glob of content files").option("-css, --css ","glob of css files").option("-c, --config ","path to the configuration file").option("-o, --output ","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 of classes that should not be removed").option("-b, --blocklist ","list of selectors that should be removed").option("-k, --skippedContentGlobs ","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(`${i}/${s}`,e.css)}}else console.log(JSON.stringify(v))}()}catch(e){console.error(e.message),process.exit(1)} +"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.1",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 --content [options]"),e.program.option("-con, --content ","glob of content files").option("-css, --css ","glob of css files").option("-c, --config ","path to the configuration file").option("-o, --output ","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 of classes that should not be removed").option("-b, --blocklist ","list of selectors that should be removed").option("-k, --skippedContentGlobs ","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(`${i}/${s}`,e.css)}}else console.log(JSON.stringify(v))}()}catch(e){console.error(e.message),process.exit(1)} diff --git a/packages/purgecss/package-lock.json b/packages/purgecss/package-lock.json index 8f3ef38a..7efb410b 100644 --- a/packages/purgecss/package-lock.json +++ b/packages/purgecss/package-lock.json @@ -1,6 +1,6 @@ { "name": "purgecss", - "version": "4.1.1", + "version": "4.1.2", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/packages/purgecss/package.json b/packages/purgecss/package.json index b4ace5f3..e16cacc0 100644 --- a/packages/purgecss/package.json +++ b/packages/purgecss/package.json @@ -1,6 +1,6 @@ { "name": "purgecss", - "version": "4.1.1", + "version": "4.1.2", "description": "Remove unused css selectors", "author": "Ffloriel", "homepage": "https://purgecss.com", diff --git a/packages/purgecss/src/VariablesStructure.ts b/packages/purgecss/src/VariablesStructure.ts index 588d60df..662a0b5b 100644 --- a/packages/purgecss/src/VariablesStructure.ts +++ b/packages/purgecss/src/VariablesStructure.ts @@ -13,7 +13,7 @@ class VariableNode { } class VariablesStructure { - public nodes: Map = new Map(); + public nodes: Map = new Map(); public usedVariables: Set = new Set(); public safelist: StringRegExpArray = []; @@ -21,7 +21,11 @@ class VariablesStructure { const { prop } = declaration; if (!this.nodes.has(prop)) { const node = new VariableNode(declaration); - this.nodes.set(prop, node); + this.nodes.set(prop, [node]); + } else { + const node = new VariableNode(declaration); + const variableNodes = this.nodes.get(prop) || []; + this.nodes.set(prop, [...variableNodes, node]); } } @@ -30,14 +34,17 @@ class VariablesStructure { matchedVariables: RegExpMatchArray[] ): void { const { prop } = declaration; - const node = this.nodes.get(prop); + const nodes = this.nodes.get(prop); for (const variableMatch of matchedVariables) { // capturing group containing the variable is in index 1 const variableName = variableMatch[1]; if (this.nodes.has(variableName)) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const usedVariableNode = this.nodes.get(variableName)!; - node?.nodes.push(usedVariableNode); + const usedVariableNodes = this.nodes.get(variableName); + nodes?.forEach((node) => { + usedVariableNodes?.forEach((usedVariableNode) => + node.nodes.push(usedVariableNode) + ); + }); } } } @@ -51,13 +58,15 @@ class VariablesStructure { } setAsUsed(variableName: string): void { - const node = this.nodes.get(variableName); - const queue = [node]; - while (queue.length !== 0) { - const currentNode = queue.pop(); - if (currentNode && !currentNode.isUsed) { - currentNode.isUsed = true; - queue.push(...currentNode.nodes); + const nodes = this.nodes.get(variableName); + if (nodes) { + const queue = [...nodes]; + while (queue.length !== 0) { + const currentNode = queue.pop(); + if (currentNode && !currentNode.isUsed) { + currentNode.isUsed = true; + queue.push(...currentNode.nodes); + } } } } @@ -65,17 +74,19 @@ class VariablesStructure { removeUnused(): void { // check unordered usage for (const used of this.usedVariables) { - const usedNode = this.nodes.get(used); - if (usedNode) { - const usedVariablesMatchesInDeclaration = matchAll( - usedNode.value.value, - /var\((.+?)[,)]/g - ); - usedVariablesMatchesInDeclaration.forEach((usage) => { - if (!this.usedVariables.has(usage[1])) { - this.usedVariables.add(usage[1]); - } - }); + const usedNodes = this.nodes.get(used); + if (usedNodes) { + for (const usedNode of usedNodes) { + const usedVariablesMatchesInDeclaration = matchAll( + usedNode.value.value, + /var\((.+?)[,)]/g + ); + usedVariablesMatchesInDeclaration.forEach((usage) => { + if (!this.usedVariables.has(usage[1])) { + this.usedVariables.add(usage[1]); + } + }); + } } } @@ -83,9 +94,11 @@ class VariablesStructure { this.setAsUsed(used); } - for (const [name, declaration] of this.nodes) { - if (!declaration.isUsed && !this.isVariablesSafelisted(name)) { - declaration.value.remove(); + for (const [name, declarations] of this.nodes) { + for (const declaration of declarations) { + if (!declaration.isUsed && !this.isVariablesSafelisted(name)) { + declaration.value.remove(); + } } } } diff --git a/packages/purgecss/src/index.ts b/packages/purgecss/src/index.ts index 802cb648..1442a74e 100644 --- a/packages/purgecss/src/index.ts +++ b/packages/purgecss/src/index.ts @@ -494,7 +494,7 @@ class PurgeCSS { } if (isRuleEmpty(parent)) parent?.remove(); - // rebuild the rule with the removed selectors and optionally its parent + // rebuild the rule with the removed selectors and optionally its parent if (this.options.rejectedCss) { if (selectorsRemovedFromRule.length > 0) { const clone = node.clone(); @@ -603,7 +603,8 @@ class PurgeCSS { ? safelistItem === selector : safelistItem.test(selector); }); - return CSS_SAFELIST.includes(selector) || isSafelisted; + const isPseudoElement = /^::.*/.test(selector); + return CSS_SAFELIST.includes(selector) || isPseudoElement || isSafelisted; } /** diff --git a/packages/purgecss/src/internal-safelist.ts b/packages/purgecss/src/internal-safelist.ts index edd91b7d..d82b7b99 100644 --- a/packages/purgecss/src/internal-safelist.ts +++ b/packages/purgecss/src/internal-safelist.ts @@ -1,8 +1 @@ -export const CSS_SAFELIST = [ - "*", - "::-webkit-scrollbar", - "::selection", - ":root", - "::before", - "::after", -]; +export const CSS_SAFELIST = ["*", ":root", ":after", ":before"]; diff --git a/packages/rollup-plugin-purgecss/package-lock.json b/packages/rollup-plugin-purgecss/package-lock.json index 5533df78..03dd3751 100644 --- a/packages/rollup-plugin-purgecss/package-lock.json +++ b/packages/rollup-plugin-purgecss/package-lock.json @@ -1,6 +1,6 @@ { "name": "rollup-plugin-purgecss", - "version": "4.1.1", + "version": "4.1.2", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/packages/rollup-plugin-purgecss/package.json b/packages/rollup-plugin-purgecss/package.json index fac7b6b5..2e857853 100644 --- a/packages/rollup-plugin-purgecss/package.json +++ b/packages/rollup-plugin-purgecss/package.json @@ -1,6 +1,6 @@ { "name": "rollup-plugin-purgecss", - "version": "4.1.1", + "version": "4.1.2", "description": "Rollup plugin for purgecss", "main": "lib/rollup-plugin-purgecss.js", "module": "./lib/rollup-plugin-purgecss.es.js", @@ -30,7 +30,7 @@ }, "homepage": "https://purgecss.com", "dependencies": { - "purgecss": "^4.1.1", + "purgecss": "^4.1.2", "rollup-pluginutils": "^2.8.0" }, "publishConfig": {