tw-migrate 0.4.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 +58 -48
- package/dist/bin.js +7 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1513 -505
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
Migrate static React/Next.js, Vue 3 SFC, and HTML stylesheet references to Tailwind v4 utilities.
|
|
4
4
|
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
Install and configure Tailwind CSS v4 in the package you want to migrate by following the [official installation guide](https://tailwindcss.com/docs/installation/). The package must have a CSS entry that loads Tailwind CSS, usually with `@import "tailwindcss";`, so `tw-migrate` can validate generated utilities against the project's Tailwind configuration.
|
|
8
|
+
|
|
5
9
|
## Usage
|
|
6
10
|
|
|
7
|
-
Run it in the package you want to migrate — no install step:
|
|
11
|
+
Run it in the package you want to migrate — no separate `tw-migrate` install step:
|
|
8
12
|
|
|
9
13
|
```bash
|
|
10
14
|
npx tw-migrate # Migrate the current package
|
|
@@ -18,6 +22,8 @@ npx tw-migrate --workspaces # Migrate every package
|
|
|
18
22
|
|
|
19
23
|
The CLI applies changes by default; `--dry-run` prints the diff without touching files. Pass `--tailwind-css path/to/globals.css` when the current package has multiple Tailwind entries. `--force` skips package groups that fail discovery or input parsing; plan-integrity and write failures always stop the run.
|
|
20
24
|
|
|
25
|
+
Unmatched media conditions are extracted into named `@custom-variant` definitions in the Tailwind entry and consumers receive stacked variants such as `screen:width-lte-700px:m-0`; verified built-ins and existing breakpoints are reused instead of redefined, and a second run recognizes the first run's definitions. Pass `--no-extract-media-queries` to keep the arbitrary-variant form instead.
|
|
26
|
+
|
|
21
27
|
## Run from a clone
|
|
22
28
|
|
|
23
29
|
The migration planner is a native addon, so a clone needs one build before the CLI runs:
|
|
@@ -50,6 +56,8 @@ node /path/to/tw-migrate/src/bin.ts
|
|
|
50
56
|
- React/Next.js `className` logical (`&&`, `||`, `??`) and conditional (`?:`) expressions: string and direct CSS Module member result leaves migrate without evaluating their conditions, `null`/`undefined`/`false`/empty-string results are warning-free, and unsupported result leaves retain with a warning while supported siblings still migrate
|
|
51
57
|
- multi-compound CSS Module selectors whose element relationships are proven from the JSX graph
|
|
52
58
|
- common state pseudo-classes, global arbitrary descendant variants, and conditional at-rules (`@media`, `@supports`, `@container`, `@starting-style`)
|
|
59
|
+
- `and`-joined media conditions decompose into stacked named variants with definitions extracted into the Tailwind entry; ordering-sensitive conflicts between overlapping conditions are retained unless the original winner is provable
|
|
60
|
+
- in `--workspaces` mode a package without its own Tailwind entry may reuse a proven ancestor entry, packages sharing one entry plan as a single group with one composed entry edit, and cross-package consumers must provably load the shared entry
|
|
53
61
|
- the tier-1 property mapping families with shorthand/longhand normalization
|
|
54
62
|
- exact Tailwind theme tokens and breakpoints with arbitrary-value fallback
|
|
55
63
|
- generated candidates are compiled against the project's Tailwind entry; failures retain the source rule
|
|
@@ -60,53 +68,55 @@ Everything outside this subset is retained and reported with one of the warning
|
|
|
60
68
|
|
|
61
69
|
## Warning codes
|
|
62
70
|
|
|
63
|
-
| Code | Meaning
|
|
64
|
-
| ------------------------------------ |
|
|
65
|
-
| `aliased-css-module-reference` | A CSS Module class is aliased to a local binding, so the module is retained.
|
|
66
|
-
| `batch-stylesheet-conflict` | Utilities generated from different stylesheets conflict on the same
|
|
67
|
-
| `candidate-compilation-failure` | A generated candidate did not compile under the project's Tailwind entry, so its rule is retained.
|
|
68
|
-
| `component-class-target` | A child component's root element can carry classes a Vue scoped rule matches, so the rule is retained.
|
|
69
|
-
| `computed-css-module-reference` | A computed CSS Module access cannot be verified, so the module is retained.
|
|
70
|
-
| `cross-package-stylesheet-link` | A linked stylesheet is owned by another package, so it is not analyzed outside workspace mode.
|
|
71
|
-
| `css-module-composes` | The rule uses or is targeted by `composes`, so it is retained.
|
|
72
|
-
| `dynamic-class-name` | A `className` value or expression result is dynamic, so that class site cannot be migrated.
|
|
73
|
-
| `dynamic-html-attribute` | An HTML attribute is not a safely writable quoted literal, so the element cannot be migrated.
|
|
74
|
-
| `dynamic-template-class` | A dynamic class binding makes a Vue template's class set unprovable, so its scoped rules are retained.
|
|
75
|
-
| `existing-tailwind-conflict` | A generated utility may conflict with a Tailwind class already on the element.
|
|
76
|
-
| `inferred-preprocessor-source` | A linked CSS file was matched to a uniquely named preprocessor source file.
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `unproven-
|
|
88
|
-
| `unproven-
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `unsupported-
|
|
94
|
-
| `unsupported-
|
|
95
|
-
| `unsupported-
|
|
96
|
-
| `unsupported-
|
|
97
|
-
| `unsupported-
|
|
98
|
-
| `unsupported-
|
|
99
|
-
| `unsupported-link
|
|
100
|
-
| `unsupported-
|
|
101
|
-
| `unsupported-
|
|
102
|
-
| `unsupported-
|
|
103
|
-
| `unsupported-rule
|
|
104
|
-
| `unsupported-
|
|
105
|
-
| `unsupported-
|
|
106
|
-
| `unsupported-
|
|
107
|
-
| `unsupported-
|
|
108
|
-
| `unsupported-
|
|
109
|
-
| `unsupported-
|
|
71
|
+
| Code | Meaning |
|
|
72
|
+
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
73
|
+
| `aliased-css-module-reference` | A CSS Module class is aliased to a local binding, so the module is retained. |
|
|
74
|
+
| `batch-stylesheet-conflict` | Utilities generated from different stylesheets, or under overlapping media conditions with an unprovable order, conflict on the same element, so the contributing rule is retained. |
|
|
75
|
+
| `candidate-compilation-failure` | A generated candidate did not compile under the project's Tailwind entry, so its rule is retained. |
|
|
76
|
+
| `component-class-target` | A child component's root element can carry classes a Vue scoped rule matches, so the rule is retained. |
|
|
77
|
+
| `computed-css-module-reference` | A computed CSS Module access cannot be verified, so the module is retained. |
|
|
78
|
+
| `cross-package-stylesheet-link` | A linked stylesheet is owned by another package, so it is not analyzed outside workspace mode. |
|
|
79
|
+
| `css-module-composes` | The rule uses or is targeted by `composes`, so it is retained. |
|
|
80
|
+
| `dynamic-class-name` | A `className` value or expression result is dynamic, so that class site cannot be migrated. |
|
|
81
|
+
| `dynamic-html-attribute` | An HTML attribute is not a safely writable quoted literal, so the element cannot be migrated. |
|
|
82
|
+
| `dynamic-template-class` | A dynamic class binding makes a Vue template's class set unprovable, so its scoped rules are retained. |
|
|
83
|
+
| `existing-tailwind-conflict` | A generated utility may conflict with a Tailwind class already on the element. |
|
|
84
|
+
| `inferred-preprocessor-source` | A linked CSS file was matched to a uniquely named preprocessor source file. |
|
|
85
|
+
| `media-query-definition-fallback` | The Tailwind entry could not be edited safely, so media behavior was preserved with arbitrary variants. |
|
|
86
|
+
| `module-utilities-conflict` | Utilities generated from different module classes on one element overlap, so their rules are retained. |
|
|
87
|
+
| `non-classname-css-module-reference` | A CSS Module class is used outside a supported `className`, so the module is retained. |
|
|
88
|
+
| `open-root-fallthrough` | A parent component can merge classes onto a Vue SFC's single root element, so its scoped rules are retained. |
|
|
89
|
+
| `preprocessor-style-block` | A Vue `<style>` block uses a preprocessor language outside the supported SCSS, Sass, and Less set. |
|
|
90
|
+
| `rebuild-required` | A preprocessor entry was migrated; rebuild it to refresh its generated CSS. |
|
|
91
|
+
| `reference-only-css-module-consumer` | A reference-only (non-writable) source uses the CSS Module, so it is retained. |
|
|
92
|
+
| `retained-global-rule` | Global CSS is never deleted automatically. |
|
|
93
|
+
| `shadowed-scoped-rule` | Other package CSS also targets a class a Vue scoped rule matches, so the rule is retained to preserve the cascade. |
|
|
94
|
+
| `shared-preprocessor-source` | A Sass partial must be analyzed through every consuming entry, so it is retained. |
|
|
95
|
+
| `unproven-css-module-relationship` | A compound selector's element relationship could not be proven for every usage. |
|
|
96
|
+
| `unproven-script-reference` | An inline script names a CSS Module class, so the module is retained. |
|
|
97
|
+
| `unproven-shared-entry-flow` | A consumer flow could not be proven to load the shared Tailwind entry, so the stylesheet is retained. |
|
|
98
|
+
| `unproven-source-map` | A generated rule does not map uniquely to one authored source rule, so it is retained. |
|
|
99
|
+
| `unresolved-selector-target` | No exclusively supported `className` references were found for the rule. |
|
|
100
|
+
| `unscoped-style-block` | An unscoped Vue style does not have a closed private single-source usage surface, so it is retained. |
|
|
101
|
+
| `unsupported-animation` | The animation references keyframes that cannot be converted. |
|
|
102
|
+
| `unsupported-at-rule` | The rule contains or sits inside an at-rule outside the supported set. |
|
|
103
|
+
| `unsupported-container-query` | The `@container` condition has no Tailwind variant equivalent. |
|
|
104
|
+
| `unsupported-css-module-reference` | The CSS Module has an import or reference that cannot be migrated safely. |
|
|
105
|
+
| `unsupported-declaration` | A declaration is outside the supported property subset. |
|
|
106
|
+
| `unsupported-html-base` | A remote or unrepresentable base URL prevents safe stylesheet link resolution. |
|
|
107
|
+
| `unsupported-html-stylesheet-link` | Only local package stylesheet links are analyzed. |
|
|
108
|
+
| `unsupported-important` | `!important` declarations are not migrated. |
|
|
109
|
+
| `unsupported-link-media` | A stylesheet link or `@import` media condition cannot be represented safely. |
|
|
110
|
+
| `unsupported-media-query` | The `@media` condition has no Tailwind variant equivalent. |
|
|
111
|
+
| `unsupported-nested-at-rule` | A nested conditional at-rule could not be fully converted. |
|
|
112
|
+
| `unsupported-overlap` | Shorthand and longhand declarations overlap in a way that cannot be normalized. |
|
|
113
|
+
| `unsupported-rule-content` | The rule contains non-declaration content that cannot be converted. |
|
|
114
|
+
| `unsupported-selector` | The selector is outside the supported subset. |
|
|
115
|
+
| `unsupported-sfc-block` | A Vue SFC or one of its blocks cannot be analyzed safely, so it is retained. |
|
|
116
|
+
| `unsupported-starting-style` | The `@starting-style` condition could not be converted. |
|
|
117
|
+
| `unsupported-supports-query` | The `@supports` condition has no Tailwind variant equivalent. |
|
|
118
|
+
| `unsupported-value` | A declaration value cannot be represented as a Tailwind utility. |
|
|
119
|
+
| `unsupported-vue-version` | The project's Vue version is not Vue 3, so its SFCs are retained. |
|
|
110
120
|
|
|
111
121
|
See the [core RFC](./rfcs/css-to-tailwind-migration-cli.md) and [preprocessor/HTML RFC](./rfcs/preprocessor-and-html-migration.md) for the complete design and remaining scope.
|
|
112
122
|
|
package/dist/bin.js
CHANGED
|
@@ -4,7 +4,7 @@ import { styleText } from "node:util";
|
|
|
4
4
|
//#region src/bin.ts
|
|
5
5
|
const { version } = {
|
|
6
6
|
name: "tw-migrate",
|
|
7
|
-
version: "0.
|
|
7
|
+
version: "0.5.0",
|
|
8
8
|
description: "Preview and migrate React, Vue, and HTML CSS to Tailwind utilities",
|
|
9
9
|
keywords: [
|
|
10
10
|
"codemod",
|
|
@@ -88,6 +88,8 @@ Options:
|
|
|
88
88
|
--tailwind-css <entry.css> Use this Tailwind CSS entry when the current package
|
|
89
89
|
has multiple entries. Cannot be used with --workspaces.
|
|
90
90
|
--workspaces Migrate every package in the workspace.
|
|
91
|
+
--no-extract-media-queries Keep arbitrary media variants instead of extracting
|
|
92
|
+
named @custom-variant definitions into the entry.
|
|
91
93
|
--force Skip packages with recoverable discovery or input errors.
|
|
92
94
|
--dry-run Preview changes without applying them.
|
|
93
95
|
-h, --help Show this help message.
|
|
@@ -113,9 +115,11 @@ async function main() {
|
|
|
113
115
|
let write = true;
|
|
114
116
|
let force = false;
|
|
115
117
|
let workspaces = false;
|
|
118
|
+
let extractMediaQueries = true;
|
|
116
119
|
for (let index = 0; index < args.length; index += 1) {
|
|
117
120
|
const argument = args[index];
|
|
118
121
|
if (argument === "--dry-run") write = false;
|
|
122
|
+
else if (argument === "--no-extract-media-queries") extractMediaQueries = false;
|
|
119
123
|
else if (argument === "--force") force = true;
|
|
120
124
|
else if (argument === "--workspaces") workspaces = true;
|
|
121
125
|
else if (argument === "--tailwind-css") {
|
|
@@ -131,7 +135,8 @@ async function main() {
|
|
|
131
135
|
tailwindCss,
|
|
132
136
|
write,
|
|
133
137
|
force,
|
|
134
|
-
workspaces
|
|
138
|
+
workspaces,
|
|
139
|
+
extractMediaQueries
|
|
135
140
|
});
|
|
136
141
|
if (report.diff) process.stdout.write(report.diff);
|
|
137
142
|
for (const warning of report.warnings) {
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,12 @@ interface MigrateOptions {
|
|
|
6
6
|
tailwindCss?: string;
|
|
7
7
|
workspaces?: boolean;
|
|
8
8
|
force?: boolean;
|
|
9
|
+
/** Extract unmatched media conditions into named `@custom-variant`
|
|
10
|
+
* definitions in the Tailwind entry and rewrite consumers to stacked
|
|
11
|
+
* variants. Enabled by default; pass `false` (CLI:
|
|
12
|
+
* `--no-extract-media-queries`) to keep the arbitrary-variant
|
|
13
|
+
* behavior. */
|
|
14
|
+
extractMediaQueries?: boolean;
|
|
9
15
|
}
|
|
10
16
|
interface MigrationWarning {
|
|
11
17
|
code: string;
|