@@ -10,12 +10,7 @@ import type {
1010 InvokeSendData ,
1111} from '../../shared/invokeMethods'
1212import { CLIENT_DIR } from '../constants'
13- import {
14- createDebugger ,
15- isCSSRequest ,
16- monotonicDateNow ,
17- normalizePath ,
18- } from '../utils'
13+ import { createDebugger , monotonicDateNow , normalizePath } from '../utils'
1914import type { InferCustomEventPayload , ViteDevServer } from '..'
2015import { getHookHandler } from '../plugins'
2116import { isExplicitImportRequired } from '../plugins/importAnalysis'
@@ -73,7 +68,7 @@ export interface HmrContext {
7368}
7469
7570interface PropagationBoundary {
76- boundary : EnvironmentModuleNode
71+ boundary : EnvironmentModuleNode & { type : 'js' | 'css' }
7772 acceptedVia : EnvironmentModuleNode
7873 isWithinCircularImport : boolean
7974}
@@ -693,7 +688,16 @@ export function updateModules(
693688 )
694689 }
695690
696- if ( needFullReload ) {
691+ // html file cannot be hot updated because it may be used as the template for a top-level request response.
692+ const isClientHtmlChange =
693+ file . endsWith ( '.html' ) &&
694+ environment . name === 'client' &&
695+ // if the html file is imported as a module, we assume that this file is
696+ // not used as the template for top-level request response
697+ // (i.e. not used by the middleware).
698+ modules . every ( ( mod ) => mod . type !== 'js' )
699+
700+ if ( needFullReload || isClientHtmlChange ) {
697701 const reason =
698702 typeof needFullReload === 'string'
699703 ? colors . dim ( ` (${ needFullReload } )` )
@@ -705,6 +709,12 @@ export function updateModules(
705709 hot . send ( {
706710 type : 'full-reload' ,
707711 triggeredBy : path . resolve ( environment . config . root , file ) ,
712+ path :
713+ ! isClientHtmlChange ||
714+ environment . config . server . middlewareMode ||
715+ updates . length > 0 // if there's an update, other URLs may be affected
716+ ? '*'
717+ : '/' + file ,
708718 } )
709719 return
710720 }
@@ -761,25 +771,13 @@ function propagateUpdate(
761771 }
762772
763773 if ( node . isSelfAccepting ) {
774+ // isSelfAccepting is only true for js and css
775+ const boundary = node as EnvironmentModuleNode & { type : 'js' | 'css' }
764776 boundaries . push ( {
765- boundary : node ,
766- acceptedVia : node ,
777+ boundary,
778+ acceptedVia : boundary ,
767779 isWithinCircularImport : isNodeWithinCircularImports ( node , currentChain ) ,
768780 } )
769-
770- // additionally check for CSS importers, since a PostCSS plugin like
771- // Tailwind JIT may register any file as a dependency to a CSS file.
772- for ( const importer of node . importers ) {
773- if ( isCSSRequest ( importer . url ) && ! currentChain . includes ( importer ) ) {
774- propagateUpdate (
775- importer ,
776- traversedModules ,
777- boundaries ,
778- currentChain . concat ( importer ) ,
779- )
780- }
781- }
782-
783781 return false
784782 }
785783
@@ -789,36 +787,29 @@ function propagateUpdate(
789787 // Also, the imported module (this one) must be updated before the importers,
790788 // so that they do get the fresh imported module when/if they are reloaded.
791789 if ( node . acceptedHmrExports ) {
790+ // acceptedHmrExports is only true for js and css
791+ const boundary = node as EnvironmentModuleNode & { type : 'js' | 'css' }
792792 boundaries . push ( {
793- boundary : node ,
794- acceptedVia : node ,
793+ boundary,
794+ acceptedVia : boundary ,
795795 isWithinCircularImport : isNodeWithinCircularImports ( node , currentChain ) ,
796796 } )
797797 } else {
798798 if ( ! node . importers . size ) {
799799 return true
800800 }
801-
802- // #3716, #3913
803- // For a non-CSS file, if all of its importers are CSS files (registered via
804- // PostCSS plugins) it should be considered a dead end and force full reload.
805- if (
806- ! isCSSRequest ( node . url ) &&
807- // we assume .svg is never an entrypoint and does not need a full reload
808- // to avoid frequent full reloads when an SVG file is referenced in CSS files (#18979)
809- ! node . file ?. endsWith ( '.svg' ) &&
810- [ ...node . importers ] . every ( ( i ) => isCSSRequest ( i . url ) )
811- ) {
812- return true
813- }
814801 }
815802
816803 for ( const importer of node . importers ) {
817804 const subChain = currentChain . concat ( importer )
818805
819806 if ( importer . acceptedHmrDeps . has ( node ) ) {
807+ // acceptedHmrDeps has value only for js and css
808+ const boundary = importer as EnvironmentModuleNode & {
809+ type : 'js' | 'css'
810+ }
820811 boundaries . push ( {
821- boundary : importer ,
812+ boundary,
822813 acceptedVia : node ,
823814 isWithinCircularImport : isNodeWithinCircularImports ( importer , subChain ) ,
824815 } )
@@ -886,11 +877,6 @@ function isNodeWithinCircularImports(
886877 // Node may import itself which is safe
887878 if ( importer === node ) continue
888879
889- // a PostCSS plugin like Tailwind JIT may register
890- // any file as a dependency to a CSS file.
891- // But in that case, the actual dependency chain is separate.
892- if ( isCSSRequest ( importer . url ) ) continue
893-
894880 // Check circular imports
895881 const importerIndex = nodeChain . indexOf ( importer )
896882 if ( importerIndex > - 1 ) {
0 commit comments