From daab926adfef0028f8b9c28c51ceada5215ca1a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Egill=20Sveinbj=C3=B6rnsson?= Date: Sun, 5 May 2024 11:36:08 +0200 Subject: [PATCH 1/2] fix: support type only imports --- src/bundle-generator.ts | 13 +++++++++++++ src/generate-output.ts | 10 ++++++++++ .../e2e/test-cases/preserve-type-imports/config.ts | 5 +++++ .../test-cases/preserve-type-imports/index.spec.js | 1 + tests/e2e/test-cases/preserve-type-imports/input.ts | 6 ++++++ .../test-cases/preserve-type-imports/output.d.ts | 8 ++++++++ .../test-cases/preserve-type-imports/tsconfig.json | 6 ++++++ 7 files changed, 49 insertions(+) create mode 100644 tests/e2e/test-cases/preserve-type-imports/config.ts create mode 100644 tests/e2e/test-cases/preserve-type-imports/index.spec.js create mode 100644 tests/e2e/test-cases/preserve-type-imports/input.ts create mode 100644 tests/e2e/test-cases/preserve-type-imports/output.d.ts create mode 100644 tests/e2e/test-cases/preserve-type-imports/tsconfig.json diff --git a/src/bundle-generator.ts b/src/bundle-generator.ts index e33dab8..cf7e70f 100644 --- a/src/bundle-generator.ts +++ b/src/bundle-generator.ts @@ -645,6 +645,7 @@ export function generateDtsBundle(entries: readonly EntryPointConfig[], options: importItem = { defaultImports: new Set(), namedImports: new Map(), + typeImports: new Map(), nsImport: null, requireImports: new Set(), reExports: new Map(), @@ -656,6 +657,12 @@ export function generateDtsBundle(entries: readonly EntryPointConfig[], options: return importItem; } + function addTypeImport(importItem: ModuleImportsSet, preferredLocalName: ts.Identifier, importedIdentifier: ts.Identifier): void { + const newLocalName = collisionsResolver.addTopLevelIdentifier(preferredLocalName); + const importedName = importedIdentifier.text; + importItem.typeImports.set(newLocalName, importedName); + } + function addRequireImport(importItem: ModuleImportsSet, preferredLocalName: ts.Identifier): void { importItem.requireImports.add(collisionsResolver.addTopLevelIdentifier(preferredLocalName)); } @@ -690,6 +697,12 @@ export function generateDtsBundle(entries: readonly EntryPointConfig[], options: const importItem = getImportItem(importModuleSpecifier); + if (ts.isTypeOnlyImportDeclaration(imp)) { + // import { type ImportedType } from 'module'; + addTypeImport(importItem, imp.name, imp.name); + return; + } + if (ts.isImportEqualsDeclaration(imp)) { // import x = require("mod"); addRequireImport(importItem, imp.name); diff --git a/src/generate-output.ts b/src/generate-output.ts index f1682b5..326e5f8 100644 --- a/src/generate-output.ts +++ b/src/generate-output.ts @@ -7,6 +7,7 @@ export interface ModuleImportsSet { defaultImports: Set; nsImport: string | null; namedImports: Map; + typeImports: Map; requireImports: Set; reExports: Map; } @@ -328,6 +329,15 @@ function generateImports(libraryName: string, imports: ModuleImportsSet): string Array.from(imports.requireImports).sort().forEach((importName: string) => result.push(`import ${importName} = require('${libraryName}');`)); Array.from(imports.defaultImports).sort().forEach((importName: string) => result.push(`import ${importName} ${fromEnding}`)); + if (imports.typeImports.size !== 0) { + result.push(`import { ${ + Array.from(imports.typeImports.entries()) + .map(([localName, importedName]: [string, string]) => renamedImportValue(importedName, localName)) + .sort() + .join(', type ') + } } ${fromEnding}`); + } + if (imports.namedImports.size !== 0) { result.push(`import { ${ Array.from(imports.namedImports.entries()) diff --git a/tests/e2e/test-cases/preserve-type-imports/config.ts b/tests/e2e/test-cases/preserve-type-imports/config.ts new file mode 100644 index 0000000..7edf47b --- /dev/null +++ b/tests/e2e/test-cases/preserve-type-imports/config.ts @@ -0,0 +1,5 @@ +import type { TestCaseConfig } from '../test-case-config'; + +const config: TestCaseConfig = {}; + +export = config; diff --git a/tests/e2e/test-cases/preserve-type-imports/index.spec.js b/tests/e2e/test-cases/preserve-type-imports/index.spec.js new file mode 100644 index 0000000..c015c26 --- /dev/null +++ b/tests/e2e/test-cases/preserve-type-imports/index.spec.js @@ -0,0 +1 @@ +require('../run-test-case').runTestCase(__dirname); diff --git a/tests/e2e/test-cases/preserve-type-imports/input.ts b/tests/e2e/test-cases/preserve-type-imports/input.ts new file mode 100644 index 0000000..b9799a4 --- /dev/null +++ b/tests/e2e/test-cases/preserve-type-imports/input.ts @@ -0,0 +1,6 @@ +import type { Diagnostic, AffectedFileResult as RenamedImport } from "typescript"; + +export type MyType = { + value: Diagnostic; + alias: RenamedImport +}; diff --git a/tests/e2e/test-cases/preserve-type-imports/output.d.ts b/tests/e2e/test-cases/preserve-type-imports/output.d.ts new file mode 100644 index 0000000..9502023 --- /dev/null +++ b/tests/e2e/test-cases/preserve-type-imports/output.d.ts @@ -0,0 +1,8 @@ +import { type Diagnostic, type AffectedFileResult as RenamedImport } from 'typescript'; + +export type MyType = { + value: Diagnostic; + alias: RenamedImport; +}; + +export {}; diff --git a/tests/e2e/test-cases/preserve-type-imports/tsconfig.json b/tests/e2e/test-cases/preserve-type-imports/tsconfig.json new file mode 100644 index 0000000..7997907 --- /dev/null +++ b/tests/e2e/test-cases/preserve-type-imports/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "verbatimModuleSyntax": true + } +} From f30c0dc1ed22c004c3d989fec95f86f38515b3ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Egill=20Sveinbj=C3=B6rnsson?= Date: Sun, 5 May 2024 12:08:26 +0200 Subject: [PATCH 2/2] dedupe type/non-type imports --- src/bundle-generator.ts | 26 +++++++------------ src/generate-output.ts | 8 +++++- .../import-type-from-deps/output.d.ts | 2 +- .../preserve-type-imports/output.d.ts | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/bundle-generator.ts b/src/bundle-generator.ts index cf7e70f..ddddba3 100644 --- a/src/bundle-generator.ts +++ b/src/bundle-generator.ts @@ -657,20 +657,18 @@ export function generateDtsBundle(entries: readonly EntryPointConfig[], options: return importItem; } - function addTypeImport(importItem: ModuleImportsSet, preferredLocalName: ts.Identifier, importedIdentifier: ts.Identifier): void { - const newLocalName = collisionsResolver.addTopLevelIdentifier(preferredLocalName); - const importedName = importedIdentifier.text; - importItem.typeImports.set(newLocalName, importedName); - } - function addRequireImport(importItem: ModuleImportsSet, preferredLocalName: ts.Identifier): void { importItem.requireImports.add(collisionsResolver.addTopLevelIdentifier(preferredLocalName)); } - function addNamedImport(importItem: ModuleImportsSet, preferredLocalName: ts.Identifier, importedIdentifier: ts.Identifier): void { + function addNamedImport(importItem: ModuleImportsSet, preferredLocalName: ts.Identifier, importedIdentifier: ts.Identifier, typeImportOrExport: boolean): void { const newLocalName = collisionsResolver.addTopLevelIdentifier(preferredLocalName); const importedName = importedIdentifier.text; - importItem.namedImports.set(newLocalName, importedName); + if (typeImportOrExport) { + importItem.typeImports.set(newLocalName, importedName); + } else { + importItem.namedImports.set(newLocalName, importedName); + } } function addReExport(importItem: ModuleImportsSet, moduleExportedName: string, reExportedName: string): void { @@ -697,12 +695,6 @@ export function generateDtsBundle(entries: readonly EntryPointConfig[], options: const importItem = getImportItem(importModuleSpecifier); - if (ts.isTypeOnlyImportDeclaration(imp)) { - // import { type ImportedType } from 'module'; - addTypeImport(importItem, imp.name, imp.name); - return; - } - if (ts.isImportEqualsDeclaration(imp)) { // import x = require("mod"); addRequireImport(importItem, imp.name); @@ -711,7 +703,8 @@ export function generateDtsBundle(entries: readonly EntryPointConfig[], options: if (ts.isExportSpecifier(imp)) { // export { El1, El2 as ExportedName } from 'module'; - addNamedImport(importItem, imp.name, imp.propertyName || imp.name); + // export { El1, type El2 as ExportedName } from 'module'; + addNamedImport(importItem, imp.name, imp.propertyName || imp.name, ts.isTypeOnlyExportDeclaration(imp)); return; } @@ -728,8 +721,9 @@ export function generateDtsBundle(entries: readonly EntryPointConfig[], options: } if (ts.isImportSpecifier(imp)) { + // import { type ImportedType } from 'module'; // import { El1, El2 as ImportedName } from 'module'; - addNamedImport(importItem, imp.name, imp.propertyName || imp.name); + addNamedImport(importItem, imp.name, imp.propertyName || imp.name, ts.isTypeOnlyImportDeclaration(imp)); return; } diff --git a/src/generate-output.ts b/src/generate-output.ts index 326e5f8..b975d13 100644 --- a/src/generate-output.ts +++ b/src/generate-output.ts @@ -329,8 +329,14 @@ function generateImports(libraryName: string, imports: ModuleImportsSet): string Array.from(imports.requireImports).sort().forEach((importName: string) => result.push(`import ${importName} = require('${libraryName}');`)); Array.from(imports.defaultImports).sort().forEach((importName: string) => result.push(`import ${importName} ${fromEnding}`)); + // For each type-only import, check if it exists in the `namedImports` map and remove it + // otherwise we might end up with a type import and a regular import in the bundle. + for (const key of imports.typeImports.keys()) { + imports.namedImports.delete(key); + } + if (imports.typeImports.size !== 0) { - result.push(`import { ${ + result.push(`import { type ${ Array.from(imports.typeImports.entries()) .map(([localName, importedName]: [string, string]) => renamedImportValue(importedName, localName)) .sort() diff --git a/tests/e2e/test-cases/import-type-from-deps/output.d.ts b/tests/e2e/test-cases/import-type-from-deps/output.d.ts index b0e6b9f..a58bc2c 100644 --- a/tests/e2e/test-cases/import-type-from-deps/output.d.ts +++ b/tests/e2e/test-cases/import-type-from-deps/output.d.ts @@ -1,4 +1,4 @@ -import { InterfaceWithFields } from 'fake-package'; +import { type InterfaceWithFields } from 'fake-package'; export declare type FakePackageType = InterfaceWithFields | string; export type TestType = InterfaceWithFields | FakePackageType; diff --git a/tests/e2e/test-cases/preserve-type-imports/output.d.ts b/tests/e2e/test-cases/preserve-type-imports/output.d.ts index 9502023..3acdd05 100644 --- a/tests/e2e/test-cases/preserve-type-imports/output.d.ts +++ b/tests/e2e/test-cases/preserve-type-imports/output.d.ts @@ -1,4 +1,4 @@ -import { type Diagnostic, type AffectedFileResult as RenamedImport } from 'typescript'; +import { type AffectedFileResult as RenamedImport, type Diagnostic } from 'typescript'; export type MyType = { value: Diagnostic;