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))) continue

View on GitHub (pinned to 89620f09af)

Solutions

  1. Install the named peer dependency: `npm i <peerDep>` (the error quotes both peerDep and parentDep).
  2. 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).
  3. Pin a version of the parent dependency that makes the peer non-optional or bundles a fallback.
  4. 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

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


AI-assisted analysis of vitejs/vite@89620f09af (2026-08-03). Data as JSON: /data/errors/f4bbc469a85cc0a9.json. Report an issue: GitHub.