vitejs/vite · error · Error
Could not resolve "${peerDep}" imported by "${parentDep}". I
Error message
Could not resolve "${peerDep}" imported by "${parentDep}". Is it installed? What it means
When a dependency declares a peer dependency as optional and the consumer hasn't installed it, Vite's resolve layer marks it with the `__vite-optional-peer-dep` id (resolve.ts:752). The rolldownDepPlugin's load hook converts that into a runtime module whose code throws `Could not resolve "<peerDep>" imported by "<parentDep>". Is it installed?` in the browser (rolldownDepPlugin.ts:310). This surfaces a missing optional peer at execution time rather than silently breaking.
Source
Thrown at packages/vite/src/node/optimizer/rolldownDepPlugin.ts:310
key !== '__proto__' &&
key !== 'constructor' &&
key !== 'splice'
) {
console.warn(\`Module "${path}" has been externalized for browser compatibility. Cannot access "${path}.\${key}" in client code. See https://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.\`)
}
}
}))`,
}
}
}
if (id.startsWith(optionalPeerDepNamespace)) {
const path = id.slice(optionalPeerDepNamespace.length)
const [, peerDep, parentDep] = path.split(':')
return {
code:
'module.exports = {};' +
`throw new Error(\`Could not resolve "${peerDep}" imported by "${parentDep}". Is it installed?\`)`,
}
}
},
},
transform: {
filter: {
code: assetImportMetaUrlRE,
},
handler(code, id) {
let s: MagicString | undefined
const re = new RegExp(assetImportMetaUrlRE)
const cleanString = stripLiteral(code)
let match: RegExpExecArray | null
while ((match = re.exec(cleanString))) {
const [[startIndex, endIndex], [urlStart, urlEnd]] =
match.indices as Array<[number, number]>
if (hasViteIgnoreRE.test(code.slice(startIndex, urlStart))) continueView on GitHub (pinned to 89620f09af)
Solutions
- Install the named peer dependency: `npm i <peerDep>` (the error quotes both peerDep and parentDep).
- If you intentionally don't use that feature, ensure your code path doesn't import the module that triggers it (tree-shake / avoid the entry).
- Pin a version of the parent dependency that makes the peer non-optional or bundles a fallback.
- Add the parent dep to `optimizeDeps.exclude` if you'd rather resolve it yourself outside the optimizer.
Example fix
// before — error: Could not resolve "@heroicons/react" imported by "my-ui-lib" // after // run: npm install @heroicons/react
Defensive patterns
Strategy: validation
Validate before calling
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)
function assertPeerInstalled(parentDep: string, peerDep: string) {
const parentPkg = require(`${parentDep}/package.json`)
const peers = { ...(parentPkg.peerDependencies || {}), ...(parentPkg.peerDependenciesMeta || {}) }
if (peerDep in peers) {
try { require.resolve(peerDep) }
catch { throw new Error(`Optional peer '${peerDep}' of '${parentDep}' is missing. Run: npm i ${peerDep}`) }
}
} Prevention
- After installing a library, install its optional peers you actually use.
- Check package.json.peerDependenciesMeta for optional peers.
- Add a postinstall check that resolves each used optional peer.
When it happens
Trigger: A pre-bundled dependency imports one of its optional peer dependencies that the user did not install; the first time the browser executes that module the thrown error fires.
Common situations: React component libraries with optional peer deps (e.g. an optional styling/icon peer, an optional framework adapter); packages that lazy-import an optional feature; mono-repos where a peer is hoisted away from the consuming workspace.
Related errors
- The following dependencies are imported but could not be res
- Unable to parse: ${filePath}.
- Failed to resolve ${JSON.stringify(id)}. This package is ESM
- Can not commit a Deps Optimization run as it was cancelled
- The build was canceled
AI-assisted analysis of vitejs/vite@89620f09af (2026-08-03).
Data as JSON: /data/errors/f4bbc469a85cc0a9.json.
Report an issue: GitHub.