tailwind-unwind 0.3.0 → 0.4.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 +97 -252
- package/dist/{chunk-4GXMK3NB.js → chunk-UXXIEFP4.js} +351 -101
- package/dist/chunk-UXXIEFP4.js.map +1 -0
- package/dist/cli/index.js +60 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +71 -27
- package/dist/index.js +11 -1
- package/package.json +3 -2
- package/dist/chunk-4GXMK3NB.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,335 +2,180 @@
|
|
|
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
|
|
|
76
38
|
```bash
|
|
77
|
-
# 1. See what repeats
|
|
39
|
+
# 1. See what repeats
|
|
78
40
|
npx tailwind-unwind analyze ./src
|
|
79
41
|
|
|
80
|
-
# 2. Generate
|
|
42
|
+
# 2. Generate CSS
|
|
81
43
|
npx tailwind-unwind generate ./src --output styles.css
|
|
82
44
|
|
|
83
|
-
# 3. Import styles.css in
|
|
84
|
-
npx tailwind-unwind apply ./src --output styles.css --dry-run # preview
|
|
45
|
+
# 3. Import styles.css in globals.css, then replace in source files
|
|
46
|
+
npx tailwind-unwind apply ./src --output styles.css --dry-run # preview first
|
|
85
47
|
npx tailwind-unwind apply ./src --output styles.css # write changes
|
|
86
48
|
```
|
|
87
49
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
## Commands
|
|
91
|
-
|
|
92
|
-
### `analyze`
|
|
93
|
-
|
|
94
|
-
Scan a directory and report the most frequent Tailwind class combinations.
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
npx tailwind-unwind analyze <path>
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
| Flag | Default | Description |
|
|
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 |
|
|
50
|
+
Install globally (optional):
|
|
111
51
|
|
|
112
52
|
```bash
|
|
113
|
-
|
|
114
|
-
npx tailwind-unwind analyze ./src --format json
|
|
115
|
-
|
|
116
|
-
# Stricter filters
|
|
117
|
-
npx tailwind-unwind analyze ./src --min-occurrences 10 --top 5
|
|
53
|
+
npm install -g tailwind-unwind
|
|
118
54
|
```
|
|
119
55
|
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
```
|
|
56
|
+
## How the three commands differ
|
|
141
57
|
|
|
142
|
-
|
|
58
|
+
| Command | What it does |
|
|
59
|
+
|---------|--------------|
|
|
60
|
+
| `analyze` | Shows which class combinations repeat and where. Safe — read-only. |
|
|
61
|
+
| `generate` | Creates a CSS file with `@layer components` + `@apply`. Does not touch your `.tsx` files. |
|
|
62
|
+
| `apply` | Does what `generate` does **and** rewrites matching `className` in source files. |
|
|
143
63
|
|
|
144
|
-
|
|
64
|
+
**Important:** `analyze` looks for frequent patterns (including subsets). `generate` and `apply` only work with **exact duplicate** class strings that appear multiple times.
|
|
145
65
|
|
|
146
|
-
|
|
66
|
+
In the analyze report, look for `Extractable: yes` — those patterns can be passed to `generate` / `apply`.
|
|
147
67
|
|
|
148
|
-
|
|
68
|
+
## Typical workflow
|
|
149
69
|
|
|
150
70
|
```bash
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
| Flag | Default | Description |
|
|
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 |
|
|
71
|
+
# Optional: create config from your project
|
|
72
|
+
npx tailwind-unwind init ./src
|
|
165
73
|
|
|
166
|
-
|
|
167
|
-
npx tailwind-unwind generate ./src --output styles.css
|
|
74
|
+
# Analyze → save report
|
|
168
75
|
npx tailwind-unwind analyze ./src --format json > report.json
|
|
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
|
-
```
|
|
172
|
-
|
|
173
|
-
**Example output (`styles.css`):**
|
|
174
|
-
|
|
175
|
-
```css
|
|
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
|
-
}
|
|
185
76
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
77
|
+
# Generate only extractable patterns from the report
|
|
78
|
+
npx tailwind-unwind generate --from-report report.json --output src/styles/components.css
|
|
189
79
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
80
|
+
# Preview replacements, then apply
|
|
81
|
+
npx tailwind-unwind apply ./src --output src/styles/components.css --dry-run
|
|
82
|
+
npx tailwind-unwind apply ./src --output src/styles/components.css --prettier
|
|
194
83
|
```
|
|
195
84
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
---
|
|
199
|
-
|
|
200
|
-
### `apply`
|
|
85
|
+
## Configuration
|
|
201
86
|
|
|
202
|
-
|
|
87
|
+
Create `tailwind-unwind.config.json` manually or run `init`:
|
|
203
88
|
|
|
204
89
|
```bash
|
|
205
|
-
npx tailwind-unwind
|
|
90
|
+
npx tailwind-unwind init ./src
|
|
206
91
|
```
|
|
207
92
|
|
|
208
|
-
|
|
93
|
+
Also supported: `.tailwind-unwindrc`, `tailwind-unwind.config.ts` / `.js`.
|
|
209
94
|
|
|
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 |
|
|
215
|
-
|
|
216
|
-
```bash
|
|
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
|
-
```
|
|
95
|
+
Example — see [`tailwind-unwind.config.example.json`](tailwind-unwind.config.example.json).
|
|
221
96
|
|
|
222
|
-
|
|
97
|
+
Key options:
|
|
223
98
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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 |
|
|
99
|
+
- `include` / `exclude` — which files to scan
|
|
100
|
+
- `names` — map utilities to your class names (`"flex p-4"` → `"toolbar"`)
|
|
101
|
+
- `analyze` / `generate` / `apply` — per-command settings
|
|
232
102
|
|
|
233
|
-
|
|
103
|
+
CLI flags override config values.
|
|
234
104
|
|
|
235
|
-
|
|
236
|
-
// before
|
|
237
|
-
<div className="flex items-center justify-between p-4">Header</div>
|
|
105
|
+
## What `apply` can replace
|
|
238
106
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
107
|
+
| Pattern | Supported? |
|
|
108
|
+
|---------|------------|
|
|
109
|
+
| `className="flex p-4 bg-blue"` | Yes |
|
|
110
|
+
| `className={cn('flex', 'p-4')}` | Yes |
|
|
111
|
+
| `className={cn('flex p-4', isActive && 'bg-blue')}` | Yes (static part only) |
|
|
112
|
+
| `` className={`flex p-4 ${x}`} `` | Yes (static part only) |
|
|
113
|
+
| `className={buttonVariants()}` | Yes (cva/tv, no arguments) |
|
|
114
|
+
| `className={getClasses()}` | No — skipped |
|
|
242
115
|
|
|
243
|
-
|
|
116
|
+
Parsed: `cn`, `clsx`, `classnames`, `twMerge`, `cva`, `tv`, template literals.
|
|
117
|
+
Class order does not matter (`flex p-4` = `p-4 flex`).
|
|
244
118
|
|
|
245
|
-
##
|
|
119
|
+
## Generated class names
|
|
246
120
|
|
|
247
|
-
|
|
121
|
+
Default prefix is `twu-` to avoid clashes with your existing styles:
|
|
248
122
|
|
|
249
|
-
|
|
|
250
|
-
|
|
123
|
+
| Repeated utilities | Becomes |
|
|
124
|
+
|--------------------|---------|
|
|
251
125
|
| `flex items-center justify-between p-4` | `twu-page-header` |
|
|
252
126
|
| `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
127
|
|
|
270
|
-
|
|
128
|
+
Override with `--prefix app-` or the `names` field in config.
|
|
271
129
|
|
|
272
|
-
##
|
|
130
|
+
## Useful flags
|
|
273
131
|
|
|
274
|
-
|
|
132
|
+
| Flag | Commands | Purpose |
|
|
133
|
+
|------|----------|---------|
|
|
134
|
+
| `--dry-run` | apply | Preview without writing files |
|
|
135
|
+
| `--prettier` | apply | Format changed files with Prettier |
|
|
136
|
+
| `--format json` | analyze, generate, apply | Output for CI / scripts |
|
|
137
|
+
| `--changed [ref]` | all | Only git-changed files |
|
|
138
|
+
| `--from-report <file>` | generate, apply | Use analyze JSON output |
|
|
139
|
+
| `--extractable-only` | generate, apply | Only patterns marked extractable |
|
|
140
|
+
| `--config <file>` | all | Custom config path |
|
|
141
|
+
| `--include` / `--exclude` | all | Filter files by glob |
|
|
275
142
|
|
|
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
|
|
143
|
+
### Defaults
|
|
281
144
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
145
|
+
| | analyze | generate / apply |
|
|
146
|
+
|--|---------|------------------|
|
|
147
|
+
| `--min-occurrences` | 5 | 3 |
|
|
148
|
+
| `--prefix` | — | `twu-` |
|
|
285
149
|
|
|
286
150
|
## Programmatic API
|
|
287
151
|
|
|
288
152
|
```typescript
|
|
289
|
-
import {
|
|
290
|
-
|
|
291
|
-
generateCommand,
|
|
292
|
-
applyCommand,
|
|
293
|
-
walkSourceFiles,
|
|
294
|
-
parseFile,
|
|
295
|
-
findFrequentPatterns,
|
|
296
|
-
findRepeatedClassSets,
|
|
297
|
-
buildComponents,
|
|
298
|
-
normalizeClasses,
|
|
299
|
-
} from 'tailwind-unwind';
|
|
300
|
-
|
|
301
|
-
// Analyze
|
|
153
|
+
import { analyzeCommand, generateCommand, applyCommand, buildComponents } from 'tailwind-unwind';
|
|
154
|
+
|
|
302
155
|
await analyzeCommand('./src', { format: 'json' });
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Full exports: `walkSourceFiles`, `parseFile`, `findRepeatedClassSets`, `buildComponents`, `loadCommandOptions`, and more.
|
|
303
159
|
|
|
304
|
-
|
|
305
|
-
const files = await walkSourceFiles('./src');
|
|
306
|
-
const result = await parseFile(files[0]);
|
|
160
|
+
## GitHub Action
|
|
307
161
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
162
|
+
```yaml
|
|
163
|
+
- uses: AVPletnev/tailwind-unwind@v0.4.0
|
|
164
|
+
with:
|
|
165
|
+
command: analyze
|
|
166
|
+
path: ./src
|
|
167
|
+
format: json
|
|
312
168
|
```
|
|
313
169
|
|
|
314
|
-
|
|
170
|
+
See [`action.yml`](action.yml) for inputs.
|
|
315
171
|
|
|
316
172
|
## Development
|
|
317
173
|
|
|
318
174
|
```bash
|
|
319
175
|
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
|
|
176
|
+
cd tailwind-unwind && npm install && npm run build && npm test
|
|
328
177
|
```
|
|
329
178
|
|
|
330
|
-
> **Note:** `bin/index.js` runs compiled code from `dist/`. Always run `npm run build` after changing source files.
|
|
331
|
-
|
|
332
|
-
---
|
|
333
|
-
|
|
334
179
|
## License
|
|
335
180
|
|
|
336
181
|
MIT — see [LICENSE](LICENSE).
|