1
- import type { HmrContext , ModuleNode } from 'vite' ;
1
+ import path from 'node:path' ;
2
+ import { appendForwardSlash } from '@astrojs/internal-helpers/path' ;
3
+ import type { HmrContext } from 'vite' ;
2
4
import type { AstroConfig } from '../@types/astro.js' ;
3
5
import type { cachedCompilation } from '../core/compile/index.js' ;
4
6
import { invalidateCompilation , isCached , type CompileResult } from '../core/compile/index.js' ;
5
7
import type { Logger } from '../core/logger/core.js' ;
6
- import { isAstroSrcFile } from '../core/logger/vite.js' ;
7
- import { isAstroScript } from './query.js' ;
8
8
9
9
export interface HandleHotUpdateOptions {
10
10
config : AstroConfig ;
11
11
logger : Logger ;
12
+ astroFileToCssAstroDeps : Map < string , Set < string > > ;
12
13
13
14
compile : ( ) => ReturnType < typeof cachedCompilation > ;
14
15
source : string ;
15
16
}
16
17
17
18
export async function handleHotUpdate (
18
19
ctx : HmrContext ,
19
- { config, logger, compile, source } : HandleHotUpdateOptions
20
+ { config, logger, astroFileToCssAstroDeps , compile, source } : HandleHotUpdateOptions
20
21
) {
21
22
let isStyleOnlyChange = false ;
22
23
if ( ctx . file . endsWith ( '.astro' ) && isCached ( config , ctx . file ) ) {
@@ -34,75 +35,45 @@ export async function handleHotUpdate(
34
35
invalidateCompilation ( config , ctx . file ) ;
35
36
}
36
37
37
- // Skip monorepo files to avoid console spam
38
- if ( isAstroSrcFile ( ctx . file ) ) {
39
- return ;
40
- }
41
-
42
- // go through each of these modules importers and invalidate any .astro compilation
43
- // that needs to be rerun.
44
- const filtered = new Set < ModuleNode > ( ctx . modules ) ;
45
- const files = new Set < string > ( ) ;
46
- for ( const mod of ctx . modules ) {
47
- // Skip monorepo files to avoid console spam
48
- if ( isAstroSrcFile ( mod . id ?? mod . file ) ) {
49
- filtered . delete ( mod ) ;
50
- continue ;
51
- }
52
-
53
- if ( mod . file && isCached ( config , mod . file ) ) {
54
- filtered . add ( mod ) ;
55
- files . add ( mod . file ) ;
56
- }
57
-
58
- for ( const imp of mod . importers ) {
59
- if ( imp . file && isCached ( config , imp . file ) ) {
60
- filtered . add ( imp ) ;
61
- files . add ( imp . file ) ;
62
- }
63
- }
64
- }
65
-
66
- // Invalidate happens as a separate step because a single .astro file
67
- // produces multiple CSS modules and we want to return all of those.
68
- for ( const file of files ) {
69
- if ( isStyleOnlyChange && file === ctx . file ) continue ;
70
- invalidateCompilation ( config , file ) ;
71
- // If `ctx.file` is depended by an .astro file, e.g. via `this.addWatchFile`,
72
- // Vite doesn't trigger updating that .astro file by default. See:
73
- // https://github.com/vitejs/vite/issues/3216
74
- // For now, we trigger the change manually here.
75
- if ( file . endsWith ( '.astro' ) ) {
76
- ctx . server . moduleGraph . onFileChange ( file ) ;
77
- }
78
- }
79
-
80
- // Bugfix: sometimes style URLs get normalized and end with `lang.css=`
81
- // These will cause full reloads, so filter them out here
82
- const mods = [ ...filtered ] . filter ( ( m ) => ! m . url . endsWith ( '=' ) ) ;
83
-
84
- // If only styles are changed, remove the component file from the update list
85
38
if ( isStyleOnlyChange ) {
86
39
logger . debug ( 'watch' , 'style-only change' ) ;
87
40
// Only return the Astro styles that have changed!
88
- return mods . filter ( ( mod ) => mod . id ?. includes ( 'astro&type=style' ) ) ;
41
+ return ctx . modules . filter ( ( mod ) => mod . id ?. includes ( 'astro&type=style' ) ) ;
89
42
}
90
43
91
- // Add hoisted scripts so these get invalidated
92
- for ( const mod of mods ) {
93
- for ( const imp of mod . importedModules ) {
94
- if ( imp . id && isAstroScript ( imp . id ) ) {
95
- mods . push ( imp ) ;
44
+ // Edge case handling usually caused by Tailwind creating circular dependencies
45
+ //
46
+ // TODO: we can also workaround this with better CSS dependency management for Astro files,
47
+ // so that changes within style tags are scoped to itself. But it'll take a bit of work.
48
+ // https://github.com/withastro/astro/issues/9370#issuecomment-1850160421
49
+ for ( const [ astroFile , cssAstroDeps ] of astroFileToCssAstroDeps ) {
50
+ // If the `astroFile` has a CSS dependency on `ctx.file`, there's a good chance this causes a
51
+ // circular dependency, which Vite doesn't issue a full page reload. Workaround it by forcing a
52
+ // full page reload ourselves. (Vite bug)
53
+ // https://github.com/vitejs/vite/pull/15585
54
+ if ( cssAstroDeps . has ( ctx . file ) ) {
55
+ // Mimic the HMR log as if this file is updated
56
+ logger . info ( 'watch' , getShortName ( ctx . file , ctx . server . config . root ) ) ;
57
+ // Invalidate the modules of `astroFile` explicitly as Vite may incorrectly soft-invalidate
58
+ // the parent if the parent actually imported `ctx.file`, but `this.addWatchFile` was also called
59
+ // on `ctx.file`. Vite should do a hard-invalidation instead. (Vite bug)
60
+ const parentModules = ctx . server . moduleGraph . getModulesByFile ( astroFile ) ;
61
+ if ( parentModules ) {
62
+ for ( const mod of parentModules ) {
63
+ ctx . server . moduleGraph . invalidateModule ( mod ) ;
64
+ }
96
65
}
66
+ ctx . server . ws . send ( { type : 'full-reload' , path : '*' } ) ;
97
67
}
98
68
}
99
-
100
- return mods ;
101
69
}
102
70
103
71
function isStyleOnlyChanged ( oldResult : CompileResult , newResult : CompileResult ) {
104
72
return (
105
73
normalizeCode ( oldResult . code ) === normalizeCode ( newResult . code ) &&
74
+ // If style tags are added/removed, we need to regenerate the main Astro file
75
+ // so that its CSS imports are also added/removed
76
+ oldResult . css . length === newResult . css . length &&
106
77
! isArrayEqual ( oldResult . css , newResult . css )
107
78
) ;
108
79
}
@@ -129,3 +100,7 @@ function isArrayEqual(a: any[], b: any[]) {
129
100
}
130
101
return true ;
131
102
}
103
+
104
+ function getShortName ( file : string , root : string ) : string {
105
+ return file . startsWith ( appendForwardSlash ( root ) ) ? path . posix . relative ( root , file ) : file ;
106
+ }
0 commit comments