Skip to content

Commit 8383f49

Browse files
haoqunjiangsxzz
andauthored
feat: skip hmr when script is merely formatted (#258)
Co-authored-by: 三咲智子 Kevin Deng <[email protected]>
1 parent 2ba0668 commit 8383f49

File tree

2 files changed

+101
-2
lines changed

2 files changed

+101
-2
lines changed

packages/plugin-vue/src/handleHotUpdate.ts

+86-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import type { SFCBlock, SFCDescriptor } from 'vue/compiler-sfc'
33
import type { HmrContext, ModuleNode } from 'vite'
44
import { isCSSRequest } from 'vite'
55

6+
// eslint-disable-next-line node/no-extraneous-import
7+
import type * as t from '@babel/types'
8+
69
import {
710
cache,
811
createDescriptor,
@@ -12,6 +15,7 @@ import {
1215
import {
1316
getResolvedScript,
1417
invalidateScript,
18+
resolveScript,
1519
setResolvedScript,
1620
} from './script'
1721
import type { ResolvedOptions } from '.'
@@ -41,6 +45,8 @@ export async function handleHotUpdate(
4145
const mainModule = getMainModule(modules)
4246
const templateModule = modules.find((m) => /type=template/.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+
198271
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+
) {
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

playground/vue/__tests__/vue.spec.ts

+15
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,21 @@ describe('hmr', () => {
184184
expect(await page.textContent('.hmr-inc')).toMatch('count is 1')
185185
})
186186

187+
test('should preserve state when script is merely formatted', async () => {
188+
// this is the state from the previous test
189+
expect(await page.textContent('.hmr-inc')).toMatch('count is 1')
190+
191+
editFile('Hmr.vue', (code) =>
192+
code
193+
.replace('let foo: number = 0', ' let foo: number = 0\n\n')
194+
// also edit the style so that we can have something to wait for
195+
.replace('color: blue;', 'color: black;'),
196+
)
197+
await untilUpdated(() => getColor('.hmr-inc'), 'black')
198+
// should preserve state
199+
expect(await page.textContent('.hmr-inc')).toMatch('count is 1')
200+
})
201+
187202
test('should reload and reset state when script is edited', async () => {
188203
editFile('Hmr.vue', (code) =>
189204
code.replace('let foo: number = 0', 'let foo: number = 100'),

0 commit comments

Comments
 (0)