Skip to content

Commit 94bf3fe

Browse files
committed
fix(build): preserve environment key ordering when user defines vite.environments.ssr (#17760)
1 parent 2043e4f commit 94bf3fe

3 files changed

Lines changed: 118 additions & 10 deletions

File tree

.changeset/witty-cloths-do.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Fixes a build failure when defining `vite.environments.ssr` in the Astro config. User-provided environment config for `ssr`, `prerender`, or `client` is now properly deep-merged with Astro's internal environment settings instead of silently breaking the server entry naming.

packages/astro/src/core/build/vite-build-config.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,21 @@ export function createViteBuildConfig(opts: CreateViteBuildConfigOptions): vite.
5050
const { settings, viteConfig, routes, plugins, builder, isRolldownInput } = opts;
5151
const legacyAdapter = !settings.adapter || isLegacyAdapter(settings.adapter);
5252

53+
// Separate Astro-managed environment keys from user-defined ones so that the
54+
// managed environments always appear last (and in a fixed order) in the object.
55+
// Vite's `configResolved` fires per environment in key-insertion order, and the
56+
// `astro:resolve-input` plugin relies on `ssr` being resolved last so its
57+
// rolldown input value is the one that persists. If a user-defined
58+
// `environments.ssr` is spread first, its key position would place SSR earlier
59+
// in the iteration order, causing later environments to overwrite the saved
60+
// input. See https://github.com/withastro/astro/issues/17760
61+
const {
62+
[ASTRO_VITE_ENVIRONMENT_NAMES.ssr]: userSsr,
63+
[ASTRO_VITE_ENVIRONMENT_NAMES.prerender]: userPrerender,
64+
[ASTRO_VITE_ENVIRONMENT_NAMES.client]: userClient,
65+
...userEnvironments
66+
} = viteConfig.environments ?? {};
67+
5368
return {
5469
...viteConfig,
5570
logLevel: viteConfig.logLevel ?? 'error',
@@ -143,8 +158,9 @@ export function createViteBuildConfig(opts: CreateViteBuildConfigOptions): vite.
143158
envPrefix: viteConfig.envPrefix ?? 'PUBLIC_',
144159
base: settings.config.base,
145160
environments: {
146-
...(viteConfig.environments ?? {}),
161+
...userEnvironments,
147162
[ASTRO_VITE_ENVIRONMENT_NAMES.prerender]: {
163+
...userPrerender,
148164
build: {
149165
emitAssets: true,
150166
outDir: fileURLToPath(getPrerenderOutputDirectory(settings)),
@@ -157,24 +173,21 @@ export function createViteBuildConfig(opts: CreateViteBuildConfigOptions): vite.
157173
output: {
158174
entryFileNames: `${PRERENDER_ENTRY_FILENAME_PREFIX}.[hash].mjs`,
159175
format: 'esm',
160-
...viteConfig.environments?.prerender?.build?.rolldownOptions?.output,
176+
...userPrerender?.build?.rolldownOptions?.output,
161177
},
162178
},
163179
ssr: true,
164180
},
165181
},
166182
[ASTRO_VITE_ENVIRONMENT_NAMES.client]: {
183+
...userClient,
167184
build: {
168185
emitAssets: true,
169186
target: 'esnext',
170187
outDir: fileURLToPath(getClientOutputDirectory(settings)),
171188
copyPublicDir: true,
172-
sourcemap:
173-
viteConfig.environments?.client?.build?.sourcemap ??
174-
viteConfig.build?.sourcemap ??
175-
false,
176-
minify:
177-
viteConfig.environments?.client?.build?.minify ?? viteConfig.build?.minify ?? true,
189+
sourcemap: userClient?.build?.sourcemap ?? viteConfig.build?.sourcemap ?? false,
190+
minify: userClient?.build?.minify ?? viteConfig.build?.minify ?? true,
178191
rolldownOptions: {
179192
preserveEntrySignatures: 'exports-only',
180193
output: {
@@ -199,17 +212,18 @@ export function createViteBuildConfig(opts: CreateViteBuildConfigOptions): vite.
199212
}
200213
return `${settings.config.build.assets}/[name].[hash][extname]`;
201214
},
202-
...viteConfig.environments?.client?.build?.rolldownOptions?.output,
215+
...userClient?.build?.rolldownOptions?.output,
203216
},
204217
},
205218
},
206219
},
207220
[ASTRO_VITE_ENVIRONMENT_NAMES.ssr]: {
221+
...userSsr,
208222
build: {
209223
outDir: fileURLToPath(getServerOutputDirectory(settings)),
210224
rolldownOptions: {
211225
output: {
212-
...viteConfig.environments?.ssr?.build?.rolldownOptions?.output,
226+
...userSsr?.build?.rolldownOptions?.output,
213227
},
214228
},
215229
},

packages/astro/test/units/build/vite-build-config.test.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,95 @@ describe('createViteBuildConfig', () => {
246246
});
247247
});
248248

