tw-migrate 0.1.0 → 0.2.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 +13 -1
- package/bin/tw-migrate.js +15 -10
- package/html.js +74 -36
- package/index.d.ts +1 -1
- package/index.js +1283 -312
- package/native.js +18 -11
- package/package.json +14 -12
- package/style-compiler.js +52 -37
- package/vue.js +541 -0
- package/rfcs/batch-css-migration.md +0 -297
- package/rfcs/browser-ecosystem-e2e.md +0 -338
- package/rfcs/css-to-tailwind-migration-cli.md +0 -521
- package/rfcs/preprocessor-and-html-migration.md +0 -497
|
@@ -1,521 +0,0 @@
|
|
|
1
|
-
# RFC: CSS-to-Tailwind Migration CLI
|
|
2
|
-
|
|
3
|
-
## Status
|
|
4
|
-
|
|
5
|
-
Proposed
|
|
6
|
-
|
|
7
|
-
## Background
|
|
8
|
-
|
|
9
|
-
Migrating an existing React or Next.js project from authored CSS to Tailwind is usually a manual process:
|
|
10
|
-
|
|
11
|
-
1. Find every JSX element affected by a CSS selector.
|
|
12
|
-
2. Translate each declaration into a Tailwind utility.
|
|
13
|
-
3. Preserve selector conditions such as pseudo-classes, descendants, and media queries.
|
|
14
|
-
4. Update `className` without rewriting unrelated source formatting.
|
|
15
|
-
5. Remove CSS Module rules only when their exports are no longer needed.
|
|
16
|
-
|
|
17
|
-
Text search is not sufficient for this work. CSS Modules rename selectors at build time, JSX expressions have multiple static forms, selectors may span component boundaries, and Tailwind class order does not directly determine CSS precedence.
|
|
18
|
-
|
|
19
|
-
This RFC proposes `tw-migrate`, a one-shot codemod distributed as an npm package. It parses CSS and React source in Rust, exposes the migration through NAPI-RS, and uses the target project's Tailwind v4 installation to validate generated utilities.
|
|
20
|
-
|
|
21
|
-
## Goals
|
|
22
|
-
|
|
23
|
-
1. Migrate one selected CSS file at a time.
|
|
24
|
-
2. Support React and Next.js JavaScript/TypeScript source containing JSX.
|
|
25
|
-
3. Resolve static global class/id references and CSS Module imports.
|
|
26
|
-
4. Prefer exact Tailwind theme utilities and fall back to arbitrary values or properties.
|
|
27
|
-
5. Preserve unrelated source formatting through span-based edits.
|
|
28
|
-
6. Preview all changes by default and write only with an explicit flag.
|
|
29
|
-
7. Expose the same operation through a high-level Node.js API.
|
|
30
|
-
8. Publish prebuilt native packages for the main macOS, Linux, and Windows targets.
|
|
31
|
-
|
|
32
|
-
## Non-Goals
|
|
33
|
-
|
|
34
|
-
1. Vue, Svelte, Angular, or template languages other than JSX/TSX.
|
|
35
|
-
2. Tailwind v3 or earlier.
|
|
36
|
-
3. Dynamic or conditional `className` evaluation.
|
|
37
|
-
4. Runtime DOM inspection or browser-based migration.
|
|
38
|
-
5. A persistent linter, watcher, or CI daemon.
|
|
39
|
-
6. Automatic deletion of global CSS rules.
|
|
40
|
-
7. Complete React rendering analysis across arbitrary higher-order components, portals, or runtime component selection.
|
|
41
|
-
8. SCSS, Sass, or Less migration in the first release.
|
|
42
|
-
9. Tailwind's unstable design-system APIs.
|
|
43
|
-
|
|
44
|
-
## User Experience
|
|
45
|
-
|
|
46
|
-
### CLI
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
# Preview a migration and print its diff.
|
|
50
|
-
npx tw-migrate src/components/Button.module.css
|
|
51
|
-
|
|
52
|
-
# Apply the previewed migration.
|
|
53
|
-
npx tw-migrate src/components/Button.module.css --write
|
|
54
|
-
|
|
55
|
-
# Select a Tailwind entry when auto-detection is ambiguous.
|
|
56
|
-
npx tw-migrate src/components/Button.module.css \
|
|
57
|
-
--tailwind-css src/app/globals.css \
|
|
58
|
-
--write
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
The command accepts exactly one positional CSS file in the first release. Directory and glob migration are deferred until single-file behavior is reliable.
|
|
62
|
-
|
|
63
|
-
Preview output includes:
|
|
64
|
-
|
|
65
|
-
- affected source files;
|
|
66
|
-
- a unified diff;
|
|
67
|
-
- converted and retained CSS rules;
|
|
68
|
-
- generated Tailwind candidates;
|
|
69
|
-
- warnings with source locations and reasons.
|
|
70
|
-
|
|
71
|
-
### Node.js API
|
|
72
|
-
|
|
73
|
-
The package exposes one public operation:
|
|
74
|
-
|
|
75
|
-
```ts
|
|
76
|
-
interface MigrateOptions {
|
|
77
|
-
cssFile: string;
|
|
78
|
-
cwd?: string;
|
|
79
|
-
write?: boolean;
|
|
80
|
-
tailwindCss?: string;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
interface MigrationReport {
|
|
84
|
-
changedFiles: string[];
|
|
85
|
-
diff: string;
|
|
86
|
-
convertedRules: number;
|
|
87
|
-
retainedRules: number;
|
|
88
|
-
warnings: MigrationWarning[];
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function migrate(options: MigrateOptions): Promise<MigrationReport>;
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
Parser ASTs, edit planners, and Tailwind mapping internals are not public API.
|
|
95
|
-
|
|
96
|
-
## Supported Projects
|
|
97
|
-
|
|
98
|
-
- Node.js 20 or newer.
|
|
99
|
-
- Tailwind v4 installed in the target project.
|
|
100
|
-
- JavaScript or TypeScript source using React JSX syntax.
|
|
101
|
-
- Plain `.css` and `.module.css` input files.
|
|
102
|
-
|
|
103
|
-
The source scanner includes `.js`, `.jsx`, `.ts`, and `.tsx` files and excludes generated/vendor directories such as `node_modules`, `.next`, `dist`, and `build`.
|
|
104
|
-
|
|
105
|
-
## Architecture
|
|
106
|
-
|
|
107
|
-
```text
|
|
108
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
109
|
-
│ npm package │
|
|
110
|
-
│ │
|
|
111
|
-
│ CLI / public migrate() │
|
|
112
|
-
│ ├─ resolve project and Tailwind v4 installation │
|
|
113
|
-
│ ├─ locate the Tailwind CSS entry │
|
|
114
|
-
│ ├─ call the NAPI-RS native core │
|
|
115
|
-
│ ├─ validate candidates with Tailwind compile() │
|
|
116
|
-
│ └─ print or apply the migration report │
|
|
117
|
-
└──────────────────────────────┬──────────────────────────────────┘
|
|
118
|
-
│ NAPI
|
|
119
|
-
┌──────────────────────────────▼──────────────────────────────────┐
|
|
120
|
-
│ Rust native core │
|
|
121
|
-
│ │
|
|
122
|
-
│ Oxc CSS parser │
|
|
123
|
-
│ └─ rules, selectors, declarations, and byte spans │
|
|
124
|
-
│ │
|
|
125
|
-
│ Oxc JS/TS parser + semantic analysis │
|
|
126
|
-
│ └─ imports, bindings, JSX attributes, and component graph │
|
|
127
|
-
│ │
|
|
128
|
-
│ Migration planner │
|
|
129
|
-
│ ├─ selector-to-element matching │
|
|
130
|
-
│ ├─ CSS-to-utility mapping │
|
|
131
|
-
│ ├─ conflict diagnostics │
|
|
132
|
-
│ └─ non-overlapping span edits │
|
|
133
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Why NAPI-RS
|
|
137
|
-
|
|
138
|
-
The product requires both a CLI and a Node.js API. NAPI-RS lets both surfaces reuse the Rust parser and migration core while preserving normal npm installation and `npx`/`pnpx` execution.
|
|
139
|
-
|
|
140
|
-
The npm package's JavaScript layer remains responsible for loading the target project's Tailwind package because Tailwind configuration and compilation are native Node.js concerns.
|
|
141
|
-
|
|
142
|
-
### Why Oxc
|
|
143
|
-
|
|
144
|
-
Both CSS and JSX/TSX analysis require byte spans for minimal source edits. Oxc provides span-bearing ASTs and semantic information needed to distinguish bindings with the same name and trace supported component paths.
|
|
145
|
-
|
|
146
|
-
`ast-grep` is not included in the first release because Oxc already supplies the required parsing, binding, and span data. Using both would duplicate parsing without removing the need for custom semantic analysis.
|
|
147
|
-
|
|
148
|
-
## Migration Pipeline
|
|
149
|
-
|
|
150
|
-
### 1. Resolve the Project
|
|
151
|
-
|
|
152
|
-
The CLI resolves the project from `cwd`, verifies Node.js and Tailwind requirements, and normalizes the selected CSS path.
|
|
153
|
-
|
|
154
|
-
A parse or configuration error is fatal before any file is written.
|
|
155
|
-
|
|
156
|
-
### 2. Resolve the Tailwind Entry
|
|
157
|
-
|
|
158
|
-
The JavaScript layer searches for CSS entries that load Tailwind, for example:
|
|
159
|
-
|
|
160
|
-
```css
|
|
161
|
-
@import "tailwindcss";
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
If exactly one entry is found, it is selected automatically. If multiple entries are found, migration stops and requires `--tailwind-css`.
|
|
165
|
-
|
|
166
|
-
The project-installed Tailwind v4 compiler is used. The tool does not bundle a separate Tailwind version and does not call `__unstable__loadDesignSystem`.
|
|
167
|
-
|
|
168
|
-
### 3. Parse the Selected CSS
|
|
169
|
-
|
|
170
|
-
The Rust core records:
|
|
171
|
-
|
|
172
|
-
- selector AST and source span;
|
|
173
|
-
- declaration names, values, importance, and spans;
|
|
174
|
-
- containing media rules;
|
|
175
|
-
- CSS Module local classes and ids;
|
|
176
|
-
- selector lists and nested conditions.
|
|
177
|
-
|
|
178
|
-
Unsupported at-rules remain unchanged and produce warnings when they block migration.
|
|
179
|
-
|
|
180
|
-
### 4. Discover Source References
|
|
181
|
-
|
|
182
|
-
#### Global CSS
|
|
183
|
-
|
|
184
|
-
Static `className` and `id` values are matched across project source files.
|
|
185
|
-
|
|
186
|
-
#### CSS Modules
|
|
187
|
-
|
|
188
|
-
The scanner follows imports of the selected file:
|
|
189
|
-
|
|
190
|
-
```tsx
|
|
191
|
-
import styles from './Button.module.css';
|
|
192
|
-
|
|
193
|
-
export function Button() {
|
|
194
|
-
return <button className={styles.button}>Save</button>;
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
Bindings are resolved semantically, so another `styles` variable or another module with the same local class name does not match.
|
|
199
|
-
|
|
200
|
-
### 5. Match Selectors to Elements
|
|
201
|
-
|
|
202
|
-
#### Global selectors
|
|
203
|
-
|
|
204
|
-
A selector may migrate when:
|
|
205
|
-
|
|
206
|
-
1. Tailwind can express its condition as a built-in or arbitrary variant; and
|
|
207
|
-
2. the target JSX nodes can be found statically.
|
|
208
|
-
|
|
209
|
-
For example:
|
|
210
|
-
|
|
211
|
-
```css
|
|
212
|
-
.parent > .child:hover {
|
|
213
|
-
color: red;
|
|
214
|
-
}
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
may add an equivalent variant utility to statically resolved `.child` elements.
|
|
218
|
-
|
|
219
|
-
Global rules remain in the source CSS after migration. The report marks them as manual cleanup candidates.
|
|
220
|
-
|
|
221
|
-
#### CSS Module selectors
|
|
222
|
-
|
|
223
|
-
CSS Module local names are hashed during the project build. A literal arbitrary variant such as `[.parent_&]` would not match the generated parent class.
|
|
224
|
-
|
|
225
|
-
The migration therefore converts a complex CSS Module selector only when its relationship can be proven statically. Supported component traversal is intentionally conservative:
|
|
226
|
-
|
|
227
|
-
- project-local function components;
|
|
228
|
-
- a single statically analyzable JSX return;
|
|
229
|
-
- direct JSX ancestry;
|
|
230
|
-
- direct `children` or prop forwarding.
|
|
231
|
-
|
|
232
|
-
The following are not inferred:
|
|
233
|
-
|
|
234
|
-
- conditional render branches;
|
|
235
|
-
- portals;
|
|
236
|
-
- higher-order components;
|
|
237
|
-
- dynamic component variables;
|
|
238
|
-
- arbitrary prop transformations;
|
|
239
|
-
- runtime-generated element trees.
|
|
240
|
-
|
|
241
|
-
No generated marker classes are introduced. Unproven selectors remain in CSS and produce a warning.
|
|
242
|
-
|
|
243
|
-
### 6. Read Supported `className` Forms
|
|
244
|
-
|
|
245
|
-
The first release supports:
|
|
246
|
-
|
|
247
|
-
```tsx
|
|
248
|
-
<div className="card featured" />
|
|
249
|
-
<div className={styles.card} />
|
|
250
|
-
<div className={`${styles.card} featured`} />
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
A template literal is supported only when every substitution is a statically resolved CSS Module member or string.
|
|
254
|
-
|
|
255
|
-
The following forms are skipped:
|
|
256
|
-
|
|
257
|
-
```tsx
|
|
258
|
-
<div className={active ? 'active' : 'inactive'} />
|
|
259
|
-
<div className={clsx(styles.card, active && styles.active)} />
|
|
260
|
-
<div className={cn(styles.card, props.className)} />
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
A skipped use prevents automatic CSS Module export deletion when that export may still be needed.
|
|
264
|
-
|
|
265
|
-
### 7. Convert Declarations
|
|
266
|
-
|
|
267
|
-
Conversion follows this order:
|
|
268
|
-
|
|
269
|
-
1. Normalize a supported declaration group to its final effective longhands.
|
|
270
|
-
2. Find an idiomatic Tailwind utility whose theme value exactly matches.
|
|
271
|
-
3. Validate the candidate with the project's Tailwind compiler.
|
|
272
|
-
4. If no exact idiomatic utility exists, generate an arbitrary value or property.
|
|
273
|
-
5. Validate the fallback candidate.
|
|
274
|
-
6. Retain the declaration and warn if neither candidate compiles safely.
|
|
275
|
-
|
|
276
|
-
Examples:
|
|
277
|
-
|
|
278
|
-
```css
|
|
279
|
-
padding: 1rem;
|
|
280
|
-
/* Exact project token → p-4 */
|
|
281
|
-
|
|
282
|
-
padding: 13px;
|
|
283
|
-
/* No exact token → p-[13px] */
|
|
284
|
-
|
|
285
|
-
scrollbar-color: red blue;
|
|
286
|
-
/* No idiomatic mapper → [scrollbar-color:red_blue] */
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
No nearest-token approximation is allowed because it would change rendered values.
|
|
290
|
-
|
|
291
|
-
### Shorthand and Longhand Overlap
|
|
292
|
-
|
|
293
|
-
CSS declaration order can carry semantics that Tailwind class string order cannot preserve:
|
|
294
|
-
|
|
295
|
-
```css
|
|
296
|
-
.card {
|
|
297
|
-
margin: 1rem;
|
|
298
|
-
margin-left: 2rem;
|
|
299
|
-
}
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
For explicitly supported mapper families, the core computes the final longhand values before selecting utilities. If an overlap cannot be normalized safely, the affected rule remains in CSS and produces a warning.
|
|
303
|
-
|
|
304
|
-
### Existing Tailwind Conflicts
|
|
305
|
-
|
|
306
|
-
If an element already has a Tailwind utility affecting the same property, the new candidate is appended and a warning is emitted:
|
|
307
|
-
|
|
308
|
-
```tsx
|
|
309
|
-
// Before
|
|
310
|
-
<div className="p-2" />
|
|
311
|
-
|
|
312
|
-
// Proposed migration
|
|
313
|
-
<div className="p-2 p-4" />
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
The tool does not assume that `p-4` wins because class attribute order does not determine Tailwind stylesheet order.
|
|
317
|
-
|
|
318
|
-
Per the selected migration policy, a fully converted CSS Module rule may still be deleted after this warning. This is an explicitly accepted risk: the resulting style may differ when existing and generated utilities conflict.
|
|
319
|
-
|
|
320
|
-
Exact duplicate candidates are not appended, keeping migration idempotent.
|
|
321
|
-
|
|
322
|
-
### Breakpoints and At-Rules
|
|
323
|
-
|
|
324
|
-
A media query migrates when its minimum-width condition exactly matches a Tailwind theme breakpoint:
|
|
325
|
-
|
|
326
|
-
```css
|
|
327
|
-
@media (min-width: 768px) {
|
|
328
|
-
.card {
|
|
329
|
-
padding: 2rem;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
may become `md:p-8` when `md` is exactly `768px` in the target theme. A bounded range such as `@media (min-width: 48rem) and (max-width: 63.999rem)` becomes `md:max-lg:p-8` when the bounds match the `md` and `lg` breakpoints. Queries with additional or unmatched conditions remain unchanged.
|
|
335
|
-
|
|
336
|
-
Supported `@media`, `@supports`, `@container`, and `@starting-style` blocks are traversed recursively, including conditions nested inside style rules. Exact built-ins become stacked variants such as `motion-reduce:starting:@md:grid`; compound media and named or ranged container queries use equivalent Tailwind arbitrary at-rule variants. If a condition or nested statement cannot be represented safely, its outer block and related CSS Module classes remain unchanged.
|
|
337
|
-
|
|
338
|
-
Local CSS Module `@keyframes` referenced by a single static `animation` or `animation-name` are renamed deterministically and moved to the Tailwind entry before the module is removed. Global definition at-rules such as `@font-face`, `@page`, `@property`, `@counter-style`, and `@view-transition` are also moved before removal. Relative URLs move only when the CSS Module and Tailwind entry share a directory; absolute and data URLs remain stable. Multiple animations, ambiguous names, path-changing relative URLs, dynamic values, and structural at-rules remain unchanged.
|
|
339
|
-
|
|
340
|
-
## CSS Rule Cleanup
|
|
341
|
-
|
|
342
|
-
### Global CSS
|
|
343
|
-
|
|
344
|
-
Global rules are never removed automatically. Their classes and ids may be used by runtime HTML, CMS content, unscanned files, or external code.
|
|
345
|
-
|
|
346
|
-
### CSS Modules
|
|
347
|
-
|
|
348
|
-
A CSS Module rule and its JSX module reference may be removed only when:
|
|
349
|
-
|
|
350
|
-
1. every declaration in the rule was converted and validated;
|
|
351
|
-
2. every known use of the export is a supported `className` use;
|
|
352
|
-
3. no alias or non-`className` JavaScript use exists;
|
|
353
|
-
4. the export is not referenced by `composes`;
|
|
354
|
-
5. every required selector relationship was proven.
|
|
355
|
-
|
|
356
|
-
Unsafe examples include:
|
|
357
|
-
|
|
358
|
-
```ts
|
|
359
|
-
container.querySelector(`.${styles.card}`);
|
|
360
|
-
const cardClass = styles.card;
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
```css
|
|
364
|
-
.featured {
|
|
365
|
-
composes: card;
|
|
366
|
-
}
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
When an export is removed, its JSX member reference is removed as well. An import is deleted only when no imported CSS Module members remain in use. A CSS Module file is deleted after every rule and movable at-rule dependency has been migrated; otherwise the file and required imports remain.
|
|
370
|
-
|
|
371
|
-
For selector lists, the entire rule is removed only when every selector is safely migrated. The first release does not split partially migrated declaration blocks.
|
|
372
|
-
|
|
373
|
-
## Source Editing
|
|
374
|
-
|
|
375
|
-
Parsing and printing an entire AST can change unrelated quotes, whitespace, semicolons, or line wrapping. The migration instead uses AST spans to produce non-overlapping text edits.
|
|
376
|
-
|
|
377
|
-
Examples of localized edits include:
|
|
378
|
-
|
|
379
|
-
- append utilities inside an existing string literal;
|
|
380
|
-
- replace a CSS Module member with a static utility string;
|
|
381
|
-
- add a new `className` attribute;
|
|
382
|
-
- remove an unused CSS Module import;
|
|
383
|
-
- remove a fully migrated CSS rule.
|
|
384
|
-
|
|
385
|
-
Before writing:
|
|
386
|
-
|
|
387
|
-
1. all edits are calculated in memory;
|
|
388
|
-
2. overlapping edits are rejected;
|
|
389
|
-
3. edited CSS and source files are reparsed with Oxc;
|
|
390
|
-
4. every generated Tailwind candidate is compiled;
|
|
391
|
-
5. files are written through temporary files and renamed.
|
|
392
|
-
|
|
393
|
-
No write occurs when preflight validation fails.
|
|
394
|
-
|
|
395
|
-
## Diagnostics
|
|
396
|
-
|
|
397
|
-
Warnings are non-fatal and include a stable reason code, file, span, and message. Initial warning categories include:
|
|
398
|
-
|
|
399
|
-
- dynamic `className`;
|
|
400
|
-
- unresolved selector target;
|
|
401
|
-
- unproven CSS Module relationship;
|
|
402
|
-
- unsupported at-rule;
|
|
403
|
-
- unnormalizable shorthand/longhand overlap;
|
|
404
|
-
- existing Tailwind property conflict;
|
|
405
|
-
- non-`className` CSS Module reference;
|
|
406
|
-
- Tailwind candidate compilation failure;
|
|
407
|
-
- retained global rule.
|
|
408
|
-
|
|
409
|
-
Fatal errors include invalid input CSS, invalid source produced by an edit, missing Tailwind v4, ambiguous Tailwind entry without an override, and filesystem write failures.
|
|
410
|
-
|
|
411
|
-
## Packaging and Distribution
|
|
412
|
-
|
|
413
|
-
The main npm package contains the CLI and JavaScript orchestration layer. NAPI-RS publishes platform-specific optional dependencies containing the native addon.
|
|
414
|
-
|
|
415
|
-
Initial targets:
|
|
416
|
-
|
|
417
|
-
| Platform | Architectures |
|
|
418
|
-
| --- | --- |
|
|
419
|
-
| macOS | arm64, x64 |
|
|
420
|
-
| Linux glibc | arm64, x64 |
|
|
421
|
-
| Windows | x64 |
|
|
422
|
-
|
|
423
|
-
Linux musl and Windows arm64 are follow-ups rather than first-release requirements.
|
|
424
|
-
|
|
425
|
-
The current `oxc-css-parser` dependency requires Rust 1.95, which becomes the initial Rust toolchain floor unless the dependency changes before implementation.
|
|
426
|
-
|
|
427
|
-
## Testing Strategy
|
|
428
|
-
|
|
429
|
-
The migration is tested before application builds. Browser and full Next.js builds are not release requirements for the first version.
|
|
430
|
-
|
|
431
|
-
Each fixture asserts:
|
|
432
|
-
|
|
433
|
-
1. the exact preview diff;
|
|
434
|
-
2. the exact warning report;
|
|
435
|
-
3. successful Oxc reparsing of every edited file;
|
|
436
|
-
4. successful Tailwind v4 compilation of every generated candidate;
|
|
437
|
-
5. a zero-diff second migration run.
|
|
438
|
-
|
|
439
|
-
Fixture groups include:
|
|
440
|
-
|
|
441
|
-
- global CSS classes and ids;
|
|
442
|
-
- CSS Modules and import aliasing;
|
|
443
|
-
- JSX and TSX syntax;
|
|
444
|
-
- static string, member, and template-literal class names;
|
|
445
|
-
- pseudo-classes and arbitrary variants;
|
|
446
|
-
- direct JSX and supported component paths;
|
|
447
|
-
- exact theme tokens and arbitrary fallbacks;
|
|
448
|
-
- exact breakpoints;
|
|
449
|
-
- shorthand/longhand normalization;
|
|
450
|
-
- existing Tailwind conflicts;
|
|
451
|
-
- `composes` and non-`className` references;
|
|
452
|
-
- unsupported selectors and at-rules;
|
|
453
|
-
- malformed CSS and source files.
|
|
454
|
-
|
|
455
|
-
Fixtures use multiple representative React and Next.js directory layouts without requiring those applications to build.
|
|
456
|
-
|
|
457
|
-
## Implementation Phases
|
|
458
|
-
|
|
459
|
-
### Phase 1: Package and Parser Foundation
|
|
460
|
-
|
|
461
|
-
- Set up the npm package, NAPI-RS crate, CLI entry, and Node 20 engine requirement.
|
|
462
|
-
- Parse CSS and JSX/TSX with Oxc.
|
|
463
|
-
- Produce a read-only report for selector and reference discovery.
|
|
464
|
-
|
|
465
|
-
### Phase 2: Tailwind Bridge and Declaration Mapping
|
|
466
|
-
|
|
467
|
-
- Resolve a project's Tailwind v4 package and CSS entry.
|
|
468
|
-
- Implement stable `compile()` validation.
|
|
469
|
-
- Add exact-token mapping and arbitrary fallback.
|
|
470
|
-
- Add effective-longhand normalization for the initial mapper families.
|
|
471
|
-
|
|
472
|
-
### Phase 3: Selector and Component Analysis
|
|
473
|
-
|
|
474
|
-
- Match global selectors to static elements.
|
|
475
|
-
- Resolve CSS Module imports and exports.
|
|
476
|
-
- Add conservative direct component traversal.
|
|
477
|
-
- Generate variants for validated selector conditions and exact breakpoints.
|
|
478
|
-
|
|
479
|
-
### Phase 4: Editing and Cleanup
|
|
480
|
-
|
|
481
|
-
- Generate span-based JSX and CSS edits.
|
|
482
|
-
- Remove eligible CSS Module rules, references, and imports.
|
|
483
|
-
- Add preview diff and `--write` preflight.
|
|
484
|
-
|
|
485
|
-
### Phase 5: Distribution
|
|
486
|
-
|
|
487
|
-
- Build and publish native artifacts for the initial platform matrix.
|
|
488
|
-
- Run the full fixture suite for each native target.
|
|
489
|
-
- Validate npm, npx, and pnpx installation paths.
|
|
490
|
-
|
|
491
|
-
## Success Criteria
|
|
492
|
-
|
|
493
|
-
1. `tw-migrate <file>` produces a deterministic preview without modifying files.
|
|
494
|
-
2. `--write` applies only a fully validated edit plan.
|
|
495
|
-
3. Supported static global and CSS Module references are found without name collisions.
|
|
496
|
-
4. Every generated utility compiles with the target project's Tailwind v4 installation.
|
|
497
|
-
5. Exact theme values are never replaced by approximate tokens.
|
|
498
|
-
6. Unrelated source formatting remains byte-for-byte unchanged.
|
|
499
|
-
7. A second run produces no diff.
|
|
500
|
-
8. Unsupported or ambiguous behavior is retained and reported rather than silently guessed.
|
|
501
|
-
9. The Node API and CLI produce the same migration report.
|
|
502
|
-
10. The npm package installs and runs on the initial platform matrix.
|
|
503
|
-
|
|
504
|
-
## Accepted Trade-offs
|
|
505
|
-
|
|
506
|
-
1. Global CSS remains after utilities are added, so global migrations require manual cleanup.
|
|
507
|
-
2. Dynamic `className` patterns reduce conversion coverage but avoid runtime guesses.
|
|
508
|
-
3. CSS Module complex selectors may remain when static component analysis cannot prove the DOM relationship.
|
|
509
|
-
4. Existing utility conflicts are appended with warnings, and converted CSS Module rules may still be deleted; this can change styling.
|
|
510
|
-
5. Arbitrary properties provide broad correctness before every declaration family has an idiomatic mapper.
|
|
511
|
-
6. Single-file migration avoids batch transaction and failure-isolation complexity in the first release.
|
|
512
|
-
|
|
513
|
-
## Follow-ups
|
|
514
|
-
|
|
515
|
-
- Tailwind v3 support.
|
|
516
|
-
- Directory and glob migration.
|
|
517
|
-
- `clsx`, `classnames`, and known `cn` helper analysis.
|
|
518
|
-
- Additional media, supports, and container-query variants.
|
|
519
|
-
- Broader React component-flow analysis.
|
|
520
|
-
- Linux musl and additional Windows targets.
|
|
521
|
-
- Optional visual regression fixtures if structural validation proves insufficient.
|