@@ -3,6 +3,9 @@ import type { SFCBlock, SFCDescriptor } from 'vue/compiler-sfc'
3
3
import type { HmrContext , ModuleNode } from 'vite'
4
4
import { isCSSRequest } from 'vite'
5
5
6
+ // eslint-disable-next-line node/no-extraneous-import
7
+ import type * as t from '@babel/types'
8
+
6
9
import {
7
10
cache ,
8
11
createDescriptor ,
@@ -12,6 +15,7 @@ import {
12
15
import {
13
16
getResolvedScript ,
14
17
invalidateScript ,
18
+ resolveScript ,
15
19
setResolvedScript ,
16
20
} from './script'
17
21
import type { ResolvedOptions } from '.'
@@ -41,6 +45,8 @@ export async function handleHotUpdate(
41
45
const mainModule = getMainModule ( modules )
42
46
const templateModule = modules . find ( ( m ) => / t y p e = t e m p l a t e / . test ( m . url ) )
43
47
48
+ // trigger resolveScript for descriptor so that we'll have the AST ready
49
+ resolveScript ( descriptor , options , false )
44
50
const scriptChanged = hasScriptChanged ( prevDescriptor , descriptor )
45
51
if ( scriptChanged ) {
46
52
affectedModules . add ( getScriptModule ( modules ) || mainModule )
@@ -195,11 +201,89 @@ export function isOnlyTemplateChanged(
195
201
)
196
202
}
197
203
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
+
198
271
function 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
+ ) {
200
281
return true
201
282
}
202
- if ( ! isEqualBlock ( prev . scriptSetup , next . scriptSetup ) ) {
283
+ if (
284
+ ! isEqualBlock ( prev . scriptSetup , next . scriptSetup ) &&
285
+ ! isEqualAst ( prevScript ?. scriptSetupAst , nextScript ?. scriptSetupAst )
286
+ ) {
203
287
return true
204
288
}
205
289
0 commit comments