Skip to content

Commit 41df81a

Browse files
authored
fix: support resolving top-level input option with plugins (#23101)
1 parent fa005d1 commit 41df81a

11 files changed

Lines changed: 283 additions & 105 deletions

File tree

packages/vite/src/node/__tests__/build.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,36 @@ describe('build', () => {
112112
expect(chunk?.code).toContain('from-top-level-input')
113113
})
114114

115+
test('top-level input can be a virtual module for build', async () => {
116+
const input = 'virtual:entry'
117+
const resolvedInput = `\0${input}`
118+
const result = (await build({
119+
root: resolve(dirname, 'packages/build-project'),
120+
logLevel: 'silent',
121+
input,
122+
build: {
123+
write: false,
124+
},
125+
plugins: [
126+
{
127+
name: 'virtual-entry',
128+
resolveId(id) {
129+
if (id === input) {
130+
return resolvedInput
131+
}
132+
},
133+
load(id) {
134+
if (id === resolvedInput) {
135+
return `console.log('from-virtual-top-level-input')`
136+
}
137+
},
138+
},
139+
],
140+
})) as RolldownOutput
141+
const chunk = result.output.find((o) => o.type === 'chunk')
142+
expect(chunk?.code).toContain('from-virtual-top-level-input')
143+
})
144+
115145
test('file hash should change when pure css chunk changes', async () => {
116146
const buildProject = async (cssColor: string) => {
117147
return (await build({

packages/vite/src/node/__tests__/config.spec.ts

Lines changed: 14 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,16 +1175,11 @@ describe('resolveConfig', () => {
11751175
)
11761176
})
11771177

1178-
const resolveInputFromRoot = (p: string) =>
1179-
normalizePath(path.resolve(process.cwd(), p))
1180-
11811178
test('top-level input applies to the client environment only (non-inherit)', async () => {
11821179
const config = await resolveConfig({ input: 'src/main.ts' }, 'serve')
11831180

1184-
expect(config.input).toBe(resolveInputFromRoot('src/main.ts'))
1185-
expect(config.environments.client.input).toBe(
1186-
resolveInputFromRoot('src/main.ts'),
1187-
)
1181+
expect(config.input).toBe('src/main.ts')
1182+
expect(config.environments.client.input).toBe('src/main.ts')
11881183
expect(
11891184
config.environments.client.build.rolldownOptions.input,
11901185
).toBeUndefined()
@@ -1203,59 +1198,31 @@ describe('resolveConfig', () => {
12031198
'serve',
12041199
)
12051200

1206-
expect(config.environments.client.input).toBe(
1207-
resolveInputFromRoot('src/main.ts'),
1208-
)
1209-
expect(config.environments.ssr.input).toBe(
1210-
resolveInputFromRoot('src/entry-server.ts'),
1211-
)
1201+
expect(config.environments.client.input).toBe('src/main.ts')
1202+
expect(config.environments.ssr.input).toBe('src/entry-server.ts')
12121203
})
12131204

1214-
test('resolves array input to absolute paths', async () => {
1205+
test('keeps array input relative to the root', async () => {
12151206
const config = await resolveConfig(
12161207
{ input: ['src/a.ts', 'src/b.ts'] },
12171208
'serve',
12181209
)
12191210

1220-
expect(config.environments.client.input).toEqual([
1221-
resolveInputFromRoot('src/a.ts'),
1222-
resolveInputFromRoot('src/b.ts'),
1223-
])
1211+
expect(config.environments.client.input).toEqual(['src/a.ts', 'src/b.ts'])
12241212
})
12251213

1226-
test('resolves record input to absolute paths', async () => {
1214+
test('keeps record input relative to the root', async () => {
12271215
const config = await resolveConfig(
12281216
{ input: { main: 'src/a.ts', admin: 'src/b.ts' } },
12291217
'serve',
12301218
)
12311219

12321220
expect(config.environments.client.input).toEqual({
1233-
main: resolveInputFromRoot('src/a.ts'),
1234-
admin: resolveInputFromRoot('src/b.ts'),
1221+
main: 'src/a.ts',
1222+
admin: 'src/b.ts',
12351223
})
12361224
})
12371225

1238-
test('adds input to server.fs.allow by default', async () => {
1239-
const inputs = {
1240-
main: resolveInputFromRoot('src/a.ts'),
1241-
admin: resolveInputFromRoot('src/b.ts'),
1242-
}
1243-
const config = await resolveConfig(
1244-
{ input: { main: 'src/a.ts', admin: 'src/b.ts' } },
1245-
'serve',
1246-
)
1247-
1248-
expect(config.server.fs.allow).toStrictEqual(
1249-
expect.arrayContaining(Object.values(inputs)),
1250-
)
1251-
})
1252-
1253-
test('adds the default index.html input to server.fs.allow', async () => {
1254-
const config = await resolveConfig({}, 'serve')
1255-
1256-
expect(config.server.fs.allow).toContain(resolveInputFromRoot('index.html'))
1257-
})
1258-
12591226
test('reserves glob characters in input', async () => {
12601227
const cases: { name: string; input: UserConfig['input'] }[] = [
12611228
{ name: 'wildcard', input: 'src/*.ts' },
@@ -1286,17 +1253,17 @@ describe('resolveConfig', () => {
12861253
{
12871254
name: 'glob',
12881255
input: 'src/\\*.ts',
1289-
expected: resolveInputFromRoot('src/*.ts'),
1256+
expected: 'src/*.ts',
12901257
},
12911258
{
12921259
name: 'array element',
12931260
input: ['src/\\*.ts'],
1294-
expected: [resolveInputFromRoot('src/*.ts')],
1261+
expected: ['src/*.ts'],
12951262
},
12961263
{
12971264
name: 'record',
12981265
input: { main: 'src/\\*.ts' },
1299-
expected: { main: resolveInputFromRoot('src/*.ts') },
1266+
expected: { main: 'src/*.ts' },
13001267
},
13011268
]
13021269

@@ -1319,14 +1286,14 @@ describe('resolveConfig', () => {
13191286
{
13201287
name: 'special characters',
13211288
input: 'src/a-b_c$.ts',
1322-
expected: resolveInputFromRoot('src/a-b_c$.ts'),
1289+
expected: 'src/a-b_c$.ts',
13231290
},
13241291
...(isWindows
13251292
? [
13261293
{
13271294
name: 'windows path',
13281295
input: 'src\\foo.ts',
1329-
expected: resolveInputFromRoot('src/foo.ts'),
1296+
expected: 'src\\foo.ts',
13301297
},
13311298
]
13321299
: []),
@@ -2047,7 +2014,6 @@ describe('resolveServerOptions', () => {
20472014
const resolved = await resolveServerOptions(
20482015
'/root',
20492016
{ allowedHosts: [] },
2050-
undefined,
20512017
logger,
20522018
)
20532019
expect(resolved.allowedHosts).toEqual(['example.com'])
@@ -2059,7 +2025,6 @@ describe('resolveServerOptions', () => {
20592025
const resolved = await resolveServerOptions(
20602026
'/root',
20612027
{ allowedHosts: [] },
2062-
undefined,
20632028
logger,
20642029
)
20652030
expect(resolved.allowedHosts).toEqual([
@@ -2075,7 +2040,6 @@ describe('resolveServerOptions', () => {
20752040
const resolved = await resolveServerOptions(
20762041
'/root',
20772042
{ allowedHosts: [] },
2078-
undefined,
20792043
logger,
20802044
)
20812045
expect(resolved.allowedHosts).toEqual([
@@ -2091,7 +2055,6 @@ describe('resolveServerOptions', () => {
20912055
const resolved = await resolveServerOptions(
20922056
'/root',
20932057
{ allowedHosts: [] },
2094-
undefined,
20952058
logger,
20962059
)
20972060
expect(resolved.allowedHosts).toEqual(['example.com', 'test.com'])
@@ -2102,7 +2065,6 @@ describe('resolveServerOptions', () => {
21022065
const resolved = await resolveServerOptions(
21032066
'/root',
21042067
{ allowedHosts: ['existing.com'] },
2105-
undefined,
21062068
logger,
21072069
)
21082070
expect(resolved.allowedHosts).toEqual([
@@ -2117,7 +2079,6 @@ describe('resolveServerOptions', () => {
21172079
const resolved = await resolveServerOptions(
21182080
'/root',
21192081
{ allowedHosts: true },
2120-
undefined,
21212082
logger,
21222083
)
21232084
expect(resolved.allowedHosts).toBe(true)
@@ -2130,7 +2091,6 @@ describe('resolveServerOptions', () => {
21302091
const resolved = await resolveServerOptions(
21312092
'/root',
21322093
{ allowedHosts: [] },
2133-
undefined,
21342094
logger,
21352095
)
21362096
expect(resolved.allowedHosts).toEqual([])

packages/vite/src/node/__tests__/dev.spec.ts

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import { afterEach, describe, expect, test } from 'vitest'
1+
import path from 'node:path'
2+
import { afterEach, describe, expect, test, vi } from 'vitest'
23
import type { ResolvedServerUrls } from 'vite'
34
import { createServer, resolveConfig } from '..'
45
import type { ViteDevServer } from '..'
56
import { promiseWithResolvers } from '../../shared/utils'
7+
import { createLogger } from '../logger'
8+
import { normalizePath } from '../utils'
69

710
describe('resolveBuildEnvironmentOptions in dev', () => {
811
test('build.rolldownOptions should not have input in lib', async () => {
@@ -28,6 +31,129 @@ describe('the dev server', () => {
2831
await server?.close()
2932
})
3033

34+
test('resolves each environment input as a safe module', async () => {
35+
const root = path.join(import.meta.dirname, 'fixtures', 'input-option')
36+
const clientEntry = normalizePath(path.join(root, 'client-entry.js'))
37+
const ssrEntry = normalizePath(path.join(root, 'ssr-entry.js'))
38+
39+
server = await createServer({
40+
configFile: false,
41+
root,
42+
input: 'virtual:client-entry',
43+
environments: {
44+
ssr: { input: { main: 'virtual:ssr-entry' } },
45+
},
46+
optimizeDeps: { noDiscovery: true },
47+
server: { fs: { allow: [] }, middlewareMode: true, ws: false },
48+
plugins: [
49+
{
50+
name: 'resolve-environment-entries',
51+
resolveId(id) {
52+
if (id === `virtual:${this.environment.name}-entry`) {
53+
return this.environment.name === 'client' ? clientEntry : ssrEntry
54+
}
55+
},
56+
},
57+
],
58+
})
59+
60+
expect(server.config.safeModulePaths).toStrictEqual(new Set([clientEntry]))
61+
await server.environments.ssr.pluginContainer.buildStart()
62+
expect(server.config.safeModulePaths).toStrictEqual(
63+
new Set([clientEntry, ssrEntry]),
64+
)
65+
})
66+
67+
test('does not mark an external environment input as safe', async () => {
68+
const root = path.join(import.meta.dirname, 'fixtures', 'input-option')
69+
const externalEntry = normalizePath(path.join(root, 'external-entry.js'))
70+
71+
server = await createServer({
72+
configFile: false,
73+
root,
74+
input: 'virtual:external-entry',
75+
optimizeDeps: { noDiscovery: true },
76+
server: { fs: { allow: [] }, middlewareMode: true, ws: false },
77+
plugins: [
78+
{
79+
name: 'external-environment-entry',
80+
resolveId(id) {
81+
if (id === 'virtual:external-entry') {
82+
return { id: externalEntry, external: true }
83+
}
84+
},
85+
},
86+
],
87+
})
88+
89+
expect(server.config.safeModulePaths).not.toContain(externalEntry)
90+
})
91+
92+
test('silently resolves index.html as the fallback for every environment', async () => {
93+
const root = path.join(import.meta.dirname, 'fixtures', 'input-option')
94+
const clientEntry = normalizePath(path.join(root, 'client-index.html'))
95+
const resolvedEnvironments = new Set<string>()
96+
const logger = createLogger('silent')
97+
logger.warn = vi.fn()
98+
99+
server = await createServer({
100+
configFile: false,
101+
root,
102+
customLogger: logger,
103+
optimizeDeps: { noDiscovery: true },
104+
server: { fs: { allow: [] }, middlewareMode: true, ws: false },
105+
plugins: [
106+
{
107+
name: 'resolve-environment-index',
108+
resolveId(id) {
109+
if (id !== 'index.html') return
110+
resolvedEnvironments.add(this.environment.name)
111+
if (this.environment.name === 'ssr') {
112+
throw new Error('ssr does not have an HTML entry')
113+
}
114+
return clientEntry
115+
},
116+
},
117+
],
118+
})
119+
120+
expect(resolvedEnvironments).toStrictEqual(new Set(['client']))
121+
await server.environments.ssr.pluginContainer.buildStart()
122+
expect(resolvedEnvironments).toStrictEqual(new Set(['client', 'ssr']))
123+
expect(server.config.safeModulePaths).toStrictEqual(new Set([clientEntry]))
124+
expect(logger.warn).not.toHaveBeenCalled()
125+
})
126+
127+
test('does not ignore buildStart errors while resolving fallback inputs', async () => {
128+
server = await createServer({
129+
configFile: false,
130+
root: path.join(import.meta.dirname, 'fixtures', 'input-option'),
131+
logLevel: 'silent',
132+
optimizeDeps: { noDiscovery: true },
133+
plugins: [
134+
{
135+
name: 'failing-build-start',
136+
perEnvironmentStartEndDuringDev: true,
137+
buildStart() {
138+
if (this.environment.name === 'ssr') {
139+
throw new Error('buildStart failed')
140+
}
141+
},
142+
},
143+
],
144+
server: {
145+
fs: { allow: [] },
146+
middlewareMode: true,
147+
watch: null,
148+
ws: false,
149+
},
150+
})
151+
152+
await expect(
153+
server.environments.ssr.pluginContainer.buildStart(),
154+
).rejects.toThrow('buildStart failed')
155+
})
156+
31157
test('resolves the server URLs before the httpServer listening events are called', async () => {
32158
expect.assertions(1)
33159

0 commit comments

Comments
 (0)