tailwind-unwind 0.3.0 → 0.5.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 +130 -239
- package/dist/{chunk-4GXMK3NB.js → chunk-RMTZCCPS.js} +664 -129
- package/dist/chunk-RMTZCCPS.js.map +1 -0
- package/dist/cli/index.js +133 -43
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +148 -39
- package/dist/index.js +21 -1
- package/package.json +3 -2
- package/dist/chunk-4GXMK3NB.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,334 +2,225 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/tailwind-unwind)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Find repeated Tailwind classes in your React code and turn them into reusable component classes.**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
If you copy-paste the same `className="flex items-center p-4 ..."` across dozens of files, this tool helps you clean that up — without doing it by hand.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Works with React / Next.js projects. Node.js 18+.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- **generate** — create `@layer components` CSS with `@apply`
|
|
13
|
-
- **apply** — auto-replace repeated class strings in `.tsx`/`.jsx` source files
|
|
14
|
-
- Parses static strings, template literals, `cn()` / `clsx()` / `classnames()`
|
|
15
|
-
- Human-readable class names (`twu-page-header`, `twu-media-cover`) with `twu-` namespace prefix
|
|
11
|
+
## The problem it solves
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
# or run without installing
|
|
23
|
-
npx tailwind-unwind analyze ./src
|
|
13
|
+
```tsx
|
|
14
|
+
// Same utilities repeated everywhere
|
|
15
|
+
<div className="flex items-center justify-between p-4">...</div>
|
|
16
|
+
<div className="flex items-center justify-between p-4">...</div>
|
|
17
|
+
<div className="flex items-center justify-between p-4">...</div>
|
|
24
18
|
```
|
|
25
19
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
## Configuration
|
|
29
|
-
|
|
30
|
-
Copy [`tailwind-unwind.config.example.json`](tailwind-unwind.config.example.json) to your project root:
|
|
20
|
+
**tailwind-unwind** finds these duplicates and can replace them with one class:
|
|
31
21
|
|
|
32
|
-
```
|
|
33
|
-
|
|
22
|
+
```tsx
|
|
23
|
+
<div className="twu-page-header">...</div>
|
|
34
24
|
```
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
```
|
|
39
|
-
{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"names": {
|
|
43
|
-
"flex items-center justify-between p-4": "page-header",
|
|
44
|
-
"w-full h-auto object-cover rounded-lg": "media-cover"
|
|
45
|
-
},
|
|
46
|
-
"analyze": {
|
|
47
|
-
"minOccurrences": 5,
|
|
48
|
-
"top": 10
|
|
49
|
-
},
|
|
50
|
-
"generate": {
|
|
51
|
-
"minOccurrences": 3,
|
|
52
|
-
"prefix": "twu-",
|
|
53
|
-
"output": "src/styles/components.css"
|
|
54
|
-
},
|
|
55
|
-
"apply": {
|
|
56
|
-
"output": "src/styles/components.css"
|
|
26
|
+
And generates the CSS for you:
|
|
27
|
+
|
|
28
|
+
```css
|
|
29
|
+
@layer components {
|
|
30
|
+
.twu-page-header {
|
|
31
|
+
@apply flex items-center justify-between p-4;
|
|
57
32
|
}
|
|
58
33
|
}
|
|
59
34
|
```
|
|
60
35
|
|
|
61
|
-
| Key | Description |
|
|
62
|
-
|-----|-------------|
|
|
63
|
-
| `include` / `exclude` | Glob patterns for file scanning |
|
|
64
|
-
| `names` | Custom class names (utilities string → base name, prefix added automatically) |
|
|
65
|
-
| `analyze` / `generate` / `apply` | Per-command overrides (`minOccurrences`, `top`, `prefix`, `output`, …) |
|
|
66
|
-
|
|
67
|
-
Config is discovered from the current directory **and** ancestors of `<path>`. CLI flags override config values.
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
npx tailwind-unwind analyze ./src --config ./tailwind-unwind.config.json
|
|
71
|
-
npx tailwind-unwind generate ./src --include "src/components/**/*.tsx"
|
|
72
|
-
```
|
|
73
|
-
|
|
74
36
|
## Quick start
|
|
75
37
|
|
|
38
|
+
Run from your project root — no paths required (defaults: scan `.`, CSS output `styles.css`):
|
|
39
|
+
|
|
76
40
|
```bash
|
|
77
|
-
# 1.
|
|
78
|
-
npx tailwind-unwind
|
|
41
|
+
# 1. Quick check — extractable duplicates + apply preview
|
|
42
|
+
npx tailwind-unwind check
|
|
79
43
|
|
|
80
|
-
# 2. Generate
|
|
81
|
-
npx tailwind-unwind generate
|
|
44
|
+
# 2. Generate CSS
|
|
45
|
+
npx tailwind-unwind generate
|
|
82
46
|
|
|
83
|
-
# 3. Import styles.css in
|
|
84
|
-
npx tailwind-unwind apply
|
|
85
|
-
npx tailwind-unwind apply
|
|
47
|
+
# 3. Import styles.css in globals.css, then replace in source files
|
|
48
|
+
npx tailwind-unwind apply --dry-run # preview first
|
|
49
|
+
npx tailwind-unwind apply # write changes
|
|
86
50
|
```
|
|
87
51
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
## Commands
|
|
52
|
+
Override defaults with flags (`./src`, `--output src/styles/components.css`) or `tailwind-unwind.config.json`.
|
|
91
53
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
Scan a directory and report the most frequent Tailwind class combinations.
|
|
54
|
+
Install globally (optional):
|
|
95
55
|
|
|
96
56
|
```bash
|
|
97
|
-
|
|
57
|
+
npm install -g tailwind-unwind
|
|
98
58
|
```
|
|
99
59
|
|
|
100
|
-
|
|
101
|
-
|------|---------|-------------|
|
|
102
|
-
| `--min-occurrences <n>` | `5` | Minimum occurrences (combinations must appear **more than** n times) |
|
|
103
|
-
| `--min-size <n>` | `2` | Minimum classes per combination |
|
|
104
|
-
| `--max-size <n>` | `5` | Maximum classes per combination |
|
|
105
|
-
| `--top <n>` | `10` | Number of top combinations to show |
|
|
106
|
-
| `--format <type>` | `console` | Output format: `console` or `json` |
|
|
107
|
-
| `--no-dedupe-subsets` | — | Include subset combinations in results |
|
|
108
|
-
| `--config <file>` | — | Path to config file |
|
|
109
|
-
| `--include <patterns>` | all `src` | Comma-separated glob include patterns |
|
|
110
|
-
| `--exclude <patterns>` | — | Comma-separated glob exclude patterns |
|
|
60
|
+
## Commands
|
|
111
61
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
62
|
+
| Command | What it does |
|
|
63
|
+
|---------|--------------|
|
|
64
|
+
| `check` | One-shot health check: extractable duplicates + apply dry-run preview. **Start here.** |
|
|
65
|
+
| `analyze` | Detailed report of frequent class combinations. Safe — read-only. |
|
|
66
|
+
| `generate` | Creates a CSS file with `@layer components` + `@apply`. Does not touch your `.tsx` files. |
|
|
67
|
+
| `apply` | Does what `generate` does **and** rewrites matching `className` in source files. |
|
|
68
|
+
| `init` | Generates `tailwind-unwind.config.json` from your project scan. |
|
|
115
69
|
|
|
116
|
-
|
|
117
|
-
npx tailwind-unwind analyze ./src --min-occurrences 10 --top 5
|
|
118
|
-
```
|
|
70
|
+
**Important:** `analyze` looks for frequent patterns (including subsets) with `--min-occurrences 5` by default. `check`, `generate`, and `apply` extract **exact duplicate** class strings with `--min-occurrences 3`.
|
|
119
71
|
|
|
120
|
-
|
|
72
|
+
In the analyze report, look for `Extractable: yes` — those patterns can be passed to `generate` / `apply`.
|
|
121
73
|
|
|
122
|
-
|
|
123
|
-
📊 Tailwind Analysis Report
|
|
124
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
125
|
-
Files scanned: 47
|
|
126
|
-
Components with className: 312
|
|
127
|
-
Unique class combinations: 89
|
|
128
|
-
|
|
129
|
-
🏆 Top 10 most frequent combinations:
|
|
130
|
-
|
|
131
|
-
1. "flex items-center justify-between p-4"
|
|
132
|
-
Occurrences: 24
|
|
133
|
-
Suggestion: .page-header
|
|
134
|
-
Extractable: yes — use generate/apply
|
|
135
|
-
Found in: src/components/Header.tsx:12, src/layout/Toolbar.tsx:5 (+18 more)
|
|
136
|
-
|
|
137
|
-
💡 Potential code reduction: 38%
|
|
138
|
-
💡 Generate CSS: npx tailwind-unwind generate <path> --output styles.css
|
|
139
|
-
💡 Apply classes: npx tailwind-unwind apply <path> --output styles.css
|
|
140
|
-
```
|
|
74
|
+
### `check` — recommended entry point
|
|
141
75
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
76
|
+
```bash
|
|
77
|
+
npx tailwind-unwind check
|
|
78
|
+
```
|
|
145
79
|
|
|
146
|
-
|
|
80
|
+
Shows how many patterns are ready to extract and previews what `apply` would change (without writing files).
|
|
147
81
|
|
|
148
|
-
|
|
82
|
+
For CI — fail when duplicates exceed a threshold:
|
|
149
83
|
|
|
150
84
|
```bash
|
|
151
|
-
npx tailwind-unwind
|
|
85
|
+
npx tailwind-unwind check --fail-on-extractable 0 # fail if any extractable pattern exists
|
|
86
|
+
npx tailwind-unwind check --format json
|
|
152
87
|
```
|
|
153
88
|
|
|
154
|
-
|
|
155
|
-
|------|---------|-------------|
|
|
156
|
-
| `--output <file>` | *(required)* | Output CSS file path |
|
|
157
|
-
| `--min-occurrences <n>` | `3` | Minimum occurrences (must appear **≥ n** times) |
|
|
158
|
-
| `--min-size <n>` | `2` | Minimum classes per set |
|
|
159
|
-
| `--max-size <n>` | `5` | Maximum classes per set |
|
|
160
|
-
| `--top <n>` | `10` | Max number of component classes to generate |
|
|
161
|
-
| `--prefix <name>` | `twu-` | Namespace prefix for generated classes |
|
|
162
|
-
| `--format <type>` | `console` | Output format: `console` or `json` |
|
|
163
|
-
| `--from-report <file>` | — | Generate from `analyze --format json` output |
|
|
164
|
-
| `--extractable-only` | — | Only patterns marked extractable in analyze |
|
|
89
|
+
## Typical workflow
|
|
165
90
|
|
|
166
91
|
```bash
|
|
167
|
-
|
|
168
|
-
npx tailwind-unwind
|
|
169
|
-
npx tailwind-unwind generate --from-report report.json --output styles.css
|
|
170
|
-
npx tailwind-unwind generate ./src --output styles.css --extractable-only --format json
|
|
171
|
-
```
|
|
92
|
+
# Optional: create config from your project
|
|
93
|
+
npx tailwind-unwind init
|
|
172
94
|
|
|
173
|
-
|
|
95
|
+
# Quick overview + dry-run preview
|
|
96
|
+
npx tailwind-unwind check
|
|
174
97
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
* Generated by tailwind-unwind
|
|
178
|
-
* Source: ./src
|
|
179
|
-
* Class prefix: twu-
|
|
180
|
-
*/
|
|
181
|
-
@layer components {
|
|
182
|
-
.twu-page-header {
|
|
183
|
-
@apply flex items-center justify-between p-4;
|
|
184
|
-
}
|
|
98
|
+
# Detailed report (optional)
|
|
99
|
+
npx tailwind-unwind analyze --format json > report.json
|
|
185
100
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
101
|
+
# Generate only extractable patterns from the report
|
|
102
|
+
npx tailwind-unwind generate --from-report report.json
|
|
189
103
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
104
|
+
# Preview replacements, then apply
|
|
105
|
+
npx tailwind-unwind apply --dry-run
|
|
106
|
+
npx tailwind-unwind apply --prettier
|
|
194
107
|
```
|
|
195
108
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
---
|
|
199
|
-
|
|
200
|
-
### `apply`
|
|
109
|
+
## Configuration
|
|
201
110
|
|
|
202
|
-
|
|
111
|
+
Create `tailwind-unwind.config.json` manually or run `init`:
|
|
203
112
|
|
|
204
113
|
```bash
|
|
205
|
-
npx tailwind-unwind
|
|
114
|
+
npx tailwind-unwind init
|
|
206
115
|
```
|
|
207
116
|
|
|
208
|
-
|
|
117
|
+
Also supported: `.tailwind-unwindrc`, `tailwind-unwind.config.ts` / `.js`.
|
|
209
118
|
|
|
210
|
-
|
|
211
|
-
|------|-------------|
|
|
212
|
-
| `--dry-run` | Preview replacements without writing files |
|
|
213
|
-
| `--prettier` | Format modified files with Prettier (optional peer dependency) |
|
|
214
|
-
| `--format json` | Machine-readable output for CI |
|
|
119
|
+
Example — see [`tailwind-unwind.config.example.json`](tailwind-unwind.config.example.json).
|
|
215
120
|
|
|
216
|
-
|
|
217
|
-
npx tailwind-unwind apply ./src --output styles.css --dry-run
|
|
218
|
-
npx tailwind-unwind apply ./src --output styles.css --prettier
|
|
219
|
-
npx tailwind-unwind apply ./src --output styles.css --from-report report.json --format json
|
|
220
|
-
```
|
|
121
|
+
Key options:
|
|
221
122
|
|
|
222
|
-
|
|
123
|
+
- `include` / `exclude` — which files to scan
|
|
124
|
+
- `names` — map utilities to your class names (`"flex p-4"` → `"toolbar"`)
|
|
125
|
+
- `analyze` / `generate` / `apply` — per-command settings
|
|
223
126
|
|
|
224
|
-
|
|
225
|
-
|---------|-----------|
|
|
226
|
-
| `className="flex items-center p-4"` | ✅ |
|
|
227
|
-
| `className={cn('flex', 'items-center', 'p-4')}` | ✅ (static args only) |
|
|
228
|
-
| `className={cn('flex p-4', isActive && 'bg-blue')}` | ✅ partial |
|
|
229
|
-
| `` className={`flex p-4 ${active ? 'bg-blue' : ''}`} `` | ✅ partial (static part) |
|
|
230
|
-
| `className={buttonVariants()}` | ✅ (cva/tv, no args) |
|
|
231
|
-
| `className={getClasses()}` | ❌ skipped |
|
|
127
|
+
CLI flags override config values.
|
|
232
128
|
|
|
233
|
-
|
|
129
|
+
## What `apply` can replace
|
|
234
130
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
131
|
+
| Pattern | Supported? |
|
|
132
|
+
|---------|------------|
|
|
133
|
+
| `className="flex p-4 bg-blue"` | Yes |
|
|
134
|
+
| `className={cn('flex', 'p-4')}` | Yes |
|
|
135
|
+
| `className={cn('flex p-4', isActive && 'bg-blue')}` | Yes (static part only) |
|
|
136
|
+
| `` className={`flex p-4 ${x}`} `` | Yes (static part only) |
|
|
137
|
+
| `className={buttonVariants()}` | Yes (cva/tv, no arguments) |
|
|
138
|
+
| `className={getClasses()}` | No — skipped |
|
|
238
139
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
```
|
|
140
|
+
Parsed: `cn`, `clsx`, `classnames`, `twMerge`, `cva`, `tv`, template literals.
|
|
141
|
+
Class order does not matter (`flex p-4` = `p-4 flex`).
|
|
242
142
|
|
|
243
|
-
|
|
143
|
+
Skipped locations are grouped by reason in the console; use `--verbose-skipped` for the full list.
|
|
244
144
|
|
|
245
|
-
##
|
|
145
|
+
## Generated class names
|
|
246
146
|
|
|
247
|
-
|
|
147
|
+
Default prefix is `twu-` to avoid clashes with your existing styles:
|
|
248
148
|
|
|
249
|
-
|
|
|
250
|
-
|
|
149
|
+
| Repeated utilities | Becomes |
|
|
150
|
+
|--------------------|---------|
|
|
251
151
|
| `flex items-center justify-between p-4` | `twu-page-header` |
|
|
252
152
|
| `w-full object-cover rounded-lg` | `twu-media-cover` |
|
|
253
|
-
| `bg-blue-500 px-4 py-2 rounded-lg` | `twu-primary-button` |
|
|
254
|
-
| `grid grid-cols-3 gap-4` | `twu-card-grid` |
|
|
255
|
-
| `fixed inset-0 bg-black/50` | `twu-backdrop` |
|
|
256
|
-
|
|
257
|
-
Customize with `--prefix app-` or the `names` field in config:
|
|
258
|
-
|
|
259
|
-
```json
|
|
260
|
-
{
|
|
261
|
-
"names": {
|
|
262
|
-
"flex items-center justify-between p-4": "page-header"
|
|
263
|
-
},
|
|
264
|
-
"generate": { "prefix": "app-" }
|
|
265
|
-
}
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
→ `.app-page-header`
|
|
269
153
|
|
|
270
|
-
|
|
154
|
+
Override with `--prefix app-` or the `names` field in config.
|
|
271
155
|
|
|
272
|
-
##
|
|
156
|
+
## Useful flags
|
|
273
157
|
|
|
274
|
-
|
|
158
|
+
| Flag | Commands | Purpose |
|
|
159
|
+
|------|----------|---------|
|
|
160
|
+
| `--fail-on-extractable <n>` | check | Exit 1 when extractable patterns exceed `n` |
|
|
161
|
+
| `--verbose-skipped` | apply, check | List every skipped replacement (default: grouped summary) |
|
|
162
|
+
| `--dry-run` | apply | Preview without writing files |
|
|
163
|
+
| `--prettier` | apply | Format changed files with Prettier |
|
|
164
|
+
| `--format json` | analyze, check, generate, apply | Output for CI / scripts |
|
|
165
|
+
| `--changed [ref]` | all | Only git-changed files |
|
|
166
|
+
| `--from-report <file>` | generate, apply | Use analyze JSON output |
|
|
167
|
+
| `--extractable-only` | generate, apply | Only patterns marked extractable |
|
|
168
|
+
| `--config <file>` | all | Custom config path |
|
|
169
|
+
| `--include` / `--exclude` | all | Filter files by glob |
|
|
275
170
|
|
|
276
|
-
|
|
277
|
-
- **Template literals:** static segments extracted; `${...}` expressions flagged
|
|
278
|
-
- **Merge utilities:** `cn()`, `clsx()`, `classnames()`, `twMerge()`, `cx()`
|
|
279
|
-
- **Variant APIs:** `cva()`, `tv()` definitions and no-arg calls like `buttonVariants()`
|
|
280
|
-
- **Dynamic expressions:** `className={getClasses()}` — warning, skipped
|
|
171
|
+
### Defaults
|
|
281
172
|
|
|
282
|
-
|
|
173
|
+
| | analyze | check / generate / apply |
|
|
174
|
+
|--|---------|--------------------------|
|
|
175
|
+
| scan path | `.` (project root) | `.` |
|
|
176
|
+
| `--output` | — | `styles.css` |
|
|
177
|
+
| `--min-occurrences` | 5 | 3 |
|
|
178
|
+
| `--prefix` | — | `twu-` |
|
|
283
179
|
|
|
284
|
-
|
|
180
|
+
Config file values override CLI defaults; explicit flags override config.
|
|
285
181
|
|
|
286
182
|
## Programmatic API
|
|
287
183
|
|
|
288
184
|
```typescript
|
|
289
185
|
import {
|
|
186
|
+
checkCommand,
|
|
290
187
|
analyzeCommand,
|
|
291
188
|
generateCommand,
|
|
292
189
|
applyCommand,
|
|
293
|
-
walkSourceFiles,
|
|
294
|
-
parseFile,
|
|
295
|
-
findFrequentPatterns,
|
|
296
|
-
findRepeatedClassSets,
|
|
297
|
-
buildComponents,
|
|
298
|
-
normalizeClasses,
|
|
299
190
|
} from 'tailwind-unwind';
|
|
300
191
|
|
|
301
|
-
|
|
302
|
-
await analyzeCommand('
|
|
192
|
+
await checkCommand('.', { output: 'styles.css' });
|
|
193
|
+
await analyzeCommand('.', { format: 'json', extractableMinOccurrences: 3 });
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Full exports: `walkSourceFiles`, `parseFile`, `findRepeatedClassSets`, `buildComponents`, `loadCommandOptions`, and more.
|
|
303
197
|
|
|
304
|
-
|
|
305
|
-
const files = await walkSourceFiles('./src');
|
|
306
|
-
const result = await parseFile(files[0]);
|
|
198
|
+
## CI
|
|
307
199
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
200
|
+
In your app repo — gate PRs on duplicate Tailwind patterns:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
npx tailwind-unwind check --fail-on-extractable 0 --format json
|
|
312
204
|
```
|
|
313
205
|
|
|
314
|
-
|
|
206
|
+
GitHub Actions composite action — see [`action.yml`](action.yml):
|
|
207
|
+
|
|
208
|
+
```yaml
|
|
209
|
+
- uses: AVPletnev/tailwind-unwind@v0.5.0
|
|
210
|
+
with:
|
|
211
|
+
command: check
|
|
212
|
+
format: json
|
|
213
|
+
args: --fail-on-extractable 0
|
|
214
|
+
```
|
|
315
215
|
|
|
316
216
|
## Development
|
|
317
217
|
|
|
318
218
|
```bash
|
|
319
219
|
git clone https://github.com/AVPletnev/tailwind-unwind.git
|
|
320
|
-
cd tailwind-unwind
|
|
321
|
-
npm install
|
|
322
|
-
npm run build # required before running CLI locally
|
|
323
|
-
npm test
|
|
324
|
-
|
|
325
|
-
node bin/index.js analyze ./test-project
|
|
326
|
-
node bin/index.js generate ./test-project --output styles.css
|
|
327
|
-
node bin/index.js apply ./test-project --output styles.css --dry-run
|
|
220
|
+
cd tailwind-unwind && npm install && npm run build && npm test
|
|
328
221
|
```
|
|
329
222
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
---
|
|
223
|
+
CI in this repo also runs `check` against `test-project` as a smoke test.
|
|
333
224
|
|
|
334
225
|
## License
|
|
335
226
|
|