Skip to content

Commit

Permalink
feat(postcss-purgecss): load options from purgecss config
Browse files Browse the repository at this point in the history
  • Loading branch information
Ffloriel committed Mar 1, 2022
1 parent 9004fb0 commit 4de3bd8
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 56 deletions.
@@ -0,0 +1,62 @@
@font-face {
font-family: 'Cerebri Sans';
font-weight: 400;
font-style: normal;
src: url('../fonts/CerebriSans-Regular.eot?') format('eot'), url('../fonts/CerebriSans-Regular.otf') format('opentype'), url('../fonts/CerebriSans-Regular.svg#Cerebri_Sans') format('svg'), url('../fonts/CerebriSans-Regular.ttf') format('truetype'), url('../fonts/CerebriSans-Regular.woff') format('woff');
}

@font-face {
font-family: 'Cerebri Bold';
font-weight: 400;
font-style: normal;
src: url('../fonts/CerebriSans-Bold.eot?') format('eot'), url('../fonts/CerebriSans-Bold.otf') format('opentype'), url('../fonts/CerebriSans-Bold.svg#Cerebri_Sans') format('svg'), url('../fonts/CerebriSans-Bold.ttf') format('truetype'), url('../fonts/CerebriSans-Bold.woff') format('woff');
}

.used {
color: red;
font-family: 'Cerebri Sans';
}

.used2 {
color: blue;
font-family: Cerebri Bold, serif;
}


@keyframes bounce {
from, 20%, 53%, 80%, to {
animation-timing-function: cubic-bezier(0.3, 0.1, 0.9, 1.000);
transform: translate3d(1, 1, 0);
}
}

.bounce {
-webkit-animation-name: bounce;
animation-name: bounce;
-webkit-transform-origin: center bottom;
transform-origin: center bottom;
}

@keyframes scale {
from {
transform: scale(1);
}

to {
transform: scale(2);
}
}

@keyframes spin {
from {
transform: rotate(0deg);
}

to {
transform: rotate(360deg);
}
}

.scale-spin {
animation: spin 300ms linear infinite forwards,scale 300ms linear infinite alternate;
}
@@ -0,0 +1,87 @@
@font-face {
font-family: 'Cerebri Sans';
font-weight: 400;
font-style: normal;
src: url('../fonts/CerebriSans-Regular.eot?') format('eot'), url('../fonts/CerebriSans-Regular.otf') format('opentype'), url('../fonts/CerebriSans-Regular.svg#Cerebri_Sans') format('svg'), url('../fonts/CerebriSans-Regular.ttf') format('truetype'), url('../fonts/CerebriSans-Regular.woff') format('woff');
}

@font-face {
font-family: 'Cerebri Bold';
font-weight: 400;
font-style: normal;
src: url('../fonts/CerebriSans-Bold.eot?') format('eot'), url('../fonts/CerebriSans-Bold.otf') format('opentype'), url('../fonts/CerebriSans-Bold.svg#Cerebri_Sans') format('svg'), url('../fonts/CerebriSans-Bold.ttf') format('truetype'), url('../fonts/CerebriSans-Bold.woff') format('woff');
}

@font-face {
font-family: 'OtherFont';
font-weight: 400;
font-style: normal;
src: url('xxx')
}

.unused {
color: black;
}

.used {
color: red;
font-family: 'Cerebri Sans';
}

.used2 {
color: blue;
font-family: Cerebri Bold, serif;
}


@keyframes bounce {
from, 20%, 53%, 80%, to {
animation-timing-function: cubic-bezier(0.3, 0.1, 0.9, 1.000);
transform: translate3d(1, 1, 0);
}
}

.bounce {
-webkit-animation-name: bounce;
animation-name: bounce;
-webkit-transform-origin: center bottom;
transform-origin: center bottom;
}

@keyframes flash {
from, 50%, to {
opacity: 1;
}

25%, 75% {
opacity: 0.5;
}
}

.flash {
animation: flash
}

@keyframes scale {
from {
transform: scale(1);
}

to {
transform: scale(2);
}
}

@keyframes spin {
from {
transform: rotate(0deg);
}

to {
transform: rotate(360deg);
}
}

.scale-spin {
animation: spin 300ms linear infinite forwards,scale 300ms linear infinite alternate;
}
@@ -0,0 +1,7 @@
<div class="bounce">
</div>

<div class="scale-spin">
</div>

<div class="used used2"></div>
@@ -0,0 +1,4 @@
module.exports = {
content: [`${__dirname}/index.html`],
fontFace: true
}
117 changes: 65 additions & 52 deletions packages/postcss-purgecss/__tests__/index.test.ts
Expand Up @@ -5,82 +5,71 @@ import purgeCSSPlugin from "../src/";

