tailwindcss/integrations/cli/plugins.test.ts

210 lines
5.4 KiB
TypeScript
Raw Permalink Normal View History

Improve compatibility with `@tailwindcss/typography` and `@tailwindcss/forms` (#14221) This PR enables compatibility for the `@tailwindcss/typography` and `@tailwindcss/forms` plugins. This required the addition of new Plugin APIs and new package exports. ## New Plugin APIs and compatibility improvements We added support for `addComponents`, `matchComponents`, and `prefix`. The component APIs are an alias for the utilities APIs because the sorting in V4 is different and emitting components in a custom `@layer` is not necessary. Since `prefix` is not supported in V4, the `prefix()` API is currently an identity function. ```js addComponents({ '.btn': { padding: '.5rem 1rem', borderRadius: '.25rem', fontWeight: '600', }, '.btn-blue': { backgroundColor: '#3490dc', color: '#fff', '&:hover': { backgroundColor: '#2779bd', }, }, '.btn-red': { backgroundColor: '#e3342f', color: '#fff', '&:hover': { backgroundColor: '#cc1f1a', }, }, }) ``` The behavioral changes effect the `addUtilities` and `matchUtilities` functions, we now: - Allow arrays of CSS property objects to be emitted: ```js addUtilities({ '.text-trim': [ {'text-box-trim': 'both'}, {'text-box-edge': 'cap alphabetic'}, ], }) ``` - Allow arrays of utilities ```js addUtilities([ { '.text-trim':{ 'text-box-trim': 'both', 'text-box-edge': 'cap alphabetic', }, } ]) ``` - Allow more complicated selector names ```js addUtilities({ '.form-input, .form-select, .form-radio': { /* styles here */ }, '.form-input::placeholder': { /* styles here */ }, '.form-checkbox:indeterminate:checked': { /* styles here */ } }) ``` ## New `tailwindcss/color` and `tailwindcss/defaultTheme` export To be compatible to v3, we're adding two new exports to the tailwindcss package. These match the default theme values as defined in v3: ```ts import colors from 'tailwindcss/colors' console.log(colors.red[600]) ``` ```ts import theme from 'tailwindcss/defaultTheme' console.log(theme.spacing[4]) ``` --------- Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 08:06:21 -04:00
import { candidate, css, html, json, test } from '../utils'
test(
'builds the `@tailwindcss/typography` plugin utilities',
Improve compatibility with `@tailwindcss/typography` and `@tailwindcss/forms` (#14221) This PR enables compatibility for the `@tailwindcss/typography` and `@tailwindcss/forms` plugins. This required the addition of new Plugin APIs and new package exports. ## New Plugin APIs and compatibility improvements We added support for `addComponents`, `matchComponents`, and `prefix`. The component APIs are an alias for the utilities APIs because the sorting in V4 is different and emitting components in a custom `@layer` is not necessary. Since `prefix` is not supported in V4, the `prefix()` API is currently an identity function. ```js addComponents({ '.btn': { padding: '.5rem 1rem', borderRadius: '.25rem', fontWeight: '600', }, '.btn-blue': { backgroundColor: '#3490dc', color: '#fff', '&:hover': { backgroundColor: '#2779bd', }, }, '.btn-red': { backgroundColor: '#e3342f', color: '#fff', '&:hover': { backgroundColor: '#cc1f1a', }, }, }) ``` The behavioral changes effect the `addUtilities` and `matchUtilities` functions, we now: - Allow arrays of CSS property objects to be emitted: ```js addUtilities({ '.text-trim': [ {'text-box-trim': 'both'}, {'text-box-edge': 'cap alphabetic'}, ], }) ``` - Allow arrays of utilities ```js addUtilities([ { '.text-trim':{ 'text-box-trim': 'both', 'text-box-edge': 'cap alphabetic', }, } ]) ``` - Allow more complicated selector names ```js addUtilities({ '.form-input, .form-select, .form-radio': { /* styles here */ }, '.form-input::placeholder': { /* styles here */ }, '.form-checkbox:indeterminate:checked': { /* styles here */ } }) ``` ## New `tailwindcss/color` and `tailwindcss/defaultTheme` export To be compatible to v3, we're adding two new exports to the tailwindcss package. These match the default theme values as defined in v3: ```ts import colors from 'tailwindcss/colors' console.log(colors.red[600]) ``` ```ts import theme from 'tailwindcss/defaultTheme' console.log(theme.spacing[4]) ``` --------- Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 08:06:21 -04:00
{
fs: {
'package.json': json`
{
"dependencies": {
"@tailwindcss/typography": "^0.5.14",
"tailwindcss": "workspace:^",
"@tailwindcss/cli": "workspace:^"
}
}
`,
'index.html': html`
<div className="prose prose-stone prose-invert">
Improve compatibility with `@tailwindcss/typography` and `@tailwindcss/forms` (#14221) This PR enables compatibility for the `@tailwindcss/typography` and `@tailwindcss/forms` plugins. This required the addition of new Plugin APIs and new package exports. ## New Plugin APIs and compatibility improvements We added support for `addComponents`, `matchComponents`, and `prefix`. The component APIs are an alias for the utilities APIs because the sorting in V4 is different and emitting components in a custom `@layer` is not necessary. Since `prefix` is not supported in V4, the `prefix()` API is currently an identity function. ```js addComponents({ '.btn': { padding: '.5rem 1rem', borderRadius: '.25rem', fontWeight: '600', }, '.btn-blue': { backgroundColor: '#3490dc', color: '#fff', '&:hover': { backgroundColor: '#2779bd', }, }, '.btn-red': { backgroundColor: '#e3342f', color: '#fff', '&:hover': { backgroundColor: '#cc1f1a', }, }, }) ``` The behavioral changes effect the `addUtilities` and `matchUtilities` functions, we now: - Allow arrays of CSS property objects to be emitted: ```js addUtilities({ '.text-trim': [ {'text-box-trim': 'both'}, {'text-box-edge': 'cap alphabetic'}, ], }) ``` - Allow arrays of utilities ```js addUtilities([ { '.text-trim':{ 'text-box-trim': 'both', 'text-box-edge': 'cap alphabetic', }, } ]) ``` - Allow more complicated selector names ```js addUtilities({ '.form-input, .form-select, .form-radio': { /* styles here */ }, '.form-input::placeholder': { /* styles here */ }, '.form-checkbox:indeterminate:checked': { /* styles here */ } }) ``` ## New `tailwindcss/color` and `tailwindcss/defaultTheme` export To be compatible to v3, we're adding two new exports to the tailwindcss package. These match the default theme values as defined in v3: ```ts import colors from 'tailwindcss/colors' console.log(colors.red[600]) ``` ```ts import theme from 'tailwindcss/defaultTheme' console.log(theme.spacing[4]) ``` --------- Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 08:06:21 -04:00
<h1>Headline</h1>
<p>
Until now, trying to style an article, document, or blog post with Tailwind has been a
tedious task that required a keen eye for typography and a lot of complex custom CSS.
</p>
</div>
`,
'src/index.css': css`
@import 'tailwindcss';
@plugin '@tailwindcss/typography';
`,
},
},
async ({ fs, exec, expect }) => {
Improve compatibility with `@tailwindcss/typography` and `@tailwindcss/forms` (#14221) This PR enables compatibility for the `@tailwindcss/typography` and `@tailwindcss/forms` plugins. This required the addition of new Plugin APIs and new package exports. ## New Plugin APIs and compatibility improvements We added support for `addComponents`, `matchComponents`, and `prefix`. The component APIs are an alias for the utilities APIs because the sorting in V4 is different and emitting components in a custom `@layer` is not necessary. Since `prefix` is not supported in V4, the `prefix()` API is currently an identity function. ```js addComponents({ '.btn': { padding: '.5rem 1rem', borderRadius: '.25rem', fontWeight: '600', }, '.btn-blue': { backgroundColor: '#3490dc', color: '#fff', '&:hover': { backgroundColor: '#2779bd', }, }, '.btn-red': { backgroundColor: '#e3342f', color: '#fff', '&:hover': { backgroundColor: '#cc1f1a', }, }, }) ``` The behavioral changes effect the `addUtilities` and `matchUtilities` functions, we now: - Allow arrays of CSS property objects to be emitted: ```js addUtilities({ '.text-trim': [ {'text-box-trim': 'both'}, {'text-box-edge': 'cap alphabetic'}, ], }) ``` - Allow arrays of utilities ```js addUtilities([ { '.text-trim':{ 'text-box-trim': 'both', 'text-box-edge': 'cap alphabetic', }, } ]) ``` - Allow more complicated selector names ```js addUtilities({ '.form-input, .form-select, .form-radio': { /* styles here */ }, '.form-input::placeholder': { /* styles here */ }, '.form-checkbox:indeterminate:checked': { /* styles here */ } }) ``` ## New `tailwindcss/color` and `tailwindcss/defaultTheme` export To be compatible to v3, we're adding two new exports to the tailwindcss package. These match the default theme values as defined in v3: ```ts import colors from 'tailwindcss/colors' console.log(colors.red[600]) ``` ```ts import theme from 'tailwindcss/defaultTheme' console.log(theme.spacing[4]) ``` --------- Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 08:06:21 -04:00
await exec('pnpm tailwindcss --input src/index.css --output dist/out.css')
// Verify that `prose-stone` is defined before `prose-invert`
{
let contents = await fs.read('dist/out.css')
let proseInvertIdx = contents.indexOf('.prose-invert')
let proseStoneIdx = contents.indexOf('.prose-stone')
expect(proseStoneIdx).toBeLessThan(proseInvertIdx)
}
Improve compatibility with `@tailwindcss/typography` and `@tailwindcss/forms` (#14221) This PR enables compatibility for the `@tailwindcss/typography` and `@tailwindcss/forms` plugins. This required the addition of new Plugin APIs and new package exports. ## New Plugin APIs and compatibility improvements We added support for `addComponents`, `matchComponents`, and `prefix`. The component APIs are an alias for the utilities APIs because the sorting in V4 is different and emitting components in a custom `@layer` is not necessary. Since `prefix` is not supported in V4, the `prefix()` API is currently an identity function. ```js addComponents({ '.btn': { padding: '.5rem 1rem', borderRadius: '.25rem', fontWeight: '600', }, '.btn-blue': { backgroundColor: '#3490dc', color: '#fff', '&:hover': { backgroundColor: '#2779bd', }, }, '.btn-red': { backgroundColor: '#e3342f', color: '#fff', '&:hover': { backgroundColor: '#cc1f1a', }, }, }) ``` The behavioral changes effect the `addUtilities` and `matchUtilities` functions, we now: - Allow arrays of CSS property objects to be emitted: ```js addUtilities({ '.text-trim': [ {'text-box-trim': 'both'}, {'text-box-edge': 'cap alphabetic'}, ], }) ``` - Allow arrays of utilities ```js addUtilities([ { '.text-trim':{ 'text-box-trim': 'both', 'text-box-edge': 'cap alphabetic', }, } ]) ``` - Allow more complicated selector names ```js addUtilities({ '.form-input, .form-select, .form-radio': { /* styles here */ }, '.form-input::placeholder': { /* styles here */ }, '.form-checkbox:indeterminate:checked': { /* styles here */ } }) ``` ## New `tailwindcss/color` and `tailwindcss/defaultTheme` export To be compatible to v3, we're adding two new exports to the tailwindcss package. These match the default theme values as defined in v3: ```ts import colors from 'tailwindcss/colors' console.log(colors.red[600]) ``` ```ts import theme from 'tailwindcss/defaultTheme' console.log(theme.spacing[4]) ``` --------- Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 08:06:21 -04:00
await fs.expectFileToContain('dist/out.css', [
candidate`prose`,
':where(h1):not(:where([class~="not-prose"],[class~="not-prose"] *))',
':where(tbody td, tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *))',
])
},
)
test(
'builds the `@tailwindcss/forms` plugin utilities',
Improve compatibility with `@tailwindcss/typography` and `@tailwindcss/forms` (#14221) This PR enables compatibility for the `@tailwindcss/typography` and `@tailwindcss/forms` plugins. This required the addition of new Plugin APIs and new package exports. ## New Plugin APIs and compatibility improvements We added support for `addComponents`, `matchComponents`, and `prefix`. The component APIs are an alias for the utilities APIs because the sorting in V4 is different and emitting components in a custom `@layer` is not necessary. Since `prefix` is not supported in V4, the `prefix()` API is currently an identity function. ```js addComponents({ '.btn': { padding: '.5rem 1rem', borderRadius: '.25rem', fontWeight: '600', }, '.btn-blue': { backgroundColor: '#3490dc', color: '#fff', '&:hover': { backgroundColor: '#2779bd', }, }, '.btn-red': { backgroundColor: '#e3342f', color: '#fff', '&:hover': { backgroundColor: '#cc1f1a', }, }, }) ``` The behavioral changes effect the `addUtilities` and `matchUtilities` functions, we now: - Allow arrays of CSS property objects to be emitted: ```js addUtilities({ '.text-trim': [ {'text-box-trim': 'both'}, {'text-box-edge': 'cap alphabetic'}, ], }) ``` - Allow arrays of utilities ```js addUtilities([ { '.text-trim':{ 'text-box-trim': 'both', 'text-box-edge': 'cap alphabetic', }, } ]) ``` - Allow more complicated selector names ```js addUtilities({ '.form-input, .form-select, .form-radio': { /* styles here */ }, '.form-input::placeholder': { /* styles here */ }, '.form-checkbox:indeterminate:checked': { /* styles here */ } }) ``` ## New `tailwindcss/color` and `tailwindcss/defaultTheme` export To be compatible to v3, we're adding two new exports to the tailwindcss package. These match the default theme values as defined in v3: ```ts import colors from 'tailwindcss/colors' console.log(colors.red[600]) ``` ```ts import theme from 'tailwindcss/defaultTheme' console.log(theme.spacing[4]) ``` --------- Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 08:06:21 -04:00
{
fs: {
'package.json': json`
{
"dependencies": {
"@tailwindcss/forms": "^0.5.7",
"tailwindcss": "workspace:^",
"@tailwindcss/cli": "workspace:^"
}
}
`,
'index.html': html`
<input type="text" class="form-input" />
<textarea class="form-textarea"></textarea>
`,
'src/index.css': css`
@import 'tailwindcss';
@plugin '@tailwindcss/forms';
`,
},
},
async ({ fs, exec }) => {
await exec('pnpm tailwindcss --input src/index.css --output dist/out.css')
await fs.expectFileToContain('dist/out.css', [
//
candidate`form-input`,
candidate`form-textarea`,
])
await fs.expectFileNotToContain('dist/out.css', [
//
candidate`form-radio`,
])
},
)
Support plugin options in CSS (#14264) Builds on #14239 — that PR needs to be merged first. This PR allows plugins defined with `plugin.withOptions` to receive options in CSS when using `@plugin` as long as the options are simple key/value pairs. For example, the following is now valid and will include the forms plugin with only the base styles enabled: ```css @plugin "@tailwindcss/forms" { strategy: base; } ``` We handle `null`, `true`, `false`, and numeric values as expected and will convert them to their JavaScript equivalents. Comma separated values are turned into arrays. All other values are converted to strings. For example, in the following plugin definition, the options that are passed to the plugin will be the correct types: - `debug` will be the boolean value `true` - `threshold` will be the number `0.5` - `message` will be the string `"Hello world"` - `features` will be the array `["base", "responsive"]` ```css @plugin "my-plugin" { debug: false; threshold: 0.5; message: Hello world; features: base, responsive; } ``` If you need to pass a number or boolean value as a string, you can do so by wrapping the value in quotes: ```css @plugin "my-plugin" { debug: "false"; threshold: "0.5"; message: "Hello world"; } ``` When duplicate options are encountered the last value wins: ```css @plugin "my-plugin" { message: Hello world; message: Hello plugin; /* this will be the value of `message` */ } ``` It's important to note that this feature is **only available for plugins defined with `plugin.withOptions`**. If you try to pass options to a plugin that doesn't support them, you'll get an error message when building: ```css @plugin "my-plugin" { debug: false; threshold: 0.5; } /* Error: The plugin "my-plugin" does not accept options */ ``` Additionally, if you try to pass in more complex values like objects or selectors you'll get an error message: ```css @plugin "my-plugin" { color: { red: 100; green: 200; blue: 300 }; } /* Error: Objects are not supported in `@plugin` options. */ ``` ```css @plugin "my-plugin" { .some-selector > * { primary: "blue"; secondary: "green"; } } /* Error: `@plugin` can only contain declarations. */ ``` --------- Co-authored-by: Philipp Spiess <hello@philippspiess.com> Co-authored-by: Robin Malfait <malfait.robin@gmail.com> Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-09-02 12:49:09 -04:00
test(
'builds the `@tailwindcss/forms` plugin utilities (with options)',
{
fs: {
'package.json': json`
{
"dependencies": {
"@tailwindcss/forms": "^0.5.7",
"tailwindcss": "workspace:^",
"@tailwindcss/cli": "workspace:^"
}
}
`,
'index.html': html`
<input type="text" class="form-input" />
<textarea class="form-textarea"></textarea>
`,
'src/index.css': css`
@import 'tailwindcss';
@plugin '@tailwindcss/forms' {
strategy: base;
}
`,
},
},
async ({ fs, exec }) => {
await exec('pnpm tailwindcss --input src/index.css --output dist/out.css')
await fs.expectFileToContain('dist/out.css', [
//
`::-webkit-date-and-time-value`,
2025-12-17 22:27:50 -05:00
`input:where([type='checkbox']):indeterminate`,
Support plugin options in CSS (#14264) Builds on #14239 — that PR needs to be merged first. This PR allows plugins defined with `plugin.withOptions` to receive options in CSS when using `@plugin` as long as the options are simple key/value pairs. For example, the following is now valid and will include the forms plugin with only the base styles enabled: ```css @plugin "@tailwindcss/forms" { strategy: base; } ``` We handle `null`, `true`, `false`, and numeric values as expected and will convert them to their JavaScript equivalents. Comma separated values are turned into arrays. All other values are converted to strings. For example, in the following plugin definition, the options that are passed to the plugin will be the correct types: - `debug` will be the boolean value `true` - `threshold` will be the number `0.5` - `message` will be the string `"Hello world"` - `features` will be the array `["base", "responsive"]` ```css @plugin "my-plugin" { debug: false; threshold: 0.5; message: Hello world; features: base, responsive; } ``` If you need to pass a number or boolean value as a string, you can do so by wrapping the value in quotes: ```css @plugin "my-plugin" { debug: "false"; threshold: "0.5"; message: "Hello world"; } ``` When duplicate options are encountered the last value wins: ```css @plugin "my-plugin" { message: Hello world; message: Hello plugin; /* this will be the value of `message` */ } ``` It's important to note that this feature is **only available for plugins defined with `plugin.withOptions`**. If you try to pass options to a plugin that doesn't support them, you'll get an error message when building: ```css @plugin "my-plugin" { debug: false; threshold: 0.5; } /* Error: The plugin "my-plugin" does not accept options */ ``` Additionally, if you try to pass in more complex values like objects or selectors you'll get an error message: ```css @plugin "my-plugin" { color: { red: 100; green: 200; blue: 300 }; } /* Error: Objects are not supported in `@plugin` options. */ ``` ```css @plugin "my-plugin" { .some-selector > * { primary: "blue"; secondary: "green"; } } /* Error: `@plugin` can only contain declarations. */ ``` --------- Co-authored-by: Philipp Spiess <hello@philippspiess.com> Co-authored-by: Robin Malfait <malfait.robin@gmail.com> Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-09-02 12:49:09 -04:00
])
// No classes are included even though they are used in the HTML
// because the `base` strategy is used
await fs.expectFileNotToContain('dist/out.css', [
//
candidate`form-input`,
candidate`form-textarea`,
candidate`form-radio`,
])
},
)
Support complex `addUtilities()` configs (#15029) This PR adds support for complex `addUtilities()` configuration objects that use child combinators and other features. For example, in v3 it was possible to add a utility that changes the behavior of all children of the utility class node by doing something like this: ```ts addUtilities({ '.red-children > *': { color: 'red', }, }); ``` This is a pattern that was used by first-party plugins like `@tailwindcss/aspect-ratio` but that we never made working in v4, since it requires parsing the selector and properly extracting all utility candidates. While working on the codemod that can transform `@layer utilities` scoped declarations like the above, we found out a pretty neat heuristics on how to migrate these cases. We're basically finding all class selectors and replace them with `&`. Then we create a nested CSS structure like this: ```css .red-children { & > * { color: red; } } ``` Due to first party support for nesting, this works as expected in v4. ## Test Plan We added unit tests to ensure the rewriting works in some edge cases. Furthermore we added an integration test running the `@tailwindcss/aspect-ratio` plugin. We've also installed the tarballs in the Remix example from the [playgrounds](https://github.com/philipp-spiess/tailwindcss-playgrounds) and ensure we can use the `@tailwindcss/aspect-ratio` plugin just like we could in v3: <img width="2560" alt="Screenshot 2024-11-18 at 13 44 52" src="https://github.com/user-attachments/assets/31889131-fad0-4c37-b574-cfac2b99f786"> --------- Co-authored-by: Robin Malfait <malfait.robin@gmail.com> Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-11-19 15:52:06 +01:00
test(
'builds the `@tailwindcss/aspect-ratio` plugin utilities',
{
fs: {
'package.json': json`
{
"dependencies": {
"@tailwindcss/aspect-ratio": "^0.4.2",
"tailwindcss": "workspace:^",
"@tailwindcss/cli": "workspace:^"
}
}
`,
'index.html': html`
<div class="aspect-w-16 aspect-h-9">
<iframe
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
</div>
`,
'src/index.css': css`
@import 'tailwindcss';
@plugin '@tailwindcss/aspect-ratio';
`,
},
},
async ({ fs, exec }) => {
await exec('pnpm tailwindcss --input src/index.css --output dist/out.css')
await fs.expectFileToContain('dist/out.css', [
//
candidate`aspect-w-16`,
candidate`aspect-h-9`,
])
},
)
test(
'builds the `tailwindcss-animate` plugin utilities',
{
fs: {
'package.json': json`
{
"dependencies": {
"tailwindcss-animate": "^1.0.7",
"tailwindcss": "workspace:^",
"@tailwindcss/cli": "workspace:^"
}
}
`,
'index.html': html`
<div class="animate-in fade-in zoom-in duration-350"></div>
`,
'src/index.css': css`
@import 'tailwindcss';
@plugin 'tailwindcss-animate';
`,
},
},
async ({ fs, exec }) => {
await exec('pnpm tailwindcss --input src/index.css --output dist/out.css')
await fs.expectFileToContain('dist/out.css', [
candidate`animate-in`,
candidate`fade-in`,
candidate`zoom-in`,
candidate`duration-350`,
'transition-duration: 350ms',
'animation-duration: 350ms',
'@keyframes enter {',
])
},
)