2024-09-18 16:45:43 +02:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
|
|
|
|
|
import { globby } from 'globby'
|
2024-10-10 09:44:04 -04:00
|
|
|
import fs from 'node:fs/promises'
|
2024-09-18 16:45:43 +02:00
|
|
|
import path from 'node:path'
|
2024-10-10 09:44:04 -04:00
|
|
|
import postcss from 'postcss'
|
|
|
|
|
import { formatNodes } from './codemods/format-nodes'
|
2024-09-18 16:45:43 +02:00
|
|
|
import { help } from './commands/help'
|
2024-10-10 09:44:04 -04:00
|
|
|
import {
|
|
|
|
|
analyze as analyzeStylesheets,
|
|
|
|
|
migrate as migrateStylesheet,
|
|
|
|
|
split as splitStylesheets,
|
|
|
|
|
} from './migrate'
|
Add simple JS config migration (#14639)
This PR implements the first version of JS config file migration to CSS.
It is based on the most simple config setups we are using in the
Tailwind UI templates Commit, Primer, Radiant, and Studio.
The example we use in the integration test is a config that looks like
this:
```js
import { type Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
module.exports = {
darkMode: 'selector',
content: ['./src/**/*.{html,js}'],
theme: {
boxShadow: {
sm: '0 2px 6px rgb(15 23 42 / 0.08)',
},
colors: {
red: {
500: '#ef4444',
},
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.5rem' }],
base: ['1rem', { lineHeight: '2rem' }],
},
extend: {
colors: {
red: {
600: '#dc2626',
},
},
fontFamily: {
sans: 'Inter, system-ui, sans-serif',
display: ['Cabinet Grotesk', ...defaultTheme.fontFamily.sans],
},
borderRadius: {
'4xl': '2rem',
},
},
},
plugins: [],
} satisfies Config
```
As you can see, this file only has a `darkMode` selector, custom
`content` globs, a `theme` (with some theme keys being overwriting the
default theme and some others extending the defaults). Note that it does
not support `plugins` and/or `presets` yet.
In the case above, we will find the CSS file containing the existing
`@tailwind` directives and are migrating it to the following:
```css
@import 'tailwindcss';
@source './**/*.{html,js}';
@variant dark (&:where(.dark, .dark *));
@theme {
--box-shadow-*: initial;
--box-shadow-sm: 0 2px 6px rgb(15 23 42 / 0.08);
--color-*: initial;
--color-red-500: #ef4444;
--font-size-*: initial;
--font-size-xs: 0.75rem;
--font-size-xs--line-height: 1rem;
--font-size-sm: 0.875rem;
--font-size-sm--line-height: 1.5rem;
--font-size-base: 1rem;
--font-size-base--line-height: 2rem;
--color-red-600: #dc2626;
--font-family-sans: Inter, system-ui, sans-serif;
--font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--border-radius-4xl: 2rem;
}
```
This replicates all features of the JS config so we can even delete the
existing JS config in this case.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-10-11 15:27:53 +02:00
|
|
|
import { migrateJsConfig } from './migrate-js-config'
|
2024-10-10 15:09:14 +02:00
|
|
|
import { migratePostCSSConfig } from './migrate-postcss'
|
2024-10-10 09:44:04 -04:00
|
|
|
import { Stylesheet } from './stylesheet'
|
Add setup for template migrations (#14502)
This PR adds the initial setup and a first codemod for the template
migrations. These are a new set of migrations that operate on files
defined in the Tailwind v3 config as part of the `content` option (so
your HTML, JavaScript, TSX files etc.).
The migration for this is integrated in the new `@tailwindcss/upgrade`
package and will require pointing the migration to an input JavaScript
config file, like this:
```
npx @tailwindcss/upgrade --config tailwind.config.js
```
The idea of template migrations is to apply breaking changes from the v3
to v4 migration within your template files.
## Migrating !important syntax
The first migration that I’m adding with this PR is to ensure we use the
v4 important syntax that has the exclamation mark at the end of the
utility.
For example, this:
```html
<div class="!flex sm:!block"></div>
```
Will now turn into:
```html
<div class="flex! sm:block!"></div>
```
## Architecture considerations
Implementation wise, we make use of Oxide to scan the content files fast
and efficiently. By relying on the same scanner als Tailwind v4, we
guarantee that all candidates that are part of the v4 output will have
gone through a migration.
Migrations itself operate on the abstract `Candidate` type, similar to
the type we use in the v4 codebase. It will parse the candidate into its
parts so they can easily be introspected/modified. Migrations are typed
as:
```ts
type TemplateMigration = (candidate: Candidate) => Candidate | null
```
`null` should be returned if the `Candidate` does not need a migration.
We currently use the v4 `parseCandidate` function to get an abstract
definition of the candidate rule that we can operate on. _This will
likely need to change in the future as we need to fork `parseCandidate`
for v3 specific syntax_.
Additionally, we're inlining a `printCandidate` function that can
stringify the abstract `Candidate` type. It is not guaranteed that this
is an identity function since some information can be lost during the
parse step. This is not a problem though, because migrations will only
run selectively and if none of the selectors trigger, the candidates are
not updated. h/t to @RobinMalfait for providing the printer.
So the overall flow of a migration looks like this:
- Scan the config file for `content` files
- Use Oxide to extract a list of candidate and their positions from
these `content` files
- Run a few migrations that operate on the `Candidate` abstract type.
- Print the updated `Candidate` back into the original `content` file.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-09-25 16:20:14 +02:00
|
|
|
import { migrate as migrateTemplate } from './template/migrate'
|
2024-10-07 18:02:28 +02:00
|
|
|
import { prepareConfig } from './template/prepare-config'
|
2024-09-18 16:45:43 +02:00
|
|
|
import { args, type Arg } from './utils/args'
|
|
|
|
|
import { isRepoDirty } from './utils/git'
|
2024-10-10 15:09:14 +02:00
|
|
|
import { pkg } from './utils/packages'
|
2024-09-18 16:45:43 +02:00
|
|
|
import { eprintln, error, header, highlight, info, success } from './utils/renderer'
|
|
|
|
|
|
|
|
|
|
const options = {
|
Add setup for template migrations (#14502)
This PR adds the initial setup and a first codemod for the template
migrations. These are a new set of migrations that operate on files
defined in the Tailwind v3 config as part of the `content` option (so
your HTML, JavaScript, TSX files etc.).
The migration for this is integrated in the new `@tailwindcss/upgrade`
package and will require pointing the migration to an input JavaScript
config file, like this:
```
npx @tailwindcss/upgrade --config tailwind.config.js
```
The idea of template migrations is to apply breaking changes from the v3
to v4 migration within your template files.
## Migrating !important syntax
The first migration that I’m adding with this PR is to ensure we use the
v4 important syntax that has the exclamation mark at the end of the
utility.
For example, this:
```html
<div class="!flex sm:!block"></div>
```
Will now turn into:
```html
<div class="flex! sm:block!"></div>
```
## Architecture considerations
Implementation wise, we make use of Oxide to scan the content files fast
and efficiently. By relying on the same scanner als Tailwind v4, we
guarantee that all candidates that are part of the v4 output will have
gone through a migration.
Migrations itself operate on the abstract `Candidate` type, similar to
the type we use in the v4 codebase. It will parse the candidate into its
parts so they can easily be introspected/modified. Migrations are typed
as:
```ts
type TemplateMigration = (candidate: Candidate) => Candidate | null
```
`null` should be returned if the `Candidate` does not need a migration.
We currently use the v4 `parseCandidate` function to get an abstract
definition of the candidate rule that we can operate on. _This will
likely need to change in the future as we need to fork `parseCandidate`
for v3 specific syntax_.
Additionally, we're inlining a `printCandidate` function that can
stringify the abstract `Candidate` type. It is not guaranteed that this
is an identity function since some information can be lost during the
parse step. This is not a problem though, because migrations will only
run selectively and if none of the selectors trigger, the candidates are
not updated. h/t to @RobinMalfait for providing the printer.
So the overall flow of a migration looks like this:
- Scan the config file for `content` files
- Use Oxide to extract a list of candidate and their positions from
these `content` files
- Run a few migrations that operate on the `Candidate` abstract type.
- Print the updated `Candidate` back into the original `content` file.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-09-25 16:20:14 +02:00
|
|
|
'--config': { type: 'string', description: 'Path to the configuration file', alias: '-c' },
|
2024-09-18 16:45:43 +02:00
|
|
|
'--help': { type: 'boolean', description: 'Display usage information', alias: '-h' },
|
|
|
|
|
'--force': { type: 'boolean', description: 'Force the migration', alias: '-f' },
|
|
|
|
|
'--version': { type: 'boolean', description: 'Display the version number', alias: '-v' },
|
|
|
|
|
} satisfies Arg
|
|
|
|
|
const flags = args(options)
|
|
|
|
|
|
|
|
|
|
if (flags['--help']) {
|
|
|
|
|
help({
|
|
|
|
|
usage: ['npx @tailwindcss/upgrade'],
|
|
|
|
|
options,
|
|
|
|
|
})
|
|
|
|
|
process.exit(0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function run() {
|
Add simple JS config migration (#14639)
This PR implements the first version of JS config file migration to CSS.
It is based on the most simple config setups we are using in the
Tailwind UI templates Commit, Primer, Radiant, and Studio.
The example we use in the integration test is a config that looks like
this:
```js
import { type Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
module.exports = {
darkMode: 'selector',
content: ['./src/**/*.{html,js}'],
theme: {
boxShadow: {
sm: '0 2px 6px rgb(15 23 42 / 0.08)',
},
colors: {
red: {
500: '#ef4444',
},
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.5rem' }],
base: ['1rem', { lineHeight: '2rem' }],
},
extend: {
colors: {
red: {
600: '#dc2626',
},
},
fontFamily: {
sans: 'Inter, system-ui, sans-serif',
display: ['Cabinet Grotesk', ...defaultTheme.fontFamily.sans],
},
borderRadius: {
'4xl': '2rem',
},
},
},
plugins: [],
} satisfies Config
```
As you can see, this file only has a `darkMode` selector, custom
`content` globs, a `theme` (with some theme keys being overwriting the
default theme and some others extending the defaults). Note that it does
not support `plugins` and/or `presets` yet.
In the case above, we will find the CSS file containing the existing
`@tailwind` directives and are migrating it to the following:
```css
@import 'tailwindcss';
@source './**/*.{html,js}';
@variant dark (&:where(.dark, .dark *));
@theme {
--box-shadow-*: initial;
--box-shadow-sm: 0 2px 6px rgb(15 23 42 / 0.08);
--color-*: initial;
--color-red-500: #ef4444;
--font-size-*: initial;
--font-size-xs: 0.75rem;
--font-size-xs--line-height: 1rem;
--font-size-sm: 0.875rem;
--font-size-sm--line-height: 1.5rem;
--font-size-base: 1rem;
--font-size-base--line-height: 2rem;
--color-red-600: #dc2626;
--font-family-sans: Inter, system-ui, sans-serif;
--font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--border-radius-4xl: 2rem;
}
```
This replicates all features of the JS config so we can even delete the
existing JS config in this case.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-10-11 15:27:53 +02:00
|
|
|
let base = process.cwd()
|
|
|
|
|
|
2024-09-18 16:45:43 +02:00
|
|
|
eprintln(header())
|
|
|
|
|
eprintln()
|
|
|
|
|
|
|
|
|
|
if (!flags['--force']) {
|
|
|
|
|
if (isRepoDirty()) {
|
|
|
|
|
error('Git directory is not clean. Please stash or commit your changes before migrating.')
|
|
|
|
|
info(
|
|
|
|
|
`You may use the ${highlight('--force')} flag to silence this warning and perform the migration.`,
|
|
|
|
|
)
|
|
|
|
|
process.exit(1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Add simple JS config migration (#14639)
This PR implements the first version of JS config file migration to CSS.
It is based on the most simple config setups we are using in the
Tailwind UI templates Commit, Primer, Radiant, and Studio.
The example we use in the integration test is a config that looks like
this:
```js
import { type Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
module.exports = {
darkMode: 'selector',
content: ['./src/**/*.{html,js}'],
theme: {
boxShadow: {
sm: '0 2px 6px rgb(15 23 42 / 0.08)',
},
colors: {
red: {
500: '#ef4444',
},
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.5rem' }],
base: ['1rem', { lineHeight: '2rem' }],
},
extend: {
colors: {
red: {
600: '#dc2626',
},
},
fontFamily: {
sans: 'Inter, system-ui, sans-serif',
display: ['Cabinet Grotesk', ...defaultTheme.fontFamily.sans],
},
borderRadius: {
'4xl': '2rem',
},
},
},
plugins: [],
} satisfies Config
```
As you can see, this file only has a `darkMode` selector, custom
`content` globs, a `theme` (with some theme keys being overwriting the
default theme and some others extending the defaults). Note that it does
not support `plugins` and/or `presets` yet.
In the case above, we will find the CSS file containing the existing
`@tailwind` directives and are migrating it to the following:
```css
@import 'tailwindcss';
@source './**/*.{html,js}';
@variant dark (&:where(.dark, .dark *));
@theme {
--box-shadow-*: initial;
--box-shadow-sm: 0 2px 6px rgb(15 23 42 / 0.08);
--color-*: initial;
--color-red-500: #ef4444;
--font-size-*: initial;
--font-size-xs: 0.75rem;
--font-size-xs--line-height: 1rem;
--font-size-sm: 0.875rem;
--font-size-sm--line-height: 1.5rem;
--font-size-base: 1rem;
--font-size-base--line-height: 2rem;
--color-red-600: #dc2626;
--font-family-sans: Inter, system-ui, sans-serif;
--font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--border-radius-4xl: 2rem;
}
```
This replicates all features of the JS config so we can even delete the
existing JS config in this case.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-10-11 15:27:53 +02:00
|
|
|
let config = await prepareConfig(flags['--config'], { base })
|
Add setup for template migrations (#14502)
This PR adds the initial setup and a first codemod for the template
migrations. These are a new set of migrations that operate on files
defined in the Tailwind v3 config as part of the `content` option (so
your HTML, JavaScript, TSX files etc.).
The migration for this is integrated in the new `@tailwindcss/upgrade`
package and will require pointing the migration to an input JavaScript
config file, like this:
```
npx @tailwindcss/upgrade --config tailwind.config.js
```
The idea of template migrations is to apply breaking changes from the v3
to v4 migration within your template files.
## Migrating !important syntax
The first migration that I’m adding with this PR is to ensure we use the
v4 important syntax that has the exclamation mark at the end of the
utility.
For example, this:
```html
<div class="!flex sm:!block"></div>
```
Will now turn into:
```html
<div class="flex! sm:block!"></div>
```
## Architecture considerations
Implementation wise, we make use of Oxide to scan the content files fast
and efficiently. By relying on the same scanner als Tailwind v4, we
guarantee that all candidates that are part of the v4 output will have
gone through a migration.
Migrations itself operate on the abstract `Candidate` type, similar to
the type we use in the v4 codebase. It will parse the candidate into its
parts so they can easily be introspected/modified. Migrations are typed
as:
```ts
type TemplateMigration = (candidate: Candidate) => Candidate | null
```
`null` should be returned if the `Candidate` does not need a migration.
We currently use the v4 `parseCandidate` function to get an abstract
definition of the candidate rule that we can operate on. _This will
likely need to change in the future as we need to fork `parseCandidate`
for v3 specific syntax_.
Additionally, we're inlining a `printCandidate` function that can
stringify the abstract `Candidate` type. It is not guaranteed that this
is an identity function since some information can be lost during the
parse step. This is not a problem though, because migrations will only
run selectively and if none of the selectors trigger, the candidates are
not updated. h/t to @RobinMalfait for providing the printer.
So the overall flow of a migration looks like this:
- Scan the config file for `content` files
- Use Oxide to extract a list of candidate and their positions from
these `content` files
- Run a few migrations that operate on the `Candidate` abstract type.
- Print the updated `Candidate` back into the original `content` file.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-09-25 16:20:14 +02:00
|
|
|
|
2024-10-07 18:02:28 +02:00
|
|
|
{
|
Add setup for template migrations (#14502)
This PR adds the initial setup and a first codemod for the template
migrations. These are a new set of migrations that operate on files
defined in the Tailwind v3 config as part of the `content` option (so
your HTML, JavaScript, TSX files etc.).
The migration for this is integrated in the new `@tailwindcss/upgrade`
package and will require pointing the migration to an input JavaScript
config file, like this:
```
npx @tailwindcss/upgrade --config tailwind.config.js
```
The idea of template migrations is to apply breaking changes from the v3
to v4 migration within your template files.
## Migrating !important syntax
The first migration that I’m adding with this PR is to ensure we use the
v4 important syntax that has the exclamation mark at the end of the
utility.
For example, this:
```html
<div class="!flex sm:!block"></div>
```
Will now turn into:
```html
<div class="flex! sm:block!"></div>
```
## Architecture considerations
Implementation wise, we make use of Oxide to scan the content files fast
and efficiently. By relying on the same scanner als Tailwind v4, we
guarantee that all candidates that are part of the v4 output will have
gone through a migration.
Migrations itself operate on the abstract `Candidate` type, similar to
the type we use in the v4 codebase. It will parse the candidate into its
parts so they can easily be introspected/modified. Migrations are typed
as:
```ts
type TemplateMigration = (candidate: Candidate) => Candidate | null
```
`null` should be returned if the `Candidate` does not need a migration.
We currently use the v4 `parseCandidate` function to get an abstract
definition of the candidate rule that we can operate on. _This will
likely need to change in the future as we need to fork `parseCandidate`
for v3 specific syntax_.
Additionally, we're inlining a `printCandidate` function that can
stringify the abstract `Candidate` type. It is not guaranteed that this
is an identity function since some information can be lost during the
parse step. This is not a problem though, because migrations will only
run selectively and if none of the selectors trigger, the candidates are
not updated. h/t to @RobinMalfait for providing the printer.
So the overall flow of a migration looks like this:
- Scan the config file for `content` files
- Use Oxide to extract a list of candidate and their positions from
these `content` files
- Run a few migrations that operate on the `Candidate` abstract type.
- Print the updated `Candidate` back into the original `content` file.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-09-25 16:20:14 +02:00
|
|
|
// Template migrations
|
|
|
|
|
|
|
|
|
|
info('Migrating templates using the provided configuration file.')
|
|
|
|
|
|
|
|
|
|
let set = new Set<string>()
|
2024-10-07 18:02:28 +02:00
|
|
|
for (let { pattern, base } of config.globs) {
|
Add setup for template migrations (#14502)
This PR adds the initial setup and a first codemod for the template
migrations. These are a new set of migrations that operate on files
defined in the Tailwind v3 config as part of the `content` option (so
your HTML, JavaScript, TSX files etc.).
The migration for this is integrated in the new `@tailwindcss/upgrade`
package and will require pointing the migration to an input JavaScript
config file, like this:
```
npx @tailwindcss/upgrade --config tailwind.config.js
```
The idea of template migrations is to apply breaking changes from the v3
to v4 migration within your template files.
## Migrating !important syntax
The first migration that I’m adding with this PR is to ensure we use the
v4 important syntax that has the exclamation mark at the end of the
utility.
For example, this:
```html
<div class="!flex sm:!block"></div>
```
Will now turn into:
```html
<div class="flex! sm:block!"></div>
```
## Architecture considerations
Implementation wise, we make use of Oxide to scan the content files fast
and efficiently. By relying on the same scanner als Tailwind v4, we
guarantee that all candidates that are part of the v4 output will have
gone through a migration.
Migrations itself operate on the abstract `Candidate` type, similar to
the type we use in the v4 codebase. It will parse the candidate into its
parts so they can easily be introspected/modified. Migrations are typed
as:
```ts
type TemplateMigration = (candidate: Candidate) => Candidate | null
```
`null` should be returned if the `Candidate` does not need a migration.
We currently use the v4 `parseCandidate` function to get an abstract
definition of the candidate rule that we can operate on. _This will
likely need to change in the future as we need to fork `parseCandidate`
for v3 specific syntax_.
Additionally, we're inlining a `printCandidate` function that can
stringify the abstract `Candidate` type. It is not guaranteed that this
is an identity function since some information can be lost during the
parse step. This is not a problem though, because migrations will only
run selectively and if none of the selectors trigger, the candidates are
not updated. h/t to @RobinMalfait for providing the printer.
So the overall flow of a migration looks like this:
- Scan the config file for `content` files
- Use Oxide to extract a list of candidate and their positions from
these `content` files
- Run a few migrations that operate on the `Candidate` abstract type.
- Print the updated `Candidate` back into the original `content` file.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-09-25 16:20:14 +02:00
|
|
|
let files = await globby([pattern], {
|
|
|
|
|
absolute: true,
|
|
|
|
|
gitignore: true,
|
|
|
|
|
cwd: base,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
for (let file of files) {
|
|
|
|
|
set.add(file)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let files = Array.from(set)
|
|
|
|
|
files.sort()
|
2024-09-18 16:45:43 +02:00
|
|
|
|
Add setup for template migrations (#14502)
This PR adds the initial setup and a first codemod for the template
migrations. These are a new set of migrations that operate on files
defined in the Tailwind v3 config as part of the `content` option (so
your HTML, JavaScript, TSX files etc.).
The migration for this is integrated in the new `@tailwindcss/upgrade`
package and will require pointing the migration to an input JavaScript
config file, like this:
```
npx @tailwindcss/upgrade --config tailwind.config.js
```
The idea of template migrations is to apply breaking changes from the v3
to v4 migration within your template files.
## Migrating !important syntax
The first migration that I’m adding with this PR is to ensure we use the
v4 important syntax that has the exclamation mark at the end of the
utility.
For example, this:
```html
<div class="!flex sm:!block"></div>
```
Will now turn into:
```html
<div class="flex! sm:block!"></div>
```
## Architecture considerations
Implementation wise, we make use of Oxide to scan the content files fast
and efficiently. By relying on the same scanner als Tailwind v4, we
guarantee that all candidates that are part of the v4 output will have
gone through a migration.
Migrations itself operate on the abstract `Candidate` type, similar to
the type we use in the v4 codebase. It will parse the candidate into its
parts so they can easily be introspected/modified. Migrations are typed
as:
```ts
type TemplateMigration = (candidate: Candidate) => Candidate | null
```
`null` should be returned if the `Candidate` does not need a migration.
We currently use the v4 `parseCandidate` function to get an abstract
definition of the candidate rule that we can operate on. _This will
likely need to change in the future as we need to fork `parseCandidate`
for v3 specific syntax_.
Additionally, we're inlining a `printCandidate` function that can
stringify the abstract `Candidate` type. It is not guaranteed that this
is an identity function since some information can be lost during the
parse step. This is not a problem though, because migrations will only
run selectively and if none of the selectors trigger, the candidates are
not updated. h/t to @RobinMalfait for providing the printer.
So the overall flow of a migration looks like this:
- Scan the config file for `content` files
- Use Oxide to extract a list of candidate and their positions from
these `content` files
- Run a few migrations that operate on the `Candidate` abstract type.
- Print the updated `Candidate` back into the original `content` file.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-09-25 16:20:14 +02:00
|
|
|
// Migrate each file
|
Template migrations: Migrate v3 prefixes to v4 (#14557)
This PR adds a new migration that can migrate Tailwind CSS v3 style
prefixes into Tailwind CSS v4.
The migration is split into three separate pieces of work:
1. Firstly, we need to read the full JavaScript config to get the _old_
prefix option. This is necessary because in v4, we will not allow things
like custom-separators for the prefix. From this option we will then try
and compute a new prefix (in 90% of the cases this is going to just
remove the trailing `-` but it can also work in more complex cases).
2. Then we migrate all Candidates. The important thing here is that we
need to operate on the raw candidate string because by relying on
`parseCandidate` (which we do for all other migrations) would not work,
as the candidates are not valid in v4 syntax. More on that in a bit.
3. Lastly we also make sure to update the CSS config to include the new
prefix. This is done by prepending the prefix option like so:
```css
@import "tailwindcss" prefix(tw);
```
### Migrating candidates
The main difference between v3 prefixes and v4 prefixes is that in v3,
the prefix was _part of the utility_ where as in v4 it is _always in
front of the CSS class.
So, for example, this candidate in v3:
```
hover:-tw-mr-4
```
Would be converted to the following in v4:
```
tw:hover:-mr-4
```
Since the first example _won't parse as a valid Candidate in v4, as the
`tw-mr` utility does not exist, we have to operate on the raw candidate
string first. To do this I created a fork of the `parseCandidate`
function _without any validation of utilities or variants_. This is used
to identify part of the candidate that is the `base` and then ensuring
the `base` starts with the old prefix. We then remove this to create an
"unprefixed" candidate that we validate against a version of the
DesignSystem _with no prefixes configured_. If the variant is valid this
way, we can then print it again with the `DesignSystem` that has the new
prefix to get the migrated version.
Since we set up the `DesignSystem` to include the new prefix, we can
also be certain that migrations that happen afterwards would still
disqualify candidates that aren't valid according to the new prefix
policy. This does mean we need to have the prefix fixup be the first
step in our pipeline.
One interesting bit is that in v3, arbitrary properties did not require
prefixes where as in v4 they do. So the following candidate:
```
[color:red]
```
Will be converted to:
```
tw:[color:red]
```
2024-10-01 18:04:08 +02:00
|
|
|
await Promise.allSettled(
|
2024-10-07 18:02:28 +02:00
|
|
|
files.map((file) => migrateTemplate(config.designSystem, config.userConfig, file)),
|
Template migrations: Migrate v3 prefixes to v4 (#14557)
This PR adds a new migration that can migrate Tailwind CSS v3 style
prefixes into Tailwind CSS v4.
The migration is split into three separate pieces of work:
1. Firstly, we need to read the full JavaScript config to get the _old_
prefix option. This is necessary because in v4, we will not allow things
like custom-separators for the prefix. From this option we will then try
and compute a new prefix (in 90% of the cases this is going to just
remove the trailing `-` but it can also work in more complex cases).
2. Then we migrate all Candidates. The important thing here is that we
need to operate on the raw candidate string because by relying on
`parseCandidate` (which we do for all other migrations) would not work,
as the candidates are not valid in v4 syntax. More on that in a bit.
3. Lastly we also make sure to update the CSS config to include the new
prefix. This is done by prepending the prefix option like so:
```css
@import "tailwindcss" prefix(tw);
```
### Migrating candidates
The main difference between v3 prefixes and v4 prefixes is that in v3,
the prefix was _part of the utility_ where as in v4 it is _always in
front of the CSS class.
So, for example, this candidate in v3:
```
hover:-tw-mr-4
```
Would be converted to the following in v4:
```
tw:hover:-mr-4
```
Since the first example _won't parse as a valid Candidate in v4, as the
`tw-mr` utility does not exist, we have to operate on the raw candidate
string first. To do this I created a fork of the `parseCandidate`
function _without any validation of utilities or variants_. This is used
to identify part of the candidate that is the `base` and then ensuring
the `base` starts with the old prefix. We then remove this to create an
"unprefixed" candidate that we validate against a version of the
DesignSystem _with no prefixes configured_. If the variant is valid this
way, we can then print it again with the `DesignSystem` that has the new
prefix to get the migrated version.
Since we set up the `DesignSystem` to include the new prefix, we can
also be certain that migrations that happen afterwards would still
disqualify candidates that aren't valid according to the new prefix
policy. This does mean we need to have the prefix fixup be the first
step in our pipeline.
One interesting bit is that in v3, arbitrary properties did not require
prefixes where as in v4 they do. So the following candidate:
```
[color:red]
```
Will be converted to:
```
tw:[color:red]
```
2024-10-01 18:04:08 +02:00
|
|
|
)
|
2024-09-18 16:45:43 +02:00
|
|
|
|
Add setup for template migrations (#14502)
This PR adds the initial setup and a first codemod for the template
migrations. These are a new set of migrations that operate on files
defined in the Tailwind v3 config as part of the `content` option (so
your HTML, JavaScript, TSX files etc.).
The migration for this is integrated in the new `@tailwindcss/upgrade`
package and will require pointing the migration to an input JavaScript
config file, like this:
```
npx @tailwindcss/upgrade --config tailwind.config.js
```
The idea of template migrations is to apply breaking changes from the v3
to v4 migration within your template files.
## Migrating !important syntax
The first migration that I’m adding with this PR is to ensure we use the
v4 important syntax that has the exclamation mark at the end of the
utility.
For example, this:
```html
<div class="!flex sm:!block"></div>
```
Will now turn into:
```html
<div class="flex! sm:block!"></div>
```
## Architecture considerations
Implementation wise, we make use of Oxide to scan the content files fast
and efficiently. By relying on the same scanner als Tailwind v4, we
guarantee that all candidates that are part of the v4 output will have
gone through a migration.
Migrations itself operate on the abstract `Candidate` type, similar to
the type we use in the v4 codebase. It will parse the candidate into its
parts so they can easily be introspected/modified. Migrations are typed
as:
```ts
type TemplateMigration = (candidate: Candidate) => Candidate | null
```
`null` should be returned if the `Candidate` does not need a migration.
We currently use the v4 `parseCandidate` function to get an abstract
definition of the candidate rule that we can operate on. _This will
likely need to change in the future as we need to fork `parseCandidate`
for v3 specific syntax_.
Additionally, we're inlining a `printCandidate` function that can
stringify the abstract `Candidate` type. It is not guaranteed that this
is an identity function since some information can be lost during the
parse step. This is not a problem though, because migrations will only
run selectively and if none of the selectors trigger, the candidates are
not updated. h/t to @RobinMalfait for providing the printer.
So the overall flow of a migration looks like this:
- Scan the config file for `content` files
- Use Oxide to extract a list of candidate and their positions from
these `content` files
- Run a few migrations that operate on the `Candidate` abstract type.
- Print the updated `Candidate` back into the original `content` file.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-09-25 16:20:14 +02:00
|
|
|
success('Template migration complete.')
|
2024-09-18 16:45:43 +02:00
|
|
|
}
|
|
|
|
|
|
Add simple JS config migration (#14639)
This PR implements the first version of JS config file migration to CSS.
It is based on the most simple config setups we are using in the
Tailwind UI templates Commit, Primer, Radiant, and Studio.
The example we use in the integration test is a config that looks like
this:
```js
import { type Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
module.exports = {
darkMode: 'selector',
content: ['./src/**/*.{html,js}'],
theme: {
boxShadow: {
sm: '0 2px 6px rgb(15 23 42 / 0.08)',
},
colors: {
red: {
500: '#ef4444',
},
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.5rem' }],
base: ['1rem', { lineHeight: '2rem' }],
},
extend: {
colors: {
red: {
600: '#dc2626',
},
},
fontFamily: {
sans: 'Inter, system-ui, sans-serif',
display: ['Cabinet Grotesk', ...defaultTheme.fontFamily.sans],
},
borderRadius: {
'4xl': '2rem',
},
},
},
plugins: [],
} satisfies Config
```
As you can see, this file only has a `darkMode` selector, custom
`content` globs, a `theme` (with some theme keys being overwriting the
default theme and some others extending the defaults). Note that it does
not support `plugins` and/or `presets` yet.
In the case above, we will find the CSS file containing the existing
`@tailwind` directives and are migrating it to the following:
```css
@import 'tailwindcss';
@source './**/*.{html,js}';
@variant dark (&:where(.dark, .dark *));
@theme {
--box-shadow-*: initial;
--box-shadow-sm: 0 2px 6px rgb(15 23 42 / 0.08);
--color-*: initial;
--color-red-500: #ef4444;
--font-size-*: initial;
--font-size-xs: 0.75rem;
--font-size-xs--line-height: 1rem;
--font-size-sm: 0.875rem;
--font-size-sm--line-height: 1.5rem;
--font-size-base: 1rem;
--font-size-base--line-height: 2rem;
--color-red-600: #dc2626;
--font-family-sans: Inter, system-ui, sans-serif;
--font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--border-radius-4xl: 2rem;
}
```
This replicates all features of the JS config so we can even delete the
existing JS config in this case.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-10-11 15:27:53 +02:00
|
|
|
// Migrate JS config
|
|
|
|
|
|
|
|
|
|
info('Migrating JavaScript configuration files using the provided configuration file.')
|
2024-10-17 22:36:47 +02:00
|
|
|
let jsConfigMigration = await migrateJsConfig(config.designSystem, config.configFilePath, base)
|
Add simple JS config migration (#14639)
This PR implements the first version of JS config file migration to CSS.
It is based on the most simple config setups we are using in the
Tailwind UI templates Commit, Primer, Radiant, and Studio.
The example we use in the integration test is a config that looks like
this:
```js
import { type Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
module.exports = {
darkMode: 'selector',
content: ['./src/**/*.{html,js}'],
theme: {
boxShadow: {
sm: '0 2px 6px rgb(15 23 42 / 0.08)',
},
colors: {
red: {
500: '#ef4444',
},
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.5rem' }],
base: ['1rem', { lineHeight: '2rem' }],
},
extend: {
colors: {
red: {
600: '#dc2626',
},
},
fontFamily: {
sans: 'Inter, system-ui, sans-serif',
display: ['Cabinet Grotesk', ...defaultTheme.fontFamily.sans],
},
borderRadius: {
'4xl': '2rem',
},
},
},
plugins: [],
} satisfies Config
```
As you can see, this file only has a `darkMode` selector, custom
`content` globs, a `theme` (with some theme keys being overwriting the
default theme and some others extending the defaults). Note that it does
not support `plugins` and/or `presets` yet.
In the case above, we will find the CSS file containing the existing
`@tailwind` directives and are migrating it to the following:
```css
@import 'tailwindcss';
@source './**/*.{html,js}';
@variant dark (&:where(.dark, .dark *));
@theme {
--box-shadow-*: initial;
--box-shadow-sm: 0 2px 6px rgb(15 23 42 / 0.08);
--color-*: initial;
--color-red-500: #ef4444;
--font-size-*: initial;
--font-size-xs: 0.75rem;
--font-size-xs--line-height: 1rem;
--font-size-sm: 0.875rem;
--font-size-sm--line-height: 1.5rem;
--font-size-base: 1rem;
--font-size-base--line-height: 2rem;
--color-red-600: #dc2626;
--font-family-sans: Inter, system-ui, sans-serif;
--font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--border-radius-4xl: 2rem;
}
```
This replicates all features of the JS config so we can even delete the
existing JS config in this case.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-10-11 15:27:53 +02:00
|
|
|
|
Add setup for template migrations (#14502)
This PR adds the initial setup and a first codemod for the template
migrations. These are a new set of migrations that operate on files
defined in the Tailwind v3 config as part of the `content` option (so
your HTML, JavaScript, TSX files etc.).
The migration for this is integrated in the new `@tailwindcss/upgrade`
package and will require pointing the migration to an input JavaScript
config file, like this:
```
npx @tailwindcss/upgrade --config tailwind.config.js
```
The idea of template migrations is to apply breaking changes from the v3
to v4 migration within your template files.
## Migrating !important syntax
The first migration that I’m adding with this PR is to ensure we use the
v4 important syntax that has the exclamation mark at the end of the
utility.
For example, this:
```html
<div class="!flex sm:!block"></div>
```
Will now turn into:
```html
<div class="flex! sm:block!"></div>
```
## Architecture considerations
Implementation wise, we make use of Oxide to scan the content files fast
and efficiently. By relying on the same scanner als Tailwind v4, we
guarantee that all candidates that are part of the v4 output will have
gone through a migration.
Migrations itself operate on the abstract `Candidate` type, similar to
the type we use in the v4 codebase. It will parse the candidate into its
parts so they can easily be introspected/modified. Migrations are typed
as:
```ts
type TemplateMigration = (candidate: Candidate) => Candidate | null
```
`null` should be returned if the `Candidate` does not need a migration.
We currently use the v4 `parseCandidate` function to get an abstract
definition of the candidate rule that we can operate on. _This will
likely need to change in the future as we need to fork `parseCandidate`
for v3 specific syntax_.
Additionally, we're inlining a `printCandidate` function that can
stringify the abstract `Candidate` type. It is not guaranteed that this
is an identity function since some information can be lost during the
parse step. This is not a problem though, because migrations will only
run selectively and if none of the selectors trigger, the candidates are
not updated. h/t to @RobinMalfait for providing the printer.
So the overall flow of a migration looks like this:
- Scan the config file for `content` files
- Use Oxide to extract a list of candidate and their positions from
these `content` files
- Run a few migrations that operate on the `Candidate` abstract type.
- Print the updated `Candidate` back into the original `content` file.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-09-25 16:20:14 +02:00
|
|
|
{
|
|
|
|
|
// Stylesheet migrations
|
2024-09-18 16:45:43 +02:00
|
|
|
|
Add setup for template migrations (#14502)
This PR adds the initial setup and a first codemod for the template
migrations. These are a new set of migrations that operate on files
defined in the Tailwind v3 config as part of the `content` option (so
your HTML, JavaScript, TSX files etc.).
The migration for this is integrated in the new `@tailwindcss/upgrade`
package and will require pointing the migration to an input JavaScript
config file, like this:
```
npx @tailwindcss/upgrade --config tailwind.config.js
```
The idea of template migrations is to apply breaking changes from the v3
to v4 migration within your template files.
## Migrating !important syntax
The first migration that I’m adding with this PR is to ensure we use the
v4 important syntax that has the exclamation mark at the end of the
utility.
For example, this:
```html
<div class="!flex sm:!block"></div>
```
Will now turn into:
```html
<div class="flex! sm:block!"></div>
```
## Architecture considerations
Implementation wise, we make use of Oxide to scan the content files fast
and efficiently. By relying on the same scanner als Tailwind v4, we
guarantee that all candidates that are part of the v4 output will have
gone through a migration.
Migrations itself operate on the abstract `Candidate` type, similar to
the type we use in the v4 codebase. It will parse the candidate into its
parts so they can easily be introspected/modified. Migrations are typed
as:
```ts
type TemplateMigration = (candidate: Candidate) => Candidate | null
```
`null` should be returned if the `Candidate` does not need a migration.
We currently use the v4 `parseCandidate` function to get an abstract
definition of the candidate rule that we can operate on. _This will
likely need to change in the future as we need to fork `parseCandidate`
for v3 specific syntax_.
Additionally, we're inlining a `printCandidate` function that can
stringify the abstract `Candidate` type. It is not guaranteed that this
is an identity function since some information can be lost during the
parse step. This is not a problem though, because migrations will only
run selectively and if none of the selectors trigger, the candidates are
not updated. h/t to @RobinMalfait for providing the printer.
So the overall flow of a migration looks like this:
- Scan the config file for `content` files
- Use Oxide to extract a list of candidate and their positions from
these `content` files
- Run a few migrations that operate on the `Candidate` abstract type.
- Print the updated `Candidate` back into the original `content` file.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-09-25 16:20:14 +02:00
|
|
|
// Use provided files
|
Add simple JS config migration (#14639)
This PR implements the first version of JS config file migration to CSS.
It is based on the most simple config setups we are using in the
Tailwind UI templates Commit, Primer, Radiant, and Studio.
The example we use in the integration test is a config that looks like
this:
```js
import { type Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
module.exports = {
darkMode: 'selector',
content: ['./src/**/*.{html,js}'],
theme: {
boxShadow: {
sm: '0 2px 6px rgb(15 23 42 / 0.08)',
},
colors: {
red: {
500: '#ef4444',
},
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.5rem' }],
base: ['1rem', { lineHeight: '2rem' }],
},
extend: {
colors: {
red: {
600: '#dc2626',
},
},
fontFamily: {
sans: 'Inter, system-ui, sans-serif',
display: ['Cabinet Grotesk', ...defaultTheme.fontFamily.sans],
},
borderRadius: {
'4xl': '2rem',
},
},
},
plugins: [],
} satisfies Config
```
As you can see, this file only has a `darkMode` selector, custom
`content` globs, a `theme` (with some theme keys being overwriting the
default theme and some others extending the defaults). Note that it does
not support `plugins` and/or `presets` yet.
In the case above, we will find the CSS file containing the existing
`@tailwind` directives and are migrating it to the following:
```css
@import 'tailwindcss';
@source './**/*.{html,js}';
@variant dark (&:where(.dark, .dark *));
@theme {
--box-shadow-*: initial;
--box-shadow-sm: 0 2px 6px rgb(15 23 42 / 0.08);
--color-*: initial;
--color-red-500: #ef4444;
--font-size-*: initial;
--font-size-xs: 0.75rem;
--font-size-xs--line-height: 1rem;
--font-size-sm: 0.875rem;
--font-size-sm--line-height: 1.5rem;
--font-size-base: 1rem;
--font-size-base--line-height: 2rem;
--color-red-600: #dc2626;
--font-family-sans: Inter, system-ui, sans-serif;
--font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--border-radius-4xl: 2rem;
}
```
This replicates all features of the JS config so we can even delete the
existing JS config in this case.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-10-11 15:27:53 +02:00
|
|
|
let files = flags._.map((file) => path.resolve(base, file))
|
Add setup for template migrations (#14502)
This PR adds the initial setup and a first codemod for the template
migrations. These are a new set of migrations that operate on files
defined in the Tailwind v3 config as part of the `content` option (so
your HTML, JavaScript, TSX files etc.).
The migration for this is integrated in the new `@tailwindcss/upgrade`
package and will require pointing the migration to an input JavaScript
config file, like this:
```
npx @tailwindcss/upgrade --config tailwind.config.js
```
The idea of template migrations is to apply breaking changes from the v3
to v4 migration within your template files.
## Migrating !important syntax
The first migration that I’m adding with this PR is to ensure we use the
v4 important syntax that has the exclamation mark at the end of the
utility.
For example, this:
```html
<div class="!flex sm:!block"></div>
```
Will now turn into:
```html
<div class="flex! sm:block!"></div>
```
## Architecture considerations
Implementation wise, we make use of Oxide to scan the content files fast
and efficiently. By relying on the same scanner als Tailwind v4, we
guarantee that all candidates that are part of the v4 output will have
gone through a migration.
Migrations itself operate on the abstract `Candidate` type, similar to
the type we use in the v4 codebase. It will parse the candidate into its
parts so they can easily be introspected/modified. Migrations are typed
as:
```ts
type TemplateMigration = (candidate: Candidate) => Candidate | null
```
`null` should be returned if the `Candidate` does not need a migration.
We currently use the v4 `parseCandidate` function to get an abstract
definition of the candidate rule that we can operate on. _This will
likely need to change in the future as we need to fork `parseCandidate`
for v3 specific syntax_.
Additionally, we're inlining a `printCandidate` function that can
stringify the abstract `Candidate` type. It is not guaranteed that this
is an identity function since some information can be lost during the
parse step. This is not a problem though, because migrations will only
run selectively and if none of the selectors trigger, the candidates are
not updated. h/t to @RobinMalfait for providing the printer.
So the overall flow of a migration looks like this:
- Scan the config file for `content` files
- Use Oxide to extract a list of candidate and their positions from
these `content` files
- Run a few migrations that operate on the `Candidate` abstract type.
- Print the updated `Candidate` back into the original `content` file.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-09-25 16:20:14 +02:00
|
|
|
|
|
|
|
|
// Discover CSS files in case no files were provided
|
|
|
|
|
if (files.length === 0) {
|
|
|
|
|
info(
|
|
|
|
|
'No input stylesheets provided. Searching for CSS files in the current directory and its subdirectories…',
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
files = await globby(['**/*.css'], {
|
|
|
|
|
absolute: true,
|
|
|
|
|
gitignore: true,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure we are only dealing with CSS files
|
|
|
|
|
files = files.filter((file) => file.endsWith('.css'))
|
|
|
|
|
|
2024-10-10 09:44:04 -04:00
|
|
|
// Analyze the stylesheets
|
|
|
|
|
let loadResults = await Promise.allSettled(files.map((filepath) => Stylesheet.load(filepath)))
|
|
|
|
|
|
|
|
|
|
// Load and parse all stylesheets
|
|
|
|
|
for (let result of loadResults) {
|
|
|
|
|
if (result.status === 'rejected') {
|
|
|
|
|
error(`${result.reason}`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let stylesheets = loadResults
|
|
|
|
|
.filter((result) => result.status === 'fulfilled')
|
|
|
|
|
.map((result) => result.value)
|
|
|
|
|
|
|
|
|
|
// Analyze the stylesheets
|
|
|
|
|
try {
|
|
|
|
|
await analyzeStylesheets(stylesheets)
|
|
|
|
|
} catch (e: unknown) {
|
|
|
|
|
error(`${e}`)
|
|
|
|
|
}
|
|
|
|
|
|
Add setup for template migrations (#14502)
This PR adds the initial setup and a first codemod for the template
migrations. These are a new set of migrations that operate on files
defined in the Tailwind v3 config as part of the `content` option (so
your HTML, JavaScript, TSX files etc.).
The migration for this is integrated in the new `@tailwindcss/upgrade`
package and will require pointing the migration to an input JavaScript
config file, like this:
```
npx @tailwindcss/upgrade --config tailwind.config.js
```
The idea of template migrations is to apply breaking changes from the v3
to v4 migration within your template files.
## Migrating !important syntax
The first migration that I’m adding with this PR is to ensure we use the
v4 important syntax that has the exclamation mark at the end of the
utility.
For example, this:
```html
<div class="!flex sm:!block"></div>
```
Will now turn into:
```html
<div class="flex! sm:block!"></div>
```
## Architecture considerations
Implementation wise, we make use of Oxide to scan the content files fast
and efficiently. By relying on the same scanner als Tailwind v4, we
guarantee that all candidates that are part of the v4 output will have
gone through a migration.
Migrations itself operate on the abstract `Candidate` type, similar to
the type we use in the v4 codebase. It will parse the candidate into its
parts so they can easily be introspected/modified. Migrations are typed
as:
```ts
type TemplateMigration = (candidate: Candidate) => Candidate | null
```
`null` should be returned if the `Candidate` does not need a migration.
We currently use the v4 `parseCandidate` function to get an abstract
definition of the candidate rule that we can operate on. _This will
likely need to change in the future as we need to fork `parseCandidate`
for v3 specific syntax_.
Additionally, we're inlining a `printCandidate` function that can
stringify the abstract `Candidate` type. It is not guaranteed that this
is an identity function since some information can be lost during the
parse step. This is not a problem though, because migrations will only
run selectively and if none of the selectors trigger, the candidates are
not updated. h/t to @RobinMalfait for providing the printer.
So the overall flow of a migration looks like this:
- Scan the config file for `content` files
- Use Oxide to extract a list of candidate and their positions from
these `content` files
- Run a few migrations that operate on the `Candidate` abstract type.
- Print the updated `Candidate` back into the original `content` file.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-09-25 16:20:14 +02:00
|
|
|
// Migrate each file
|
2024-10-10 09:44:04 -04:00
|
|
|
let migrateResults = await Promise.allSettled(
|
Add simple JS config migration (#14639)
This PR implements the first version of JS config file migration to CSS.
It is based on the most simple config setups we are using in the
Tailwind UI templates Commit, Primer, Radiant, and Studio.
The example we use in the integration test is a config that looks like
this:
```js
import { type Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
module.exports = {
darkMode: 'selector',
content: ['./src/**/*.{html,js}'],
theme: {
boxShadow: {
sm: '0 2px 6px rgb(15 23 42 / 0.08)',
},
colors: {
red: {
500: '#ef4444',
},
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.5rem' }],
base: ['1rem', { lineHeight: '2rem' }],
},
extend: {
colors: {
red: {
600: '#dc2626',
},
},
fontFamily: {
sans: 'Inter, system-ui, sans-serif',
display: ['Cabinet Grotesk', ...defaultTheme.fontFamily.sans],
},
borderRadius: {
'4xl': '2rem',
},
},
},
plugins: [],
} satisfies Config
```
As you can see, this file only has a `darkMode` selector, custom
`content` globs, a `theme` (with some theme keys being overwriting the
default theme and some others extending the defaults). Note that it does
not support `plugins` and/or `presets` yet.
In the case above, we will find the CSS file containing the existing
`@tailwind` directives and are migrating it to the following:
```css
@import 'tailwindcss';
@source './**/*.{html,js}';
@variant dark (&:where(.dark, .dark *));
@theme {
--box-shadow-*: initial;
--box-shadow-sm: 0 2px 6px rgb(15 23 42 / 0.08);
--color-*: initial;
--color-red-500: #ef4444;
--font-size-*: initial;
--font-size-xs: 0.75rem;
--font-size-xs--line-height: 1rem;
--font-size-sm: 0.875rem;
--font-size-sm--line-height: 1.5rem;
--font-size-base: 1rem;
--font-size-base--line-height: 2rem;
--color-red-600: #dc2626;
--font-family-sans: Inter, system-ui, sans-serif;
--font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--border-radius-4xl: 2rem;
}
```
This replicates all features of the JS config so we can even delete the
existing JS config in this case.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-10-11 15:27:53 +02:00
|
|
|
stylesheets.map((sheet) => migrateStylesheet(sheet, { ...config, jsConfigMigration })),
|
2024-10-10 09:44:04 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for (let result of migrateResults) {
|
|
|
|
|
if (result.status === 'rejected') {
|
|
|
|
|
error(`${result.reason}`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Split up stylesheets (as needed)
|
|
|
|
|
try {
|
|
|
|
|
await splitStylesheets(stylesheets)
|
|
|
|
|
} catch (e: unknown) {
|
|
|
|
|
error(`${e}`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Format nodes
|
|
|
|
|
for (let sheet of stylesheets) {
|
|
|
|
|
await postcss([formatNodes()]).process(sheet.root!, { from: sheet.file! })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write all files to disk
|
|
|
|
|
for (let sheet of stylesheets) {
|
|
|
|
|
if (!sheet.file) continue
|
|
|
|
|
|
|
|
|
|
await fs.writeFile(sheet.file, sheet.root.toString())
|
|
|
|
|
}
|
Add setup for template migrations (#14502)
This PR adds the initial setup and a first codemod for the template
migrations. These are a new set of migrations that operate on files
defined in the Tailwind v3 config as part of the `content` option (so
your HTML, JavaScript, TSX files etc.).
The migration for this is integrated in the new `@tailwindcss/upgrade`
package and will require pointing the migration to an input JavaScript
config file, like this:
```
npx @tailwindcss/upgrade --config tailwind.config.js
```
The idea of template migrations is to apply breaking changes from the v3
to v4 migration within your template files.
## Migrating !important syntax
The first migration that I’m adding with this PR is to ensure we use the
v4 important syntax that has the exclamation mark at the end of the
utility.
For example, this:
```html
<div class="!flex sm:!block"></div>
```
Will now turn into:
```html
<div class="flex! sm:block!"></div>
```
## Architecture considerations
Implementation wise, we make use of Oxide to scan the content files fast
and efficiently. By relying on the same scanner als Tailwind v4, we
guarantee that all candidates that are part of the v4 output will have
gone through a migration.
Migrations itself operate on the abstract `Candidate` type, similar to
the type we use in the v4 codebase. It will parse the candidate into its
parts so they can easily be introspected/modified. Migrations are typed
as:
```ts
type TemplateMigration = (candidate: Candidate) => Candidate | null
```
`null` should be returned if the `Candidate` does not need a migration.
We currently use the v4 `parseCandidate` function to get an abstract
definition of the candidate rule that we can operate on. _This will
likely need to change in the future as we need to fork `parseCandidate`
for v3 specific syntax_.
Additionally, we're inlining a `printCandidate` function that can
stringify the abstract `Candidate` type. It is not guaranteed that this
is an identity function since some information can be lost during the
parse step. This is not a problem though, because migrations will only
run selectively and if none of the selectors trigger, the candidates are
not updated. h/t to @RobinMalfait for providing the printer.
So the overall flow of a migration looks like this:
- Scan the config file for `content` files
- Use Oxide to extract a list of candidate and their positions from
these `content` files
- Run a few migrations that operate on the `Candidate` abstract type.
- Print the updated `Candidate` back into the original `content` file.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-09-25 16:20:14 +02:00
|
|
|
|
|
|
|
|
success('Stylesheet migration complete.')
|
|
|
|
|
}
|
2024-09-18 16:45:43 +02:00
|
|
|
|
2024-10-10 15:09:14 +02:00
|
|
|
{
|
|
|
|
|
// PostCSS config migration
|
Add simple JS config migration (#14639)
This PR implements the first version of JS config file migration to CSS.
It is based on the most simple config setups we are using in the
Tailwind UI templates Commit, Primer, Radiant, and Studio.
The example we use in the integration test is a config that looks like
this:
```js
import { type Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
module.exports = {
darkMode: 'selector',
content: ['./src/**/*.{html,js}'],
theme: {
boxShadow: {
sm: '0 2px 6px rgb(15 23 42 / 0.08)',
},
colors: {
red: {
500: '#ef4444',
},
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.5rem' }],
base: ['1rem', { lineHeight: '2rem' }],
},
extend: {
colors: {
red: {
600: '#dc2626',
},
},
fontFamily: {
sans: 'Inter, system-ui, sans-serif',
display: ['Cabinet Grotesk', ...defaultTheme.fontFamily.sans],
},
borderRadius: {
'4xl': '2rem',
},
},
},
plugins: [],
} satisfies Config
```
As you can see, this file only has a `darkMode` selector, custom
`content` globs, a `theme` (with some theme keys being overwriting the
default theme and some others extending the defaults). Note that it does
not support `plugins` and/or `presets` yet.
In the case above, we will find the CSS file containing the existing
`@tailwind` directives and are migrating it to the following:
```css
@import 'tailwindcss';
@source './**/*.{html,js}';
@variant dark (&:where(.dark, .dark *));
@theme {
--box-shadow-*: initial;
--box-shadow-sm: 0 2px 6px rgb(15 23 42 / 0.08);
--color-*: initial;
--color-red-500: #ef4444;
--font-size-*: initial;
--font-size-xs: 0.75rem;
--font-size-xs--line-height: 1rem;
--font-size-sm: 0.875rem;
--font-size-sm--line-height: 1.5rem;
--font-size-base: 1rem;
--font-size-base--line-height: 2rem;
--color-red-600: #dc2626;
--font-family-sans: Inter, system-ui, sans-serif;
--font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--border-radius-4xl: 2rem;
}
```
This replicates all features of the JS config so we can even delete the
existing JS config in this case.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-10-11 15:27:53 +02:00
|
|
|
await migratePostCSSConfig(base)
|
2024-10-10 15:09:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Upgrade Tailwind CSS
|
Add simple JS config migration (#14639)
This PR implements the first version of JS config file migration to CSS.
It is based on the most simple config setups we are using in the
Tailwind UI templates Commit, Primer, Radiant, and Studio.
The example we use in the integration test is a config that looks like
this:
```js
import { type Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
module.exports = {
darkMode: 'selector',
content: ['./src/**/*.{html,js}'],
theme: {
boxShadow: {
sm: '0 2px 6px rgb(15 23 42 / 0.08)',
},
colors: {
red: {
500: '#ef4444',
},
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.5rem' }],
base: ['1rem', { lineHeight: '2rem' }],
},
extend: {
colors: {
red: {
600: '#dc2626',
},
},
fontFamily: {
sans: 'Inter, system-ui, sans-serif',
display: ['Cabinet Grotesk', ...defaultTheme.fontFamily.sans],
},
borderRadius: {
'4xl': '2rem',
},
},
},
plugins: [],
} satisfies Config
```
As you can see, this file only has a `darkMode` selector, custom
`content` globs, a `theme` (with some theme keys being overwriting the
default theme and some others extending the defaults). Note that it does
not support `plugins` and/or `presets` yet.
In the case above, we will find the CSS file containing the existing
`@tailwind` directives and are migrating it to the following:
```css
@import 'tailwindcss';
@source './**/*.{html,js}';
@variant dark (&:where(.dark, .dark *));
@theme {
--box-shadow-*: initial;
--box-shadow-sm: 0 2px 6px rgb(15 23 42 / 0.08);
--color-*: initial;
--color-red-500: #ef4444;
--font-size-*: initial;
--font-size-xs: 0.75rem;
--font-size-xs--line-height: 1rem;
--font-size-sm: 0.875rem;
--font-size-sm--line-height: 1.5rem;
--font-size-base: 1rem;
--font-size-base--line-height: 2rem;
--color-red-600: #dc2626;
--font-family-sans: Inter, system-ui, sans-serif;
--font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--border-radius-4xl: 2rem;
}
```
This replicates all features of the JS config so we can even delete the
existing JS config in this case.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-10-11 15:27:53 +02:00
|
|
|
await pkg('add tailwindcss@next', base)
|
2024-10-10 15:09:14 +02:00
|
|
|
} catch {}
|
|
|
|
|
|
Add simple JS config migration (#14639)
This PR implements the first version of JS config file migration to CSS.
It is based on the most simple config setups we are using in the
Tailwind UI templates Commit, Primer, Radiant, and Studio.
The example we use in the integration test is a config that looks like
this:
```js
import { type Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
module.exports = {
darkMode: 'selector',
content: ['./src/**/*.{html,js}'],
theme: {
boxShadow: {
sm: '0 2px 6px rgb(15 23 42 / 0.08)',
},
colors: {
red: {
500: '#ef4444',
},
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.5rem' }],
base: ['1rem', { lineHeight: '2rem' }],
},
extend: {
colors: {
red: {
600: '#dc2626',
},
},
fontFamily: {
sans: 'Inter, system-ui, sans-serif',
display: ['Cabinet Grotesk', ...defaultTheme.fontFamily.sans],
},
borderRadius: {
'4xl': '2rem',
},
},
},
plugins: [],
} satisfies Config
```
As you can see, this file only has a `darkMode` selector, custom
`content` globs, a `theme` (with some theme keys being overwriting the
default theme and some others extending the defaults). Note that it does
not support `plugins` and/or `presets` yet.
In the case above, we will find the CSS file containing the existing
`@tailwind` directives and are migrating it to the following:
```css
@import 'tailwindcss';
@source './**/*.{html,js}';
@variant dark (&:where(.dark, .dark *));
@theme {
--box-shadow-*: initial;
--box-shadow-sm: 0 2px 6px rgb(15 23 42 / 0.08);
--color-*: initial;
--color-red-500: #ef4444;
--font-size-*: initial;
--font-size-xs: 0.75rem;
--font-size-xs--line-height: 1rem;
--font-size-sm: 0.875rem;
--font-size-sm--line-height: 1.5rem;
--font-size-base: 1rem;
--font-size-base--line-height: 2rem;
--color-red-600: #dc2626;
--font-family-sans: Inter, system-ui, sans-serif;
--font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--border-radius-4xl: 2rem;
}
```
This replicates all features of the JS config so we can even delete the
existing JS config in this case.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-10-11 15:27:53 +02:00
|
|
|
// Remove the JS config if it was fully migrated
|
|
|
|
|
if (jsConfigMigration !== null) {
|
|
|
|
|
await fs.rm(config.configFilePath)
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-18 16:45:43 +02:00
|
|
|
// Figure out if we made any changes
|
|
|
|
|
if (isRepoDirty()) {
|
Add setup for template migrations (#14502)
This PR adds the initial setup and a first codemod for the template
migrations. These are a new set of migrations that operate on files
defined in the Tailwind v3 config as part of the `content` option (so
your HTML, JavaScript, TSX files etc.).
The migration for this is integrated in the new `@tailwindcss/upgrade`
package and will require pointing the migration to an input JavaScript
config file, like this:
```
npx @tailwindcss/upgrade --config tailwind.config.js
```
The idea of template migrations is to apply breaking changes from the v3
to v4 migration within your template files.
## Migrating !important syntax
The first migration that I’m adding with this PR is to ensure we use the
v4 important syntax that has the exclamation mark at the end of the
utility.
For example, this:
```html
<div class="!flex sm:!block"></div>
```
Will now turn into:
```html
<div class="flex! sm:block!"></div>
```
## Architecture considerations
Implementation wise, we make use of Oxide to scan the content files fast
and efficiently. By relying on the same scanner als Tailwind v4, we
guarantee that all candidates that are part of the v4 output will have
gone through a migration.
Migrations itself operate on the abstract `Candidate` type, similar to
the type we use in the v4 codebase. It will parse the candidate into its
parts so they can easily be introspected/modified. Migrations are typed
as:
```ts
type TemplateMigration = (candidate: Candidate) => Candidate | null
```
`null` should be returned if the `Candidate` does not need a migration.
We currently use the v4 `parseCandidate` function to get an abstract
definition of the candidate rule that we can operate on. _This will
likely need to change in the future as we need to fork `parseCandidate`
for v3 specific syntax_.
Additionally, we're inlining a `printCandidate` function that can
stringify the abstract `Candidate` type. It is not guaranteed that this
is an identity function since some information can be lost during the
parse step. This is not a problem though, because migrations will only
run selectively and if none of the selectors trigger, the candidates are
not updated. h/t to @RobinMalfait for providing the printer.
So the overall flow of a migration looks like this:
- Scan the config file for `content` files
- Use Oxide to extract a list of candidate and their positions from
these `content` files
- Run a few migrations that operate on the `Candidate` abstract type.
- Print the updated `Candidate` back into the original `content` file.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-09-25 16:20:14 +02:00
|
|
|
success('Verify the changes and commit them to your repository.')
|
2024-09-18 16:45:43 +02:00
|
|
|
} else {
|
Add setup for template migrations (#14502)
This PR adds the initial setup and a first codemod for the template
migrations. These are a new set of migrations that operate on files
defined in the Tailwind v3 config as part of the `content` option (so
your HTML, JavaScript, TSX files etc.).
The migration for this is integrated in the new `@tailwindcss/upgrade`
package and will require pointing the migration to an input JavaScript
config file, like this:
```
npx @tailwindcss/upgrade --config tailwind.config.js
```
The idea of template migrations is to apply breaking changes from the v3
to v4 migration within your template files.
## Migrating !important syntax
The first migration that I’m adding with this PR is to ensure we use the
v4 important syntax that has the exclamation mark at the end of the
utility.
For example, this:
```html
<div class="!flex sm:!block"></div>
```
Will now turn into:
```html
<div class="flex! sm:block!"></div>
```
## Architecture considerations
Implementation wise, we make use of Oxide to scan the content files fast
and efficiently. By relying on the same scanner als Tailwind v4, we
guarantee that all candidates that are part of the v4 output will have
gone through a migration.
Migrations itself operate on the abstract `Candidate` type, similar to
the type we use in the v4 codebase. It will parse the candidate into its
parts so they can easily be introspected/modified. Migrations are typed
as:
```ts
type TemplateMigration = (candidate: Candidate) => Candidate | null
```
`null` should be returned if the `Candidate` does not need a migration.
We currently use the v4 `parseCandidate` function to get an abstract
definition of the candidate rule that we can operate on. _This will
likely need to change in the future as we need to fork `parseCandidate`
for v3 specific syntax_.
Additionally, we're inlining a `printCandidate` function that can
stringify the abstract `Candidate` type. It is not guaranteed that this
is an identity function since some information can be lost during the
parse step. This is not a problem though, because migrations will only
run selectively and if none of the selectors trigger, the candidates are
not updated. h/t to @RobinMalfait for providing the printer.
So the overall flow of a migration looks like this:
- Scan the config file for `content` files
- Use Oxide to extract a list of candidate and their positions from
these `content` files
- Run a few migrations that operate on the `Candidate` abstract type.
- Print the updated `Candidate` back into the original `content` file.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-09-25 16:20:14 +02:00
|
|
|
success('No changes were made to your repository.')
|
2024-09-18 16:45:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
run()
|
|
|
|
|
.then(() => process.exit(0))
|
|
|
|
|
.catch((err) => {
|
|
|
|
|
console.error(err)
|
|
|
|
|
process.exit(1)
|
|
|
|
|
})
|