From cb75ce5ee095104da020ee64e4790620a6b5c504 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 22 Aug 2024 18:04:59 -0400 Subject: [PATCH 01/17] Support plugin options in CSS wip --- packages/tailwindcss/src/index.test.ts | 169 +++++++++++++++++++++++-- packages/tailwindcss/src/index.ts | 56 ++++++-- packages/tailwindcss/src/plugin-api.ts | 22 +++- 3 files changed, 227 insertions(+), 20 deletions(-) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 15dfaa942131..7a76ad32af4d 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -2,6 +2,7 @@ import fs from 'node:fs' import path from 'node:path' import { describe, expect, it, test } from 'vitest' import { compile } from '.' +import plugin from './plugin' import type { PluginAPI } from './plugin-api' import { compileCss, optimizeCss, run } from './test-utils/run' @@ -1294,12 +1295,12 @@ describe('Parsing themes values from CSS', () => { }) describe('plugins', () => { - test('@plugin can not have a body.', async () => + test('@plugin cannot be nested.', () => expect( compile( css` - @plugin { - color: red; + div { + @plugin "my-plugin"; } `, { @@ -1310,25 +1311,171 @@ describe('plugins', () => { }, }, ), - ).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: \`@plugin\` cannot have a body.]`)) + ).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: \`@plugin\` cannot be nested.]`)) - test('@plugin cannot be nested.', () => + test('@plugin can provide options', async () => { + expect.hasAssertions() + + let { build } = await compile( + css` + @tailwind utilities; + @plugin { + color: red; + } + `, + { + loadPlugin: async () => { + return plugin.withOptions((options) => { + expect(options).toEqual({ + color: 'red', + }) + + return ({ addUtilities }) => { + addUtilities({ + '.text-primary': { + color: options.color, + }, + }) + } + }) + }, + }, + ) + + let compiled = build(['text-primary']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + ".text-primary { + color: red; + }" + `) + }) + + test('@plugin options can specify null, booleans, or numbers', async () => { + expect.hasAssertions() + + await compile( + css` + @tailwind utilities; + @plugin { + is-null: null; + is-true: true; + is-false: false; + is-int: 1234567; + is-float: 1.35; + is-sci: 1.35e-5; + } + `, + { + loadPlugin: async () => { + return plugin.withOptions((options) => { + expect(options).toEqual({ + 'is-null': null, + 'is-true': true, + 'is-false': false, + 'is-int': 1234567, + 'is-float': 1.35, + 'is-sci': 1.35e-5, + }) + + return () => {} + }) + }, + }, + ) + }) + + test('@plugin options can only be simple key/value pairs', () => { expect( compile( css` - div { - @plugin "my-plugin"; + @plugin { + color: red; + sizes { + sm: 1rem; + md: 2rem; + } } `, { loadPlugin: async () => { - return ({ addVariant }: PluginAPI) => { - addVariant('hocus', '&:hover, &:focus') - } + return plugin.withOptions((options) => { + return ({ addUtilities }) => { + addUtilities({ + '.text-primary': { + color: options.color, + }, + }) + } + }) }, }, ), - ).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: \`@plugin\` cannot be nested.]`)) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: \`@plugin\` can only contain declarations.]`, + ) + }) + + test('@plugin options can only be provided to plugins using withOptions', () => { + expect( + compile( + css` + @plugin "my-plugin" { + color: red; + } + `, + { + loadPlugin: async () => { + return plugin(({ addUtilities }) => { + addUtilities({ + '.text-primary': { + color: 'red', + }, + }) + }) + }, + }, + ), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: The plugin "my-plugin" does not accept options]`, + ) + }) + + test('@plugin errors on array-like syntax', () => { + expect( + compile( + css` + @plugin "my-plugin" { + --color: [ 'red', 'green', 'blue']; + } + `, + { + loadPlugin: async () => plugin(() => {}), + }, + ), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Arrays are not supported in \`@plugin\` options.]`, + ) + }) + + test('@plugin errors on object-like syntax', () => { + expect( + compile( + css` + @plugin "my-plugin" { + --color: { + red: 100; + green: 200; + blue: 300; + }; + } + `, + { + loadPlugin: async () => plugin(() => {}), + }, + ), + ).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Objects are not supported in \`@plugin\` options.]`) + }) test('addVariant with string selector', async () => { let { build } = await compile( diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index c7f3916c8ad3..bb7f5fe1aeba 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -6,7 +6,7 @@ import { compileCandidates } from './compile' import * as CSS from './css-parser' import { buildDesignSystem, type DesignSystem } from './design-system' import { substituteFunctions, THEME_FUNCTION_INVOCATION } from './functions' -import { registerPlugins, type Plugin } from './plugin-api' +import { registerPlugins, type CssPluginOptions, type Plugin } from './plugin-api' import { Theme } from './theme' import { segment } from './utils/segment' @@ -48,7 +48,7 @@ async function parseCss( // Find all `@theme` declarations let theme = new Theme() - let pluginPaths: string[] = [] + let pluginPaths: [string, CssPluginOptions | null][] = [] let configPaths: string[] = [] let customVariants: ((designSystem: DesignSystem) => void)[] = [] let customUtilities: ((designSystem: DesignSystem) => void)[] = [] @@ -61,15 +61,49 @@ async function parseCss( // Collect paths from `@plugin` at-rules if (node.selector === '@plugin' || node.selector.startsWith('@plugin ')) { - if (node.nodes.length > 0) { - throw new Error('`@plugin` cannot have a body.') - } - if (parent !== null) { throw new Error('`@plugin` cannot be nested.') } - pluginPaths.push(node.selector.slice(9, -1)) + let options: CssPluginOptions = {} + + for (let decl of node.nodes ?? []) { + if (decl.kind !== 'declaration') { + throw new Error('`@plugin` can only contain declarations.') + } + + if (decl.value === undefined) continue + + // Parse the declaration value as a primitive type + // These are the same primitive values supported by JSON + let value: CssPluginOptions[keyof CssPluginOptions] = decl.value + + if (value === 'null') { + value = null + } else if (value === 'true') { + value = true + } else if (value === 'false') { + value = false + } else if (!Number.isNaN(Number(value))) { + value = Number(value) + } else if (value[0] === '"' && value[value.length - 1] === '"') { + value = value.slice(1, -1) + } else if (value[0] === "'" && value[value.length - 1] === "'") { + value = value.slice(1, -1) + } else if (value[0] === '[' && value[value.length - 1] === ']') { + throw new Error('Arrays are not supported in `@plugin` options.') + } else if (value[0] === '{' && value[value.length - 1] === '}') { + throw new Error('Objects are not supported in `@plugin` options.') + } + + options[decl.property] = value + } + + pluginPaths.push([ + node.selector.slice(9, -1), + Object.keys(options).length > 0 ? options : null, + ]) + replaceWith([]) return } @@ -304,7 +338,13 @@ async function parseCss( })), ) - let plugins = await Promise.all(pluginPaths.map(loadPlugin)) + let plugins = await Promise.all( + pluginPaths.map(async ([pluginPath, pluginOptions]) => ({ + path: pluginPath, + plugin: await loadPlugin(pluginPath), + options: pluginOptions, + })), + ) let { pluginApi, resolvedConfig } = registerPlugins(plugins, designSystem, ast, configs) diff --git a/packages/tailwindcss/src/plugin-api.ts b/packages/tailwindcss/src/plugin-api.ts index 6f86f30b00fe..0f33f0d609cb 100644 --- a/packages/tailwindcss/src/plugin-api.ts +++ b/packages/tailwindcss/src/plugin-api.ts @@ -346,12 +346,32 @@ function objectToAst(rules: CssInJs | CssInJs[]): AstNode[] { return ast } +export type CssPluginOptions = Record + +interface PluginDetail { + path: string + plugin: Plugin + options: CssPluginOptions | null +} + export function registerPlugins( - plugins: Plugin[], + pluginDetails: PluginDetail[], designSystem: DesignSystem, ast: AstNode[], configs: ConfigFile[], ) { + let plugins = pluginDetails.map((detail) => { + if (!detail.options) { + return detail.plugin + } + + if ('__isOptionsFunction' in detail.plugin) { + return detail.plugin(detail.options) + } + + throw new Error(`The plugin "${detail.path}" does not accept options`) + }) + let userConfig = [{ config: { plugins } }, ...configs] let resolvedConfig = resolveConfig(designSystem, [ From e4648a670a874108a3b9ffb274d44c242f47caef Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 26 Aug 2024 16:04:01 -0400 Subject: [PATCH 02/17] Add integration test --- integrations/cli/plugins.test.ts | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/integrations/cli/plugins.test.ts b/integrations/cli/plugins.test.ts index 0dce0c2bcd98..6fe04f022175 100644 --- a/integrations/cli/plugins.test.ts +++ b/integrations/cli/plugins.test.ts @@ -77,6 +77,51 @@ test( }, ) +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` + + + `, + '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`, + `[type='checkbox']:indeterminate`, + ]) + + // 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`, + ]) + }, +) + test( 'builds the `tailwindcss-animate` plugin utilities', { From 758606deb81ca02c8d3a1cc67c0bba7c54d6c358 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 26 Aug 2024 16:21:09 -0400 Subject: [PATCH 03/17] wip --- packages/tailwindcss/src/index.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 7a76ad32af4d..2e67db465b1b 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1474,7 +1474,9 @@ describe('plugins', () => { loadPlugin: async () => plugin(() => {}), }, ), - ).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Objects are not supported in \`@plugin\` options.]`) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Objects are not supported in \`@plugin\` options.]`, + ) }) test('addVariant with string selector', async () => { From 05df9fcf6e3295f151358d50d0e83dc46ca7f10f Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 26 Aug 2024 16:21:36 -0400 Subject: [PATCH 04/17] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c3c14e4e479..5e7867098813 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add new standalone builds of Tailwind CSS v4 ([#14270](https://github.com/tailwindlabs/tailwindcss/pull/14270)) - Support JavaScript configuration files using `@config` ([#14239](https://github.com/tailwindlabs/tailwindcss/pull/14239)) +- Add support for `@config` to load V3 JavaScript config files ([#14239](https://github.com/tailwindlabs/tailwindcss/pull/14239)) +- Support plugin options in CSS ([#14264](https://github.com/tailwindlabs/tailwindcss/pull/14264)) ### Fixed From 7d55a1460e2cbb944253a59d0aecc4a6a1fae74b Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Tue, 27 Aug 2024 14:54:26 +0200 Subject: [PATCH 05/17] Improve test harness and error wording --- packages/tailwindcss/src/index.test.ts | 46 ++++++++++++++++++++++---- packages/tailwindcss/src/index.ts | 34 ++++++++++++------- 2 files changed, 62 insertions(+), 18 deletions(-) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 2e67db465b1b..247e2806db97 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1319,7 +1319,7 @@ describe('plugins', () => { let { build } = await compile( css` @tailwind utilities; - @plugin { + @plugin "my-plugin" { color: red; } `, @@ -1357,13 +1357,19 @@ describe('plugins', () => { await compile( css` @tailwind utilities; - @plugin { + @plugin "my-plugin" { is-null: null; is-true: true; is-false: false; is-int: 1234567; is-float: 1.35; is-sci: 1.35e-5; + is-str-null: 'null'; + is-str-true: 'true'; + is-str-false: 'false'; + is-str-int: '1234567'; + is-str-float: '1.35'; + is-str-sci: '1.35e-5'; } `, { @@ -1376,6 +1382,12 @@ describe('plugins', () => { 'is-int': 1234567, 'is-float': 1.35, 'is-sci': 1.35e-5, + 'is-str-null': 'null', + 'is-str-true': 'true', + 'is-str-false': 'false', + 'is-str-int': '1234567', + 'is-str-float': '1.35', + 'is-str-sci': '1.35e-5', }) return () => {} @@ -1389,7 +1401,7 @@ describe('plugins', () => { expect( compile( css` - @plugin { + @plugin "my-plugin" { color: red; sizes { sm: 1rem; @@ -1412,7 +1424,17 @@ describe('plugins', () => { }, ), ).rejects.toThrowErrorMatchingInlineSnapshot( - `[Error: \`@plugin\` can only contain declarations.]`, + ` + [Error: Unexpected \`@plugin\` option: + + sizes { + sm: 1rem; + md: 2rem; + } + + + Plugins can only contain a flat list of declarations.] + `, ) }) @@ -1454,7 +1476,11 @@ describe('plugins', () => { }, ), ).rejects.toThrowErrorMatchingInlineSnapshot( - `[Error: Arrays are not supported in \`@plugin\` options.]`, + ` + [Error: Unexpected \`@plugin\` option: Value of declaration \`--color: [ 'red', 'green', 'blue'];\` is not supported. + + It looks like you want to pass an array to plugin options. This is not supported in CSS.] + `, ) }) @@ -1475,7 +1501,15 @@ describe('plugins', () => { }, ), ).rejects.toThrowErrorMatchingInlineSnapshot( - `[Error: Objects are not supported in \`@plugin\` options.]`, + ` + [Error: Unexpected \`@plugin\` option: Value of declaration \`--color: { + red: 100; + green: 200; + blue: 300; + };\` is not supported. + + It looks like you want to pass an object to plugin options. This is not supported in CSS.] + `, ) }) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index bb7f5fe1aeba..e1ace6bd6012 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -65,11 +65,18 @@ async function parseCss( throw new Error('`@plugin` cannot be nested.') } + let pluginPath = node.selector.slice(9, -1) + if (pluginPath.length === 0) { + throw new Error('`@plugin` must have a path.') + } + let options: CssPluginOptions = {} for (let decl of node.nodes ?? []) { if (decl.kind !== 'declaration') { - throw new Error('`@plugin` can only contain declarations.') + throw new Error( + `Unexpected \`@plugin\` option:\n\n${toCss([decl])}\n\nPlugins can only contain a flat list of declarations.`, + ) } if (decl.value === undefined) continue @@ -86,23 +93,26 @@ async function parseCss( value = false } else if (!Number.isNaN(Number(value))) { value = Number(value) - } else if (value[0] === '"' && value[value.length - 1] === '"') { + } else if ( + (value[0] === '"' && value[value.length - 1] === '"') || + (value[0] === "'" && value[value.length - 1] === "'") + ) { value = value.slice(1, -1) - } else if (value[0] === "'" && value[value.length - 1] === "'") { - value = value.slice(1, -1) - } else if (value[0] === '[' && value[value.length - 1] === ']') { - throw new Error('Arrays are not supported in `@plugin` options.') - } else if (value[0] === '{' && value[value.length - 1] === '}') { - throw new Error('Objects are not supported in `@plugin` options.') + } else if ( + (value[0] === '[' && value[value.length - 1] === ']') || + (value[0] === '{' && value[value.length - 1] === '}') + ) { + throw new Error( + `Unexpected \`@plugin\` option: Value of declaration \`${toCss([decl]).trim()}\` is not supported.\n\nIt looks like you want to pass an ${ + value[0] === '[' ? 'array' : 'object' + } to plugin options. This is not supported in CSS.`, + ) } options[decl.property] = value } - pluginPaths.push([ - node.selector.slice(9, -1), - Object.keys(options).length > 0 ? options : null, - ]) + pluginPaths.push([pluginPath, Object.keys(options).length > 0 ? options : null]) replaceWith([]) return From 57323a1ed5156087e97d949ccd0fb79ac0039e85 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Wed, 28 Aug 2024 15:24:04 +0200 Subject: [PATCH 06/17] Add tests for empty path case --- packages/tailwindcss/src/index.test.ts | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 247e2806db97..4519fba38971 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1295,6 +1295,38 @@ describe('Parsing themes values from CSS', () => { }) describe('plugins', () => { + test('@plugin need a path', () => + expect( + compile( + css` + @plugin; + `, + { + loadPlugin: async () => { + return ({ addVariant }: PluginAPI) => { + addVariant('hocus', '&:hover, &:focus') + } + }, + }, + ), + ).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: \`@plugin\` must have a path.]`)) + + test('@plugin can not have an empty path', () => + expect( + compile( + css` + @plugin ''; + `, + { + loadPlugin: async () => { + return ({ addVariant }: PluginAPI) => { + addVariant('hocus', '&:hover, &:focus') + } + }, + }, + ), + ).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: \`@plugin\` must have a path.]`)) + test('@plugin cannot be nested.', () => expect( compile( From c2479ee2bceffe0ab45882817593fc1b9e8912a6 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 2 Sep 2024 18:12:11 +0200 Subject: [PATCH 07/17] turn comma separated values into arrays --- packages/tailwindcss/src/index.ts | 52 ++++++++++++++------------ packages/tailwindcss/src/plugin-api.ts | 3 +- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index e1ace6bd6012..81d11062e567 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -85,31 +85,35 @@ async function parseCss( // These are the same primitive values supported by JSON let value: CssPluginOptions[keyof CssPluginOptions] = decl.value - if (value === 'null') { - value = null - } else if (value === 'true') { - value = true - } else if (value === 'false') { - value = false - } else if (!Number.isNaN(Number(value))) { - value = Number(value) - } else if ( - (value[0] === '"' && value[value.length - 1] === '"') || - (value[0] === "'" && value[value.length - 1] === "'") - ) { - value = value.slice(1, -1) - } else if ( - (value[0] === '[' && value[value.length - 1] === ']') || - (value[0] === '{' && value[value.length - 1] === '}') - ) { - throw new Error( - `Unexpected \`@plugin\` option: Value of declaration \`${toCss([decl]).trim()}\` is not supported.\n\nIt looks like you want to pass an ${ - value[0] === '[' ? 'array' : 'object' - } to plugin options. This is not supported in CSS.`, - ) - } + let parts = segment(value, ',').map((part) => { + if (part === 'null') { + return null + } else if (part === 'true') { + return true + } else if (part === 'false') { + return false + } else if (!Number.isNaN(Number(part))) { + return Number(part) + } else if ( + (part[0] === '"' && part[part.length - 1] === '"') || + (part[0] === "'" && part[part.length - 1] === "'") + ) { + return part.slice(1, -1) + } else if ( + (part[0] === '[' && part[part.length - 1] === ']') || + (part[0] === '{' && part[part.length - 1] === '}') + ) { + throw new Error( + `Unexpected \`@plugin\` option: Value of declaration \`${toCss([decl]).trim()}\` is not supported.\n\nIt looks like you want to pass an ${ + part[0] === '[' ? 'array' : 'object' + } to plugin options. This is not supported in CSS.`, + ) + } + + return value + }) - options[decl.property] = value + options[decl.property] = parts.length === 1 ? parts[0] : parts } pluginPaths.push([pluginPath, Object.keys(options).length > 0 ? options : null]) diff --git a/packages/tailwindcss/src/plugin-api.ts b/packages/tailwindcss/src/plugin-api.ts index 0f33f0d609cb..53732e37b740 100644 --- a/packages/tailwindcss/src/plugin-api.ts +++ b/packages/tailwindcss/src/plugin-api.ts @@ -346,7 +346,8 @@ function objectToAst(rules: CssInJs | CssInJs[]): AstNode[] { return ast } -export type CssPluginOptions = Record +type Primitive = string | number | boolean | null +export type CssPluginOptions = Record interface PluginDetail { path: string From 91151c11c8e4264c9cd027a42effb26143167686 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 2 Sep 2024 12:17:50 -0400 Subject: [PATCH 08/17] Add tests --- packages/tailwindcss/src/index.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 4519fba38971..03478e6231dc 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1402,6 +1402,8 @@ describe('plugins', () => { is-str-int: '1234567'; is-str-float: '1.35'; is-str-sci: '1.35e-5'; + is-arr: foo, bar; + is-arr-mixed: null, true, false, 1234567, 1.35, foo, 'bar', 'true'; } `, { @@ -1420,6 +1422,8 @@ describe('plugins', () => { 'is-str-int': '1234567', 'is-str-float': '1.35', 'is-str-sci': '1.35e-5', + 'is-arr': ['foo', 'bar'], + 'is-arr-mixed': [null, true, false, 1234567, 1.35, 'foo', 'bar', 'true'], }) return () => {} From 973da410b7736625278f4c89b2edde7811857249 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 2 Sep 2024 12:17:57 -0400 Subject: [PATCH 09/17] Fix bugs --- packages/tailwindcss/src/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 81d11062e567..4cd09fb8cd3d 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -86,6 +86,8 @@ async function parseCss( let value: CssPluginOptions[keyof CssPluginOptions] = decl.value let parts = segment(value, ',').map((part) => { + part = part.trim() + if (part === 'null') { return null } else if (part === 'true') { @@ -110,7 +112,7 @@ async function parseCss( ) } - return value + return part }) options[decl.property] = parts.length === 1 ? parts[0] : parts From b32366daa438f7fb7cd4b30f6ce4134a2382d9eb Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 2 Sep 2024 12:30:23 -0400 Subject: [PATCH 10/17] Update changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e7867098813..833ae86c86cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add new standalone builds of Tailwind CSS v4 ([#14270](https://github.com/tailwindlabs/tailwindcss/pull/14270)) - Support JavaScript configuration files using `@config` ([#14239](https://github.com/tailwindlabs/tailwindcss/pull/14239)) -- Add support for `@config` to load V3 JavaScript config files ([#14239](https://github.com/tailwindlabs/tailwindcss/pull/14239)) - Support plugin options in CSS ([#14264](https://github.com/tailwindlabs/tailwindcss/pull/14264)) ### Fixed From 0e0943744ebdea2da4d2e4ec257cc7d969e734a0 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Mon, 2 Sep 2024 12:31:04 -0400 Subject: [PATCH 11/17] Update packages/tailwindcss/src/index.test.ts --- packages/tailwindcss/src/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 03478e6231dc..0d7c5216adf1 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1345,7 +1345,7 @@ describe('plugins', () => { ), ).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: \`@plugin\` cannot be nested.]`)) - test('@plugin can provide options', async () => { + test('@plugin can accept options', async () => { expect.hasAssertions() let { build } = await compile( From a65f6df858996344173ef2d6a74a456d3c3faaf8 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Mon, 2 Sep 2024 12:31:50 -0400 Subject: [PATCH 12/17] Update packages/tailwindcss/src/index.test.ts --- packages/tailwindcss/src/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 0d7c5216adf1..f0bc3212bdee 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1383,7 +1383,7 @@ describe('plugins', () => { `) }) - test('@plugin options can specify null, booleans, or numbers', async () => { + test('@plugin options can be null, booleans, string, numbers, or arrays including those types', async () => { expect.hasAssertions() await compile( From 2075109e68c163b527e4e097923a7e7da1313676 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Mon, 2 Sep 2024 12:33:46 -0400 Subject: [PATCH 13/17] Update packages/tailwindcss/src/index.ts --- packages/tailwindcss/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 4cd09fb8cd3d..c687178b714d 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -75,7 +75,7 @@ async function parseCss( for (let decl of node.nodes ?? []) { if (decl.kind !== 'declaration') { throw new Error( - `Unexpected \`@plugin\` option:\n\n${toCss([decl])}\n\nPlugins can only contain a flat list of declarations.`, + `Unexpected \`@plugin\` option:\n\n${toCss([decl])}\n\n\`@plugin\` options must be a flat list of declarations.`, ) } From b2642bab5dbabeff271925f03f4a48f3f7618c80 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 2 Sep 2024 12:35:05 -0400 Subject: [PATCH 14/17] Fix snapshot --- packages/tailwindcss/src/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index f0bc3212bdee..d51e21b793ba 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1469,7 +1469,7 @@ describe('plugins', () => { } - Plugins can only contain a flat list of declarations.] + \`@plugin\` options must be a flat list of declarations.] `, ) }) From b7441672b35d3d684b75d326afe00e5a56e974d1 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 2 Sep 2024 12:38:09 -0400 Subject: [PATCH 15/17] Tweak error messages --- packages/tailwindcss/src/index.test.ts | 8 ++------ packages/tailwindcss/src/index.ts | 9 ++------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index d51e21b793ba..a22097a60b66 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1512,11 +1512,7 @@ describe('plugins', () => { }, ), ).rejects.toThrowErrorMatchingInlineSnapshot( - ` - [Error: Unexpected \`@plugin\` option: Value of declaration \`--color: [ 'red', 'green', 'blue'];\` is not supported. - - It looks like you want to pass an array to plugin options. This is not supported in CSS.] - `, + `[Error: The plugin "my-plugin" does not accept options]`, ) }) @@ -1544,7 +1540,7 @@ describe('plugins', () => { blue: 300; };\` is not supported. - It looks like you want to pass an object to plugin options. This is not supported in CSS.] + Using an object as a plugin option is currently only supported in JavaScript configuration files.] `, ) }) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index c687178b714d..2af735f0ba7a 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -101,14 +101,9 @@ async function parseCss( (part[0] === "'" && part[part.length - 1] === "'") ) { return part.slice(1, -1) - } else if ( - (part[0] === '[' && part[part.length - 1] === ']') || - (part[0] === '{' && part[part.length - 1] === '}') - ) { + } else if (part[0] === '{' && part[part.length - 1] === '}') { throw new Error( - `Unexpected \`@plugin\` option: Value of declaration \`${toCss([decl]).trim()}\` is not supported.\n\nIt looks like you want to pass an ${ - part[0] === '[' ? 'array' : 'object' - } to plugin options. This is not supported in CSS.`, + `Unexpected \`@plugin\` option: Value of declaration \`${toCss([decl]).trim()}\` is not supported.\n\nUsing an object as a plugin option is currently only supported in JavaScript configuration files.`, ) } From f056b9cd3895d666cda841469a7ba6feaa7f0436 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Mon, 2 Sep 2024 12:44:00 -0400 Subject: [PATCH 16/17] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 833ae86c86cc..77592c99efce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add new standalone builds of Tailwind CSS v4 ([#14270](https://github.com/tailwindlabs/tailwindcss/pull/14270)) - Support JavaScript configuration files using `@config` ([#14239](https://github.com/tailwindlabs/tailwindcss/pull/14239)) -- Support plugin options in CSS ([#14264](https://github.com/tailwindlabs/tailwindcss/pull/14264)) +- Support plugin options in `@plugin` in CSS ([#14264](https://github.com/tailwindlabs/tailwindcss/pull/14264)) ### Fixed From 220f37fbee20db22eab726d08e7b77c415bf5f9c Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Mon, 2 Sep 2024 12:44:31 -0400 Subject: [PATCH 17/17] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77592c99efce..cd1132782320 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add new standalone builds of Tailwind CSS v4 ([#14270](https://github.com/tailwindlabs/tailwindcss/pull/14270)) - Support JavaScript configuration files using `@config` ([#14239](https://github.com/tailwindlabs/tailwindcss/pull/14239)) -- Support plugin options in `@plugin` in CSS ([#14264](https://github.com/tailwindlabs/tailwindcss/pull/14264)) +- Support plugin options in `@plugin` ([#14264](https://github.com/tailwindlabs/tailwindcss/pull/14264)) ### Fixed