Skip to content

Commit

Permalink
feat(postcss-purgecss): add package
Browse files Browse the repository at this point in the history
  • Loading branch information
Ffloriel committed Oct 8, 2019
1 parent 8978216 commit 2b0616f
Show file tree
Hide file tree
Showing 19 changed files with 456 additions and 19 deletions.
21 changes: 21 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,33 @@
"@types/node": "^12.7.11",
"jest": "^24.9.0",
"lerna": "^3.14.1",
"lint-staged": "^9.4.2",
"prettier": "^1.18.2",
"rollup": "^1.23.1",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-terser": "^5.1.2",
"rollup-plugin-typescript2": "^0.24.3",
"ts-jest": "^24.1.0",
"ts-node": "^8.4.1",
"typescript": "^3.6.3"
},
"scripts": {
"build": "ts-node scripts/build.ts",
"prettier": "prettier --write --parser typescript 'packages/**/*.ts'",
"test": "jest"
},
"gitHooks": {
"pre-commit": "lint-staged",
"commit-msg": "node scripts/verify-commit.js"
},
"lint-staged": {
"*.js": [
"prettier --write",
"git add"
],
"*.ts": [
"npm run prettier",
"git add"
]
}
}
11 changes: 11 additions & 0 deletions packages/postcss-purgecss/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# `postcss-purgecss`

> TODO: description
## Usage

```
const postcssPurgecss = require('postcss-purgecss');
// TODO: DEMONSTRATE API
```
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.used-class {
color: black;
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div class="bounce">
</div>

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

<div class="used used2"></div>
11 changes: 11 additions & 0 deletions packages/postcss-purgecss/__tests__/fixtures/src/simple/simple.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.used-class {
color: black;
}

.unused-class {
color: black;
}

.another-one-not-found {
color: black;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<html>

<body>

<div class="used-class"></div>
</body>

</html>
58 changes: 58 additions & 0 deletions packages/postcss-purgecss/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const fs = require("fs");
const postcss = require("postcss");

import purgeCSSPlugin from "./../src/";

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

for (const file of files) {
it(`remove unused css successfully: ${file}`, done => {
const input = fs
.readFileSync(`${__dirname}/fixtures/src/${file}/${file}.css`)
.toString();
const expected = fs
.readFileSync(`${__dirname}/fixtures/expected/${file}.css`)
.toString();
postcss([
purgeCSSPlugin({
content: [`${__dirname}/fixtures/src/${file}/${file}.html`],
fontFace: true,
keyframes: true
})
])
.process(input, { from: undefined })
.then((result: any) => {
expect(result.css).toBe(expected);
expect(result.warnings().length).toBe(0);
done();
});
});
}

for (const file of ["simple"]) {
it(`queues messages when using reject flag: ${file}`, done => {
const input = fs
.readFileSync(`${__dirname}/fixtures/src/${file}/${file}.css`)
.toString();
const expected = fs
.readFileSync(`${__dirname}/fixtures/expected/${file}.css`)
.toString();
postcss([
purgeCSSPlugin({
content: [`${__dirname}/fixtures/src/${file}/${file}.html`],
rejected: true
})
])
.process(input, { from: undefined })
.then((result: any) => {
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();
});
});
}
});
30 changes: 30 additions & 0 deletions packages/postcss-purgecss/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "postcss-purgecss",
"version": "2.0.0",
"description": "> TODO: description",
"author": "Ffloriel <florielfedry@gmail.com>",
"homepage": "https://github.com/FullHuman/purgecss#readme",
"license": "ISC",
"main": "lib/postcss-purgecss.js",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/FullHuman/purgecss.git"
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1"
},
"bugs": {
"url": "https://github.com/FullHuman/purgecss/issues"
},
"dependencies": {
"postcss": "^7.0.18",
"purgecss": "^2.0.0"
}
}
67 changes: 67 additions & 0 deletions packages/postcss-purgecss/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import postcss from "postcss";
import {
walkThroughCSS,
extractSelectorsFromFiles,
extractSelectorsFromString,
setPurgeCSSOptions,
removeUnusedFontFaces,
removeUnusedKeyframes,
selectorsRemoved
} from "purgecss/src/index";
import { RawContent, UserDefinedOptions } from "purgecss/src/types";
import { defaultOptions } from "purgecss/src/options";

type PurgeCSSPostCSSOptions = Omit<UserDefinedOptions, "css">;

const purgeCSSPlugin = postcss.plugin("postcss-plugin-purgecss", function(
opts: PurgeCSSPostCSSOptions
) {
return async function(root, result) {
const options = {
...defaultOptions,
...opts
};

setPurgeCSSOptions(options);

const { content, extractors } = options;

const fileFormatContents = content.filter(
o => typeof o === "string"
) as string[];
const rawFormatContents = content.filter(
o => typeof o === "object"
) as RawContent[];

const cssFileSelectors = await extractSelectorsFromFiles(
fileFormatContents,
extractors
);
const cssRawSelectors = extractSelectorsFromString(
rawFormatContents,
extractors
);

const selectors = new Set([...cssFileSelectors, ...cssRawSelectors]);

//purge unused selectors
walkThroughCSS(root, selectors);

if (options.fontFace) removeUnusedFontFaces();
if (options.keyframes) removeUnusedKeyframes();

if (options.rejected && selectorsRemoved.size > 0) {
result.messages.push({
type: "purgecss",
plugin: "postcss-purgecss",
text: `purging ${selectorsRemoved.size} selectors:
${Array.from(selectorsRemoved)
.map(selector => selector.trim())
.join("\n ")}`
});
selectorsRemoved.clear();
}
};
});

export default purgeCSSPlugin;
Loading

0 comments on commit 2b0616f

Please sign in to comment.