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 CHANGED
@@ -2,334 +2,225 @@
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
 
38
+ Run from your project root — no paths required (defaults: scan `.`, CSS output `styles.css`):
39
+
76
40
  ```bash
77
- # 1. See what repeats in your project
78
- npx tailwind-unwind analyze ./src
41
+ # 1. Quick check extractable duplicates + apply preview
42
+ npx tailwind-unwind check
79
43
 
80
- # 2. Generate component CSS
81
- npx tailwind-unwind generate ./src --output styles.css
44
+ # 2. Generate CSS
45
+ npx tailwind-unwind generate
82
46
 
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
85
- npx tailwind-unwind apply ./src --output styles.css # write changes
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
- ### `analyze`
93
-
94
- Scan a directory and report the most frequent Tailwind class combinations.
54
+ Install globally (optional):
95
55
 
96
56
  ```bash
97
- npx tailwind-unwind analyze <path>
57
+ npm install -g tailwind-unwind
98
58
  ```
99
59
 
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 |
60
+ ## Commands
111
61
 
112
- ```bash
113
- # JSON report for CI
114
- npx tailwind-unwind analyze ./src --format json
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
- # Stricter filters
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
- **Example output:**
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
- `analyze` finds frequent **subsets** of classes (2–5 utilities) and deduplicates subsets by default.
143
-
144
- ---
76
+ ```bash
77
+ npx tailwind-unwind check
78
+ ```
145
79
 
146
- ### `generate`
80
+ Shows how many patterns are ready to extract and previews what `apply` would change (without writing files).
147
81
 
148
- Extract **exact duplicate `className` strings** into reusable component classes.
82
+ For CI fail when duplicates exceed a threshold:
149
83
 
150
84
  ```bash
151
- npx tailwind-unwind generate <path> --output <file.css>
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
- | 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 |
89
+ ## Typical workflow
165
90
 
166
91
  ```bash
167
- npx tailwind-unwind generate ./src --output styles.css
168
- 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
- ```
92
+ # Optional: create config from your project
93
+ npx tailwind-unwind init
172
94
 
173
- **Example output (`styles.css`):**
95
+ # Quick overview + dry-run preview
96
+ npx tailwind-unwind check
174
97
 
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
- }
98
+ # Detailed report (optional)
99
+ npx tailwind-unwind analyze --format json > report.json
185
100
 
186
- .twu-media-cover {
187
- @apply w-full h-auto object-cover rounded-lg;
188
- }
101
+ # Generate only extractable patterns from the report
102
+ npx tailwind-unwind generate --from-report report.json
189
103
 
190
- .twu-primary-button {
191
- @apply bg-blue-500 text-white px-4 py-2 rounded-lg;
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
- Import `styles.css` in your global CSS, then use the generated classes in JSX.
197
-
198
- ---
199
-
200
- ### `apply`
109
+ ## Configuration
201
110
 
202
- Generate CSS **and** replace matching `className` strings in source files.
111
+ Create `tailwind-unwind.config.json` manually or run `init`:
203
112
 
204
113
  ```bash
205
- npx tailwind-unwind apply <path> --output <file.css>
114
+ npx tailwind-unwind init
206
115
  ```
207
116
 
208
- Supports the same flags as `generate`, plus:
117
+ Also supported: `.tailwind-unwindrc`, `tailwind-unwind.config.ts` / `.js`.
209
118
 
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 |
119
+ Example see [`tailwind-unwind.config.example.json`](tailwind-unwind.config.example.json).
215
120
 
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
- ```
121
+ Key options:
221
122
 
222
- **What gets replaced:**
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
- | 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 |
127
+ CLI flags override config values.
232
128
 
233
- **Before After:**
129
+ ## What `apply` can replace
234
130
 
235
- ```tsx
236
- // before
237
- <div className="flex items-center justify-between p-4">Header</div>
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
- // after
240
- <div className="twu-page-header">Header</div>
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
- ## Class naming
145
+ ## Generated class names
246
146
 
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:
147
+ Default prefix is `twu-` to avoid clashes with your existing styles:
248
148
 
249
- | Utilities | Generated class |
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
- ## What gets parsed
156
+ ## Useful flags
273
157
 
274
- Scans `.tsx`, `.jsx`, `.ts`, `.js` recursively. Ignores `node_modules`, `.next`, `dist`, `build`, `.git`.
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
- - **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
171
+ ### Defaults
281
172
 
282
- Class order is normalized: `flex p-4` and `p-4 flex` are treated as the same set.
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
- // Analyze
302
- await analyzeCommand('./src', { format: 'json' });
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
- // Build component map
305
- const files = await walkSourceFiles('./src');
306
- const result = await parseFile(files[0]);
198
+ ## CI
307
199
 
308
- const { components, css, replacementMap } = buildComponents(
309
- [{ classes: result.extractions[0].classes, filePath: files[0] }],
310
- { sourcePath: './src', prefix: 'twu-' },
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
- > **Note:** `bin/index.js` runs compiled code from `dist/`. Always run `npm run build` after changing source files.
331
-
332
- ---
223
+ CI in this repo also runs `check` against `test-project` as a smoke test.
333
224
 
334
225
  ## License
335
226