@@ -6,9 +6,11 @@ import { resolve as _resolveExports } from 'resolve.exports'
66import { hasESMSyntax } from 'mlly'
77import type { Plugin } from '../plugin'
88import {
9+ CLIENT_ENTRY ,
910 DEFAULT_EXTENSIONS ,
1011 DEFAULT_MAIN_FIELDS ,
1112 DEP_VERSION_RE ,
13+ ENV_ENTRY ,
1214 FS_PREFIX ,
1315 OPTIMIZABLE_ENTRY_RE ,
1416 SPECIAL_QUERY_RE
@@ -42,12 +44,17 @@ import type { SSROptions } from '..'
4244import type { PackageCache , PackageData } from '../packages'
4345import { loadPackageData , resolvePackageData } from '../packages'
4446
47+ const normalizedClientEntry = normalizePath ( CLIENT_ENTRY )
48+ const normalizedEnvEntry = normalizePath ( ENV_ENTRY )
49+
4550// special id for paths marked with browser: false
4651// https://github.com/defunctzombie/package-browser-field-spec#ignore-a-module
4752export const browserExternalId = '__vite-browser-external'
4853// special id for packages that are optional peer deps
4954export const optionalPeerDepId = '__vite-optional-peer-dep'
5055
56+ const nodeModulesInPathRE = / ( ^ | \/ ) n o d e _ m o d u l e s \/ /
57+
5158const isDebug = process . env . DEBUG
5259const debug = createDebugger ( 'vite:resolve-details' , {
5360 onlyWhenFocused : true
@@ -155,14 +162,38 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin {
155162 return optimizedPath
156163 }
157164
165+ const ensureVersionQuery = ( resolved : string ) : string => {
166+ if (
167+ ! options . isBuild &&
168+ depsOptimizer &&
169+ ! (
170+ resolved === normalizedClientEntry ||
171+ resolved === normalizedEnvEntry
172+ )
173+ ) {
174+ // Ensure that direct imports of node_modules have the same version query
175+ // as if they would have been imported through a bare import
176+ // Use the original id to do the check as the resolved id may be the real
177+ // file path after symlinks resolution
178+ const isNodeModule = ! ! normalizePath ( id ) . match ( nodeModulesInPathRE )
179+ if ( isNodeModule && ! resolved . match ( DEP_VERSION_RE ) ) {
180+ const versionHash = depsOptimizer . metadata . browserHash
181+ if ( versionHash && OPTIMIZABLE_ENTRY_RE . test ( resolved ) ) {
182+ resolved = injectQuery ( resolved , `v=${ versionHash } ` )
183+ }
184+ }
185+ }
186+ return resolved
187+ }
188+
158189 // explicit fs paths that starts with /@fs /*
159190 if ( asSrc && id . startsWith ( FS_PREFIX ) ) {
160191 const fsPath = fsPathFromId ( id )
161192 res = tryFsResolve ( fsPath , options )
162193 isDebug && debug ( `[@fs] ${ colors . cyan ( id ) } -> ${ colors . dim ( res ) } ` )
163194 // always return here even if res doesn't exist since /@fs/ is explicit
164195 // if the file doesn't exist it should be a 404
165- return res || fsPath
196+ return ensureVersionQuery ( res || fsPath )
166197 }
167198
168199 // URL
@@ -171,7 +202,7 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin {
171202 const fsPath = path . resolve ( root , id . slice ( 1 ) )
172203 if ( ( res = tryFsResolve ( fsPath , options ) ) ) {
173204 isDebug && debug ( `[url] ${ colors . cyan ( id ) } -> ${ colors . dim ( res ) } ` )
174- return res
205+ return ensureVersionQuery ( res )
175206 }
176207 }
177208
@@ -201,26 +232,6 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin {
201232 return normalizedFsPath
202233 }
203234
204- const pathFromBasedir = normalizedFsPath . slice ( basedir . length )
205- if ( pathFromBasedir . startsWith ( '/node_modules/' ) ) {
206- // normalize direct imports from node_modules to bare imports, so the
207- // hashing logic is shared and we avoid duplicated modules #2503
208- const bareImport = pathFromBasedir . slice ( '/node_modules/' . length )
209- if (
210- ( res = tryNodeResolve (
211- bareImport ,
212- importer ,
213- options ,
214- targetWeb ,
215- depsOptimizer ,
216- ssr
217- ) ) &&
218- res . id . startsWith ( normalizedFsPath )
219- ) {
220- return res
221- }
222- }
223-
224235 if (
225236 targetWeb &&
226237 ( res = tryResolveBrowserMapping ( fsPath , importer , options , true ) )
@@ -229,6 +240,7 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin {
229240 }
230241
231242 if ( ( res = tryFsResolve ( fsPath , options ) ) ) {
243+ res = ensureVersionQuery ( res )
232244 isDebug &&
233245 debug ( `[relative] ${ colors . cyan ( id ) } -> ${ colors . dim ( res ) } ` )
234246 const pkg = importer != null && idToPkgMap . get ( importer )
@@ -250,7 +262,7 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin {
250262 if ( ( res = tryFsResolve ( fsPath , options ) ) ) {
251263 isDebug &&
252264 debug ( `[drive-relative] ${ colors . cyan ( id ) } -> ${ colors . dim ( res ) } ` )
253- return res
265+ return ensureVersionQuery ( res )
254266 }
255267 }
256268
@@ -260,7 +272,7 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin {
260272 ( res = tryFsResolve ( id , options ) )
261273 ) {
262274 isDebug && debug ( `[fs] ${ colors . cyan ( id ) } -> ${ colors . dim ( res ) } ` )
263- return res
275+ return ensureVersionQuery ( res )
264276 }
265277
266278 // external
@@ -405,7 +417,7 @@ function tryFsResolve(
405417
406418 let res : string | undefined
407419
408- // if we fould postfix exist, we should first try resolving file with postfix. details see #4703.
420+ // if there is a postfix, try resolving it as a complete path first ( #4703)
409421 if (
410422 postfix &&
411423 ( res = tryResolveFile (
0 commit comments