diff --git a/packages/plugin-vue/src/handleHotUpdate.ts b/packages/plugin-vue/src/handleHotUpdate.ts
index f6fc455e..2b15a4e9 100644
--- a/packages/plugin-vue/src/handleHotUpdate.ts
+++ b/packages/plugin-vue/src/handleHotUpdate.ts
@@ -202,14 +202,25 @@ export function isOnlyTemplateChanged(
)
}
-function deepEqual(obj1: any, obj2: any, excludeProps: string[] = []): boolean {
+function deepEqual(
+ obj1: any,
+ obj2: any,
+ excludeProps: string[] = [],
+ deepParentsOfObj1: any[] = [],
+): boolean {
// Check if both objects are of the same type
if (typeof obj1 !== typeof obj2) {
return false
}
// Check if both objects are primitive types or null
- if (obj1 == null || obj2 == null || typeof obj1 !== 'object') {
+ // or circular reference
+ if (
+ obj1 == null ||
+ obj2 == null ||
+ typeof obj1 !== 'object' ||
+ deepParentsOfObj1.includes(obj1)
+ ) {
return obj1 === obj2
}
@@ -229,7 +240,12 @@ function deepEqual(obj1: any, obj2: any, excludeProps: string[] = []): boolean {
continue
}
- if (!deepEqual(obj1[key], obj2[key], excludeProps)) {
+ if (
+ !deepEqual(obj1[key], obj2[key], excludeProps, [
+ ...deepParentsOfObj1,
+ obj1,
+ ])
+ ) {
return false
}
}
diff --git a/playground/vue/HmrCircularReference.vue b/playground/vue/HmrCircularReference.vue
new file mode 100644
index 00000000..98dbd3f7
--- /dev/null
+++ b/playground/vue/HmrCircularReference.vue
@@ -0,0 +1,24 @@
+
+ HMR Circular reference
+ Click the button then edit this message. The count should be preserved.
+
+
+
+
+
+
diff --git a/playground/vue/HmrCircularReferenceFile.d.ts b/playground/vue/HmrCircularReferenceFile.d.ts
new file mode 100644
index 00000000..ef34a5c1
--- /dev/null
+++ b/playground/vue/HmrCircularReferenceFile.d.ts
@@ -0,0 +1,8 @@
+// this file will become a TypeScope through option `script.globalTypeFiles`
+// which has a circular reference on `TypeScope._ownerScope` (issue 325)
+
+declare namespace App {
+ interface User {
+ name: string
+ }
+}
diff --git a/playground/vue/Main.vue b/playground/vue/Main.vue
index b34155e4..b29ae881 100644
--- a/playground/vue/Main.vue
+++ b/playground/vue/Main.vue
@@ -8,6 +8,7 @@
+
@@ -60,6 +61,7 @@ import PreCompiled from './pre-compiled/foo.vue'
import PreCompiledExternalScoped from './pre-compiled/external-scoped.vue'
import PreCompiledExternalCssModules from './pre-compiled/external-cssmodules.vue'
import ParserOptions from './ParserOptions.vue'
+import HmrCircularReference from './HmrCircularReference.vue'
const TsGeneric = defineAsyncComponent(() => import('./TsGeneric.vue'))
diff --git a/playground/vue/__tests__/vue.spec.ts b/playground/vue/__tests__/vue.spec.ts
index e5ae6e53..aa88ec19 100644
--- a/playground/vue/__tests__/vue.spec.ts
+++ b/playground/vue/__tests__/vue.spec.ts
@@ -225,6 +225,16 @@ describe('hmr', () => {
'updatedCount is 0',
)
})
+
+ test('should handle circular reference (issue 325)', async () => {
+ editFile('HmrCircularReference.vue', (code) =>
+ code.replace('let foo: number = 0', 'let foo: number = 100'),
+ )
+ await untilUpdated(
+ () => page.textContent('.hmr-circular-reference-inc'),
+ 'count is 100',
+ )
+ })
})
describe('src imports', () => {
diff --git a/playground/vue/vite.config.ts b/playground/vue/vite.config.ts
index 5a719cb5..7b971526 100644
--- a/playground/vue/vite.config.ts
+++ b/playground/vue/vite.config.ts
@@ -1,3 +1,4 @@
+import { resolve } from 'node:path'
import { defineConfig, splitVendorChunkPlugin } from 'vite'
import vuePlugin from '@vitejs/plugin-vue'
import { vueI18nPlugin } from './CustomBlockPlugin'
@@ -11,6 +12,9 @@ export default defineConfig({
},
plugins: [
vuePlugin({
+ script: {
+ globalTypeFiles: [resolve(__dirname, 'HmrCircularReferenceFile.d.ts')],
+ },
template: {
compilerOptions: {
isCustomElement: (tag) => tag.startsWith('my-'),