🧩 Import ESM-only types from a CommonJS TypeScript project
tl;dr Add an import attribute to the type-only import.
import type { Plugin } from "vite" with { "resolution-mode": "import" };
You ship a CommonJS tool, a rollup plugin or a CLI, and you want Vite’s types. With "module": "node18" and Vite 8.2.2, the obvious import fails:
import type { Plugin } from "vite";
error TS1541: Type-only import of an ECMAScript module from a CommonJS module must have a 'resolution-mode' attribute.
On Vite 5.4.21 the very same line fails differently, and much more confusingly:
error TS2305: Module '"vite"' has no exported member 'Plugin'.
Why
Under module: node16 and up, TypeScript emulates Node’s exports conditions. The condition it picks depends on the format of the importing file, so a CommonJS file resolves through require. Vite 5 pointed that condition at a stub, which is the whole file:
declare const module: any;
export = module;
It resolves, it just has no named exports. Hence TS2305. Vite 7 removed the stub entirely, so newer versions report the format mismatch directly instead.
⚠️
import typedoes not get you out of this. Type-only imports still resolve using the importing file’s format.
The fix
import type { Plugin } from "vite" with { "resolution-mode": "import" };
You’re telling the resolver to pretend, for this one specifier, that the importing file is an ES module. The import condition wins, the real .d.ts loads, and Plugin is the actual type.
There is also a type-query spelling when you can’t add a top-level import:
type ServerOptions = import("vite", {
with: { "resolution-mode": "import" },
}).ServerOptions;
Things worth knowing:
- It needs TypeScript 5.3+. Older posts show
assert { "resolution-mode": "import" }, a nightly-only spelling from before 5.3, and that now errors withTS2880. - It only does something under
node16,node18,node20ornodenext. Undermodule: commonjsit is accepted and ignored. - It is type-only and fully erased, so nothing reaches your JavaScript.
- It survives into your
.d.tsoutput verbatim, so your consumers need TypeScript 5.3+ too.
💡 For values, no attribute is needed. A dynamic
import("vite")is always resolved with theimportcondition, so it’s correctly typed on its own. Just keepmoduleonnode16or higher, sincemodule: commonjsrewritesimport()into arequire.
Thanks for reading my blog posts! 🎉
- Created At
- 9/3/2026
- Published At
- 9/3/2026