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 CHANGED
@@ -2,335 +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
53
+ npm install -g tailwind-unwind
118
54
  ```
119
55
 
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
140
- ```
56
+ ## How the three commands differ
141
57
 
142
- `analyze` finds frequent **subsets** of classes (2–5 utilities) and deduplicates subsets by default.
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
- ### `generate`
66
+ In the analyze report, look for `Extractable: yes` — those patterns can be passed to `generate` / `apply`.
147
67
 
148
- Extract **exact duplicate `className` strings** into reusable component classes.
68
+ ## Typical workflow
149
69
 
150
70
  ```bash
151
- npx tailwind-unwind generate <path> --output <file.css>
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
- ```bash
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
- .twu-media-cover {
187
- @apply w-full h-auto object-cover rounded-lg;
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
- .twu-primary-button {
191
- @apply bg-blue-500 text-white px-4 py-2 rounded-lg;
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
- Import `styles.css` in your global CSS, then use the generated classes in JSX.
197
-
198
- ---
199
-
200
- ### `apply`
85
+ ## Configuration
201
86
 
202
- Generate CSS **and** replace matching `className` strings in source files.
87
+ Create `tailwind-unwind.config.json` manually or run `init`:
203
88
 
204
89
  ```bash
205
- npx tailwind-unwind apply <path> --output <file.css>
90
+ npx tailwind-unwind init ./src
206
91
  ```
207
92
 
208
- Supports the same flags as `generate`, plus:
93
+ Also supported: `.tailwind-unwindrc`, `tailwind-unwind.config.ts` / `.js`.
209
94
 
210
- | Flag | Description |
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
- **What gets replaced:**
97
+ Key options:
223
98
 
224
- | Pattern | Replaced? |
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 |
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
- **Before After:**
103
+ CLI flags override config values.
234
104
 
235
- ```tsx
236
- // before
237
- <div className="flex items-center justify-between p-4">Header</div>
105
+ ## What `apply` can replace
238
106
 
239
- // after
240
- <div className="twu-page-header">Header</div>
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
- ## Class naming
119
+ ## Generated class names
246
120
 
247
- 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:
248
122
 
249
- | Utilities | Generated class |
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
- ## What gets parsed
130
+ ## Useful flags
273
131
 
274
- 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 |
275
142
 
276
- - **Static strings:** `className="flex p-4"` / `class="flex p-4"`
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
- Class order is normalized: `flex p-4` and `p-4 flex` are treated as the same set.
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
- analyzeCommand,
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
- // Build component map
305
- const files = await walkSourceFiles('./src');
306
- const result = await parseFile(files[0]);
160
+ ## GitHub Action
307
161
 
308
- const { components, css, replacementMap } = buildComponents(
309
- [{ classes: result.extractions[0].classes, filePath: files[0] }],
310
- { sourcePath: './src', prefix: 'twu-' },
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).