Skip to content

fix(ssr): cache externalization per importer directory (fix #22078) - #23919

Open
oyobbeb wants to merge 2 commits into
vitejs:mainfrom
oyobbeb:fix/ssr-externalize-cache-per-dir
Open

oyobbeb wants to merge 2 commits into
vitejs:mainfrom
oyobbeb:fix/ssr-externalize-cache-per-dir

Conversation

@oyobbeb

@oyobbeb oyobbeb commented Oct 27, 2026 •

Copy link
Copy Markdown

fix #22078

Externalization decisions depend on where a package is imported from, but the current cache only uses the package ID. When the same ID resolves to different packages, the cached decision can incorrectly externalize linked source or inline a CommonJS dependency, depending on import order.

This change adds the directory tryNodeResolve resolves bare imports from to the cache key: the importer's directory if the importer exists on disk, otherwise root. That rule is now a helper (getNodeResolveBasedir) shared by both, so they stay in sync. The externalization resolver also gets its own packageCache.

Unlike #22080's file-level key, importers in the same directory share cached decisions. #22080 is slow mainly because this resolver had no package cache, so every new importer re-reads package.json. With a cache, both keys perform about the same.

Validation

  • The three new tests fail on main (a later call gets an earlier call's result) and pass with this change.
  • With the reproduction, b a fails on main with ReferenceError: module is not defined, and a b externalizes dep@2. With this change, both orders match loading each package alone.

Benchmark

Vite e954a9c, Node v24.12.0, Apple M5; 2,000 modules across 100 folders, 10 bare imports per module. Median of five cold loads.

Variant Run 1 Run 2
main 745 ms 671 ms
#22080 1710 ms 1820 ms
Directory key only 734 ms 712 ms
#22080 + shared config.packageCache 731 ms 719 ms
This PR 614 ms 664 ms

Only #22080 without a package cache is clearly slower; the other variants are within run-to-run noise of main.

Reproduction and benchmark: https://github.com/oyobbeb/vite-22078-repro

Review notes

The dedicated packageCache is not cleared when a package.json changes, because the watcher only invalidates config.packageCache. processedIds was never cleared on main either, so this only affects (id, directory) pairs first seen after the change. I kept it separate because config.packageCache stores resolved entries without isProduction in the key, and isProduction is fixed to false here.

AI assistance

AI tools assisted with the investigation, implementation, and tests. I read how tryNodeResolve picks its base directory, reviewed the diff, and chose this approach after comparing the variants above.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SSR externalization cache (processedIds) ignores importer, serves wrong decision for duplicate dependencies

1 participant