From 6cf1af26b39f6ddf64bbab92812649e971492f55 Mon Sep 17 00:00:00 2001 From: silverwind Date: Wed, 29 Apr 2026 20:48:08 +0200 Subject: [PATCH] Fix TS2742 error when inferring exported Config type (#19707) Type aliases get resolved through to `UserConfig` during declaration emit, which lives in a hashed internal DTS chunk with no stable import path, causing TS2742. This is 100% backwards-compatible because type aliases and interfaces are structurally comparable. Fixes: https://github.com/tailwindlabs/tailwindcss/issues/15844 Fixes: https://github.com/tailwindlabs/tailwindcss/issues/19706 Fixes: https://github.com/tailwindlabs/tailwindcss/discussions/15458 --------- Co-authored-by: Claude Opus 4.6 Co-authored-by: Robin Malfait --- CHANGELOG.md | 1 + integrations/cli/config.test.ts | 66 +++++++++++++++++++++++ integrations/cli/plugins.test.ts | 87 +++++++++++++++++++++++++++++- packages/tailwindcss/src/index.ts | 3 +- packages/tailwindcss/src/plugin.ts | 1 + 5 files changed, 156 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 540903b7b..2b13ad86b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Canonicalization: migrate arbitrary `:has()` variants from `[&:has(…)]` to `has-[…]` ([#19991](https://github.com/tailwindlabs/tailwindcss/pull/19991)) - Upgrade: don’t migrate inline `style` attributes ([#19918](https://github.com/tailwindlabs/tailwindcss/pull/19918)) - Allow multiple `@utility` definitions with the same name but different value types ([#19777](https://github.com/tailwindlabs/tailwindcss/pull/19777)) +- Export missing `PluginWithConfig` type from `tailwindcss/plugin` to fix errors when inferring plugin config types ([#19707](https://github.com/tailwindlabs/tailwindcss/pull/19707)) ## [4.2.4] - 2026-04-21 diff --git a/integrations/cli/config.test.ts b/integrations/cli/config.test.ts index dd93ade64..dda70814b 100644 --- a/integrations/cli/config.test.ts +++ b/integrations/cli/config.test.ts @@ -126,6 +126,72 @@ test( }, ) +// https://github.com/tailwindlabs/tailwindcss/issues/19706 +test( + 'Config files (TS, valid types)', + { + fs: { + 'package.json': json` + { + "type": "module", + "dependencies": { + "tailwindcss": "workspace:^", + "@tailwindcss/cli": "workspace:^" + }, + "devDependencies": { + "typescript": "^5.9.3" + } + } + `, + 'tsconfig.json': json` + { + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "declaration": true, + "composite": true, + "outDir": "./.build", + "skipLibCheck": true + }, + "include": ["tailwind.config.ts"] + } + `, + 'index.html': html` +
+ `, + 'tailwind.config.ts': ts` + import type { Config } from 'tailwindcss' + + function defineConfig(config: Config): Config { + return config + } + + export default defineConfig({ + theme: { + extend: { + colors: { + primary: 'blue', + }, + }, + }, + }) + `, + 'src/index.css': css` + @import 'tailwindcss'; + @config '../tailwind.config.ts'; + `, + }, + }, + async ({ fs, exec }) => { + // We expect that these commands don't crash: + await exec('pnpm tsc -b') + await exec('pnpm tailwindcss --input src/index.css --output dist/out.css') + + await fs.expectFileToContain('dist/out.css', [candidate`text-primary`]) + }, +) + test( 'Config files (CJS, watch mode)', { diff --git a/integrations/cli/plugins.test.ts b/integrations/cli/plugins.test.ts index c0f7c39c3..1f822a05b 100644 --- a/integrations/cli/plugins.test.ts +++ b/integrations/cli/plugins.test.ts @@ -1,4 +1,4 @@ -import { candidate, css, html, json, test } from '../utils' +import { candidate, css, html, json, test, ts } from '../utils' test( 'builds the `@tailwindcss/typography` plugin utilities', @@ -207,3 +207,88 @@ test( ]) }, ) + +// https://github.com/tailwindlabs/tailwindcss/issues/15844 +test( + 'builds CSS with a custom plugin compiled from TypeScript', + { + fs: { + 'package.json': json` + { + "type": "module", + "dependencies": { + "tailwindcss": "workspace:^", + "@tailwindcss/cli": "workspace:^" + }, + "devDependencies": { + "typescript": "^5.7.2" + } + } + `, + 'tsconfig.json': json` + { + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "declaration": true, + "composite": true, + "rootDir": "./src", + "outDir": "./.build", + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] + } + `, + 'index.html': html` +
+ `, + 'src/index.css': css` + @import 'tailwindcss'; + @plugin '../.build/plugin.js'; + `, + 'src/plugin.ts': ts` + import plugin from 'tailwindcss/plugin' + + export const typedPlugin = plugin(() => { + return ({ matchComponents }) => { + matchComponents( + { + test: (content: string) => ({ + color: content, + }), + }, + { + values: { + red: 'red', + }, + }, + ) + } + }) + + export default plugin(({ matchComponents }) => { + matchComponents( + { + test: (content: string) => ({ + color: content, + }), + }, + { + values: { + red: 'red', + }, + }, + ) + }) + `, + }, + }, + async ({ fs, exec }) => { + // We expect that these commands don't crash: + await exec('pnpm tsc -b') + await exec('pnpm tailwindcss --input src/index.css --output dist/out.css') + + await fs.expectFileToContain('dist/out.css', [candidate`test-red`]) + }, +) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index a511b0838..dc1742d1e 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -34,7 +34,8 @@ import { segment } from './utils/segment' import { topologicalSort } from './utils/topological-sort' import { compoundsForSelectors, IS_VALID_VARIANT_NAME, substituteAtVariant } from './variants' import { walk, WalkAction } from './walk' -export type Config = UserConfig + +export interface Config extends UserConfig {} const IS_VALID_PREFIX = /^[a-z]+$/ diff --git a/packages/tailwindcss/src/plugin.ts b/packages/tailwindcss/src/plugin.ts index 5425f03c1..8035633b0 100644 --- a/packages/tailwindcss/src/plugin.ts +++ b/packages/tailwindcss/src/plugin.ts @@ -41,5 +41,6 @@ export type { PluginFn as PluginCreator, Plugin as PluginsConfig, PluginUtils, + PluginWithConfig, ThemeConfig, }