diff --git a/packages/tailwindcss/src/compat/config/resolve-config.ts b/packages/tailwindcss/src/compat/config/resolve-config.ts index c119b29e4..3a1c0e8d1 100644 --- a/packages/tailwindcss/src/compat/config/resolve-config.ts +++ b/packages/tailwindcss/src/compat/config/resolve-config.ts @@ -1,4 +1,5 @@ import type { DesignSystem } from '../../design-system' +import type { PluginWithConfig } from '../../plugin-api' import { createThemeFn } from '../../theme-fn' import { deepMerge, isPlainObject } from './deep-merge' import { @@ -11,6 +12,7 @@ import { interface ResolutionContext { design: DesignSystem configs: UserConfig[] + plugins: PluginWithConfig[] theme: Record extend: Record result: ResolvedConfig @@ -18,12 +20,14 @@ interface ResolutionContext { let minimal: ResolvedConfig = { theme: {}, + plugins: [], } export function resolveConfig(design: DesignSystem, configs: UserConfig[]): ResolvedConfig { let ctx: ResolutionContext = { design, - configs, + configs: [], + plugins: [], theme: {}, extend: {}, @@ -31,11 +35,16 @@ export function resolveConfig(design: DesignSystem, configs: UserConfig[]): Reso result: structuredClone(minimal), } + for (let config of configs) { + resolveInternal(ctx, config) + } + // Merge themes mergeTheme(ctx) return { theme: ctx.theme as ResolvedConfig['theme'], + plugins: ctx.plugins, } } @@ -71,6 +80,41 @@ export interface PluginUtils { theme(keypath: string, defaultValue?: any): any } +function resolveInternal(ctx: ResolutionContext, user: UserConfig): void { + let plugins: PluginWithConfig[] = [] + + // Normalize plugins so they share the same shape + for (let plugin of user.plugins ?? []) { + if ('__isOptionsFunction' in plugin) { + // Happens with `plugin.withOptions()` when no options were passed: + // e.g. `require("my-plugin")` instead of `require("my-plugin")(options)` + plugins.push(plugin()) + } else if ('handler' in plugin) { + // Happens with `plugin(…)`: + // e.g. `require("my-plugin")` + // + // or with `plugin.withOptions()` when the user passed options: + // e.g. `require("my-plugin")(options)` + plugins.push(plugin) + } else { + // Just a plain function without using the plugin(…) API + plugins.push({ handler: plugin }) + } + } + + // Apply configs from plugins + for (let plugin of plugins) { + ctx.plugins.push(plugin) + + if (plugin.config) { + resolveInternal(ctx, plugin.config) + } + } + + // Then apply the "user" config + ctx.configs.push(user) +} + function mergeTheme(ctx: ResolutionContext) { let api: PluginUtils = { theme: createThemeFn(ctx.design, () => ctx.theme, resolveValue), diff --git a/packages/tailwindcss/src/compat/config/types.ts b/packages/tailwindcss/src/compat/config/types.ts index db28725d7..187f2af84 100644 --- a/packages/tailwindcss/src/compat/config/types.ts +++ b/packages/tailwindcss/src/compat/config/types.ts @@ -1,9 +1,11 @@ +import type { Plugin, PluginWithConfig } from '../../plugin-api' import type { PluginUtils } from './resolve-config' export type ResolvableTo = T | ((utils: PluginUtils) => T) export interface UserConfig { theme?: ThemeConfig + plugins?: Plugin[] } export type ThemeValue = ResolvableTo> | null | undefined @@ -15,4 +17,5 @@ export type ThemeConfig = Record & { export interface ResolvedConfig { theme: Record> + plugins: PluginWithConfig[] } diff --git a/packages/tailwindcss/src/plugin-api.ts b/packages/tailwindcss/src/plugin-api.ts index 9cb2aece3..1a161f956 100644 --- a/packages/tailwindcss/src/plugin-api.ts +++ b/packages/tailwindcss/src/plugin-api.ts @@ -326,37 +326,19 @@ function objectToAst(rules: CssInJs | CssInJs[]): AstNode[] { return ast } -export function registerPlugins(plugins: Plugin[], designSystem: DesignSystem, ast: AstNode[]) { - let pluginObjects = [] - - for (let plugin of plugins) { - if ('__isOptionsFunction' in plugin) { - // Happens with `plugin.withOptions()` when no options were passed: - // e.g. `require("my-plugin")` instead of `require("my-plugin")(options)` - pluginObjects.push(plugin()) - } else if ('handler' in plugin) { - // Happens with `plugin(…)`: - // e.g. `require("my-plugin")` - // - // or with `plugin.withOptions()` when the user passed options: - // e.g. `require("my-plugin")(options)` - pluginObjects.push(plugin) - } else { - // Just a plain function without using the plugin(…) API - pluginObjects.push({ handler: plugin, config: {} as UserConfig }) - } - } - - // Now merge all the configs and make all that crap work +export function registerPlugins( + plugins: Plugin[], + designSystem: DesignSystem, + ast: AstNode[], +) { let resolvedConfig = resolveConfig(designSystem, [ createCompatConfig(designSystem.theme), - ...pluginObjects.map(({ config }) => config ?? {}), + { plugins }, ]) let pluginApi = buildPluginApi(designSystem, ast, resolvedConfig) - // Loop over the handlers and run them all with the resolved config + CSS theme probably somehow - for (let { handler } of pluginObjects) { + for (let { handler } of resolvedConfig.plugins) { handler(pluginApi) }