From e7a7597d755c41549ab9fb590b94b6ca73367e9c Mon Sep 17 00:00:00 2001 From: NateScarlet Date: Mon, 8 Jan 2024 12:45:34 +0800 Subject: [PATCH 1/6] fix(plugin-vue): hmr throw `Maximum call stack size exceeded` (fix #325) --- packages/plugin-vue/src/handleHotUpdate.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/plugin-vue/src/handleHotUpdate.ts b/packages/plugin-vue/src/handleHotUpdate.ts index f6fc455e..c368d054 100644 --- a/packages/plugin-vue/src/handleHotUpdate.ts +++ b/packages/plugin-vue/src/handleHotUpdate.ts @@ -202,14 +202,14 @@ 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') { + if (obj1 == null || obj2 == null || typeof obj1 !== 'object' || deepParentsOfObj1.includes(obj1)) { return obj1 === obj2 } @@ -229,7 +229,7 @@ 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 } } From aeefa410fa7c9618fb93604f10f6bc6ac5b9ae26 Mon Sep 17 00:00:00 2001 From: NateScarlet Date: Mon, 8 Jan 2024 13:05:18 +0800 Subject: [PATCH 2/6] docs: add comment for circular reference --- packages/plugin-vue/src/handleHotUpdate.ts | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/plugin-vue/src/handleHotUpdate.ts b/packages/plugin-vue/src/handleHotUpdate.ts index c368d054..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[] = [], deepParentsOfObj1: any[] = []): 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' || deepParentsOfObj1.includes(obj1)) { + // 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[] = [], deepParent continue } - if (!deepEqual(obj1[key], obj2[key], excludeProps, [...deepParentsOfObj1, obj1])) { + if ( + !deepEqual(obj1[key], obj2[key], excludeProps, [ + ...deepParentsOfObj1, + obj1, + ]) + ) { return false } } From 55d6a53eaf7cf8709b2ec2d69518ed9288758d4e Mon Sep 17 00:00:00 2001 From: NateScarlet Date: Tue, 9 Jan 2024 11:14:29 +0800 Subject: [PATCH 3/6] test: add test case --- playground/vue/HmrTypeScopeFile.d.ts | 9 +++++++++ playground/vue/__tests__/vue.spec.ts | 14 ++++++++++++++ playground/vue/vite.config.ts | 4 ++++ 3 files changed, 27 insertions(+) create mode 100644 playground/vue/HmrTypeScopeFile.d.ts diff --git a/playground/vue/HmrTypeScopeFile.d.ts b/playground/vue/HmrTypeScopeFile.d.ts new file mode 100644 index 00000000..5528c9ca --- /dev/null +++ b/playground/vue/HmrTypeScopeFile.d.ts @@ -0,0 +1,9 @@ +// this file will become a TypeScope +// which has a circular reference on `TypeScope._ownerScope` +// will cause all test hook timeout if not handled (issue 325) + +declare namespace App { + interface User { + name: string + } +} diff --git a/playground/vue/__tests__/vue.spec.ts b/playground/vue/__tests__/vue.spec.ts index e5ae6e53..dd83f095 100644 --- a/playground/vue/__tests__/vue.spec.ts +++ b/playground/vue/__tests__/vue.spec.ts @@ -225,6 +225,20 @@ describe('hmr', () => { 'updatedCount is 0', ) }) + + test('should handle circular reference (issue 325)', async () => { + editFile('Hmr.vue', (code) => + code.replace( + 'let foo: number = 0', + 'defineProps()\nlet foo: number = 0', + ), + ) + await untilUpdated(() => page.textContent('.hmr-inc'), 'count is 0') + editFile('Hmr.vue', (code) => + code.replace('let foo: number = 0', 'let foo: number = 100'), + ) + await untilUpdated(() => page.textContent('.hmr-inc'), 'count is 100') + }) }) describe('src imports', () => { diff --git a/playground/vue/vite.config.ts b/playground/vue/vite.config.ts index 5a719cb5..1865c8fe 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, 'HmrTypeScopeFile.d.ts')], + }, template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('my-'), From 7c8b8420c6d607581c7144f85fe0e3897d09f01a Mon Sep 17 00:00:00 2001 From: NateScarlet Date: Tue, 9 Jan 2024 11:26:21 +0800 Subject: [PATCH 4/6] docs: update comment --- playground/vue/HmrTypeScopeFile.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/vue/HmrTypeScopeFile.d.ts b/playground/vue/HmrTypeScopeFile.d.ts index 5528c9ca..e277ec7d 100644 --- a/playground/vue/HmrTypeScopeFile.d.ts +++ b/playground/vue/HmrTypeScopeFile.d.ts @@ -1,4 +1,4 @@ -// this file will become a TypeScope +// this file will become a TypeScope through option `script.globalTypeFiles` // which has a circular reference on `TypeScope._ownerScope` // will cause all test hook timeout if not handled (issue 325) From fdfc4623c56d47341114892d8fe7e8c1ea6c90ee Mon Sep 17 00:00:00 2001 From: NateScarlet Date: Tue, 9 Jan 2024 14:40:51 +0800 Subject: [PATCH 5/6] test: update test case --- playground/vue/HmrCircularReference.vue | 24 ++++++++++++++++++++++++ playground/vue/HmrTypeScopeFile.d.ts | 3 +-- playground/vue/Main.vue | 2 ++ playground/vue/__tests__/vue.spec.ts | 14 +++++--------- 4 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 playground/vue/HmrCircularReference.vue 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 @@ + + + + + diff --git a/playground/vue/HmrTypeScopeFile.d.ts b/playground/vue/HmrTypeScopeFile.d.ts index e277ec7d..ef34a5c1 100644 --- a/playground/vue/HmrTypeScopeFile.d.ts +++ b/playground/vue/HmrTypeScopeFile.d.ts @@ -1,6 +1,5 @@ // this file will become a TypeScope through option `script.globalTypeFiles` -// which has a circular reference on `TypeScope._ownerScope` -// will cause all test hook timeout if not handled (issue 325) +// which has a circular reference on `TypeScope._ownerScope` (issue 325) declare namespace App { interface User { 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 dd83f095..aa88ec19 100644 --- a/playground/vue/__tests__/vue.spec.ts +++ b/playground/vue/__tests__/vue.spec.ts @@ -227,17 +227,13 @@ describe('hmr', () => { }) test('should handle circular reference (issue 325)', async () => { - editFile('Hmr.vue', (code) => - code.replace( - 'let foo: number = 0', - 'defineProps()\nlet foo: number = 0', - ), - ) - await untilUpdated(() => page.textContent('.hmr-inc'), 'count is 0') - editFile('Hmr.vue', (code) => + editFile('HmrCircularReference.vue', (code) => code.replace('let foo: number = 0', 'let foo: number = 100'), ) - await untilUpdated(() => page.textContent('.hmr-inc'), 'count is 100') + await untilUpdated( + () => page.textContent('.hmr-circular-reference-inc'), + 'count is 100', + ) }) }) From 1b3df3fac7e4916ea389b4f30ad8647d2715b95b Mon Sep 17 00:00:00 2001 From: NateScarlet Date: Tue, 9 Jan 2024 14:49:41 +0800 Subject: [PATCH 6/6] chore: rename file --- .../{HmrTypeScopeFile.d.ts => HmrCircularReferenceFile.d.ts} | 0 playground/vue/vite.config.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename playground/vue/{HmrTypeScopeFile.d.ts => HmrCircularReferenceFile.d.ts} (100%) diff --git a/playground/vue/HmrTypeScopeFile.d.ts b/playground/vue/HmrCircularReferenceFile.d.ts similarity index 100% rename from playground/vue/HmrTypeScopeFile.d.ts rename to playground/vue/HmrCircularReferenceFile.d.ts diff --git a/playground/vue/vite.config.ts b/playground/vue/vite.config.ts index 1865c8fe..7b971526 100644 --- a/playground/vue/vite.config.ts +++ b/playground/vue/vite.config.ts @@ -13,7 +13,7 @@ export default defineConfig({ plugins: [ vuePlugin({ script: { - globalTypeFiles: [resolve(__dirname, 'HmrTypeScopeFile.d.ts')], + globalTypeFiles: [resolve(__dirname, 'HmrCircularReferenceFile.d.ts')], }, template: { compilerOptions: {