tailwind-class-cleaner 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -0
- package/babel/index.js +24 -0
- package/core/groups.js +8 -0
- package/core/parser.js +5 -0
- package/core/resolver.js +48 -0
- package/eslint/index.js +7 -0
- package/eslint/rules/no-conflict.js +84 -0
- package/package.json +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Tailwind Class Cleaner
|
|
2
|
+
|
|
3
|
+
Automatically detect and fix conflicting Tailwind CSS classes.
|
|
4
|
+
|
|
5
|
+
Works in two ways:
|
|
6
|
+
|
|
7
|
+
* 🔍 ESLint → detect + auto-fix while coding
|
|
8
|
+
* ⚡ Babel → optimize during build
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 🚀 Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install tailwind-class-cleaner
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 🧠 What It Does
|
|
21
|
+
|
|
22
|
+
Fixes class conflicts like:
|
|
23
|
+
|
|
24
|
+
```jsx
|
|
25
|
+
<div className="p-2 p-4 text-sm text-lg bg-red-500 bg-blue-500" />
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
➡️ Becomes:
|
|
29
|
+
|
|
30
|
+
```jsx
|
|
31
|
+
<div className="p-4 text-lg bg-blue-500" />
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 🔍 ESLint Usage (Recommended)
|
|
37
|
+
|
|
38
|
+
### 1. Add plugin
|
|
39
|
+
|
|
40
|
+
In your `.eslintrc.json`:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"plugins": ["tailwind-cleaner"],
|
|
45
|
+
"rules": {
|
|
46
|
+
"tailwind-cleaner/no-conflicts": "warn"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
### 2. Run ESLint
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx eslint . --fix
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
### 💡 Result
|
|
62
|
+
|
|
63
|
+
* Shows warnings in editor
|
|
64
|
+
* Auto-fixes on save / fix
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## ⚡ Babel Usage (Build-time Optimization)
|
|
69
|
+
|
|
70
|
+
Add to your Babel config:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"plugins": ["tailwind-class-cleaner/babel"]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## ⚠️ Limitations (v1)
|
|
81
|
+
|
|
82
|
+
* Only supports static strings:
|
|
83
|
+
|
|
84
|
+
```jsx
|
|
85
|
+
className="p-2 p-4"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
* Does NOT yet support:
|
|
89
|
+
|
|
90
|
+
* template literals
|
|
91
|
+
* classnames()
|
|
92
|
+
* conditional classes
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 🔭 Roadmap
|
|
97
|
+
|
|
98
|
+
* Support for template literals
|
|
99
|
+
* px vs p conflict handling
|
|
100
|
+
* Tailwind config awareness
|
|
101
|
+
* Vite plugin
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 📄 License
|
|
106
|
+
|
|
107
|
+
MIT
|
|
108
|
+
"# Tailwind-class-conflict-cleaner"
|
package/babel/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import parse from "../core/parser.js";
|
|
2
|
+
import resolve from "../core/resolver.js";
|
|
3
|
+
|
|
4
|
+
export default function () {
|
|
5
|
+
return {
|
|
6
|
+
visitor: {
|
|
7
|
+
JSXAttribute(path) {
|
|
8
|
+
if (path.node.name.name !== "className") return;
|
|
9
|
+
|
|
10
|
+
const valueNode = path.node.value;
|
|
11
|
+
|
|
12
|
+
if (!valueNode || valueNode.type !== "StringLiteral") return;
|
|
13
|
+
|
|
14
|
+
const original = valueNode.value;
|
|
15
|
+
|
|
16
|
+
const newClass = resolve(parse(original)).join(" ");
|
|
17
|
+
|
|
18
|
+
if (newClass !== original) {
|
|
19
|
+
path.node.value.value = newClass;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
package/core/groups.js
ADDED
package/core/parser.js
ADDED
package/core/resolver.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { groups } from "./groups.js";
|
|
2
|
+
|
|
3
|
+
export default function resolve(classList) {
|
|
4
|
+
const finalClasses = [];
|
|
5
|
+
|
|
6
|
+
for (const cls of classList) {
|
|
7
|
+
const prefix = getPrefix(cls);
|
|
8
|
+
|
|
9
|
+
const groupKey = findGroup(prefix);
|
|
10
|
+
|
|
11
|
+
if (!groupKey) {
|
|
12
|
+
finalClasses.push(cls);
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
removeSamePrefix(finalClasses, prefix);
|
|
16
|
+
|
|
17
|
+
finalClasses.push(cls);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return finalClasses;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
//get prefix: p-4 → p, px-2 → px
|
|
25
|
+
function getPrefix(cls) {
|
|
26
|
+
return cls.split("-")[0];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
//find group (padding, margin, etc.)
|
|
31
|
+
function findGroup(prefix) {
|
|
32
|
+
for (const key in groups) {
|
|
33
|
+
if (groups[key].includes(prefix)) {
|
|
34
|
+
return key;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
//remove only exact prefix conflicts
|
|
42
|
+
function removeSamePrefix(arr, prefix) {
|
|
43
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
44
|
+
if (getPrefix(arr[i]) === prefix) {
|
|
45
|
+
arr.splice(i, 1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
package/eslint/index.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import parse from "../../core/parser.js";
|
|
2
|
+
import resolve from "../../core/resolver.js";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
meta: {
|
|
6
|
+
type: "problem",
|
|
7
|
+
fixable: "code",
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
create(context) {
|
|
11
|
+
return {
|
|
12
|
+
JSXAttribute(node) {
|
|
13
|
+
if (node.name.name !== "className") return;
|
|
14
|
+
|
|
15
|
+
const value = node.value;
|
|
16
|
+
|
|
17
|
+
if (value?.type === "Literal" && typeof value.value === "string") {
|
|
18
|
+
handleString(context, node, value.value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (value?.type === "JSXExpressionContainer" &&
|
|
22
|
+
value.expression.type === "TemplateLiteral") {
|
|
23
|
+
handleTemplate(context, node, value.expression);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
function handleString(context, node, original) {
|
|
32
|
+
const newClass = resolve(parse(original)).join(" ");
|
|
33
|
+
|
|
34
|
+
if (newClass === original) return;
|
|
35
|
+
|
|
36
|
+
context.report({
|
|
37
|
+
node,
|
|
38
|
+
message: `Tailwind conflict: "${original}" → "${newClass}"`,
|
|
39
|
+
fix(fixer) {
|
|
40
|
+
return fixer.replaceText(node.value, `"${newClass}"`);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
function handleTemplate(context, node, template) {
|
|
47
|
+
const quasis = template.quasis;
|
|
48
|
+
|
|
49
|
+
let changed = false;
|
|
50
|
+
|
|
51
|
+
const newQuasis = quasis.map((q) => {
|
|
52
|
+
const original = q.value.raw;
|
|
53
|
+
|
|
54
|
+
const resolved = resolve(parse(original)).join(" ");
|
|
55
|
+
|
|
56
|
+
if (resolved !== original) {
|
|
57
|
+
changed = true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return resolved;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (!changed) return;
|
|
64
|
+
|
|
65
|
+
context.report({
|
|
66
|
+
node,
|
|
67
|
+
message: "Tailwind conflicts in template literal",
|
|
68
|
+
fix(fixer) {
|
|
69
|
+
let result = "`";
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < newQuasis.length; i++) {
|
|
72
|
+
result += newQuasis[i];
|
|
73
|
+
|
|
74
|
+
if (template.expressions[i]) {
|
|
75
|
+
result += "${" + context.sourceCode.getText(template.expressions[i]) + "}";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
result += "`";
|
|
80
|
+
|
|
81
|
+
return fixer.replaceText(node.value, `{${result}}`);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tailwind-class-cleaner",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
|
|
6
|
+
"main": "eslint/index.js",
|
|
7
|
+
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./eslint/index.js",
|
|
10
|
+
"./babel": "./babel/index.js"
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
"files": [
|
|
14
|
+
"eslint",
|
|
15
|
+
"babel",
|
|
16
|
+
"core"
|
|
17
|
+
],
|
|
18
|
+
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"eslint": ">=8.0.0",
|
|
21
|
+
"@babel/core": "^7.0.0"
|
|
22
|
+
}
|
|
23
|
+
}
|