Skip to content

Commit a4f86f1

Browse files
authored
fix: respect ssr.noExternal when externalizing dependencies (#8862)
1 parent b9aabf4 commit a4f86f1

File tree

5 files changed

+382
-34
lines changed

5 files changed

+382
-34
lines changed

packages/vitest/src/node/config/resolveConfig.ts

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -369,29 +369,6 @@ export function resolveConfig(
369369
resolved.deps ??= {}
370370
resolved.deps.moduleDirectories ??= []
371371

372-
const envModuleDirectories
373-
= process.env.VITEST_MODULE_DIRECTORIES
374-
|| process.env.npm_config_VITEST_MODULE_DIRECTORIES
375-
376-
if (envModuleDirectories) {
377-
resolved.deps.moduleDirectories.push(...envModuleDirectories.split(','))
378-
}
379-
380-
resolved.deps.moduleDirectories = resolved.deps.moduleDirectories.map(
381-
(dir) => {
382-
if (dir[0] !== '/') {
383-
dir = `/${dir}`
384-
}
385-
if (!dir.endsWith('/')) {
386-
dir += '/'
387-
}
388-
return normalize(dir)
389-
},
390-
)
391-
if (!resolved.deps.moduleDirectories.includes('/node_modules/')) {
392-
resolved.deps.moduleDirectories.push('/node_modules/')
393-
}
394-
395372
resolved.deps.optimizer ??= {}
396373
resolved.deps.optimizer.ssr ??= {}
397374
resolved.deps.optimizer.ssr.enabled ??= false

packages/vitest/src/node/plugins/runnerTransform.ts

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { ResolvedConfig, UserConfig, Plugin as VitePlugin } from 'vite'
22
import { builtinModules } from 'node:module'
3+
import { normalize } from 'pathe'
34
import { mergeConfig } from 'vite'
5+
import { escapeRegExp } from '../../utils/base'
46
import { resolveOptimizerConfig } from './utils'
57

68
export 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 {
146201
function 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+
}

packages/vitest/src/node/resolver.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,13 @@ async function _shouldExternalize(
161161

162162
const moduleDirectories = options?.moduleDirectories || ['/node_modules/']
163163

164-
if (matchExternalizePattern(id, moduleDirectories, options?.inline)) {
164+
if (matchPattern(id, moduleDirectories, options?.inline)) {
165165
return false
166166
}
167167
if (options?.inlineFiles && options?.inlineFiles.includes(id)) {
168168
return false
169169
}
170-
if (matchExternalizePattern(id, moduleDirectories, options?.external)) {
170+
if (matchPattern(id, moduleDirectories, options?.external)) {
171171
return id
172172
}
173173

@@ -181,10 +181,10 @@ async function _shouldExternalize(
181181
const guessCJS = isLibraryModule && options?.fallbackCJS
182182
id = guessCJS ? guessCJSversion(id) || id : id
183183

184-
if (matchExternalizePattern(id, moduleDirectories, defaultInline)) {
184+
if (matchPattern(id, moduleDirectories, defaultInline)) {
185185
return false
186186
}
187-
if (matchExternalizePattern(id, moduleDirectories, depsExternal)) {
187+
if (matchPattern(id, moduleDirectories, depsExternal)) {
188188
return id
189189
}
190190

@@ -195,7 +195,7 @@ async function _shouldExternalize(
195195
return false
196196
}
197197

198-
function matchExternalizePattern(
198+
function matchPattern(
199199
id: string,
200200
moduleDirectories: string[],
201201
patterns?: (string | RegExp)[] | true,

packages/vitest/src/public/node.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export { isValidApiRequest } from '../api/check'
77
export { escapeTestName } from '../node/ast-collect'
88
export { parseCLI } from '../node/cli/cac'
99
export type { CliParseOptions } from '../node/cli/cac'
10+
export type { CliOptions } from '../node/cli/cli-api'
1011
export { startVitest } from '../node/cli/cli-api'
1112
export { resolveApiServerConfig } from '../node/config/resolveConfig'
1213
export type {

0 commit comments

Comments
 (0)