diff --git a/src/utils.ts b/src/utils.ts index 8f498648..d80ce14b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -180,21 +180,41 @@ export function stripFileExtension(path: string) { return path.replace(/\.[a-z]+$/i, '') } +function groupImportsByName(imports: Import[]): Map { + const map = new Map() + for (const i of imports) { + const name = i.as ?? i.name + if (!map.has(name)) { + map.set(name, []) + } + map.get(name)!.push(i) + } + return map +} + export function toTypeDeclarationItems(imports: Import[], options?: TypeDeclarationOptions) { - return imports - .map((i) => { - const from = options?.resolvePath?.(i) || stripFileExtension(i.typeFrom || i.from) - let typeDef = '' - if (i.with) - typeDef += `import('${from}', { with: ${stringifyWith(i.with)} })` - else - typeDef += `import('${from}')` - - if (i.name !== '*' && i.name !== '=') - typeDef += identifierRE.test(i.name) ? `.${i.name}` : `['${i.name}']` - - return `const ${i.as}: typeof ${typeDef}` - }) + const groupedImports = groupImportsByName(imports) + + function generateTypeDef(i: Import) { + const from = options?.resolvePath?.(i) || stripFileExtension(i.typeFrom || i.from) + let typeDef = '' + if (i.with) + typeDef += `import('${from}', { with: ${stringifyWith(i.with)} })` + else + typeDef += `import('${from}')` + + if (i.name !== '*' && i.name !== '=') + typeDef += identifierRE.test(i.name) ? `.${i.name}` : `['${i.name}']` + return typeDef + } + + const declarations: string[] = [] + for (const [name, importGroup] of groupedImports.entries()) { + const typeDefs = importGroup.map(i => `typeof ${generateTypeDef(i)}`) + declarations.push(`const ${name}: ${typeDefs.join(' | ')}`) + } + + return declarations .sort() } diff --git a/test/dts.test.ts b/test/dts.test.ts index 8a4f688d..967f10ac 100644 --- a/test/dts.test.ts +++ b/test/dts.test.ts @@ -1,5 +1,6 @@ -import { expect, it } from 'vitest' -import { createUnimport } from '../src' +import type { Import } from '../src' +import { describe, expect, it } from 'vitest' +import { createUnimport, toTypeDeclarationFile } from '../src' it('dts', async () => { const cwd = process.cwd().replace(/\\/g, '/') @@ -143,3 +144,22 @@ it('should compat with `export =`', async () => { }" `) }) + +describe('toTypeDeclarationFile', () => { + it('should merge multiple imports into union type', async () => { + const imports: Import[] = [ + { name: 'a', from: 'source-1', as: 'merged' }, + { name: 'b', from: 'source-2', as: 'merged' }, + { name: 'c', from: 'source-3', as: 'merged' }, + { name: 'd', from: 'source-4', as: 'other' }, + ] + + expect(toTypeDeclarationFile(imports)).toMatchInlineSnapshot(` + "export {} + declare global { + const merged: typeof import('source-1').a | typeof import('source-2').b | typeof import('source-3').c + const other: typeof import('source-4').d + }" + `) + }) +})