Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new purgecss-from-jsx plugin #692

Merged
merged 3 commits into from
Jul 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"postcss-purgecss",
"purgecss",
"purgecss-from-html",
"purgecss-from-jsx",
"purgecss-from-pug",
"purgecss-from-twig",
"purgecss-webpack-plugin",
Expand Down
11 changes: 11 additions & 0 deletions packages/purgecss-from-jsx/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# `purgecss-from-jsx`

> TODO: description

## Usage

```
const purgecssFromJsx = require('purgecss-from-jsx');

// TODO: DEMONSTRATE API
```
24 changes: 24 additions & 0 deletions packages/purgecss-from-jsx/__tests__/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export const TEST_1_CONTENT = `
import React from "react";

class MyComponent extends React.Component {
render() {
return (
<React.Fragment>
<div className="test-container">Well</div>
<div className="test-footer" id="an-id"></div>
<a href="#" id="a-link" className="a-link"></a>
<input id="blo" type="text" disabled/>
</React.Fragment>
);
}
}

export default MyComponent;
`;

export const TEST_1_TAG = ["div", "a", "input"];

export const TEST_1_CLASS = ["test-container", "test-footer", "a-link"];

export const TEST_1_ID = ["a-link", "blo"];
38 changes: 38 additions & 0 deletions packages/purgecss-from-jsx/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import purgeJsx from "../src/index";

import { TEST_1_CONTENT, TEST_1_TAG, TEST_1_CLASS, TEST_1_ID } from "./data";

const plugin = purgeJsx({sourceType: "module"});

describe("purgePug", () => {
describe("from a normal html document", () => {
it("finds tag selectors", () => {
const received = plugin(TEST_1_CONTENT);
for (const item of TEST_1_TAG) {
expect(received.includes(item)).toBe(true);
}
});

it("finds classes selectors", () => {
const received = plugin(TEST_1_CONTENT);
for (const item of TEST_1_CLASS) {
expect(received.includes(item)).toBe(true);
}
});

it("finds id selectors", () => {
const received = plugin(TEST_1_CONTENT);
for (const item of TEST_1_ID) {
expect(received.includes(item)).toBe(true);
}
});

it("finds all selectors", () => {
const received = plugin(TEST_1_CONTENT);
const selectors = [...TEST_1_TAG, ...TEST_1_CLASS, ...TEST_1_ID];
for (const item of selectors) {
expect(received.includes(item)).toBe(true);
}
});
});
});
28 changes: 28 additions & 0 deletions packages/purgecss-from-jsx/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions packages/purgecss-from-jsx/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "purgecss-from-jsx",
"version": "4.0.3",
"description": "JSX extractor for PurgeCSS",
"author": "Ffloriel",
"homepage": "https://github.com/FullHuman/purgecss#readme",
"license": "ISC",
"main": "lib/purgecss-from-jsx.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": {
"acorn": "^7.4.0",
"acorn-jsx": "^5.3.1",
"acorn-jsx-walk": "^2.0.0",
"acorn-walk": "^8.1.1"
}
}
79 changes: 79 additions & 0 deletions packages/purgecss-from-jsx/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as acorn from "acorn";
import * as walk from "acorn-walk";
import jsx from "acorn-jsx";
import {extend} from "acorn-jsx-walk";

extend(walk.base);

function purgeFromJsx(options: acorn.Options) {
return (content: string): string[] => {
// Will be filled during walk
const state = {selectors: []};

// Parse and walk any JSXElement
walk.recursive(
acorn.Parser.extend(jsx()).parse(content, options),
state,
{
JSXOpeningElement(node: any, state: any, callback) {
// JSXIdentifier | JSXMemberExpression | JSXNamespacedName
const nameState: any = {};
callback(node.name, nameState);
if (nameState.text) {
state.selectors.push(nameState.text);
}

for (let i = 0; i < node.attributes.length; ++i) {
callback(node.attributes[i], state);
}
},
JSXAttribute(node: any, state: any, callback) {
// Literal | JSXExpressionContainer | JSXElement | nil
if (!node.value) {
return;
}

// JSXIdentifier | JSXNamespacedName
const nameState: any = {};
callback(node.name, nameState);

// node.name is id or className
switch (nameState.text) {
case "id":
case "className":
{
// Get text in node.value
const valueState: any = {};
callback(node.value, valueState);

// node.value is not empty
if (valueState.text) {
state.selectors.push(...valueState.text.split(" "));
}
}
break;
default:
break;
}
},
JSXIdentifier(node: any, state: any) {
state.text = node.name;
},
JSXNamespacedName(node: any, state: any) {
state.text = node.namespace.name + ":" + node.name.name;
},
// Only handle Literal for now, not JSXExpressionContainer | JSXElement
Literal(node: any, state: any) {
if (typeof node.value === "string") {
state.text = node.value;
}
}
},
{...walk.base}
);

return state.selectors;
};
}

export default purgeFromJsx;
4 changes: 4 additions & 0 deletions scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ const packages = [
name: "purgecss-from-pug",
external: ["pug-lexer"],
},
{
name: "purgecss-from-jsx",
external: ["acorn", "acorn-walk", "acorn-jsx", "acorn-jsx-walk"],
}
];

async function build(): Promise<void> {
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"*" : ["types/*"],
"purgecss": ["packages/purgecss/src"],
"@fullhuman/purgecss-from-html": ["packages/purgecss-from-html/src"],
"@fullhuman/purgecss-from-pug": ["packages/purgecss-from-pug/src"]
"@fullhuman/purgecss-from-pug": ["packages/purgecss-from-pug/src"],
"@fullhuman/purgecss-from-jsx": ["packages/purgecss-from-jsx/src"]
}
},
"include": [
Expand Down
1 change: 1 addition & 0 deletions types/acorn-jsx-walk.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export function extend(base: any): void;
9 changes: 9 additions & 0 deletions types/acorn-jsx.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import acorn from "acorn";
declare function jsx(options?: jsx.Options): (BaseParser: typeof acorn.Parser) => typeof acorn.Parser;
export declare namespace jsx {
interface Options {
allowNamespaces?: boolean;
allowNamespacedObjects?: boolean;
}
}
export default jsx;