Skip to content

export * loses type modifiers when flattening re-exports #354

@karlhorky

Description

@karlhorky

Originally discovered in listr2 issue: listr2/listr2#748 (comment)

Also reported in rolldown-plugin-dts: sxzz/rolldown-plugin-dts#95

Checklist

  • I can reproduce this issue when running this plugin on its own.
    Other plugins, such as node-resolve are known to cause issues.
  • I am running this plugin on .d.ts files generated by TypeScript.
    The plugin can consume .ts and even .js files (with allowJs: true), but this is known to cause issues.
  • This issue is not related to rolling up @types.
    The plugin ignores these by default, unless respectExternal is set. @types can contain hand-crafted code which is known to cause issues.

Describe the bug / Code snippets

When using export * from './foo' in a source file, type-only exports from ./foo lose their type modifier in the bundled declaration output. This causes types to be emitted as value exports in the generated d.ts, which breaks with classes under verbatimModuleSyntax because they appear as runtime symbols.

index.d.ts

export * from "./interfaces";

interfaces.d.ts

export { type Task, type TaskWrapper } from "./tasks";

tasks.d.ts

export declare class TaskWrapper {
  run(): void;
}
export interface Task {
  title: string;
}

rollup.config.mjs

import dts from "rollup-plugin-dts";

/** @type {import('rollup').RollupOptions} */
export default {
  input: "./index.d.ts",
  plugins: [dts()],
  output: { file: "./dist/index.d.ts", format: "es" }
};

Output (dist/index.d.ts):

declare class TaskWrapper {
  run(): void;
}
interface Task {
  title: string;
}

export { TaskWrapper };
export type { Task };

(no type keyword in TaskWrapper export)

Image

Repro (CodeSandbox): https://codesandbox.io/p/devbox/ancient-dream-forked-lc2szt

Error Message

  1. No TS error reported on TaskWrapper in this "consumer" code with verbatimModuleSyntax: true (an error should be reported like it is for Task, because there is a missing type keyword on the import)

    import {
      Task /* ✅ error as expected with `verbatimModuleSyntax: true` */,
    } from "./dist/index.js";
    
    import {
      TaskWrapper /* 💥 no error as expected with `verbatimModuleSyntax: true` */,
    } from "./dist/index.js";
    Image
  2. Using Node.js type stripping also yields a syntax error, although no TS error present:

    consumer.ts

    import { TaskWrapper } from "./dist/index.js";
    $ node consumer.ts
    file:///project/workspace/consumer.ts:1
    import { TaskWrapper } from "./dist/index.js";
          ^^^^^^^^^^^
    SyntaxError: The requested module './dist/index.js' does not provide an export named 'TaskWrapper'
        at ModuleJob._instantiate (node:internal/modules/esm/module_job:228:21)
        at async ModuleJob.run (node:internal/modules/esm/module_job:337:5)
        at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:651:26)
        at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:117:5)
    
    Node.js v22.19.0

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