diff --git a/packages/tailwindcss/src/plugins.test.ts b/packages/tailwindcss/src/plugins.test.ts index 7212be371..274e4a829 100644 --- a/packages/tailwindcss/src/plugins.test.ts +++ b/packages/tailwindcss/src/plugins.test.ts @@ -1,14 +1,64 @@ -import { expect, test } from 'vitest' +import { test, vi } from 'vitest' import type { UserConfig } from './config' import { run } from './test-utils/run' -test('Custom static utilities', () => { +test('Plugins are called', ({ expect }) => { + let fn = vi.fn() + let handler = vi.fn() + let config: UserConfig = { + plugins: [fn, { handler }], + } + + run([], config) + + expect(fn).toHaveBeenCalledWith( + expect.objectContaining({ + addUtility: expect.any(Function), + addUtilities: expect.any(Function), + addVariant: expect.any(Function), + addVariants: expect.any(Function), + }), + ) + + expect(handler).toHaveBeenCalledWith( + expect.objectContaining({ + addUtility: expect.any(Function), + addUtilities: expect.any(Function), + addVariant: expect.any(Function), + addVariants: expect.any(Function), + }), + ) +}) + +test('Asynchronous plugins are waited on', async ({ expect }) => { + let before = vi.fn() + let after = vi.fn() + let config: UserConfig = { + plugins: [ + async () => { + before() + await new Promise((resolve) => setTimeout(resolve)) + after() + }, + ], + } + + await run([], config) + + expect(before).toHaveBeenCalled() + expect(after).toHaveBeenCalled() +}) + +test('Custom static utilities', ({ expect }) => { let config: UserConfig = { plugins: [ ({ addUtilities }) => { addUtilities({ '.my-red': { color: 'red', + '& > *': { + color: 'lightred', + }, }, '.my-blue': { color: 'red', @@ -26,7 +76,7 @@ test('Custom static utilities', () => { expect(run(['-my-red', 'my-red-[--value]'], config)).toEqual('') }) -test('Custom functional utilities', () => { +test('Custom functional utilities', ({ expect }) => { let config: UserConfig = { plugins: [ ({ addUtilities }) => { @@ -54,7 +104,7 @@ test('Custom functional utilities', () => { `) }) -test('Custom static variants', () => { +test('Custom static variants', ({ expect }) => { let config: UserConfig = { plugins: [ ({ addVariant }) => { @@ -72,7 +122,7 @@ test('Custom static variants', () => { expect(run(['hover/foo:underline', 'hover-[123]:underline'], config)).toEqual('') }) -test('Custom functional variants', () => { +test('Custom functional variants', ({ expect }) => { let config: UserConfig = { plugins: [ ({ addVariant }) => { diff --git a/packages/tailwindcss/src/plugins.ts b/packages/tailwindcss/src/plugins.ts index fa6a73e38..ce06ed16a 100644 --- a/packages/tailwindcss/src/plugins.ts +++ b/packages/tailwindcss/src/plugins.ts @@ -2,27 +2,35 @@ import { decl, rule, type AstNode, type Rule } from './ast' import type { Candidate } from './candidate' import type { DesignSystem } from './design-system' -type MatchData = { +export type MatchData = { value: string | null modifier: string | null } -type CssSelectors = string | string[] +export type SelectorFormat = CssSelectors | ((candidate: MatchData) => CssSelectors) +export type UtilityFormat = CssTree | ((value: string | null, candidate: MatchData) => CssTree) +export type CssSelectors = string | string[] export interface CssTree extends Record {} -type SelectorFormat = CssSelectors | ((candidate: MatchData) => CssSelectors) -type UtilityFormat = CssTree | ((value: string | null, candidate: MatchData) => CssTree) - export interface PluginAPI { - addVariant(name: string, selector: SelectorFormat): void + addVariant(name: string, format: SelectorFormat): void + addVariants(variants: Record): void + + addUtility(name: string, format: UtilityFormat): void addUtilities(utilities: Record): void } -export interface Plugin { +export type Plugin = FunctionalPlugin | ConfigurablePlugin + +export interface FunctionalPlugin { (api: PluginAPI): void | Promise } +export interface ConfigurablePlugin { + handler(api: PluginAPI): void | Promise +} + /** * Convert a recursive object structure to an AST * Leaf nodes are declarations and branches are rules @@ -141,11 +149,21 @@ export function registerPlugins(design: DesignSystem) { ) } + function addVariants(list: Record) { + for (let [name, format] of Object.entries(list)) { + addVariant(name, format) + } + } + for (let plugin of design.plugins) { - plugin({ + let handler = typeof plugin === 'function' ? plugin : plugin.handler + + handler({ // v3 compatible legacy API + addUtility, addUtilities, addVariant, + addVariants, }) } }