Skip to content

Commit b5e0f3d

Browse files
authored
fix(vite): set ssr.noExternal even if not in project package.json (#404)
1 parent f4b1508 commit b5e0f3d

File tree

4 files changed

+277
-1
lines changed

4 files changed

+277
-1
lines changed

src/__tests__/utils.js

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export const IS_JSDOM = window.navigator.userAgent.includes('jsdom')
44

55
export const IS_HAPPYDOM = !IS_JSDOM // right now it's happy or js
66

7+
export const IS_JEST = Boolean(process.env.JEST_WORKER_ID)
8+
79
export const IS_SVELTE_5 = SVELTE_VERSION >= '5'
810

911
export const MODE_LEGACY = 'legacy'

src/__tests__/vite-plugin.test.js

+227
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import { beforeEach, describe, expect, test, vi } from 'vitest'
2+
3+
import { svelteTesting } from '../vite.js'
4+
import { IS_JEST } from './utils.js'
5+
6+
describe.skipIf(IS_JEST)('vite plugin', () => {
7+
beforeEach(() => {
8+
vi.stubEnv('VITEST', '1')
9+
})
10+
11+
test('does not modify config if disabled', () => {
12+
const subject = svelteTesting({
13+
resolveBrowser: false,
14+
autoCleanup: false,
15+
noExternal: false,
16+
})
17+
18+
const result = {}
19+
subject.config(result)
20+
21+
expect(result).toEqual({})
22+
})
23+
24+
test('does not modify config if not Vitest', () => {
25+
vi.stubEnv('VITEST', '')
26+
27+
const subject = svelteTesting()
28+
29+
const result = {}
30+
subject.config(result)
31+
32+
expect(result).toEqual({})
33+
})
34+
35+
test.each([
36+
{
37+
config: () => ({ resolve: { conditions: ['node'] } }),
38+
expectedConditions: ['browser', 'node'],
39+
},
40+
{
41+
config: () => ({ resolve: { conditions: ['svelte', 'node'] } }),
42+
expectedConditions: ['svelte', 'browser', 'node'],
43+
},
44+
])(
45+
'adds browser condition if necessary',
46+
({ config, expectedConditions }) => {
47+
const subject = svelteTesting({
48+
resolveBrowser: true,
49+
autoCleanup: false,
50+
noExternal: false,
51+
})
52+
53+
const result = config()
54+
subject.config(result)
55+
56+
expect(result).toEqual({
57+
resolve: {
58+
conditions: expectedConditions,
59+
},
60+
})
61+
}
62+
)
63+
64+
test.each([
65+
{
66+
config: () => ({}),
67+
expectedConditions: [],
68+
},
69+
{
70+
config: () => ({ resolve: { conditions: [] } }),
71+
expectedConditions: [],
72+
},
73+
{
74+
config: () => ({ resolve: { conditions: ['svelte'] } }),
75+
expectedConditions: ['svelte'],
76+
},
77+
])(
78+
'skips browser condition if possible',
79+
({ config, expectedConditions }) => {
80+
const subject = svelteTesting({
81+
resolveBrowser: true,
82+
autoCleanup: false,
83+
noExternal: false,
84+
})
85+
86+
const result = config()
87+
subject.config(result)
88+
89+
expect(result).toEqual({
90+
resolve: {
91+
conditions: expectedConditions,
92+
},
93+
})
94+
}
95+
)
96+
97+
test.each([
98+
{
99+
config: () => ({}),
100+
expectedSetupFiles: [expect.stringMatching(/src\/vitest.js$/u)],
101+
},
102+
{
103+
config: () => ({ test: { setupFiles: [] } }),
104+
expectedSetupFiles: [expect.stringMatching(/src\/vitest.js$/u)],
105+
},
106+
{
107+
config: () => ({ test: { setupFiles: 'other-file.js' } }),
108+
expectedSetupFiles: [
109+
'other-file.js',
110+
expect.stringMatching(/src\/vitest.js$/u),
111+
],
112+
},
113+
])('adds cleanup', ({ config, expectedSetupFiles }) => {
114+
const subject = svelteTesting({
115+
resolveBrowser: false,
116+
autoCleanup: true,
117+
noExternal: false,
118+
})
119+
120+
const result = config()
121+
subject.config(result)
122+
123+
expect(result).toEqual({
124+
test: {
125+
setupFiles: expectedSetupFiles,
126+
},
127+
})
128+
})
129+
130+
test('skips cleanup in global mode', () => {
131+
const subject = svelteTesting({
132+
resolveBrowser: false,
133+
autoCleanup: true,
134+
noExternal: false,
135+
})
136+
137+
const result = { test: { globals: true } }
138+
subject.config(result)
139+
140+
expect(result).toEqual({
141+
test: {
142+
globals: true,
143+
},
144+
})
145+
})
146+
147+
test.each([
148+
{
149+
config: () => ({ ssr: { noExternal: [] } }),
150+
expectedNoExternal: ['@testing-library/svelte'],
151+
},
152+
{
153+
config: () => ({}),
154+
expectedNoExternal: ['@testing-library/svelte'],
155+
},
156+
{
157+
config: () => ({ ssr: { noExternal: 'other-file.js' } }),
158+
expectedNoExternal: ['other-file.js', '@testing-library/svelte'],
159+
},
160+
{
161+
config: () => ({ ssr: { noExternal: /other/u } }),
162+
expectedNoExternal: [/other/u, '@testing-library/svelte'],
163+
},
164+
])('adds noExternal rule', ({ config, expectedNoExternal }) => {
165+
const subject = svelteTesting({
166+
resolveBrowser: false,
167+
autoCleanup: false,
168+
noExternal: true,
169+
})
170+
171+
const result = config()
172+
subject.config(result)
173+
174+
expect(result).toEqual({
175+
ssr: {
176+
noExternal: expectedNoExternal,
177+
},
178+
})
179+
})
180+
181+
test.each([
182+
{
183+
config: () => ({ ssr: { noExternal: true } }),
184+
expectedNoExternal: true,
185+
},
186+
{
187+
config: () => ({ ssr: { noExternal: '@testing-library/svelte' } }),
188+
expectedNoExternal: '@testing-library/svelte',
189+
},
190+
{
191+
config: () => ({ ssr: { noExternal: /svelte/u } }),
192+
expectedNoExternal: /svelte/u,
193+
},
194+
])('skips noExternal if able', ({ config, expectedNoExternal }) => {
195+
const subject = svelteTesting({
196+
resolveBrowser: false,
197+
autoCleanup: false,
198+
noExternal: true,
199+
})
200+
201+
const result = config()
202+
subject.config(result)
203+
204+
expect(result).toEqual({
205+
ssr: {
206+
noExternal: expectedNoExternal,
207+
},
208+
})
209+
})
210+
211+
test('bails on noExternal if input is unexpected', () => {
212+
const subject = svelteTesting({
213+
resolveBrowser: false,
214+
autoCleanup: false,
215+
noExternal: true,
216+
})
217+
218+
const result = { ssr: { noExternal: false } }
219+
subject.config(result)
220+
221+
expect(result).toEqual({
222+
ssr: {
223+
noExternal: false,
224+
},
225+
})
226+
})
227+
})

src/vite.js

+47-1
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ import { fileURLToPath } from 'node:url'
77
* Ensures Svelte is imported correctly in tests
88
* and that the DOM is cleaned up after each test.
99
*
10-
* @param {{resolveBrowser?: boolean, autoCleanup?: boolean}} options
10+
* @param {{resolveBrowser?: boolean, autoCleanup?: boolean, noExternal?: boolean}} options
1111
* @returns {import('vite').Plugin}
1212
*/
1313
export const svelteTesting = ({
1414
resolveBrowser = true,
1515
autoCleanup = true,
16+
noExternal = true,
1617
} = {}) => ({
1718
name: 'vite-plugin-svelte-testing-library',
1819
config: (config) => {
@@ -27,6 +28,10 @@ export const svelteTesting = ({
2728
if (autoCleanup) {
2829
addAutoCleanup(config)
2930
}
31+
32+
if (noExternal) {
33+
addNoExternal(config)
34+
}
3035
},
3136
})
3237

@@ -64,6 +69,10 @@ const addAutoCleanup = (config) => {
6469
const test = config.test ?? {}
6570
let setupFiles = test.setupFiles ?? []
6671

72+
if (test.globals) {
73+
return
74+
}
75+
6776
if (typeof setupFiles === 'string') {
6877
setupFiles = [setupFiles]
6978
}
@@ -73,3 +82,40 @@ const addAutoCleanup = (config) => {
7382
test.setupFiles = setupFiles
7483
config.test = test
7584
}
85+
86+
/**
87+
* Add `@testing-library/svelte` to Vite's noExternal rules, if not present.
88+
*
89+
* This ensures `@testing-library/svelte` is processed by `@sveltejs/vite-plugin-svelte`
90+
* in certain monorepo setups.
91+
*/
92+
const addNoExternal = (config) => {
93+
const ssr = config.ssr ?? {}
94+
let noExternal = ssr.noExternal ?? []
95+
96+
if (noExternal === true) {
97+
return
98+
}
99+
100+
if (typeof noExternal === 'string' || noExternal instanceof RegExp) {
101+
noExternal = [noExternal]
102+
}
103+
104+
if (!Array.isArray(noExternal)) {
105+
return
106+
}
107+
108+
for (const rule of noExternal) {
109+
if (typeof rule === 'string' && rule === '@testing-library/svelte') {
110+
return
111+
}
112+
113+
if (rule instanceof RegExp && rule.test('@testing-library/svelte')) {
114+
return
115+
}
116+
}
117+
118+
noExternal.push('@testing-library/svelte')
119+
ssr.noExternal = noExternal
120+
config.ssr = ssr
121+
}

vite.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export default defineConfig({
1111
setupFiles: ['./src/__tests__/_vitest-setup.js'],
1212
mockReset: true,
1313
unstubGlobals: true,
14+
unstubEnvs: true,
1415
coverage: {
1516
provider: 'v8',
1617
include: ['src/**/*'],

0 commit comments

Comments
 (0)