Skip to content

Plugin does not respect aliasesExclude option #472

Description

Describe the bug

When building with a monorepo where packages depend on each other, adding aliasExclude to make sure that the output d.ts files import the package by node_modules name is not respected. The output transforms the import statement to the local ts files instead of importing from the package.

This is a regression from v4, which worked properly.

Reproduction

https://github.com/anthony-bonta-gaf-energy/gafe-play-test

Steps to reproduce

git clone https://github.com/anthony-bonta-gaf-energy/gafe-play-test
cd gafe-play-test
yarn install
yarn build

Note the output of the dist src/index.d.ts files in package-b and package-c. Package C is correct, in that it outputted import { Hello } from '@gafe/package-a'. Package C depends on vite-plugin-dts version 4.

Now observe Package B:
import { Hello } from 'import { Hello } from '../../package-a/src/index.mts';

That is not correct. I had AI analyze this and it figured out that these options are not properly passed to the Runtime:

===============================================

Root Cause: Two bugs in unplugin-dts v1.0.0
Bug 1 — aliasesExclude is never forwarded to the Runtime
In buildStart, the aliasesExclude you pass to dts() is correctly used to filter the Vite resolve.alias list before Runtime.toInstance() is called. But it is never passed to Runtime.toInstance() itself:

// line 1758 — aliasesExclude is conspicuously absent
runtime = await Runtime.toInstance({
processor, root, outDirs, entryRoot,
tsconfigPath, compilerOptions,
pathsToAliases, // ✅ forwarded
aliases: options.aliases ?? aliases, // ✅ forwarded (pre-filtered)
...
// ❌ aliasesExclude is NOT forwarded
});
Inside Runtime, aliasesExclude therefore defaults to []:

// line 1020 — defaults to [] because the caller never passes it
aliasesExclude = [],
So when emitOutput calls transformCode({ ..., aliasesExclude }), aliasesExclude is always [], and the guard inside transformAlias (!aliasesExclude.some(...)) is always true, meaning every alias is transformed.

Bug 2 — tsconfig paths bypass the filter entirely
Even if Bug 1 were fixed, there's a second problem. The Runtime constructor first calls parseAliases(options.aliases, aliasesExclude) which does apply the filter — but then appends tsconfig paths (via parseTsAliases) after the filter, with no filtering applied:

// line 1031 — filter IS applied here, to the explicit aliases option
const aliases = parseAliases(options.aliases, aliasesExclude);
// line 1052 — tsconfig paths are pushed AFTER the filter, unconditionally
if (pathsToAliases && resolvedBaseUrl && paths) {
aliases.push(
...parseTsAliases(resolvedBaseUrl, paths) // ← no aliasesExclude check
);
}

With pathsToAliases: true (the default), all three land in the alias list unchecked. aliasesExclude: [/^@gafe//] should have removed the @gafe/ two, but because of both bugs it never does — so @gafe/package-a gets transformed from the import string into ../../../../package-a/src/index.mts.

The Fix — patch unplugin-dts
The minimal code change needed is two lines. Use yarn patch to make it permanent:

yarn patch unplugin-dts
Then make these two changes in the patched copy of dist/shared/unplugin-dts.BSDVFQ9C.cjs:

Fix 1 — forward aliasesExclude to Runtime.toInstance (line ~1772, inside buildStart):

pathsToAliases, include, exclude, resolvers, entries,
aliases: options.aliases ?? aliases,
libName, indexName, logger,
aliasesExclude,
});
Fix 2 — filter tsconfig paths through aliasesExclude before pushing them (line ~1052, inside the Runtime constructor):

if (pathsToAliases && resolvedBaseUrl && paths) {
const tsAliases = parseTsAliases(resolvedBaseUrl, paths);
aliases.push(
...parseTsAliases(resolvedBaseUrl, paths)
...(aliasesExclude.length > 0
? tsAliases.filter(({ find }) =>
!aliasesExclude.some((e) =>
isRegExp(find)
? find.toString() === e.toString()
: isRegExp(e) ? find.match(e)?.[0] : find === e
)
)
: tsAliases)
);
}
Then commit the patch:

yarn patch-commit /tmp/unplugin-dts-... # path yarn printed
Why this worked in vite-plugin-dts v4
vite-plugin-dts v4 had the same two-phase structure, but it passed aliasesExclude all the way through to the runtime correctly and also filtered the tsconfig path results before merging them. unplugin-dts is a rewrite/port and the forwarding was simply omitted.

System Info

System:
    OS: macOS 15.7.5
    CPU: (12) arm64 Apple M4 Pro
    Memory: 2.11 GB / 48.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 24.15.0 - ~/.nvm/versions/node/v24.15.0/bin/node
    Yarn: 4.14.1 - /opt/homebrew/bin/yarn
    npm: 11.12.1 - ~/.nvm/versions/node/v24.15.0/bin/npm
  Browsers:
    Chrome: 146.0.7680.178
    Safari: 26.3.1

Validations

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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