@@ -4,35 +4,21 @@ import * as vite from 'vite';
44import { globalContentLayer } from '../../content/instance.js' ;
55import { attachContentServerListeners } from '../../content/server-listeners.js' ;
66import { eventCliSession , telemetry } from '../../events/index.js' ;
7+ import { runHookConfigDone , runHookConfigSetup } from '../../integrations/hooks.js' ;
78import { SETTINGS_FILE } from '../../preferences/constants.js' ;
9+ import { getPrerenderDefault } from '../../prerender/utils.js' ;
810import type { AstroSettings } from '../../types/astro.js' ;
911import type { AstroInlineConfig } from '../../types/public/config.js' ;
1012import { createSettings , resolveConfig } from '../config/index.js' ;
11- import { createNodeLogger } from '../logger/node .js' ;
13+ import { createVite } from '../create-vite .js' ;
1214import { collectErrorMetadata } from '../errors/dev/utils.js' ;
1315import { isAstroConfigZodError } from '../errors/errors.js' ;
1416import { createSafeError } from '../errors/index.js' ;
17+ import { createNodeLogger } from '../logger/node.js' ;
1518import { formatErrorMessage , warnIfCspWithShiki } from '../messages/runtime.js' ;
19+ import { createRoutesList } from '../routing/create-manifest.js' ;
1620import type { Container } from './container.js' ;
17- import { createContainer , startContainer } from './container.js' ;
18-
19- async function createRestartedContainer (
20- container : Container ,
21- settings : AstroSettings ,
22- ) : Promise < Container > {
23- const { logger, fs, inlineConfig } = container ;
24- const newContainer = await createContainer ( {
25- isRestart : true ,
26- logger : logger ,
27- settings,
28- inlineConfig,
29- fs,
30- } ) ;
31-
32- await startContainer ( newContainer ) ;
33-
34- return newContainer ;
35- }
21+ import { createContainer } from './container.js' ;
3622
3723const configRE = / .* a s t r o .c o n f i g .(?: m j s | m t s | c j s | c t s | j s | t s ) $ / ;
3824
@@ -45,25 +31,20 @@ function shouldRestartContainer(
4531 let shouldRestart = false ;
4632 const normalizedChangedFile = vite . normalizePath ( changedFile ) ;
4733
48- // If the config file changed, reload the config and restart the server.
4934 if ( inlineConfig . configFile ) {
5035 shouldRestart = vite . normalizePath ( inlineConfig . configFile ) === normalizedChangedFile ;
51- }
52- // Otherwise, watch for any astro.config.* file changes in project root
53- else {
36+ } else {
5437 shouldRestart = configRE . test ( normalizedChangedFile ) ;
5538 const settingsPath = vite . normalizePath (
5639 fileURLToPath ( new URL ( SETTINGS_FILE , settings . dotAstroDir ) ) ,
5740 ) ;
5841 if ( settingsPath . endsWith ( normalizedChangedFile ) ) {
5942 shouldRestart = settings . preferences . ignoreNextPreferenceReload ? false : true ;
60-
6143 settings . preferences . ignoreNextPreferenceReload = false ;
6244 }
6345 }
6446
6547 if ( ! shouldRestart && settings . watchFiles . length > 0 ) {
66- // If the config file didn't change, check if any of the watched files changed.
6748 shouldRestart = settings . watchFiles . some (
6849 ( path ) => vite . normalizePath ( path ) === vite . normalizePath ( changedFile ) ,
6950 ) ;
@@ -72,46 +53,79 @@ function shouldRestartContainer(
7253 return shouldRestart ;
7354}
7455
75- async function restartContainer ( container : Container ) : Promise < Container | Error > {
76- const { logger, close, settings : existingSettings } = container ;
56+ /**
57+ * Restart the dev server in-place by reusing the existing Vite server instance.
58+ *
59+ * Instead of tearing down and recreating the entire container (which creates a
60+ * brand new Vite server), this function re-reads the Astro config, builds a new
61+ * Vite inline config with updated plugins, patches it onto the existing server,
62+ * then calls Vite's own native restart. Vite's restart does an in-place mutation
63+ * of the server object, keeping the same HTTP server / TCP socket alive and
64+ * passing `previousEnvironments` to plugins — allowing adapters like
65+ * `@cloudflare/vite-plugin` to reuse their miniflare instance rather than
66+ * disposing and recreating it.
67+ */
68+ async function restartContainerInPlace ( container : Container ) : Promise < AstroSettings | Error > {
69+ const { logger, settings : existingSettings , inlineConfig, fs } = container ;
7770 container . restartInFlight = true ;
7871
7972 try {
80- const { astroConfig } = await resolveConfig ( container . inlineConfig , 'dev' , container . fs ) ;
81- if ( astroConfig . security . csp ) {
82- logger . warn (
83- 'config' ,
84- "Astro's Content Security Policy (CSP) does not work in development mode. To verify your CSP implementation, build the project and run the preview server." ,
85- ) ;
86- }
73+ const { astroConfig } = await resolveConfig ( inlineConfig , 'dev' , fs ) ;
8774 warnIfCspWithShiki ( astroConfig , logger ) ;
88- const settings = await createSettings (
75+ let settings = await createSettings (
8976 astroConfig ,
90- container . inlineConfig . logLevel ,
77+ inlineConfig . logLevel ,
9178 fileURLToPath ( existingSettings . config . root ) ,
9279 ) ;
93- await close ( ) ;
94- return await createRestartedContainer ( container , settings ) ;
80+
81+ settings = await runHookConfigSetup ( { settings, command : 'dev' , logger, isRestart : true } ) ;
82+ if ( ! settings . adapter ?. adapterFeatures ?. buildOutput ) {
83+ settings . buildOutput = getPrerenderDefault ( settings . config ) ? 'static' : 'server' ;
84+ }
85+ await runHookConfigDone ( { settings, logger, command : 'dev' } ) ;
86+
87+ const mode = inlineConfig ?. mode ?? 'development' ;
88+ const {
89+ server : { host, headers, allowedHosts } ,
90+ } = settings . config ;
91+ const rendererClientEntries = settings . renderers
92+ . map ( ( r ) => r . clientEntrypoint )
93+ . filter ( Boolean ) as string [ ] ;
94+ const routesList = await createRoutesList ( { settings, fsMod : fs } , logger , { dev : true } ) ;
95+ const address = container . viteServer . httpServer ?. address ( ) ;
96+ const port = address !== null && typeof address === 'object' ? address . port : undefined ;
97+ const newViteConfig = await createVite (
98+ {
99+ server : { host, headers, allowedHosts, port } ,
100+ optimizeDeps : { include : rendererClientEntries } ,
101+ } ,
102+ { settings, logger, mode, command : 'dev' , fs, sync : false , routesList } ,
103+ ) ;
104+
105+ // Resolve the new inline config into a full ResolvedConfig and assign it
106+ // onto the existing server so Vite's restartServer() uses the new plugins.
107+ container . viteServer . config = await vite . resolveConfig ( newViteConfig , 'serve' ) ;
108+
109+ await container . viteServer . restart ( ) ;
110+
111+ container . settings = settings ;
112+ return settings ;
95113 } catch ( _err ) {
96114 const error = createSafeError ( _err ) ;
97- // Print all error messages except ZodErrors from AstroConfig as the pre-logged error is sufficient
98115 if ( ! isAstroConfigZodError ( _err ) ) {
99116 logger . error (
100117 'config' ,
101118 formatErrorMessage ( collectErrorMetadata ( error ) , logger . level ( ) === 'debug' ) + '\n' ,
102119 ) ;
103120 }
104- // Inform connected clients of the config error
105- container . viteServer . environments . client . hot . send ( {
121+ container . viteServer . environments ?. client ?. hot ?. send ( {
106122 type : 'error' ,
107- err : {
108- message : error . message ,
109- stack : error . stack || '' ,
110- } ,
123+ err : { message : error . message , stack : error . stack || '' } ,
111124 } ) ;
112- container . restartInFlight = false ;
113125 logger . error ( null , 'Continuing with previous valid configuration\n' ) ;
114126 return error ;
127+ } finally {
128+ container . restartInFlight = false ;
115129 }
116130}
117131
@@ -132,12 +146,6 @@ export async function createContainerWithAutomaticRestart({
132146} : CreateContainerWithAutomaticRestart ) : Promise < Restart > {
133147 const logger = createNodeLogger ( inlineConfig ?? { } ) ;
134148 const { userConfig, astroConfig } = await resolveConfig ( inlineConfig ?? { } , 'dev' , fs ) ;
135- if ( astroConfig . security . csp ) {
136- logger . warn (
137- 'config' ,
138- "Astro's Content Security Policy (CSP) does not work in development mode. To verify your CSP implementation, build the project and run the preview server." ,
139- ) ;
140- }
141149 warnIfCspWithShiki ( astroConfig , logger ) ;
142150 telemetry . record ( eventCliSession ( 'dev' , userConfig ) ) ;
143151
@@ -163,7 +171,6 @@ export async function createContainerWithAutomaticRestart({
163171 container : initialContainer ,
164172 bindCLIShortcuts ( ) {
165173 const customShortcuts : Array < vite . CLIShortcut > = [
166- // Disable default Vite shortcuts that don't work well with Astro
167174 { key : 'r' , description : '' } ,
168175 { key : 'u' , description : '' } ,
169176 { key : 'c' , description : '' } ,
@@ -185,54 +192,42 @@ export async function createContainerWithAutomaticRestart({
185192 } ,
186193 } ;
187194
188- async function handleServerRestart ( logMsg = '' , server ?: vite . ViteDevServer ) {
189- logger . info ( null , ( logMsg + ' Restarting...' ) . trim ( ) ) ;
190- const container = restart . container ;
191- const result = await restartContainer ( container ) ;
192- if ( result instanceof Error ) {
193- // Failed to restart, use existing container
194- resolveRestart ( result ) ;
195- } else {
196- // Restart success. Add new watches because this is a new container with a new Vite server
197- restart . container = result ;
198- setupContainer ( ) ;
199- await attachContentServerListeners ( restart . container ) ;
200-
201- if ( server ) {
202- // Vite expects the resolved URLs to be available
203- server . resolvedUrls = result . viteServer . resolvedUrls ;
204- }
205-
206- resolveRestart ( null ) ;
207- }
208- restartComplete = new Promise < Error | null > ( ( resolve ) => {
209- resolveRestart = resolve ;
210- } ) ;
211- }
212-
213195 function handleChangeRestart ( logMsg : string ) {
214196 return async function ( changedFile : string ) {
215197 if ( shouldRestartContainer ( restart . container , changedFile ) ) {
216- handleServerRestart ( logMsg ) ;
198+ logger . info ( null , ( logMsg + ' Restarting...' ) . trim ( ) ) ;
199+ const result = await restartContainerInPlace ( restart . container ) ;
200+ if ( result instanceof Error ) {
201+ resolveRestart ( result ) ;
202+ } else {
203+ setupContainer ( ) ;
204+ await attachContentServerListeners ( restart . container ) ;
205+ resolveRestart ( null ) ;
206+ }
207+ restartComplete = new Promise < Error | null > ( ( resolve ) => {
208+ resolveRestart = resolve ;
209+ } ) ;
217210 }
218211 } ;
219212 }
220213
221- // Set up watchers, vite restart API, and shortcuts
214+ let changeHandler : ( file : string ) => void ;
215+ let unlinkHandler : ( file : string ) => void ;
216+ let addHandler : ( file : string ) => void ;
217+
222218 function setupContainer ( ) {
223219 const watcher = restart . container . viteServer . watcher ;
224- watcher . on ( 'change' , handleChangeRestart ( 'Configuration file updated.' ) ) ;
225- watcher . on ( 'unlink' , handleChangeRestart ( 'Configuration file removed.' ) ) ;
226- watcher . on ( 'add' , handleChangeRestart ( 'Configuration file added.' ) ) ;
227-
228- // Restart the Astro dev server instead of Vite's when the API is called by plugins.
229- // Ignore the `forceOptimize` parameter for now.
230- restart . container . viteServer . restart = async ( ) => {
231- if ( ! restart . container . restartInFlight ) {
232- await handleServerRestart ( '' , restart . container . viteServer ) ;
233- }
234- } ;
220+ if ( changeHandler ) watcher . off ( 'change' , changeHandler ) ;
221+ if ( unlinkHandler ) watcher . off ( 'unlink' , unlinkHandler ) ;
222+ if ( addHandler ) watcher . off ( 'add' , addHandler ) ;
223+ changeHandler = handleChangeRestart ( 'Configuration file updated.' ) ;
224+ unlinkHandler = handleChangeRestart ( 'Configuration file removed.' ) ;
225+ addHandler = handleChangeRestart ( 'Configuration file added.' ) ;
226+ watcher . on ( 'change' , changeHandler ) ;
227+ watcher . on ( 'unlink' , unlinkHandler ) ;
228+ watcher . on ( 'add' , addHandler ) ;
235229 }
230+
236231 setupContainer ( ) ;
237232 return restart ;
238233}
0 commit comments