Skip to content

Commit 9bc3377

Browse files
committed
fix: preserve cross-package generic types in emitDts
Update moduleResolution from node10 to node16 to properly resolve generic types across package boundaries in monorepo setups. Previously, cross-package generic type information was lost during .d.ts generation, causing types like `GenericType<T>` to be emitted as `any`. - Add test case for cross-package generic type preservation
1 parent 99e29f2 commit 9bc3377

File tree

9 files changed

+96
-3
lines changed

9 files changed

+96
-3
lines changed

packages/svelte2tsx/src/emitDts.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,7 @@ function loadTsconfig(config: EmitDtsConfig, svelteMap: SvelteMap) {
111111
options: {
112112
...options,
113113
noEmit: false, // Set to true in case of jsconfig, force false, else nothing is emitted
114-
moduleResolution:
115-
// NodeJS: up to 4.9, Node10: since 5.0
116-
(ts.ModuleResolutionKind as any).NodeJs ?? ts.ModuleResolutionKind.Node10, // Classic if not set, which gives wrong results
114+
moduleResolution: ts.ModuleResolutionKind.Node16,
117115
declaration: true, // Needed for d.ts file generation
118116
emitDeclarationOnly: true, // We only want d.ts file generation
119117
declarationDir: config.declarationDir, // Where to put the declarations
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Cross-Package Generic Types Test
2+
3+
This test validates that generic type information is correctly preserved when importing generic classes from external packages during `.d.ts` generation.
4+
5+
## Issue
6+
Before the fix, when using modern module resolution (bundler/node16), cross-package imports of generic classes would lose their type information and fall back to `any` in generated declaration files.
7+
8+
## Test Structure
9+
- `core-package/` - Mock external package containing `GenericToken<T>` class
10+
- `src/consumer.ts` - Consumes the generic class with type parameters
11+
- `expected/` - Expected output showing preserved generic types
12+
13+
## Key Test Case
14+
```typescript
15+
// This should preserve the generic type as GenericToken<MyService>
16+
// Before the fix: compiled as 'any' due to module resolution issues
17+
// After the fix: correctly typed as GenericToken<MyService>
18+
export const SERVICE_TOKEN = new GenericToken<MyService>('MyService');
19+
```
20+
21+
## Fix
22+
Updated `emitDts.ts` to use modern module resolution (bundler/node16) instead of legacy node10 resolution.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "@test/core",
3+
"version": "1.0.0",
4+
"type": "module",
5+
"exports": {
6+
".": {
7+
"types": "./dist/index.d.ts",
8+
"import": "./dist/index.js"
9+
}
10+
}
11+
}
12+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { GenericToken } from '@test/core';
2+
export declare class MyService {
3+
private name;
4+
constructor(name: string);
5+
getName(): string;
6+
}
7+
export declare const SERVICE_TOKEN: GenericToken<MyService>;
8+
export declare const ANNOTATED_TOKEN: GenericToken<MyService>;
9+
export declare const ASSERTION_TOKEN: GenericToken<MyService>;
10+
export declare class AnotherService {
11+
value: number;
12+
}
13+
export declare const ANOTHER_TOKEN: GenericToken<AnotherService>;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './consumer.js';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "test-cross-package-generics",
3+
"version": "0.0.1",
4+
"type": "module",
5+
"dependencies": {
6+
"@test/core": "1.0.0"
7+
}
8+
}
9+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { GenericToken } from '@test/core';
2+
3+
export class MyService {
4+
constructor(private name: string) {}
5+
getName() { return this.name; }
6+
}
7+
8+
// This should preserve the generic type as GenericToken<MyService>
9+
// Before the fix, this would be compiled as 'any' due to module resolution issues
10+
export const SERVICE_TOKEN = new GenericToken<MyService>('MyService');
11+
12+
// These explicit annotations should work regardless
13+
export const ANNOTATED_TOKEN: GenericToken<MyService> = new GenericToken<MyService>('MyService');
14+
export const ASSERTION_TOKEN = new GenericToken<MyService>('MyService') as GenericToken<MyService>;
15+
16+
// Test with different generic types
17+
export class AnotherService {
18+
value = 42;
19+
}
20+
21+
export const ANOTHER_TOKEN = new GenericToken<AnotherService>('AnotherService');
22+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './consumer.js';
2+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2022",
4+
"lib": ["ES2022", "DOM"],
5+
"module": "ESNext",
6+
"declaration": true,
7+
"strict": true,
8+
"skipLibCheck": true,
9+
"esModuleInterop": true,
10+
"allowSyntheticDefaultImports": true
11+
},
12+
"include": ["src/**/*"]
13+
}
14+

0 commit comments

Comments
 (0)