Skip to content

Commit 82fa153

Browse files
committed
fix(exports): detect types fields nested in conditional exports
Recursively walk the exports tree so nested type conditions (e.g. exports['.'].import.types) are picked up as a signal for auto-enabling dts generation. Previously only top-level and exports['.'].types were checked. Closes #885
1 parent c3ad60e commit 82fa153

3 files changed

Lines changed: 69 additions & 31 deletions

File tree

src/config/options.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,9 @@ export async function resolveUserConfig(
171171
exe = resolveFeatureOption(exe, {})
172172

173173
if (dts == null) {
174-
dts = exe ? false : !!(pkg?.types || pkg?.typings || hasExportsTypes(pkg))
174+
dts = exe
175+
? false
176+
: !!(pkg?.types || pkg?.typings || hasExportsTypes(pkg?.exports))
175177
}
176178
dts = resolveFeatureOption(dts, {})
177179

src/features/pkg/exports.test.ts

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import path from 'node:path'
22
import process from 'node:process'
3-
import { describe, test } from 'vitest'
3+
import { describe, expect, test } from 'vitest'
44
import { globalLogger } from '../../utils/logger.ts'
5-
import { generateExports as _generateExports } from './exports.ts'
5+
import {
6+
generateExports as _generateExports,
7+
hasExportsTypes,
8+
} from './exports.ts'
69
import type { ResolvedConfig } from '../../config/types.ts'
710
import type { ChunksByFormat, RolldownChunk } from '../../utils/chunks.ts'
811

@@ -1149,6 +1152,62 @@ describe('generateExports', () => {
11491152
})
11501153
})
11511154

1155+
describe('hasExportsTypes', () => {
1156+
test('nullish or string', () => {
1157+
expect(hasExportsTypes(undefined)).toBe(false)
1158+
expect(hasExportsTypes(null)).toBe(false)
1159+
expect(hasExportsTypes('./index.js')).toBe(false)
1160+
})
1161+
1162+
test('top-level types condition', () => {
1163+
expect(
1164+
hasExportsTypes({ types: './index.d.ts', default: './index.js' }),
1165+
).toBe(true)
1166+
})
1167+
1168+
test('types under "."', () => {
1169+
expect(
1170+
hasExportsTypes({
1171+
'.': { types: './index.d.ts', default: './index.js' },
1172+
}),
1173+
).toBe(true)
1174+
})
1175+
1176+
test('types nested under "import" / "require" (issue #885)', () => {
1177+
expect(
1178+
hasExportsTypes({
1179+
'.': {
1180+
import: { default: './esm/index.js', types: './esm/index.d.ts' },
1181+
require: { default: './cjs/index.cjs', types: './cjs/index.d.cts' },
1182+
},
1183+
}),
1184+
).toBe(true)
1185+
})
1186+
1187+
test('types under arbitrary subpath', () => {
1188+
expect(
1189+
hasExportsTypes({
1190+
'./utils': { types: './utils.d.ts', default: './utils.js' },
1191+
}),
1192+
).toBe(true)
1193+
})
1194+
1195+
test('types in array fallback', () => {
1196+
expect(
1197+
hasExportsTypes({ '.': [{ types: './index.d.ts' }, './index.js'] }),
1198+
).toBe(true)
1199+
})
1200+
1201+
test('no types anywhere', () => {
1202+
expect(
1203+
hasExportsTypes({
1204+
'.': { import: './esm/index.js', require: './cjs/index.cjs' },
1205+
'./utils': './utils.js',
1206+
}),
1207+
).toBe(false)
1208+
})
1209+
})
1210+
11521211
function genChunk(
11531212
fileName: string,
11541213
isEntry = true,

src/features/pkg/exports.ts

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -487,34 +487,11 @@ function exportCss(
487487
}
488488
}
489489

490-
export function hasExportsTypes(pkg?: PackageJson): boolean {
491-
const exports = pkg?.exports
492-
if (!exports) return false
493-
494-
if (
495-
typeof exports === 'object' &&
496-
exports !== null &&
497-
!Array.isArray(exports)
498-
) {
499-
// Check if exports.types exists
500-
if ('types' in exports) {
501-
return true
502-
}
503-
504-
// Check if exports['.'].types exists
505-
if ('.' in exports) {
506-
const mainExport = exports['.']
507-
if (
508-
typeof mainExport === 'object' &&
509-
mainExport !== null &&
510-
'types' in mainExport
511-
) {
512-
return true
513-
}
514-
}
515-
}
516-
517-
return false
490+
export function hasExportsTypes(value: unknown): boolean {
491+
if (value == null || typeof value !== 'object') return false
492+
if (Array.isArray(value)) return value.some(hasExportsTypes)
493+
if ('types' in value) return true
494+
return Object.values(value).some(hasExportsTypes)
518495
}
519496

520497
const RE_SHEBANG = /^#!.*/

0 commit comments

Comments
 (0)