@@ -3,6 +3,9 @@ import type { SFCBlock, SFCDescriptor } from 'vue/compiler-sfc'
33import type { HmrContext , ModuleNode } from 'vite'
44import { isCSSRequest } from 'vite'
55
6+ // eslint-disable-next-line node/no-extraneous-import
7+ import type * as t from '@babel/types'
8+
69import {
710 cache ,
811 createDescriptor ,
@@ -12,6 +15,7 @@ import {
1215import {
1316 getResolvedScript ,
1417 invalidateScript ,
18+ resolveScript ,
1519 setResolvedScript ,
1620} from './script'
1721import type { ResolvedOptions } from '.'
@@ -41,6 +45,8 @@ export async function handleHotUpdate(
4145 const mainModule = getMainModule ( modules )
4246 const templateModule = modules . find ( ( m ) => / t y p e = t e m p l a t e / . test ( m . url ) )
4347
48+ // trigger resolveScript for descriptor so that we'll have the AST ready
49+ resolveScript ( descriptor , options , false )
4450 const scriptChanged = hasScriptChanged ( prevDescriptor , descriptor )
4551 if ( scriptChanged ) {
4652 affectedModules . add ( getScriptModule ( modules ) || mainModule )
@@ -195,11 +201,89 @@ export function isOnlyTemplateChanged(
195201 )
196202}
197203
204+ function deepEqual ( obj1 : any , obj2 : any , excludeProps : string [ ] = [ ] ) : boolean {
205+ // Check if both objects are of the same type
206+ if ( typeof obj1 !== typeof obj2 ) {
207+ return false
208+ }
209+
210+ // Check if both objects are primitive types or null
211+ if ( obj1 == null || obj2 == null || typeof obj1 !== 'object' ) {
212+ return obj1 === obj2
213+ }
214+
215+ // Get the keys of the objects
216+ const keys1 = Object . keys ( obj1 )
217+ const keys2 = Object . keys ( obj2 )
218+
219+ // Check if the number of keys is the same
220+ if ( keys1 . length !== keys2 . length ) {
221+ return false
222+ }
223+
224+ // Iterate through the keys and recursively compare the values
225+ for ( const key of keys1 ) {
226+ // Check if the current key should be excluded
227+ if ( excludeProps . includes ( key ) ) {
228+ continue
229+ }
230+
231+ if ( ! deepEqual ( obj1 [ key ] , obj2 [ key ] , excludeProps ) ) {
232+ return false
233+ }
234+ }
235+
236+ // If all comparisons passed, the objects are deep equal
237+ return true
238+ }
239+
240+ function isEqualAst ( prev ?: t . Statement [ ] , next ?: t . Statement [ ] ) : boolean {
241+ if ( typeof prev === 'undefined' || typeof next === 'undefined' ) {
242+ return prev === next
243+ }
244+
245+ // deep equal, but ignore start/end/loc/range/leadingComments/trailingComments/innerComments
246+ if ( prev . length !== next . length ) {
247+ return false
248+ }
249+
250+ for ( let i = 0 ; i < prev . length ; i ++ ) {
251+ const prevNode = prev [ i ]
252+ const nextNode = next [ i ]
253+ if (
254+ ! deepEqual ( prevNode , nextNode , [
255+ 'start' ,
256+ 'end' ,
257+ 'loc' ,
258+ 'range' ,
259+ 'leadingComments' ,
260+ 'trailingComments' ,
261+ 'innerComments' ,
262+ ] )
263+ ) {
264+ return false
265+ }
266+ }
267+
268+ return true
269+ }
270+
198271function hasScriptChanged ( prev : SFCDescriptor , next : SFCDescriptor ) : boolean {
199- if ( ! isEqualBlock ( prev . script , next . script ) ) {
272+ // check for scriptAst/scriptSetupAst changes
273+ // note that the next ast is not available yet, so we need to trigger parsing
274+ const prevScript = getResolvedScript ( prev , false )
275+ const nextScript = getResolvedScript ( next , false )
276+
277+ if (
278+ ! isEqualBlock ( prev . script , next . script ) &&
279+ ! isEqualAst ( prevScript ?. scriptAst , nextScript ?. scriptAst )
280+ ) {
200281 return true
201282 }
202- if ( ! isEqualBlock ( prev . scriptSetup , next . scriptSetup ) ) {
283+ if (
284+ ! isEqualBlock ( prev . scriptSetup , next . scriptSetup ) &&
285+ ! isEqualAst ( prevScript ?. scriptSetupAst , nextScript ?. scriptSetupAst )
286+ ) {
203287 return true
204288 }
205289
0 commit comments