TypeScript "moduleResolution bundler" Errors - Fix Module Config in CI
moduleResolution: "bundler" tells tsc to resolve modules the way a bundler does (honoring exports, no mandatory extensions). It must be paired with a compatible module setting; mismatches raise TS5095, and switching from Node resolution can surface subpath-import errors.
What this error means
tsc fails with TS5095: Option 'bundler' can only be used when 'module' is set to 'preserve' or to 'es2015' or later, or previously-resolving subpath imports now error after switching moduleResolution to bundler.
error TS5095: Option 'bundler' can only be used when 'module' is set to
'preserve' or to 'es2015' or later.
# or, after switching:
src/x.ts:2:23 - error TS2307: Cannot find module 'pkg/sub' or its
corresponding type declarations.Diagnose it: which tsconfig and which compiler?
A TypeScript error that appears only in CI usually means the runner is compiling with a different config or a different compiler version than your editor. Your editor uses the workspace TypeScript and the nearest tsconfig.json; CI uses whatever the lockfile resolved and whatever config the build script names.
# what CI will actually use
npx tsc --version
npx tsc --showConfig | head -40
# which files are in the program (a missing include is a common cause)
npx tsc --listFiles | wc -l
# type-check only, no emit, same as most CI gates
npx tsc --noEmitCommon causes
module not compatible with bundler resolution
moduleResolution: "bundler" requires module to be esnext/preserve/es2015+. A module: "commonjs" (or node16) paired with it triggers TS5095.
Resolution semantics changed from Node
bundler resolves exports maps and omitted extensions differently than node/node16. A subpath that resolved under one can fail under the other if the package's exports does not declare it.
How to fix it
Pair bundler with a compatible module setting
Set module to esnext/preserve when using moduleResolution: "bundler".
// tsconfig.json
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler"
}
}Fix subpath imports the exports map allows
- If a subpath import now fails, check the package's
exportsfor that path. - Import a path the
exportsmap actually exposes, or use the package's documented entry. - Keep tsc resolution aligned with how your bundler resolves at build time.
Pin the compiler so unrelated updates cannot break the gate
TypeScript adds errors in minor releases. An unpinned compiler turns a routine dependency update into a red build on code nobody touched, which is the most common false alarm in a TypeScript CI pipeline.
// package.json
{
"devDependencies": {
"typescript": "5.6.3" // exact, not ^5.6.3
}
}How to prevent it
- Match
moduletomoduleResolution(bundlerneedsesnext/preserve). - Use
bundlerresolution only when a bundler does the loading. - Import only subpaths a package's
exportsmap declares.