Problem
Consumers with skipLibCheck: false get TS2307 errors from dist/index.d.mts because it hard-imports types from all 10 bundler packages. If a consumer only uses one bundler (e.g., Vite), they still need webpack, @farmfe/core, @rspack/core, bun, rolldown, and unloader installed to satisfy the type imports.
This has been reported multiple times:
Reproduction
mkdir unplugin-repro && cd unplugin-repro
pnpm init && pnpm add unplugin vite typescript @types/node
tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"skipLibCheck": false,
"noEmit": true
},
"include": ["src"]
}
src/index.ts:
import type { UnpluginInstance } from 'unplugin'
export type { UnpluginInstance }
Result: 7 TS2307 errors — 6 from unplugin's dist/index.d.mts, 1 from webpack-virtual-modules:
node_modules/.pnpm/unplugin@3.1.0/node_modules/unplugin/dist/index.d.mts:2:50 - error TS2307: Cannot find module '@farmfe/core' or its corresponding type declarations.
node_modules/.pnpm/unplugin@3.1.0/node_modules/unplugin/dist/index.d.mts:3:90 - error TS2307: Cannot find module '@rspack/core' or its corresponding type declarations.
node_modules/.pnpm/unplugin@3.1.0/node_modules/unplugin/dist/index.d.mts:4:49 - error TS2307: Cannot find module 'bun' or its corresponding type declarations.
node_modules/.pnpm/unplugin@3.1.0/node_modules/unplugin/dist/index.d.mts:6:42 - error TS2307: Cannot find module 'rolldown' or its corresponding type declarations.
node_modules/.pnpm/unplugin@3.1.0/node_modules/unplugin/dist/index.d.mts:8:43 - error TS2307: Cannot find module 'unloader' or its corresponding type declarations.
node_modules/.pnpm/unplugin@3.1.0/node_modules/unplugin/dist/index.d.mts:10:98 - error TS2307: Cannot find module 'webpack' or its corresponding type declarations.
node_modules/.pnpm/webpack-virtual-modules@0.6.2/node_modules/webpack-virtual-modules/lib/index.d.ts:1:33 - error TS2307: Cannot find module 'webpack' or its corresponding type declarations.
Root Cause
src/types.ts imports types from all bundler packages. The tsdown.config.ts externalizes them via external (now deps.neverBundle), which correctly keeps them external in the JS output — but also keeps them as hard import type statements in the DTS output. Consumers who don't install every bundler hit TS2307.
Analysis
I investigated four approaches before filing this issue:
1. Ambient declare module fallbacks
Ship a .d.ts file with declare module 'webpack' {} etc., referenced via /// <reference path> in the DTS banner.
Result: Does not work. Ambient declare module in script files globally overrides module resolution — it replaces real module types even when the real package is installed, breaking other packages (e.g., Vite depends on Rollup's full types).
2. peerDependencies + peerDependenciesMeta
Declare all bundlers as optional peer dependencies. Semantically correct, but TypeScript doesn't care about peerDependencies metadata — TS2307 remains.
3. tsdown DTS-specific dependency config
The ideal fix: keep packages external for JS (deps.neverBundle) but inline their types in DTS output. However, tsdown currently has no DTS-specific dependency configuration — deps applies uniformly to both JS and DTS. rolldown-plugin-dts also has no resolve option for selective type inlining (discussed in rolldown/rolldown-plugin-dts#106, closed without implementation).
This is the most elegant long-term solution, but it requires a feature that doesn't exist yet in tsdown/rolldown-plugin-dts.
4. Source-level type stubs
Replace import type { Plugin } from 'vite' with locally-defined minimal interfaces like type VitePlugin = { name: string }. Eliminates all external type imports.
Trade-off: This is a breaking change — consumers lose structural compatibility between unplugin's exported types and the real bundler plugin types. For example, UnpluginInstance.vite would return a { name: string } instead of Vite's real Plugin type.
Suggested Path Forward
Given that this has been open for 3+ years (#49) and affects all consumers with skipLibCheck: false (the TypeScript-recommended setting for library authors), I'd suggest one of:
-
Short-term: Source-level minimal type stubs for bundler types that most consumers never interact with directly (e.g., @farmfe/core, bun, unloader). Keep full types for the most common ones (vite, rollup, esbuild) which are typically already installed as transitive deps.
-
Long-term: Wait for / request DTS-specific dependency inlining in tsdown (approach 3 above).
-
Something else — I'm happy to implement whatever approach the maintainers prefer.
I can submit a PR for whichever direction you'd like to go.
Problem
Consumers with
skipLibCheck: falseget TS2307 errors fromdist/index.d.mtsbecause it hard-imports types from all 10 bundler packages. If a consumer only uses one bundler (e.g., Vite), they still needwebpack,@farmfe/core,@rspack/core,bun,rolldown, andunloaderinstalled to satisfy the type imports.This has been reported multiple times:
Reproduction
tsconfig.json:{ "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "bundler", "skipLibCheck": false, "noEmit": true }, "include": ["src"] }src/index.ts:Result: 7 TS2307 errors — 6 from unplugin's
dist/index.d.mts, 1 fromwebpack-virtual-modules:Root Cause
src/types.tsimports types from all bundler packages. Thetsdown.config.tsexternalizes them viaexternal(nowdeps.neverBundle), which correctly keeps them external in the JS output — but also keeps them as hardimport typestatements in the DTS output. Consumers who don't install every bundler hit TS2307.Analysis
I investigated four approaches before filing this issue:
1. Ambient
declare modulefallbacksShip a
.d.tsfile withdeclare module 'webpack' {}etc., referenced via/// <reference path>in the DTS banner.Result: Does not work. Ambient
declare modulein script files globally overrides module resolution — it replaces real module types even when the real package is installed, breaking other packages (e.g., Vite depends on Rollup's full types).2.
peerDependencies+peerDependenciesMetaDeclare all bundlers as optional peer dependencies. Semantically correct, but TypeScript doesn't care about peerDependencies metadata — TS2307 remains.
3. tsdown DTS-specific dependency config
The ideal fix: keep packages external for JS (
deps.neverBundle) but inline their types in DTS output. However, tsdown currently has no DTS-specific dependency configuration —depsapplies uniformly to both JS and DTS.rolldown-plugin-dtsalso has noresolveoption for selective type inlining (discussed in rolldown/rolldown-plugin-dts#106, closed without implementation).This is the most elegant long-term solution, but it requires a feature that doesn't exist yet in tsdown/rolldown-plugin-dts.
4. Source-level type stubs
Replace
import type { Plugin } from 'vite'with locally-defined minimal interfaces liketype VitePlugin = { name: string }. Eliminates all external type imports.Trade-off: This is a breaking change — consumers lose structural compatibility between unplugin's exported types and the real bundler plugin types. For example,
UnpluginInstance.vitewould return a{ name: string }instead of Vite's realPlugintype.Suggested Path Forward
Given that this has been open for 3+ years (#49) and affects all consumers with
skipLibCheck: false(the TypeScript-recommended setting for library authors), I'd suggest one of:Short-term: Source-level minimal type stubs for bundler types that most consumers never interact with directly (e.g.,
@farmfe/core,bun,unloader). Keep full types for the most common ones (vite,rollup,esbuild) which are typically already installed as transitive deps.Long-term: Wait for / request DTS-specific dependency inlining in tsdown (approach 3 above).
Something else — I'm happy to implement whatever approach the maintainers prefer.
I can submit a PR for whichever direction you'd like to go.