249+
describe('environment key ordering', () => {
250+
it('places ssr environment last even when user defines environments.ssr', async () => {
251+
const settings = await createBasicSettings();
252+
const config = buildConfig({
253+
settings,
254+
viteConfig: {
255+
environments: {
256+
ssr: {
257+
resolve: { external: ['some-pkg'] },
258+
},
259+
},
260+
},
261+
});
262+
263+
const envKeys = Object.keys(config.environments ?? {});
264+
assert.equal(envKeys.at(-1), 'ssr', 'ssr must be the last environment key');
265+
});
266+
267+
it('preserves user non-build properties on the ssr environment', async () => {
268+
const settings = await createBasicSettings();
269+
const config = buildConfig({
270+
settings,
271+
viteConfig: {
272+
environments: {
273+
ssr: {
274+
resolve: { external: ['some-pkg'] },
275+
},
276+
},
277+
},
278+
});
279+
280+
const ssrEnv = config.environments?.ssr as Record<string, any>;
281+
assert.deepEqual(ssrEnv.resolve, { external: ['some-pkg'] });
282+
});
283+
284+
it('preserves user non-build properties on the prerender environment', async () => {
285+
const settings = await createBasicSettings();
286+
const config = buildConfig({
287+
settings,
288+
viteConfig: {
289+
environments: {
290+
prerender: {
291+
resolve: { external: ['some-pkg'] },
292+
},
293+
},
294+
},
295+
});
296+
297+
const prerenderEnv = config.environments?.prerender as Record<string, any>;
298+
assert.deepEqual(prerenderEnv.resolve, { external: ['some-pkg'] });
299+
});
300+
301+
it('preserves user non-build properties on the client environment', async () => {
302+
const settings = await createBasicSettings();
303+
const config = buildConfig({
304+
settings,
305+
viteConfig: {
306+
environments: {
307+
client: {
308+
resolve: { external: ['some-pkg'] },
309+
},
310+
},
311+
},
312+
});
313+
314+
const clientEnv = config.environments?.client as Record<string, any>;
315+
assert.deepEqual(clientEnv.resolve, { external: ['some-pkg'] });
316+
});
317+
318+
it('does not include managed keys when user sets environments.ssr before other keys', async () => {
319+
const settings = await createBasicSettings();
320+
const config = buildConfig({
321+
settings,
322+
viteConfig: {
323+
environments: {
324+
ssr: { resolve: { external: ['a'] } },
325+
prerender: { resolve: { external: ['b'] } },
326+
client: { resolve: { external: ['c'] } },
327+
},
328+
},
329+
});
330+
331+
const envKeys = Object.keys(config.environments ?? {});
332+
// Managed keys appear exactly once each, at the end
333+
const lastThree = envKeys.slice(-3);
334+
assert.deepEqual(lastThree, ['prerender', 'client', 'ssr']);
335+
});
336+
});
337+
249338
describe('general config', () => {
250339
it('sets base from settings config', async () => {
251340
const settings = await createBasicSettings({ base: '/blog' });

0 commit comments

Comments
 (0)