tailwind-unwind 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AVPletnev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # tailwind-unwind
2
+
3
+ Analyze Tailwind CSS class usage patterns in React and Next.js projects. Find repeated utility combinations and opportunities to extract reusable component classes.
4
+
5
+ **Repository:** [github.com/AVPletnev/tailwind-unwind](https://github.com/AVPletnev/tailwind-unwind)
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install -g tailwind-unwind
11
+ # or run without installing
12
+ npx tailwind-unwind analyze ./src
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```bash
18
+ npx tailwind-unwind analyze <path>
19
+ ```
20
+
21
+ ### CLI options
22
+
23
+ | Flag | Default | Description |
24
+ |------|---------|-------------|
25
+ | `--min-occurrences <n>` | `5` | Minimum occurrences for a combination |
26
+ | `--min-size <n>` | `2` | Minimum classes per combination |
27
+ | `--max-size <n>` | `5` | Maximum classes per combination |
28
+ | `--top <n>` | `10` | Number of top combinations to show |
29
+ | `--format <type>` | `console` | Output format: `console` or `json` |
30
+ | `--no-dedupe-subsets` | — | Include subset combinations in results |
31
+
32
+ ```bash
33
+ # JSON report for CI
34
+ npx tailwind-unwind analyze ./src --format json
35
+
36
+ # Stricter filters
37
+ npx tailwind-unwind analyze ./src --min-occurrences 10 --top 5
38
+ ```
39
+
40
+ Scan a directory recursively for `.tsx`, `.jsx`, `.ts`, and `.js` files. The tool ignores `node_modules`, `.next`, `dist`, `build`, and `.git`.
41
+
42
+ ### Generate CSS
43
+
44
+ Extract **exact duplicate className strings** into reusable component classes:
45
+
46
+ ```bash
47
+ npx tailwind-unwind generate ./src --output styles.css
48
+ # Custom namespace (default: twu-)
49
+ npx tailwind-unwind generate ./src --output styles.css --prefix app-
50
+ ```
51
+
52
+ Unlike `analyze` (which finds frequent subsets), `generate` looks for identical full class lists on JSX elements. Default `--min-occurrences` is `3`.
53
+
54
+ Example output file:
55
+
56
+ ```css
57
+ @layer components {
58
+ .twu-toolbar {
59
+ @apply flex items-center justify-between p-4;
60
+ }
61
+
62
+ .twu-media-cover {
63
+ @apply w-full h-auto object-cover rounded-lg;
64
+ }
65
+ }
66
+ ```
67
+
68
+ Import the generated file in your global CSS (e.g. `globals.css`), then replace repeated `className` strings with the new classes.
69
+
70
+ Supports filter flags: `--min-occurrences`, `--min-size`, `--max-size`, `--top`.
71
+
72
+ ### Apply (replace className in source)
73
+
74
+ Generate CSS **and** replace matching `className` strings in your `.tsx`/`.jsx` files:
75
+
76
+ ```bash
77
+ # Preview changes without writing files
78
+ npx tailwind-unwind apply ./src --output styles.css --dry-run
79
+
80
+ # Apply replacements and write styles.css
81
+ npx tailwind-unwind apply ./src --output styles.css
82
+ ```
83
+
84
+ Only **exact static matches** are replaced (string literals and static `cn()`/`clsx()` calls). Dynamic expressions are left unchanged.
85
+
86
+ ### Example output
87
+
88
+ ```
89
+ 📊 Tailwind Analysis Report
90
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
91
+ Files scanned: 5
92
+ Components with className: 18
93
+ Unique class combinations: 4
94
+
95
+ 🏆 Top 10 most frequent combinations:
96
+
97
+ 1. "flex items-center justify-between p-4"
98
+ Occurrences: 8
99
+ Suggestion: .flex-items-center-justify-between
100
+
101
+ 2. "w-full h-auto object-cover rounded-lg"
102
+ Occurrences: 7
103
+ Suggestion: .w-full-h-auto-object-cover
104
+
105
+ 💡 Potential code reduction: 42%
106
+ 💡 Generate CSS: npx tailwind-unwind generate <path> --output styles.css
107
+ ```
108
+
109
+ ## What it analyzes
110
+
111
+ - **Static strings:** `className="flex p-4"` and `class="flex p-4"`
112
+ - **Template literals:** `className={\`flex p-4 ${active ? 'bg-blue' : ''}\`}` — static segments extracted
113
+ - **Class merge utilities:** `cn()`, `clsx()`, `classnames()`, `twMerge()`, `cx()` — string arguments and conditionals extracted
114
+ - **Fully dynamic expressions:** `className={getClasses()}` — skipped with a warning
115
+
116
+ Class combinations are normalized (order-independent): `flex p-4` and `p-4 flex` count as the same pattern.
117
+
118
+ Subset combinations are deduplicated by default (e.g. `flex p-4` is hidden when `flex items-center p-4` is present). Each result includes file locations.
119
+
120
+ ## Programmatic API
121
+
122
+ ```typescript
123
+ import {
124
+ analyzeCommand,
125
+ walkSourceFiles,
126
+ parseFile,
127
+ findFrequentPatterns,
128
+ normalizeClasses,
129
+ } from 'tailwind-unwind';
130
+
131
+ const files = await walkSourceFiles('./src');
132
+ const result = await parseFile(files[0]);
133
+ const extraction = result.extractions[0];
134
+
135
+ const patterns = findFrequentPatterns([
136
+ {
137
+ classes: extraction.classes,
138
+ filePath: files[0],
139
+ line: extraction.line,
140
+ },
141
+ ]);
142
+ ```
143
+
144
+ ## Development
145
+
146
+ ```bash
147
+ npm install
148
+ npm run build
149
+ npm test
150
+ node bin/index.js analyze ./test-project
151
+ node bin/index.js generate ./test-project --output styles.css
152
+ node bin/index.js apply ./test-project --output styles.css --dry-run
153
+ ```
154
+
155
+ ## License
156
+
157
+ MIT — see [LICENSE](LICENSE).
package/bin/index.js ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+
3
+ import('../dist/cli/index.js').catch((error) => {
4
+ console.error('Failed to start tailwind-unwind:', error);
5
+ process.exit(1);
6
+ });