From 8bff142f99b646e9dd15897ec75368fbf34f1534 Mon Sep 17 00:00:00 2001 From: edison Date: Thu, 14 Nov 2024 20:55:18 +0800 Subject: [PATCH 01/18] fix(teleport): handle deferred teleport update before mounted (#12168) close #12161 --- .../__tests__/components/Teleport.spec.ts | 43 +++++++++++++++++++ .../runtime-core/src/components/Teleport.ts | 23 +++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/__tests__/components/Teleport.spec.ts b/packages/runtime-core/__tests__/components/Teleport.spec.ts index 5dc333ad690..79125cd04df 100644 --- a/packages/runtime-core/__tests__/components/Teleport.spec.ts +++ b/packages/runtime-core/__tests__/components/Teleport.spec.ts @@ -87,6 +87,49 @@ describe('renderer: teleport', () => { ``, ) }) + + test('update before mounted with defer', async () => { + const root = document.createElement('div') + document.body.appendChild(root) + + const show = ref(false) + const foo = ref('foo') + const Header = { + props: { foo: String }, + setup(props: any) { + return () => h('div', props.foo) + }, + } + const Footer = { + setup() { + foo.value = 'bar' + return () => h('div', 'Footer') + }, + } + createDOMApp({ + render() { + return show.value + ? [ + h( + Teleport, + { to: '#targetId', defer: true }, + h(Header, { foo: foo.value }), + ), + h(Footer), + h('div', { id: 'targetId' }), + ] + : [h('div')] + }, + }).mount(root) + + expect(root.innerHTML).toMatchInlineSnapshot(`"
"`) + + show.value = true + await nextTick() + expect(root.innerHTML).toMatchInlineSnapshot( + `"
Footer
bar
"`, + ) + }) }) function runSharedTests(deferMode: boolean) { diff --git a/packages/runtime-core/src/components/Teleport.ts b/packages/runtime-core/src/components/Teleport.ts index 5def1b2d721..fe6fa36c1ca 100644 --- a/packages/runtime-core/src/components/Teleport.ts +++ b/packages/runtime-core/src/components/Teleport.ts @@ -164,11 +164,32 @@ export const TeleportImpl = { } if (isTeleportDeferred(n2.props)) { - queuePostRenderEffect(mountToTarget, parentSuspense) + queuePostRenderEffect(() => { + mountToTarget() + n2.el!.__isMounted = true + }, parentSuspense) } else { mountToTarget() } } else { + if (isTeleportDeferred(n2.props) && !n1.el!.__isMounted) { + queuePostRenderEffect(() => { + TeleportImpl.process( + n1, + n2, + container, + anchor, + parentComponent, + parentSuspense, + namespace, + slotScopeIds, + optimized, + internals, + ) + delete n1.el!.__isMounted + }, parentSuspense) + return + } // update content n2.el = n1.el n2.targetStart = n1.targetStart From a49858f3eecba55ab188e3bdb8afed7db70ff335 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 14 Nov 2024 23:33:58 +0800 Subject: [PATCH 02/18] build: strip pure comments in minified builds --- rollup.config.js | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/rollup.config.js b/rollup.config.js index 1d6f0da4c6a..da7de554b64 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -46,6 +46,12 @@ const pkg = require(resolve(`package.json`)) const packageOptions = pkg.buildOptions || {} const name = packageOptions.filename || path.basename(packageDir) +const banner = `/** +* ${pkg.name} v${masterVersion} +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/` + const [enumPlugin, enumDefines] = inlineEnums() /** @type {Record} */ @@ -136,11 +142,7 @@ function createConfig(format, output, plugins = []) { (isGlobalBuild || isBrowserESMBuild || isBundlerESMBuild) && !packageOptions.enableNonBrowserBranches - output.banner = `/** -* ${pkg.name} v${masterVersion} -* (c) 2018-present Yuxi (Evan) You and Vue contributors -* @license MIT -**/` + output.banner = banner output.exports = isCompatPackage ? 'auto' : 'named' if (isCJSBuild) { @@ -372,24 +374,21 @@ function createMinifiedConfig(/** @type {PackageFormat} */ format) { { name: 'swc-minify', - async renderChunk( - contents, - _, - { format, sourcemap, sourcemapExcludeSources }, - ) { - const { code, map } = await minifySwc(contents, { + async renderChunk(contents, _, { format }) { + const { code } = await minifySwc(contents, { module: format === 'es', + format: { + comments: false, + }, compress: { ecma: 2016, pure_getters: true, }, safari10: true, mangle: true, - sourceMap: !!sourcemap, - inlineSourcesContent: !sourcemapExcludeSources, }) - return { code, map: map || null } + return { code: banner + code, map: null } }, }, ], From 70b44ca835e001b393605d7409f3400e3ca066ed Mon Sep 17 00:00:00 2001 From: edison Date: Fri, 15 Nov 2024 10:37:24 +0800 Subject: [PATCH 03/18] chore(reactivity): remove unecessary array copy (#12400) --- packages/reactivity/src/effectScope.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/reactivity/src/effectScope.ts b/packages/reactivity/src/effectScope.ts index e045d30e89b..cb4e057c480 100644 --- a/packages/reactivity/src/effectScope.ts +++ b/packages/reactivity/src/effectScope.ts @@ -119,9 +119,8 @@ export class EffectScope { if (this._active) { this._active = false let i, l - const effects = this.effects.slice() - for (i = 0, l = effects.length; i < l; i++) { - effects[i].stop() + for (i = 0, l = this.effects.length; i < l; i++) { + this.effects[i].stop() } this.effects.length = 0 From 4aeff318bda747aec4e0c5b98a1e73a2e2031987 Mon Sep 17 00:00:00 2001 From: edison Date: Fri, 15 Nov 2024 10:37:55 +0800 Subject: [PATCH 04/18] chore(deps): update dependency postcss-selector-parser to v7 (#12289) --- packages/compiler-sfc/package.json | 2 +- packages/compiler-sfc/src/style/pluginScoped.ts | 3 +-- pnpm-lock.yaml | 13 +++++++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json index bd9224e65be..df16e4663e1 100644 --- a/packages/compiler-sfc/package.json +++ b/packages/compiler-sfc/package.json @@ -60,7 +60,7 @@ "merge-source-map": "^1.1.0", "minimatch": "~9.0.5", "postcss-modules": "^6.0.0", - "postcss-selector-parser": "^6.1.2", + "postcss-selector-parser": "^7.0.0", "pug": "^3.0.3", "sass": "^1.80.6" } diff --git a/packages/compiler-sfc/src/style/pluginScoped.ts b/packages/compiler-sfc/src/style/pluginScoped.ts index b0224cf20d8..d0aaddd7676 100644 --- a/packages/compiler-sfc/src/style/pluginScoped.ts +++ b/packages/compiler-sfc/src/style/pluginScoped.ts @@ -189,8 +189,7 @@ function rewriteSelector( // global: replace with inner selector and do not inject [id]. // ::v-global(.foo) -> .foo if (value === ':global' || value === '::v-global') { - selectorRoot.insertAfter(selector, n.nodes[0]) - selectorRoot.removeChild(selector) + selector.replaceWith(n.nodes[0]) return false } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 74a863972bd..83a347d885b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -327,8 +327,8 @@ importers: specifier: ^6.0.0 version: 6.0.0(postcss@8.4.48) postcss-selector-parser: - specifier: ^6.1.2 - version: 6.1.2 + specifier: ^7.0.0 + version: 7.0.0 pug: specifier: ^3.0.3 version: 3.0.3 @@ -2845,6 +2845,10 @@ packages: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} + postcss-selector-parser@7.0.0: + resolution: {integrity: sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==} + engines: {node: '>=4'} + postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -5861,6 +5865,11 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 + postcss-selector-parser@7.0.0: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss-value-parser@4.2.0: {} postcss@8.4.41: From 660132df6c6a8c14bf75e593dc47d2fdada30322 Mon Sep 17 00:00:00 2001 From: edison Date: Fri, 15 Nov 2024 10:40:26 +0800 Subject: [PATCH 05/18] fix(Transition): fix transition memory leak edge case (#12182) close #12181 --- .../src/components/BaseTransition.ts | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts index 6ce06d28239..2b58bc3fc43 100644 --- a/packages/runtime-core/src/components/BaseTransition.ts +++ b/packages/runtime-core/src/components/BaseTransition.ts @@ -198,8 +198,7 @@ const BaseTransitionImpl: ComponentOptions = { setTransitionHooks(innerChild, enterHooks) } - const oldChild = instance.subTree - const oldInnerChild = oldChild && getInnerChild(oldChild) + let oldInnerChild = instance.subTree && getInnerChild(instance.subTree) // handle mode if ( @@ -208,7 +207,7 @@ const BaseTransitionImpl: ComponentOptions = { !isSameVNodeType(innerChild, oldInnerChild) && recursiveGetSubtree(instance).type !== Comment ) { - const leavingHooks = resolveTransitionHooks( + let leavingHooks = resolveTransitionHooks( oldInnerChild, rawProps, state, @@ -228,6 +227,7 @@ const BaseTransitionImpl: ComponentOptions = { instance.update() } delete leavingHooks.afterLeave + oldInnerChild = undefined } return emptyPlaceholder(child) } else if (mode === 'in-out' && innerChild.type !== Comment) { @@ -238,18 +238,27 @@ const BaseTransitionImpl: ComponentOptions = { ) => { const leavingVNodesCache = getLeavingNodesForType( state, - oldInnerChild, + oldInnerChild!, ) - leavingVNodesCache[String(oldInnerChild.key)] = oldInnerChild + leavingVNodesCache[String(oldInnerChild!.key)] = oldInnerChild! // early removal callback el[leaveCbKey] = () => { earlyRemove() el[leaveCbKey] = undefined delete enterHooks.delayedLeave + oldInnerChild = undefined + } + enterHooks.delayedLeave = () => { + delayedLeave() + delete enterHooks.delayedLeave + oldInnerChild = undefined } - enterHooks.delayedLeave = delayedLeave } + } else { + oldInnerChild = undefined } + } else if (oldInnerChild) { + oldInnerChild = undefined } return child From 1022eabaa1aaf8436876f5ec5573cb1e4b3959a6 Mon Sep 17 00:00:00 2001 From: edison Date: Fri, 15 Nov 2024 10:46:59 +0800 Subject: [PATCH 06/18] fix(types): defineEmits w/ interface declaration (#12343) close #8457 --- packages-private/dts-test/setupHelpers.test-d.ts | 8 ++++++++ packages/runtime-core/src/apiSetupHelpers.ts | 4 +--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages-private/dts-test/setupHelpers.test-d.ts b/packages-private/dts-test/setupHelpers.test-d.ts index 7b5d6f147f0..656f1da79f2 100644 --- a/packages-private/dts-test/setupHelpers.test-d.ts +++ b/packages-private/dts-test/setupHelpers.test-d.ts @@ -306,6 +306,14 @@ describe('defineEmits w/ type declaration', () => { emit2('baz') }) +describe('defineEmits w/ interface declaration', () => { + interface Emits { + foo: [value: string] + } + const emit = defineEmits() + emit('foo', 'hi') +}) + describe('defineEmits w/ alt type declaration', () => { const emit = defineEmits<{ foo: [id: string] diff --git a/packages/runtime-core/src/apiSetupHelpers.ts b/packages/runtime-core/src/apiSetupHelpers.ts index 54712c6807a..2ddaeb509ad 100644 --- a/packages/runtime-core/src/apiSetupHelpers.ts +++ b/packages/runtime-core/src/apiSetupHelpers.ts @@ -149,9 +149,7 @@ export function defineEmits() { return null as any } -export type ComponentTypeEmits = - | ((...args: any[]) => any) - | Record +export type ComponentTypeEmits = ((...args: any[]) => any) | Record type RecordToUnion> = T[keyof T] From 54812eacaaa6afb5cfa0afa0cd7f1dea9ff9a8ea Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 15 Nov 2024 10:50:26 +0800 Subject: [PATCH 07/18] test: add test case for transition memory leaks from https://github.com/vuejs/core/pull/12190 --- packages/vue/__tests__/e2e/Transition.spec.ts | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/packages/vue/__tests__/e2e/Transition.spec.ts b/packages/vue/__tests__/e2e/Transition.spec.ts index c0863a75991..60274ad134c 100644 --- a/packages/vue/__tests__/e2e/Transition.spec.ts +++ b/packages/vue/__tests__/e2e/Transition.spec.ts @@ -3121,4 +3121,124 @@ describe('e2e: Transition', () => { }, E2E_TIMEOUT, ) + + // https://github.com/vuejs/core/issues/12181#issuecomment-2414380955 + describe('not leaking', async () => { + test('switching VNodes', async () => { + const client = await page().createCDPSession() + await page().evaluate(async () => { + const { createApp, ref, nextTick } = (window as any).Vue + const empty = ref(true) + + createApp({ + components: { + Child: { + setup: () => { + // Big arrays kick GC earlier + const test = ref([...Array(30_000_000)].map((_, i) => ({ i }))) + // TODO: Use a diferent TypeScript env for testing + // @ts-expect-error - Custom property and same lib as runtime is used + window.__REF__ = new WeakRef(test) + + return { test } + }, + template: ` +

{{ test.length }}

+ `, + }, + Empty: { + template: '
', + }, + }, + template: ` + + + + `, + setup() { + return { empty } + }, + }).mount('#app') + + await nextTick() + empty.value = false + await nextTick() + empty.value = true + await nextTick() + }) + + const isCollected = async () => + // @ts-expect-error - Custom property + await page().evaluate(() => window.__REF__.deref() === undefined) + + while ((await isCollected()) === false) { + await client.send('HeapProfiler.collectGarbage') + } + + expect(await isCollected()).toBe(true) + }) + + // https://github.com/vuejs/core/issues/12181#issue-2588232334 + test('switching deep vnodes edge case', async () => { + const client = await page().createCDPSession() + await page().evaluate(async () => { + const { createApp, ref, nextTick } = (window as any).Vue + const shown = ref(false) + + createApp({ + components: { + Child: { + setup: () => { + // Big arrays kick GC earlier + const test = ref([...Array(30_000_000)].map((_, i) => ({ i }))) + // TODO: Use a diferent TypeScript env for testing + // @ts-expect-error - Custom property and same lib as runtime is used + window.__REF__ = new WeakRef(test) + + return { test } + }, + template: ` +

{{ test.length }}

+ `, + }, + Wrapper: { + template: ` + +
+ +
+
+ `, + }, + }, + template: ` + + + +
+
+ `, + setup() { + return { shown } + }, + }).mount('#app') + + await nextTick() + shown.value = true + await nextTick() + shown.value = false + await nextTick() + }) + + const isCollected = async () => + // @ts-expect-error - Custom property + await page().evaluate(() => window.__REF__.deref() === undefined) + + while ((await isCollected()) === false) { + await client.send('HeapProfiler.collectGarbage') + } + + expect(await isCollected()).toBe(true) + }) + }) }) From 2d78539da35322aea5f821b3cf9b02d006abac72 Mon Sep 17 00:00:00 2001 From: edison Date: Fri, 15 Nov 2024 10:56:08 +0800 Subject: [PATCH 08/18] fix(compiler-dom): properly stringify template string style (#12392) close #12391 --- .../stringifyStatic.spec.ts.snap | 10 +++++++++ .../transforms/stringifyStatic.spec.ts | 21 +++++++++++++++++++ .../shared/__tests__/normalizeProp.spec.ts | 4 ++-- packages/shared/src/normalizeProp.ts | 6 +++--- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap b/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap index a863eb32e61..2ed15ef5e62 100644 --- a/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap +++ b/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap @@ -32,6 +32,16 @@ return function render(_ctx, _cache) { }" `; +exports[`stringify static html > serializing template string style 1`] = ` +"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue + +return function render(_ctx, _cache) { + return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ + _createStaticVNode("
1 + false1 + false1 + false1 + false1 + false
", 1) + ]))) +}" +`; + exports[`stringify static html > should bail for