11import type { ResolvedConfig , UserConfig , Plugin as VitePlugin } from 'vite'
22import { builtinModules } from 'node:module'
3+ import { normalize } from 'pathe'
34import { mergeConfig } from 'vite'
5+ import { escapeRegExp } from '../../utils/base'
46import { resolveOptimizerConfig } from './utils'
57
68export function ModuleRunnerTransform ( ) : VitePlugin {
@@ -23,6 +25,34 @@ export function ModuleRunnerTransform(): VitePlugin {
2325 names . add ( '__vitest_vm__' )
2426 }
2527
28+ let moduleDirectories = testConfig . deps ?. moduleDirectories || [ ]
29+
30+ const envModuleDirectories
31+ = process . env . VITEST_MODULE_DIRECTORIES
32+ || process . env . npm_config_VITEST_MODULE_DIRECTORIES
33+
34+ if ( envModuleDirectories ) {
35+ moduleDirectories . push ( ...envModuleDirectories . split ( ',' ) )
36+ }
37+
38+ moduleDirectories = moduleDirectories . map (
39+ ( dir ) => {
40+ if ( dir [ 0 ] !== '/' ) {
41+ dir = `/${ dir } `
42+ }
43+ if ( ! dir . endsWith ( '/' ) ) {
44+ dir += '/'
45+ }
46+ return normalize ( dir )
47+ } ,
48+ )
49+ if ( ! moduleDirectories . includes ( '/node_modules/' ) ) {
50+ moduleDirectories . push ( '/node_modules/' )
51+ }
52+
53+ testConfig . deps ??= { }
54+ testConfig . deps . moduleDirectories = moduleDirectories
55+
2656 const external : ( string | RegExp ) [ ] = [ ]
2757 const noExternal : ( string | RegExp ) [ ] = [ ]
2858
@@ -64,18 +94,43 @@ export function ModuleRunnerTransform(): VitePlugin {
6494 environment . resolve || { } ,
6595 ) as ResolvedConfig [ 'resolve' ]
6696
67- const envNoExternal = resolveViteResolveOptions ( 'noExternal' , currentResolveOptions )
97+ const envNoExternal = resolveViteResolveOptions ( 'noExternal' , currentResolveOptions , moduleDirectories )
6898 if ( envNoExternal === true ) {
6999 noExternalAll = true
70100 }
71- else {
101+ else if ( envNoExternal . length ) {
72102 noExternal . push ( ...envNoExternal )
73103 }
104+ else if ( name === 'client' || name === 'ssr' ) {
105+ const deprecatedNoExternal = resolveDeprecatedOptions (
106+ name === 'client'
107+ ? config . resolve ?. noExternal
108+ : config . ssr ?. noExternal ,
109+ moduleDirectories ,
110+ )
111+ if ( deprecatedNoExternal === true ) {
112+ noExternalAll = true
113+ }
114+ else {
115+ noExternal . push ( ...deprecatedNoExternal )
116+ }
117+ }
74118
75- const envExternal = resolveViteResolveOptions ( 'external' , currentResolveOptions )
76- if ( envExternal !== true ) {
119+ const envExternal = resolveViteResolveOptions ( 'external' , currentResolveOptions , moduleDirectories )
120+ if ( envExternal !== true && envExternal . length ) {
77121 external . push ( ...envExternal )
78122 }
123+ else if ( name === 'client' || name === 'ssr' ) {
124+ const deprecatedExternal = resolveDeprecatedOptions (
125+ name === 'client'
126+ ? config . resolve ?. external
127+ : config . ssr ?. external ,
128+ moduleDirectories ,
129+ )
130+ if ( deprecatedExternal !== true ) {
131+ external . push ( ...deprecatedExternal )
132+ }
133+ }
79134
80135 // remove Vite's externalization logic because we have our own (unfortunetly)
81136 environment . resolve ??= { }
@@ -146,18 +201,52 @@ export function ModuleRunnerTransform(): VitePlugin {
146201function resolveViteResolveOptions (
147202 key : 'noExternal' | 'external' ,
148203 options : ResolvedConfig [ 'resolve' ] ,
204+ moduleDirectories : string [ ] | undefined ,
149205) : true | ( string | RegExp ) [ ] {
150206 if ( Array . isArray ( options [ key ] ) ) {
151- return options [ key ]
207+ // mergeConfig will merge a custom `true` into an array
208+ if ( options [ key ] . some ( p => ( p as any ) === true ) ) {
209+ return true
210+ }
211+ return options [ key ] . map ( dep => processWildcard ( dep , moduleDirectories ) )
152212 }
153213 else if (
154214 typeof options [ key ] === 'string'
155215 || options [ key ] instanceof RegExp
156216 ) {
157- return [ options [ key ] ]
217+ return [ options [ key ] ] . map ( dep => processWildcard ( dep , moduleDirectories ) )
158218 }
159219 else if ( typeof options [ key ] === 'boolean' ) {
160220 return true
161221 }
162222 return [ ]
163223}
224+
225+ function resolveDeprecatedOptions (
226+ options : string | RegExp | ( string | RegExp ) [ ] | true | undefined ,
227+ moduleDirectories : string [ ] | undefined ,
228+ ) : true | ( string | RegExp ) [ ] {
229+ if ( options === true ) {
230+ return true
231+ }
232+ else if ( Array . isArray ( options ) ) {
233+ return options . map ( dep => processWildcard ( dep , moduleDirectories ) )
234+ }
235+ else if ( options != null ) {
236+ return [ processWildcard ( options , moduleDirectories ) ]
237+ }
238+ return [ ]
239+ }
240+
241+ function processWildcard ( dep : string | RegExp , moduleDirectories : string [ ] | undefined ) {
242+ if ( typeof dep !== 'string' ) {
243+ return dep
244+ }
245+ if ( typeof dep === 'string' && dep . includes ( '*' ) ) {
246+ const directories = ( moduleDirectories || [ '/node_modules/' ] ) . map ( r => escapeRegExp ( r ) )
247+ return new RegExp (
248+ `(${ directories . join ( '|' ) } )${ dep . replace ( / \* / g, '[\\w/]+' ) } ` ,
249+ )
250+ }
251+ return dep
252+ }
0 commit comments