describe("Purgecss postcss plugin", () => {
const files = ["simple", "font-keyframes"];

for (const file of files) {
it(`remove unused css with content option successfully: ${file}`, async () => {
const input = fs
const fileContents = files.map((file) => {
return {
name: file,
input: fs
.readFileSync(`${__dirname}/fixtures/src/${file}/${file}.css`)
.toString();
const expected = fs
.toString(),
output: fs
.readFileSync(`${__dirname}/fixtures/expected/${file}.css`)
.toString();
.toString(),
};
});

it.each(fileContents)(
"remove unused css with content option successfully: $name",
async ({ name, input, output }) => {
const result = await postcss([
purgeCSSPlugin({
content: [`${__dirname}/fixtures/src/${file}/${file}.html`],
content: [`${__dirname}/fixtures/src/${name}/${name}.html`],
fontFace: true,
keyframes: true,
}),
]).process(input, { from: undefined });

expect(result.css).toBe(expected);
expect(result.css).toBe(output);
expect(result.warnings().length).toBe(0);
});
}

for (const file of files) {
it(`remove unused css with contentFunction option successfully: ${file}`, (done) => {
const input = fs
.readFileSync(`${__dirname}/fixtures/src/${file}/${file}.css`)
.toString();
const expected = fs
.readFileSync(`${__dirname}/fixtures/expected/${file}.css`)
.toString();
}
);

const sourceFileName = `src/${file}/${file}.css`;
it.each(fileContents)(
"remove unused css with contentFunction option successfully: $name",
async ({ name, input, output }) => {
const sourceFileName = `src/${name}/${name}.css`;
const contentFunction = jest
.fn()
.mockReturnValue([`${__dirname}/fixtures/src/${file}/${file}.html`]);
.mockReturnValue([`${__dirname}/fixtures/src/${name}/${name}.html`]);

postcss([
const result = await postcss([
purgeCSSPlugin({
contentFunction,
fontFace: true,
keyframes: true,
}),
])
.process(input, { from: sourceFileName })
.then((result) => {
expect(result.css).toBe(expected);
expect(result.warnings().length).toBe(0);
expect(contentFunction).toHaveBeenCalledTimes(1);
expect(contentFunction.mock.calls[0][0]).toContain(sourceFileName);
done();
});
});
}

it(`queues messages when using reject flag: simple`, (done) => {
const input = fs
.readFileSync(`${__dirname}/fixtures/src/simple/simple.css`)
.toString();
const expected = fs
.readFileSync(`${__dirname}/fixtures/expected/simple.css`)
.toString();
postcss([
]).process(input, { from: sourceFileName });

expect(result.css).toBe(output);
expect(result.warnings().length).toBe(0);
expect(contentFunction).toHaveBeenCalledTimes(1);
expect(contentFunction.mock.calls[0][0]).toContain(sourceFileName);
}
);

it(`queues messages when using reject flag: simple`, async () => {
const result = await postcss([
purgeCSSPlugin({
content: [`${__dirname}/fixtures/src/simple/simple.html`],
rejected: true,
}),
])
.process(input, { from: undefined })
.then((result) => {
expect(result.css).toBe(expected);
expect(result.warnings().length).toBe(0);
expect(result.messages.length).toBeGreaterThan(0);
expect(result.messages[0].text).toMatch(/unused-class/);
expect(result.messages[0].text).toMatch(/another-one-not-found/);
done();
});
.process(fileContents[0].input, { from: undefined });

expect(result.css).toBe(fileContents[0].output);
expect(result.warnings().length).toBe(0);
expect(result.messages.length).toBeGreaterThan(0);
expect(result.messages[0].text).toMatch(/unused-class/);
expect(result.messages[0].text).toMatch(/another-one-not-found/);
});

it(`lets other plugins transform selectors before purging`, async () => {
Expand Down Expand Up @@ -109,4 +98,28 @@ describe("Purgecss postcss plugin", () => {
expect(result.css).toBe(expected);
expect(result.warnings().length).toBe(0);
});

it('should work with a purgecss config file', async () => {
const cwd = process.cwd();
const configTestDirectory = `${__dirname}/fixtures/src/config-test/`;
process.chdir(configTestDirectory);

const input = fs
.readFileSync(`${configTestDirectory}index.css`)
.toString();
const output = fs
.readFileSync(`${configTestDirectory}expected.css`)
.toString();

const result = await postcss([
purgeCSSPlugin({
keyframes: true,
}),
]).process(input, { from: undefined });

expect(result.css).toBe(output);
expect(result.warnings().length).toBe(0);

process.chdir(cwd);
})
});
1 change: 1 addition & 0 deletions packages/postcss-purgecss/build.ts
Expand Up @@ -14,6 +14,7 @@ import * as path from "path";
const rollupConfig = createRollupConfig("postcss-purgecss", [
"postcss",
"purgecss",
"path"
]);
await buildRollup(rollupConfig);
await extractAPI(__dirname);
Expand Down
36 changes: 32 additions & 4 deletions packages/postcss-purgecss/src/index.ts
@@ -1,3 +1,13 @@
/**
* PostCSS Plugin for PurgeCSS
*
* Most bundlers and frameworks to build websites are using PostCSS.
* The easiest way to configure PurgeCSS is with its PostCSS plugin.
*
* @packageDocumentation
*/

import * as path from "path";
import * as postcss from "postcss";
import {
PurgeCSS,
Expand All @@ -12,16 +22,33 @@ export * from "./types";

const PLUGIN_NAME = "postcss-purgecss";

/**
* Execute PurgeCSS process on the postCSS root node
*
* @param opts - PurgeCSS options
* @param root - root node of postCSS
* @param helpers - postCSS helpers
*/
async function purgeCSS(
opts: UserDefinedOptions,
root: postcss.Root,
{ result }: postcss.Helpers
): Promise<void> {
const purgeCSS = new PurgeCSS();

let configFileOptions: UserDefinedOptions | undefined;
try {
const t = path.resolve(process.cwd(), "purgecss.config.js");
configFileOptions = await import(t);
} catch {
// no config file present
}

const options = {
...defaultOptions,
...configFileOptions,
...opts,
safelist: standardizeSafelist(opts?.safelist),
safelist: standardizeSafelist(opts?.safelist || configFileOptions?.safelist),
};

if (opts && typeof opts.contentFunction === "function") {
Expand Down Expand Up @@ -77,9 +104,10 @@ async function purgeCSS(
}

/**
*
* @param opts - opts
* @returns
* PostCSS Plugin for PurgeCSS
*
* @param opts - PurgeCSS Options
* @returns the postCSS plugin
*
* @public
*/
Expand Down

0 comments on commit 4de3bd8

Please sign in to comment.