Skip to content

Commit

Permalink
feat: add option to ouput unused css (#763)
Browse files Browse the repository at this point in the history
* naive implementation of output-unused-css

* undo part of the changes, since i broke some tests

* add missing change to types

* setup basic test

* attempt to fix test that is failing because of line ending drama

* test if rejected and rejectedCss stay in sync

* update the docs

* add a test case to preserve empty parent nodes

Co-authored-by: Ffloriel <florielfedry@gmail.com>
  • Loading branch information
kevinramharak and Ffloriel committed Nov 26, 2021
1 parent d80600f commit 3a3d958
Show file tree
Hide file tree
Showing 12 changed files with 114 additions and 12 deletions.
27 changes: 17 additions & 10 deletions docs/CLI.md
Expand Up @@ -37,18 +37,25 @@ npm i -g purgecss
To see the available options for the CLI: `purgecss --help`

```text
Usage: purgecss --css <css> --content <content> [options]
Usage: purgecss --css <css...> --content <content...> [options]
Remove unused css selectors
Options:
-con, --content <files> glob of content files
-css, --css <files> glob of css files
-c, --config <path> path to the configuration file
-o, --output <path> file path directory to write purged css files to
-font, --font-face option to remove unused font-faces
-keyframes, --keyframes option to remove unused keyframes
-rejected, --rejected option to output rejected selectors
-s, --safelist <list> list of classes that should not be removed
-h, --help display help for command
-V, --version output the version number
-con, --content <files...> glob of content files
-css, --css <files...> glob of css files
-c, --config <path> path to the configuration file
-o, --output <path> file path directory to write purged css files to
-font, --font-face option to remove unused font-faces
-keyframes, --keyframes option to remove unused keyframes
-v, --variables option to remove unused variables
-rejected, --rejected option to output rejected selectors
-rejected-css, --rejected-css option to output rejected css
-s, --safelist <list...> list of classes that should not be removed
-b, --blocklist <list...> list of selectors that should be removed
-k, --skippedContentGlobs <list...> list of glob patterns for folders/files that should not be scanned
-h, --help display help for command
```

The options available through the CLI are similar to the ones available with a configuration file. You can also use the CLI with a configuration file.
Expand Down
1 change: 1 addition & 0 deletions docs/api.md
Expand Up @@ -74,5 +74,6 @@ interface ResultPurge {
css: string;
file?: string;
rejected?: string[];
rejectedCss?: string;
}
```
12 changes: 12 additions & 0 deletions docs/configuration.md
Expand Up @@ -57,6 +57,7 @@ interface UserDefinedOptions {
keyframes?: boolean;
output?: string;
rejected?: boolean;
rejectedCss?: boolean;
stdin?: boolean;
stdout?: boolean;
variables?: boolean;
Expand Down Expand Up @@ -236,6 +237,17 @@ await new PurgeCSS().purge({
rejected: true
})
```
- **rejectedCss \(default: false\)**

If you would like to keep the discarded CSS you can do so by using the `rejectedCss` option.

```js
await new PurgeCSS().purge({
content: ['index.html', '**/*.js', '**/*.html', '**/*.vue'],
css: ['css/app.css'],
rejectedCss: true
})
```

- **safelist**

Expand Down
41 changes: 41 additions & 0 deletions packages/purgecss/__tests__/rejectedCss.test.ts
@@ -0,0 +1,41 @@
import PurgeCSS from "./../src/index";
import { ROOT_TEST_EXAMPLES } from "./utils";

describe("rejectedCss", () => {
it("returns the rejected css as part of the result", async () => {
expect.assertions(1);
const resultsPurge = await new PurgeCSS().purge({
content: [`${ROOT_TEST_EXAMPLES}rejectedCss/simple.js`],
css: [`${ROOT_TEST_EXAMPLES}rejectedCss/simple.css`],
rejectedCss: true,
});
const expected = `
.rejected {
color: blue;
}`;
expect(resultsPurge[0].rejectedCss?.trim()).toBe(expected.trim());
});
it("contains the rejected selectors as part of the rejected css", async () => {
expect.assertions(1);
const resultsPurge = await new PurgeCSS().purge({
content: [`${ROOT_TEST_EXAMPLES}rejectedCss/simple.js`],
css: [`${ROOT_TEST_EXAMPLES}rejectedCss/simple.css`],
rejected: true,
rejectedCss: true,
});
expect(resultsPurge[0].rejectedCss?.trim()).toContain(resultsPurge[0].rejected?.[0]);
});
/**
* https://github.com/FullHuman/purgecss/pull/763#discussion_r754618902
*/
it("preserves the node correctly when having an empty parent node", async () => {
expect.assertions(1);
const resultsPurge = await new PurgeCSS().purge({
content: [`${ROOT_TEST_EXAMPLES}rejectedCss/empty-parent-node.js`],
css: [`${ROOT_TEST_EXAMPLES}rejectedCss/empty-parent-node.css`],
rejectedCss: true,
});
const expected = `@media (max-width: 66666px) {\n .unused-class, .unused-class2 {\n color: black;\n }\n}`;
expect(resultsPurge[0].rejectedCss?.trim()).toEqual(expected);
});
});
@@ -0,0 +1,5 @@
@media (max-width: 66666px) {
.unused-class, .unused-class2 {
color: black;
}
}
Empty file.
@@ -0,0 +1,7 @@
.critical {
color: red;
}

.rejected {
color: blue;
}
@@ -0,0 +1,2 @@

"critical"
4 changes: 4 additions & 0 deletions packages/purgecss/src/bin.ts
Expand Up @@ -25,6 +25,7 @@ type CommandOptions = {
keyframes?: boolean;
variables?: boolean;
rejected?: boolean;
rejectedCss?: boolean;
safelist?: string[];
blocklist?: string[];
skippedContentGlobs: string[];
Expand All @@ -48,6 +49,7 @@ function parseCommandOptions() {
.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"
Expand Down Expand Up @@ -77,6 +79,7 @@ async function run() {
keyframes,
variables,
rejected,
rejectedCss,
safelist,
blocklist,
skippedContentGlobs,
Expand All @@ -99,6 +102,7 @@ async function run() {
if (fontFace) options.fontFace = fontFace;
if (keyframes) options.keyframes = keyframes;
if (rejected) options.rejected = rejected;
if (rejectedCss) options.rejectedCss = rejectedCss;
if (variables) options.variables = variables;
if (safelist) options.safelist = standardizeSafelist(safelist);
if (blocklist) options.blocklist = blocklist;
Expand Down
23 changes: 21 additions & 2 deletions packages/purgecss/src/index.ts
Expand Up @@ -268,6 +268,7 @@ class PurgeCSS {
private usedAnimations: Set<string> = new Set();
private usedFontFaces: Set<string> = new Set();
public selectorsRemoved: Set<string> = new Set();
public removedNodes: postcss.Node[] = [];
private variablesStructure: VariablesStructure = new VariablesStructure();

public options: Options = defaultOptions;
Expand Down Expand Up @@ -456,6 +457,7 @@ class PurgeCSS {
}

let keepSelector = true;
const originalSelector = node.selector;
node.selector = selectorParser((selectorsParsed) => {
selectorsParsed.walk((selector) => {
if (selector.type !== "selector") {
Expand All @@ -465,8 +467,9 @@ class PurgeCSS {
keepSelector = this.shouldKeepSelector(selector, selectors);

if (!keepSelector) {
if (this.options.rejected)
if (this.options.rejected) {
this.selectorsRemoved.add(selector.toString());
}
selector.remove();
}
});
Expand All @@ -482,7 +485,19 @@ class PurgeCSS {

// remove empty rules
const parent = node.parent;
if (!node.selector) node.remove();
if (!node.selector) {
node.remove();
if (this.options.rejectedCss) {
node.selector = originalSelector;
if (parent && isRuleEmpty(parent)) {
const clone = parent.clone();
clone.append(node);
this.removedNodes.push(clone);
} else {
this.removedNodes.push(node);
}
}
}
if (isRuleEmpty(parent)) parent?.remove();
}

Expand Down Expand Up @@ -538,6 +553,10 @@ class PurgeCSS {
this.selectorsRemoved.clear();
}

if (this.options.rejectedCss) {
result.rejectedCss = postcss.root({ nodes: this.removedNodes }).toString();
}

sources.push(result);
}
return sources;
Expand Down
1 change: 1 addition & 0 deletions packages/purgecss/src/options.ts
Expand Up @@ -9,6 +9,7 @@ export const defaultOptions: Options = {
fontFace: false,
keyframes: false,
rejected: false,
rejectedCss: false,
stdin: false,
stdout: false,
variables: false,
Expand Down
3 changes: 3 additions & 0 deletions packages/purgecss/src/types/index.ts
Expand Up @@ -63,6 +63,7 @@ export interface UserDefinedOptions {
keyframes?: boolean;
output?: string;
rejected?: boolean;
rejectedCss?: boolean;
stdin?: boolean;
stdout?: boolean;
variables?: boolean;
Expand All @@ -81,6 +82,7 @@ export interface Options {
keyframes: boolean;
output?: string;
rejected: boolean;
rejectedCss: boolean;
stdin: boolean;
stdout: boolean;
variables: boolean;
Expand All @@ -92,6 +94,7 @@ export interface Options {

export interface ResultPurge {
css: string;
rejectedCss?: string;
file?: string;
rejected?: string[];
}

0 comments on commit 3a3d958

Please sign in to comment.