diff --git a/packages/postcss-purgecss/__tests__/fixtures/src/config-test/expected.css b/packages/postcss-purgecss/__tests__/fixtures/src/config-test/expected.css
new file mode 100644
index 00000000..21f4b319
--- /dev/null
+++ b/packages/postcss-purgecss/__tests__/fixtures/src/config-test/expected.css
@@ -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;
+}
diff --git a/packages/postcss-purgecss/__tests__/fixtures/src/config-test/index.css b/packages/postcss-purgecss/__tests__/fixtures/src/config-test/index.css
new file mode 100644
index 00000000..fa28b67b
--- /dev/null
+++ b/packages/postcss-purgecss/__tests__/fixtures/src/config-test/index.css
@@ -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;
+}
diff --git a/packages/postcss-purgecss/__tests__/fixtures/src/config-test/index.html b/packages/postcss-purgecss/__tests__/fixtures/src/config-test/index.html
new file mode 100644
index 00000000..87ab68b0
--- /dev/null
+++ b/packages/postcss-purgecss/__tests__/fixtures/src/config-test/index.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/postcss-purgecss/__tests__/fixtures/src/config-test/purgecss.config.js b/packages/postcss-purgecss/__tests__/fixtures/src/config-test/purgecss.config.js
new file mode 100644
index 00000000..f928c97f
--- /dev/null
+++ b/packages/postcss-purgecss/__tests__/fixtures/src/config-test/purgecss.config.js
@@ -0,0 +1,4 @@
+module.exports = {
+ content: [`${__dirname}/index.html`],
+ fontFace: true
+}
\ No newline at end of file
diff --git a/packages/postcss-purgecss/__tests__/index.test.ts b/packages/postcss-purgecss/__tests__/index.test.ts
index 8de72574..2c3a3196 100644
--- a/packages/postcss-purgecss/__tests__/index.test.ts
+++ b/packages/postcss-purgecss/__tests__/index.test.ts
@@ -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 () => {
@@ -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);
+ })
});
diff --git a/packages/postcss-purgecss/build.ts b/packages/postcss-purgecss/build.ts
index 5f42e0ed..7aa97aee 100644
--- a/packages/postcss-purgecss/build.ts
+++ b/packages/postcss-purgecss/build.ts
@@ -14,6 +14,7 @@ import * as path from "path";
const rollupConfig = createRollupConfig("postcss-purgecss", [
"postcss",
"purgecss",
+ "path"
]);
await buildRollup(rollupConfig);
await extractAPI(__dirname);
diff --git a/packages/postcss-purgecss/src/index.ts b/packages/postcss-purgecss/src/index.ts
index 7c901126..74830e8e 100644
--- a/packages/postcss-purgecss/src/index.ts
+++ b/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,
@@ -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 {
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") {
@@ -77,9 +104,10 @@ async function purgeCSS(
}
/**
- *
- * @param opts - opts
- * @returns
+ * PostCSS Plugin for PurgeCSS
+ *
+ * @param opts - PurgeCSS Options
+ * @returns the postCSS plugin
*
* @public
*/