tailwind-unwind 0.2.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 CHANGED
@@ -2,325 +2,180 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/tailwind-unwind.svg)](https://www.npmjs.com/package/tailwind-unwind)
4
4
 
5
- CLI tool to analyze, extract, and refactor repeated Tailwind CSS utility patterns in React and Next.js projects.
5
+ **Find repeated Tailwind classes in your React code and turn them into reusable component classes.**
6
6
 
7
- **Repository:** [github.com/AVPletnev/tailwind-unwind](https://github.com/AVPletnev/tailwind-unwind)
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
- ## Features
9
+ Works with React / Next.js projects. Node.js 18+.
10
10
 
11
- - **analyze** find frequent `className` patterns with file locations
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
- ## Installation
18
-
19
- ```bash
20
- npm install -g tailwind-unwind
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
- Requires **Node.js 18+**.
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
- ```bash
33
- cp node_modules/tailwind-unwind/tailwind-unwind.config.example.json tailwind-unwind.config.json
22
+ ```tsx
23
+ <div className="twu-page-header">...</div>
34
24
  ```
35
25
 
36
- Supported filenames: `tailwind-unwind.config.json`, `.tailwind-unwindrc`, `tailwind-unwind.config.js` / `.mjs` / `.cjs`.
37
-
38
- ```json
39
- {
40
- "include": ["src/**/*.tsx"],
41
- "exclude": ["**/*.test.tsx", "**/*.stories.tsx"],
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 in your project
39
+ # 1. See what repeats
78
40
  npx tailwind-unwind analyze ./src
79
41
 
80
- # 2. Generate component CSS
42
+ # 2. Generate CSS
81
43
  npx tailwind-unwind generate ./src --output styles.css
82
44
 
83
- # 3. Import styles.css in your global CSS (e.g. globals.css), then apply replacements
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
- # JSON report for CI
114
- npx tailwind-unwind analyze ./src --format json
115
-
116
- # Stricter filters
117
- npx tailwind-unwind analyze ./src --min-occurrences 10 --top 5
118
- ```
119
-
120
- **Example output:**
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
53
+ npm install -g tailwind-unwind
140
54
  ```
141
55
 
142
- `analyze` finds frequent **subsets** of classes (2–5 utilities) and deduplicates subsets by default.
143
-
144
- ---
56
+ ## How the three commands differ
145
57
 
146
- ### `generate`
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. |
147
63
 
148
- Extract **exact duplicate `className` strings** into reusable component classes.
64
+ **Important:** `analyze` looks for frequent patterns (including subsets). `generate` and `apply` only work with **exact duplicate** class strings that appear multiple times.
149
65
 
150
- ```bash
151
- npx tailwind-unwind generate <path> --output <file.css>
152
- ```
66
+ In the analyze report, look for `Extractable: yes` — those patterns can be passed to `generate` / `apply`.
153
67
 
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 |
68
+ ## Typical workflow
162
69
 
163
70
  ```bash
164
- npx tailwind-unwind generate ./src --output styles.css
165
- npx tailwind-unwind generate ./src --output styles.css --prefix app-
166
- npx tailwind-unwind generate ./src --output styles.css --min-occurrences 2 --top 20
167
- ```
71
+ # Optional: create config from your project
72
+ npx tailwind-unwind init ./src
168
73
 
169
- **Example output (`styles.css`):**
170
-
171
- ```css
172
- /**
173
- * Generated by tailwind-unwind
174
- * Source: ./src
175
- * Class prefix: twu-
176
- */
177
- @layer components {
178
- .twu-page-header {
179
- @apply flex items-center justify-between p-4;
180
- }
74
+ # Analyze → save report
75
+ npx tailwind-unwind analyze ./src --format json > report.json
181
76
 
182
- .twu-media-cover {
183
- @apply w-full h-auto object-cover rounded-lg;
184
- }
77
+ # Generate only extractable patterns from the report
78
+ npx tailwind-unwind generate --from-report report.json --output src/styles/components.css
185
79
 
186
- .twu-primary-button {
187
- @apply bg-blue-500 text-white px-4 py-2 rounded-lg;
188
- }
189
- }
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
190
83
  ```
191
84
 
192
- Import `styles.css` in your global CSS, then use the generated classes in JSX.
193
-
194
- ---
195
-
196
- ### `apply`
85
+ ## Configuration
197
86
 
198
- Generate CSS **and** replace matching `className` strings in source files.
87
+ Create `tailwind-unwind.config.json` manually or run `init`:
199
88
 
200
89
  ```bash
201
- npx tailwind-unwind apply <path> --output <file.css>
90
+ npx tailwind-unwind init ./src
202
91
  ```
203
92
 
204
- Supports the same flags as `generate`, plus:
93
+ Also supported: `.tailwind-unwindrc`, `tailwind-unwind.config.ts` / `.js`.
205
94
 
206
- | Flag | Description |
207
- |------|-------------|
208
- | `--dry-run` | Preview replacements without writing files |
209
-
210
- ```bash
211
- npx tailwind-unwind apply ./src --output styles.css --dry-run
212
- npx tailwind-unwind apply ./src --output styles.css
213
- ```
95
+ Example see [`tailwind-unwind.config.example.json`](tailwind-unwind.config.example.json).
214
96
 
215
- **What gets replaced:**
97
+ Key options:
216
98
 
217
- | Pattern | Replaced? |
218
- |---------|-----------|
219
- | `className="flex items-center p-4"` | |
220
- | `className={cn('flex', 'items-center', 'p-4')}` | ✅ (static args only) |
221
- | `className={getClasses()}` | ❌ skipped |
222
- | `` className={`flex ${active ? 'p-4' : ''}`} `` | ❌ skipped (dynamic) |
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
223
102
 
224
- **Before After:**
103
+ CLI flags override config values.
225
104
 
226
- ```tsx
227
- // before
228
- <div className="flex items-center justify-between p-4">Header</div>
105
+ ## What `apply` can replace
229
106
 
230
- // after
231
- <div className="twu-page-header">Header</div>
232
- ```
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 |
233
115
 
234
- ---
116
+ Parsed: `cn`, `clsx`, `classnames`, `twMerge`, `cva`, `tv`, template literals.
117
+ Class order does not matter (`flex p-4` = `p-4 flex`).
235
118
 
236
- ## Class naming
119
+ ## Generated class names
237
120
 
238
- Generated classes use a **`twu-` prefix** by default to avoid conflicts with existing project styles. Names are derived from semantic rules, not utility concatenation:
121
+ Default prefix is `twu-` to avoid clashes with your existing styles:
239
122
 
240
- | Utilities | Generated class |
241
- |-----------|-----------------|
123
+ | Repeated utilities | Becomes |
124
+ |--------------------|---------|
242
125
  | `flex items-center justify-between p-4` | `twu-page-header` |
243
126
  | `w-full object-cover rounded-lg` | `twu-media-cover` |
244
- | `bg-blue-500 px-4 py-2 rounded-lg` | `twu-primary-button` |
245
- | `grid grid-cols-3 gap-4` | `twu-card-grid` |
246
- | `fixed inset-0 bg-black/50` | `twu-backdrop` |
247
-
248
- Customize with `--prefix app-` or the `names` field in config:
249
-
250
- ```json
251
- {
252
- "names": {
253
- "flex items-center justify-between p-4": "page-header"
254
- },
255
- "generate": { "prefix": "app-" }
256
- }
257
- ```
258
-
259
- → `.app-page-header`
260
127
 
261
- ---
128
+ Override with `--prefix app-` or the `names` field in config.
262
129
 
263
- ## What gets parsed
130
+ ## Useful flags
264
131
 
265
- Scans `.tsx`, `.jsx`, `.ts`, `.js` recursively. Ignores `node_modules`, `.next`, `dist`, `build`, `.git`.
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 |
266
142
 
267
- - **Static strings:** `className="flex p-4"` / `class="flex p-4"`
268
- - **Template literals:** static segments extracted; `${...}` expressions flagged
269
- - **Merge utilities:** `cn()`, `clsx()`, `classnames()`, `twMerge()`, `cx()`
270
- - **Dynamic expressions:** `className={getClasses()}` — warning, skipped
143
+ ### Defaults
271
144
 
272
- Class order is normalized: `flex p-4` and `p-4 flex` are treated as the same set.
273
-
274
- ---
145
+ | | analyze | generate / apply |
146
+ |--|---------|------------------|
147
+ | `--min-occurrences` | 5 | 3 |
148
+ | `--prefix` | — | `twu-` |
275
149
 
276
150
  ## Programmatic API
277
151
 
278
152
  ```typescript
279
- import {
280
- analyzeCommand,
281
- generateCommand,
282
- applyCommand,
283
- walkSourceFiles,
284
- parseFile,
285
- findFrequentPatterns,
286
- findRepeatedClassSets,
287
- buildComponents,
288
- normalizeClasses,
289
- } from 'tailwind-unwind';
290
-
291
- // Analyze
153
+ import { analyzeCommand, generateCommand, applyCommand, buildComponents } from 'tailwind-unwind';
154
+
292
155
  await analyzeCommand('./src', { format: 'json' });
156
+ ```
157
+
158
+ Full exports: `walkSourceFiles`, `parseFile`, `findRepeatedClassSets`, `buildComponents`, `loadCommandOptions`, and more.
293
159
 
294
- // Build component map
295
- const files = await walkSourceFiles('./src');
296
- const result = await parseFile(files[0]);
160
+ ## GitHub Action
297
161
 
298
- const { components, css, replacementMap } = buildComponents(
299
- [{ classes: result.extractions[0].classes, filePath: files[0] }],
300
- { sourcePath: './src', prefix: 'twu-' },
301
- );
162
+ ```yaml
163
+ - uses: AVPletnev/tailwind-unwind@v0.4.0
164
+ with:
165
+ command: analyze
166
+ path: ./src
167
+ format: json
302
168
  ```
303
169
 
304
- ---
170
+ See [`action.yml`](action.yml) for inputs.
305
171
 
306
172
  ## Development
307
173
 
308
174
  ```bash
309
175
  git clone https://github.com/AVPletnev/tailwind-unwind.git
310
- cd tailwind-unwind
311
- npm install
312
- npm run build # required before running CLI locally
313
- npm test
314
-
315
- node bin/index.js analyze ./test-project
316
- node bin/index.js generate ./test-project --output styles.css
317
- node bin/index.js apply ./test-project --output styles.css --dry-run
176
+ cd tailwind-unwind && npm install && npm run build && npm test
318
177
  ```
319
178
 
320
- > **Note:** `bin/index.js` runs compiled code from `dist/`. Always run `npm run build` after changing source files.
321
-
322
- ---
323
-
324
179
  ## License
325
180
 
326
181
  MIT — see [LICENSE](LICENSE).