Skip to content

Duplicated export when bundling .d.ts with function overloads #209

@hugs7

Description

@hugs7

Reproduction link or steps

Given a source file with function overloads:

// src/useConfig.ts
interface Config {
  name: string;
  version: number;
}

export function useConfig(): Config;
export function useConfig<T>(selector: (config: Config) => T): T;
export function useConfig<T>(selector?: (config: Config) => T) {
  const config: Config = { name: 'app', version: 1 };
  return selector ? selector(config) : config;
}

tsc emits the following valid .d.ts:

// src/useConfig.d.ts
interface Config {
  name: string;
  version: number;
}
export declare function useConfig(): Config;
export declare function useConfig<T>(selector: (config: Config) => T): T;

When rolldown-plugin-dts tries to bundle this, it fails with:

[PARSE_ERROR] Error: Duplicated export 'useConfig'
   ╭─[ src/useConfig.d.ts:2:12 ]
   │
 2 │ export var useConfig = [
   │            ────┬────  
   │                ╰────── Export has already been declared here
   │ 
 7 │ export var useConfig = [
   │            ────┬────  
   │                ╰────── It cannot be redeclared here
───╯

Describe the bug

When using rolldown-plugin-dts in tsc mode (i.e. isolatedDeclarations is not enabled), function overloads in the generated .d.ts files cause a PARSE_ERROR: Duplicated export error during bundling.

TypeScript's tsc correctly emits overloaded function signatures as multiple export declare function declarations with the same name — this is valid .d.ts syntax. However, Rolldown's parser rejects these as duplicate exports.

Expected behaviour

rolldown-plugin-dts should correctly handle function overloads in .d.ts files, merging the overload signatures into the bundled output — matching the behaviour of rollup-plugin-dts.

Workaround

Replace function overloads with a single generic signature using rest args and a default type parameter:

export function useConfig<T = Config>(
  ...args: [selector: (config: Config) => T] | []
): T {
  const selector = args[0];
  const config: Config = { name: 'app', version: 1 };
  return (selector ? selector(config) : config) as T;
}

This avoids multiple export declare function lines in the .d.ts output, but requires a cast and is less idiomatic than overloads.

System Info

System:
    OS: Linux 6.6 Ubuntu 22.04.5 LTS 22.04.5 LTS (Jammy Jellyfish)
    CPU: (16) x64 AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics
    Memory: 19.78 GB / 50.98 GB
    Container: Yes
    Shell: 5.1.16 - /bin/bash
  Browsers:
    Chrome: 146.0.7680.80


---

## Project environment

- `rolldown-plugin-dts`: `0.22.5`
- `vite`: `8.0.0`
- `typescript`: `5.9.3`
- `isolatedDeclarations`: **not enabled** (using `tsc` mode for `.d.ts` generation)

Validations

  • Follow our Code of Conduct
  • Read the Contributing Guide.
  • Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
  • Check that this is a concrete bug. For Q&A, please open a GitHub Discussion instead.
  • The provided reproduction is a minimal reproducible of the bug.

Contributions

  • I am willing to submit a PR to fix this issue
  • I am willing to submit a PR with failing tests (actually just go ahead and do it, thanks!)

Compensating engineering work will speed up resolution and support the project

  • I'm willing to offer $16 for financial support

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions