Skip to content

Unbundle mode does not emit reachable transitive facade module #9209

Description

@amanmahajan7

Reproduction link or steps

I am trying to use tsdown with unbundle: true for a package that has an aggregate entry file and separate public subpath/facade entry files.

The simplified setup looks like this:

packages/
  ui/
    index.js
  card/
    index.js
    components/
      card.js
      content-card.js

The aggregate entry is packages/ui/index.js:

export * from '../card';

The card package has its own public facade file at packages/card/index.js:

export { default as Card } from './components/card.js';
export { BigCard } from './components/content-card.js';

The package export map points the root export at the aggregate file and the ./card subpath export at the card facade:

{
  "exports": {
    ".": "./dist/packages/ui/index.mjs",
    "./card": "./dist/packages/card/index.mjs"
  }
}

What is expected?

With unbundle: true, I expected tsdown to transform the reachable module graph while preserving file-level public entry/facade modules. In this case, I expected the build output to include:

dist/packages/ui/index.mjs
dist/packages/card/index.mjs
dist/packages/card/components/card.mjs
dist/packages/card/components/content-card.mjs

What is actually happening?

The important file is dist/packages/card/index.mjs. I expected it to preserve the public facade exports, something equivalent to:

export { default as Card } from './components/card.mjs';
export { BigCard } from './components/content-card.mjs';

Instead, tsdown emits the aggregate root and the implementation files, but does not emit the transitive facade module:

dist/packages/ui/index.mjs
dist/packages/card/components/card.mjs
dist/packages/card/components/content-card.mjs

The generated root file directly imports the implementation modules:

import Card from "../card/components/card.mjs";
import { BigCard } from "../card/components/content-card.mjs";
export { BigCard, Card };

So the root package import works:

import { Card } from 'tsdown-unbundle-facade-repro';

But the subpath export is broken because dist/packages/card/index.mjs

tsdown-unbundle-facade-repro.zip

does not exist:

import { Card } from 'tsdown-unbundle-facade-repro/card';

Any additional comments?

This is surprising because packages/card/index.js is directly reachable from the aggregate entry and is also a public package facade/subpath target. Since unbundle mode is described as focusing on code transformation without combining files, I expected the reachable facade module to remain present in the output rather than being bypassed.

The workaround is to add packages/card/index.js as an explicit entry, but that does not scale well for a package that has many public facade/subpath modules reachable from a single aggregate entry.

Metadata

Metadata

Assignees

No one assigned

    Type

    Fields

    Priority

    None yet

    Effort

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions