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 <noreply@anthropic.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This commit is contained in:
silverwind 2026-04-29 20:48:08 +02:00 • committed by GitHub
parent f036f80195
commit 6cf1af26b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 156 additions and 2 deletions

View file

@ -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

View file

@ -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`
<div class="text-primary"></div>
`,
'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)',
{

View file

@ -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`
<div class="test-red"></div>
`,
'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`])
},
)

View file

@ -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]+$/

View file

@ -41,5 +41,6 @@ export type {
PluginFn as PluginCreator,
Plugin as PluginsConfig,
PluginUtils,
PluginWithConfig,
ThemeConfig,
}