From 83430a35f4865f02e6ab1029add66bf36dd26c5c Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 13 Nov 2024 14:13:32 +0800 Subject: [PATCH 01/28] workflow: improve bench scripts --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 763d6fa13..12f1159e6 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "test-dts": "run-s build-dts test-dts-only", "test-dts-only": "tsc -p packages-private/dts-built-test/tsconfig.json && tsc -p ./packages-private/dts-test/tsconfig.test.json", "test-coverage": "vitest run --project unit --coverage", - "test-bench": "vitest bench", + "bench": "vitest bench --project=unit --outputJson=temp/bench.json", + "bench-compare": "vitest bench --project=unit --compare=temp/bench.json", "release": "node scripts/release.js", "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s", "dev-esm": "node scripts/dev.js -if esm-bundler-runtime", From 506ed4e75ff70632b3f1954d1abdfe25f4aa76c6 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 13 Nov 2024 15:31:17 +0800 Subject: [PATCH 02/28] chore: enable format on save in workspace settings --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1dcc2819c..302428290 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,5 +13,6 @@ }, "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" - } + }, + "editor.formatOnSave": true } From 3656364b0661b0e0c80ccb087eb172217faf7e7c Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 14 Nov 2024 00:22:38 +0800 Subject: [PATCH 03/28] chore: add well-known funding manifest urls [ci skip] --- .well-known/funding-manifest-urls | 1 + 1 file changed, 1 insertion(+) create mode 100644 .well-known/funding-manifest-urls diff --git a/.well-known/funding-manifest-urls b/.well-known/funding-manifest-urls new file mode 100644 index 000000000..f26079d41 --- /dev/null +++ b/.well-known/funding-manifest-urls @@ -0,0 +1 @@ +https://vuejs.org/funding.json From e9f3e6b546a519a6f7d72a1e0880189fffe5ce7b Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 14 Nov 2024 08:38:48 +0800 Subject: [PATCH 04/28] workflow: bench against bundled dist file to avoid import access overhead ref https://github.com/vitest-dev/vitest/issues/6903 --- package.json | 2 ++ packages/reactivity/__benchmarks__/computed.bench.ts | 7 ++++++- packages/reactivity/__benchmarks__/effect.bench.ts | 3 ++- packages/reactivity/__benchmarks__/reactiveArray.bench.ts | 6 +++++- packages/reactivity/__benchmarks__/reactiveMap.bench.ts | 3 ++- packages/reactivity/__benchmarks__/reactiveObject.bench.ts | 2 +- packages/reactivity/__benchmarks__/ref.bench.ts | 2 +- packages/server-renderer/__tests__/createBuffer.bench.ts | 6 +++++- packages/server-renderer/__tests__/unrollBuffer.bench.ts | 6 +++++- 9 files changed, 29 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 12f1159e6..2d3af0f27 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,8 @@ "test-dts": "run-s build-dts test-dts-only", "test-dts-only": "tsc -p packages-private/dts-built-test/tsconfig.json && tsc -p ./packages-private/dts-test/tsconfig.test.json", "test-coverage": "vitest run --project unit --coverage", + "prebench": "node scripts/build.js -pf esm-browser reactivity", + "prebench-compare": "node scripts/build.js -pf esm-browser reactivity", "bench": "vitest bench --project=unit --outputJson=temp/bench.json", "bench-compare": "vitest bench --project=unit --compare=temp/bench.json", "release": "node scripts/release.js", diff --git a/packages/reactivity/__benchmarks__/computed.bench.ts b/packages/reactivity/__benchmarks__/computed.bench.ts index d9757501f..a2a49486a 100644 --- a/packages/reactivity/__benchmarks__/computed.bench.ts +++ b/packages/reactivity/__benchmarks__/computed.bench.ts @@ -1,5 +1,10 @@ import { bench, describe } from 'vitest' -import { type ComputedRef, type Ref, computed, effect, ref } from '../src' +import type { ComputedRef, Ref } from '../src' +import { computed, effect, ref } from '../dist/reactivity.esm-browser.prod' + +declare module '../dist/reactivity.esm-browser.prod' { + function computed(...args: any[]): any +} describe('computed', () => { bench('create computed', () => { diff --git a/packages/reactivity/__benchmarks__/effect.bench.ts b/packages/reactivity/__benchmarks__/effect.bench.ts index 8d3d6ecfb..a1026db21 100644 --- a/packages/reactivity/__benchmarks__/effect.bench.ts +++ b/packages/reactivity/__benchmarks__/effect.bench.ts @@ -1,5 +1,6 @@ import { bench, describe } from 'vitest' -import { type Ref, effect, ref } from '../src' +import type { Ref } from '../src' +import { effect, ref } from '../dist/reactivity.esm-browser.prod' describe('effect', () => { { diff --git a/packages/reactivity/__benchmarks__/reactiveArray.bench.ts b/packages/reactivity/__benchmarks__/reactiveArray.bench.ts index f5032cf7a..910c10015 100644 --- a/packages/reactivity/__benchmarks__/reactiveArray.bench.ts +++ b/packages/reactivity/__benchmarks__/reactiveArray.bench.ts @@ -1,5 +1,9 @@ import { bench } from 'vitest' -import { effect, reactive, shallowReadArray } from '../src' +import { + effect, + reactive, + shallowReadArray, +} from '../dist/reactivity.esm-browser.prod' for (let amount = 1e1; amount < 1e4; amount *= 10) { { diff --git a/packages/reactivity/__benchmarks__/reactiveMap.bench.ts b/packages/reactivity/__benchmarks__/reactiveMap.bench.ts index f8b461115..5af03ba4b 100644 --- a/packages/reactivity/__benchmarks__/reactiveMap.bench.ts +++ b/packages/reactivity/__benchmarks__/reactiveMap.bench.ts @@ -1,5 +1,6 @@ import { bench } from 'vitest' -import { type ComputedRef, computed, reactive } from '../src' +import type { ComputedRef } from '../src' +import { computed, reactive } from '../dist/reactivity.esm-browser.prod' function createMap(obj: Record) { const map = new Map() diff --git a/packages/reactivity/__benchmarks__/reactiveObject.bench.ts b/packages/reactivity/__benchmarks__/reactiveObject.bench.ts index a326a111b..1b444d4fe 100644 --- a/packages/reactivity/__benchmarks__/reactiveObject.bench.ts +++ b/packages/reactivity/__benchmarks__/reactiveObject.bench.ts @@ -1,5 +1,5 @@ import { bench } from 'vitest' -import { reactive } from '../src' +import { reactive } from '../dist/reactivity.esm-browser.prod' bench('create reactive obj', () => { reactive({ a: 1 }) diff --git a/packages/reactivity/__benchmarks__/ref.bench.ts b/packages/reactivity/__benchmarks__/ref.bench.ts index 0c0589017..4c1d2b5b8 100644 --- a/packages/reactivity/__benchmarks__/ref.bench.ts +++ b/packages/reactivity/__benchmarks__/ref.bench.ts @@ -1,5 +1,5 @@ import { bench, describe } from 'vitest' -import { ref } from '../src/index' +import { ref } from '../dist/reactivity.esm-browser.prod' describe('ref', () => { bench('create ref', () => { diff --git a/packages/server-renderer/__tests__/createBuffer.bench.ts b/packages/server-renderer/__tests__/createBuffer.bench.ts index fff20f927..002626d9e 100644 --- a/packages/server-renderer/__tests__/createBuffer.bench.ts +++ b/packages/server-renderer/__tests__/createBuffer.bench.ts @@ -1,6 +1,10 @@ import { bench, describe } from 'vitest' -import { createBuffer } from '../src/render' +import { createBuffer as _createBuffer } from '../src/render' + +// move to local const to avoid import access overhead +// https://github.com/vitest-dev/vitest/issues/6903 +const createBuffer = _createBuffer describe('createBuffer', () => { let stringBuffer = createBuffer() diff --git a/packages/server-renderer/__tests__/unrollBuffer.bench.ts b/packages/server-renderer/__tests__/unrollBuffer.bench.ts index b5e03cea6..b26ab9b28 100644 --- a/packages/server-renderer/__tests__/unrollBuffer.bench.ts +++ b/packages/server-renderer/__tests__/unrollBuffer.bench.ts @@ -1,7 +1,11 @@ import { bench, describe } from 'vitest' import { type SSRBuffer, createBuffer } from '../src/render' -import { unrollBuffer } from '../src/renderToString' +import { unrollBuffer as _unrollBuffer } from '../src/renderToString' + +// move to local const to avoid import access overhead +// https://github.com/vitest-dev/vitest/issues/6903 +const unrollBuffer = _unrollBuffer function createSyncBuffer(levels: number, itemsPerLevel: number): SSRBuffer { const buffer = createBuffer() From 21932840eae72ffcd357a62ec596aaecc7ec224a Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 14 Nov 2024 14:12:41 +0800 Subject: [PATCH 05/28] fix(reactiivty): avoid unnecessary watcher effect removal from inactive scope close #5783 close #5806 --- .../reactivity/__tests__/effectScope.spec.ts | 37 +++++++++++++++++++ packages/reactivity/src/effectScope.ts | 2 +- packages/reactivity/src/watch.ts | 2 +- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/__tests__/effectScope.spec.ts b/packages/reactivity/__tests__/effectScope.spec.ts index 8a95f3252..537cac64c 100644 --- a/packages/reactivity/__tests__/effectScope.spec.ts +++ b/packages/reactivity/__tests__/effectScope.spec.ts @@ -322,4 +322,41 @@ describe('reactivity/effect/scope', () => { scope.resume() expect(fnSpy).toHaveBeenCalledTimes(3) }) + + test('removing a watcher while stopping its effectScope', async () => { + const count = ref(0) + const scope = effectScope() + let watcherCalls = 0 + let cleanupCalls = 0 + + scope.run(() => { + const stop1 = watch(count, () => { + watcherCalls++ + }) + watch(count, (val, old, onCleanup) => { + watcherCalls++ + onCleanup(() => { + cleanupCalls++ + stop1() + }) + }) + watch(count, () => { + watcherCalls++ + }) + }) + + expect(watcherCalls).toBe(0) + expect(cleanupCalls).toBe(0) + + count.value++ + await nextTick() + expect(watcherCalls).toBe(3) + expect(cleanupCalls).toBe(0) + + scope.stop() + count.value++ + await nextTick() + expect(watcherCalls).toBe(3) + expect(cleanupCalls).toBe(1) + }) }) diff --git a/packages/reactivity/src/effectScope.ts b/packages/reactivity/src/effectScope.ts index 4fa968608..98e45fb57 100644 --- a/packages/reactivity/src/effectScope.ts +++ b/packages/reactivity/src/effectScope.ts @@ -117,6 +117,7 @@ export class EffectScope { stop(fromParent?: boolean): void { if (this._active) { + this._active = false let i, l for (i = 0, l = this.effects.length; i < l; i++) { this.effects[i].stop() @@ -139,7 +140,6 @@ export class EffectScope { } } this.parent = undefined - this._active = false } } } diff --git a/packages/reactivity/src/watch.ts b/packages/reactivity/src/watch.ts index 073bf88b9..659121ca3 100644 --- a/packages/reactivity/src/watch.ts +++ b/packages/reactivity/src/watch.ts @@ -213,7 +213,7 @@ export function watch( const scope = getCurrentScope() const watchHandle: WatchHandle = () => { effect.stop() - if (scope) { + if (scope && scope.active) { remove(scope.effects, effect) } } From bee2f5ee62dc0cd04123b737779550726374dd0a Mon Sep 17 00:00:00 2001 From: edison Date: Thu, 14 Nov 2024 14:24:22 +0800 Subject: [PATCH 06/28] fix(reactivity): release nested effects/scopes on effect scope stop (#12373) close #12370 --- packages/reactivity/__tests__/effectScope.spec.ts | 5 ++++- packages/reactivity/src/effectScope.ts | 11 +++++++++-- packages/runtime-core/__tests__/apiWatch.spec.ts | 3 +-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/reactivity/__tests__/effectScope.spec.ts b/packages/reactivity/__tests__/effectScope.spec.ts index 537cac64c..debbdafb1 100644 --- a/packages/reactivity/__tests__/effectScope.spec.ts +++ b/packages/reactivity/__tests__/effectScope.spec.ts @@ -176,7 +176,7 @@ describe('reactivity/effect/scope', () => { expect('[Vue warn] cannot run an inactive effect scope.').toHaveBeenWarned() - expect(scope.effects.length).toBe(1) + expect(scope.effects.length).toBe(0) counter.num = 7 expect(dummy).toBe(0) @@ -358,5 +358,8 @@ describe('reactivity/effect/scope', () => { await nextTick() expect(watcherCalls).toBe(3) expect(cleanupCalls).toBe(1) + + expect(scope.effects.length).toBe(0) + expect(scope.cleanups.length).toBe(0) }) }) diff --git a/packages/reactivity/src/effectScope.ts b/packages/reactivity/src/effectScope.ts index 98e45fb57..e045d30e8 100644 --- a/packages/reactivity/src/effectScope.ts +++ b/packages/reactivity/src/effectScope.ts @@ -119,17 +119,24 @@ export class EffectScope { if (this._active) { this._active = false let i, l - for (i = 0, l = this.effects.length; i < l; i++) { - this.effects[i].stop() + const effects = this.effects.slice() + for (i = 0, l = effects.length; i < l; i++) { + effects[i].stop() } + this.effects.length = 0 + for (i = 0, l = this.cleanups.length; i < l; i++) { this.cleanups[i]() } + this.cleanups.length = 0 + if (this.scopes) { for (i = 0, l = this.scopes.length; i < l; i++) { this.scopes[i].stop(true) } + this.scopes.length = 0 } + // nested scope, dereference from parent to avoid memory leaks if (!this.detached && this.parent && !fromParent) { // optimized O(1) removal diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index 10a4fe659..7d2a1e73c 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -25,7 +25,6 @@ import { } from '@vue/runtime-test' import { type DebuggerEvent, - EffectFlags, ITERATE_KEY, type Ref, type ShallowRef, @@ -1341,7 +1340,7 @@ describe('api: watch', () => { await nextTick() await nextTick() - expect(instance!.scope.effects[0].flags & EffectFlags.ACTIVE).toBeFalsy() + expect(instance!.scope.effects.length).toBe(0) }) test('this.$watch should pass `this.proxy` to watch source as the first argument ', () => { From 37300fc26190a7299efddbf98800ffd96d5cad96 Mon Sep 17 00:00:00 2001 From: edison Date: Thu, 14 Nov 2024 14:53:55 +0800 Subject: [PATCH 07/28] fix(v-once): setting hasOnce to current block only when in v-once (#12374) close #12371 --- .../__snapshots__/vOnce.spec.ts.snap | 10 +-- packages/compiler-core/src/ast.ts | 3 + packages/compiler-core/src/codegen.ts | 4 +- packages/compiler-core/src/transform.ts | 5 +- .../compiler-core/src/transforms/vOnce.ts | 6 +- .../__tests__/rendererOptimizedMode.spec.ts | 63 ++++++++++++++++++- packages/runtime-core/__tests__/vnode.spec.ts | 2 +- packages/runtime-core/src/vnode.ts | 8 +-- 8 files changed, 86 insertions(+), 15 deletions(-) diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap index 3d13c4066..6660865a5 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap @@ -8,7 +8,7 @@ return function render(_ctx, _cache) { const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode } = _Vue return _cache[0] || ( - _setBlockTracking(-1), + _setBlockTracking(-1, true), (_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0, _setBlockTracking(1), _cache[0] @@ -28,7 +28,7 @@ return function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", null, [ _cache[0] || ( - _setBlockTracking(-1), + _setBlockTracking(-1, true), (_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0, _setBlockTracking(1), _cache[0] @@ -47,7 +47,7 @@ return function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", null, [ _cache[0] || ( - _setBlockTracking(-1), + _setBlockTracking(-1, true), (_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0, _setBlockTracking(1), _cache[0] @@ -66,7 +66,7 @@ return function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", null, [ _cache[0] || ( - _setBlockTracking(-1), + _setBlockTracking(-1, true), (_cache[0] = _renderSlot($slots, "default")).cacheIndex = 0, _setBlockTracking(1), _cache[0] @@ -85,7 +85,7 @@ return function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", null, [ _cache[0] || ( - _setBlockTracking(-1), + _setBlockTracking(-1, true), (_cache[0] = _createElementVNode("div")).cacheIndex = 0, _setBlockTracking(1), _cache[0] diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index cfd5fee25..2d6df9d90 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -418,6 +418,7 @@ export interface CacheExpression extends Node { index: number value: JSChildNode needPauseTracking: boolean + inVOnce: boolean needArraySpread: boolean } @@ -774,12 +775,14 @@ export function createCacheExpression( index: number, value: JSChildNode, needPauseTracking: boolean = false, + inVOnce: boolean = false, ): CacheExpression { return { type: NodeTypes.JS_CACHE_EXPRESSION, index, value, needPauseTracking: needPauseTracking, + inVOnce, needArraySpread: false, loc: locStub, } diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 6b6f24b3a..70116cfb6 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -1017,7 +1017,9 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) { push(`_cache[${node.index}] || (`) if (needPauseTracking) { indent() - push(`${helper(SET_BLOCK_TRACKING)}(-1),`) + push(`${helper(SET_BLOCK_TRACKING)}(-1`) + if (node.inVOnce) push(`, true`) + push(`),`) newline() push(`(`) } diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index b47b6b8d4..aeb96cc2b 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -116,7 +116,7 @@ export interface TransformContext addIdentifiers(exp: ExpressionNode | string): void removeIdentifiers(exp: ExpressionNode | string): void hoist(exp: string | JSChildNode | ArrayExpression): SimpleExpressionNode - cache(exp: JSChildNode, isVNode?: boolean): CacheExpression + cache(exp: JSChildNode, isVNode?: boolean, inVOnce?: boolean): CacheExpression constantCache: WeakMap // 2.x Compat only @@ -297,11 +297,12 @@ export function createTransformContext( identifier.hoisted = exp return identifier }, - cache(exp, isVNode = false) { + cache(exp, isVNode = false, inVOnce = false) { const cacheExp = createCacheExpression( context.cached.length, exp, isVNode, + inVOnce, ) context.cached.push(cacheExp) return cacheExp diff --git a/packages/compiler-core/src/transforms/vOnce.ts b/packages/compiler-core/src/transforms/vOnce.ts index 483b98da9..685da59cc 100644 --- a/packages/compiler-core/src/transforms/vOnce.ts +++ b/packages/compiler-core/src/transforms/vOnce.ts @@ -17,7 +17,11 @@ export const transformOnce: NodeTransform = (node, context) => { context.inVOnce = false const cur = context.currentNode as ElementNode | IfNode | ForNode if (cur.codegenNode) { - cur.codegenNode = context.cache(cur.codegenNode, true /* isVNode */) + cur.codegenNode = context.cache( + cur.codegenNode, + true /* isVNode */, + true /* inVOnce */, + ) } } } diff --git a/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts b/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts index 2658f4071..958c12748 100644 --- a/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts +++ b/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts @@ -17,6 +17,7 @@ import { serializeInner as inner, nextTick, nodeOps, + onBeforeMount, onBeforeUnmount, onUnmounted, openBlock, @@ -1199,7 +1200,7 @@ describe('renderer: optimized mode', () => { createBlock('div', null, [ createVNode('div', null, [ cache[0] || - (setBlockTracking(-1), + (setBlockTracking(-1, true), ((cache[0] = createVNode('div', null, [ createVNode(Child), ])).cacheIndex = 0), @@ -1233,4 +1234,64 @@ describe('renderer: optimized mode', () => { expect(inner(root)).toBe('') expect(spyUnmounted).toHaveBeenCalledTimes(2) }) + + // #12371 + test('unmount children when the user calls a compiled slot', async () => { + const beforeMountSpy = vi.fn() + const beforeUnmountSpy = vi.fn() + + const Child = { + setup() { + onBeforeMount(beforeMountSpy) + onBeforeUnmount(beforeUnmountSpy) + return () => 'child' + }, + } + + const Wrapper = { + setup(_: any, { slots }: SetupContext) { + return () => ( + openBlock(), + createElementBlock('section', null, [ + (openBlock(), + createElementBlock('div', { key: 1 }, [ + createTextVNode(slots.header!() ? 'foo' : 'bar', 1 /* TEXT */), + renderSlot(slots, 'content'), + ])), + ]) + ) + }, + } + + const show = ref(false) + const app = createApp({ + render() { + return show.value + ? (openBlock(), + createBlock(Wrapper, null, { + header: withCtx(() => [createVNode({})]), + content: withCtx(() => [createVNode(Child)]), + _: 1, + })) + : createCommentVNode('v-if', true) + }, + }) + + app.mount(root) + expect(inner(root)).toMatchInlineSnapshot(`""`) + expect(beforeMountSpy).toHaveBeenCalledTimes(0) + expect(beforeUnmountSpy).toHaveBeenCalledTimes(0) + + show.value = true + await nextTick() + expect(inner(root)).toMatchInlineSnapshot( + `"
foochild
"`, + ) + expect(beforeMountSpy).toHaveBeenCalledTimes(1) + + show.value = false + await nextTick() + expect(inner(root)).toBe('') + expect(beforeUnmountSpy).toHaveBeenCalledTimes(1) + }) }) diff --git a/packages/runtime-core/__tests__/vnode.spec.ts b/packages/runtime-core/__tests__/vnode.spec.ts index 2e0eee1f2..a7f6a2d56 100644 --- a/packages/runtime-core/__tests__/vnode.spec.ts +++ b/packages/runtime-core/__tests__/vnode.spec.ts @@ -629,7 +629,7 @@ describe('vnode', () => { const vnode = (openBlock(), createBlock('div', null, [ - setBlockTracking(-1), + setBlockTracking(-1, true), (vnode1 = (openBlock(), createBlock('div'))), setBlockTracking(1), vnode1, diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 30200789b..a8c5340cd 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -301,7 +301,7 @@ export let isBlockTreeEnabled = 1 * * ``` js * _cache[1] || ( - * setBlockTracking(-1), + * setBlockTracking(-1, true), * _cache[1] = createVNode(...), * setBlockTracking(1), * _cache[1] @@ -310,11 +310,11 @@ export let isBlockTreeEnabled = 1 * * @private */ -export function setBlockTracking(value: number): void { +export function setBlockTracking(value: number, inVOnce = false): void { isBlockTreeEnabled += value - if (value < 0 && currentBlock) { + if (value < 0 && currentBlock && inVOnce) { // mark current block so it doesn't take fast path and skip possible - // nested components duriung unmount + // nested components during unmount currentBlock.hasOnce = true } } From 99009eee0efc238392daba93792d478525b21afa Mon Sep 17 00:00:00 2001 From: linzhe <40790268+linzhe141@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:14:29 +0800 Subject: [PATCH 08/28] fix(compiler-core): handle v-memo + v-for with functional key (#12014) close #12013 --- .../__snapshots__/vMemo.spec.ts.snap | 18 ++++++++++++++++++ .../__tests__/transforms/vMemo.spec.ts | 8 ++++++++ .../src/transforms/transformExpression.ts | 12 ++++++++++-- packages/compiler-core/src/transforms/vFor.ts | 14 ++++++++++++-- 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vMemo.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vMemo.spec.ts.snap index 220bc1774..86e0b3d2f 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vMemo.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vMemo.spec.ts.snap @@ -1,5 +1,23 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`compiler: v-memo transform > element v-for key expression prefixing + v-memo 1`] = ` +"import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, isMemoSame as _isMemoSame, withMemo as _withMemo } from "vue" + +export function render(_ctx, _cache) { + return (_openBlock(), _createElementBlock("div", null, [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.tableData, (data, __, ___, _cached) => { + const _memo = (_ctx.getLetter(data)) + if (_cached && _cached.key === _ctx.getId(data) && _isMemoSame(_cached, _memo)) return _cached + const _item = (_openBlock(), _createElementBlock("span", { + key: _ctx.getId(data) + })) + _item.memo = _memo + return _item + }, _cache, 0), 128 /* KEYED_FRAGMENT */)) + ])) +}" +`; + exports[`compiler: v-memo transform > on component 1`] = ` "import { resolveComponent as _resolveComponent, createVNode as _createVNode, withMemo as _withMemo, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue" diff --git a/packages/compiler-core/__tests__/transforms/vMemo.spec.ts b/packages/compiler-core/__tests__/transforms/vMemo.spec.ts index 85769e6e9..41e7d922e 100644 --- a/packages/compiler-core/__tests__/transforms/vMemo.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vMemo.spec.ts @@ -53,4 +53,12 @@ describe('compiler: v-memo transform', () => { ), ).toMatchSnapshot() }) + + test('element v-for key expression prefixing + v-memo', () => { + expect( + compile( + ``, + ), + ).toMatchSnapshot() + }) }) diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index ec2d46853..9ae8897e6 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -24,7 +24,7 @@ import { isStaticPropertyKey, walkIdentifiers, } from '../babelUtils' -import { advancePositionWithClone, isSimpleIdentifier } from '../utils' +import { advancePositionWithClone, findDir, isSimpleIdentifier } from '../utils' import { genPropsAccessExp, hasOwn, @@ -54,6 +54,7 @@ export const transformExpression: NodeTransform = (node, context) => { ) } else if (node.type === NodeTypes.ELEMENT) { // handle directives on element + const memo = findDir(node, 'memo') for (let i = 0; i < node.props.length; i++) { const dir = node.props[i] // do not process for v-on & v-for since they are special handled @@ -65,7 +66,14 @@ export const transformExpression: NodeTransform = (node, context) => { if ( exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && - !(dir.name === 'on' && arg) + !(dir.name === 'on' && arg) && + // key has been processed in transformFor(vMemo + vFor) + !( + memo && + arg && + arg.type === NodeTypes.SIMPLE_EXPRESSION && + arg.content === 'key' + ) ) { dir.exp = processExpression( exp, diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts index ec1c21ff8..0dca0ba9a 100644 --- a/packages/compiler-core/src/transforms/vFor.ts +++ b/packages/compiler-core/src/transforms/vFor.ts @@ -63,17 +63,27 @@ export const transformFor: NodeTransform = createStructuralDirectiveTransform( const isTemplate = isTemplateNode(node) const memo = findDir(node, 'memo') const keyProp = findProp(node, `key`, false, true) - if (keyProp && keyProp.type === NodeTypes.DIRECTIVE && !keyProp.exp) { + const isDirKey = keyProp && keyProp.type === NodeTypes.DIRECTIVE + if (isDirKey && !keyProp.exp) { // resolve :key shorthand #10882 transformBindShorthand(keyProp, context) } - const keyExp = + let keyExp = keyProp && (keyProp.type === NodeTypes.ATTRIBUTE ? keyProp.value ? createSimpleExpression(keyProp.value.content, true) : undefined : keyProp.exp) + + if (memo && keyExp && isDirKey) { + if (!__BROWSER__) { + keyProp.exp = keyExp = processExpression( + keyExp as SimpleExpressionNode, + context, + ) + } + } const keyProperty = keyProp && keyExp ? createObjectProperty(`key`, keyExp) : null From 2d5c5e25e9b7a56e883674fb434135ac514429b5 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 14 Nov 2024 15:49:39 +0800 Subject: [PATCH 09/28] fix(runtime-dom): set css vars before user onMounted hooks close #11533 --- .../__tests__/helpers/useCssVars.spec.ts | 22 +++++++++++++++++++ .../runtime-dom/src/helpers/useCssVars.ts | 11 ++++------ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/packages/runtime-dom/__tests__/helpers/useCssVars.spec.ts b/packages/runtime-dom/__tests__/helpers/useCssVars.spec.ts index c4df08731..acba33158 100644 --- a/packages/runtime-dom/__tests__/helpers/useCssVars.spec.ts +++ b/packages/runtime-dom/__tests__/helpers/useCssVars.spec.ts @@ -7,6 +7,7 @@ import { defineCustomElement, h, nextTick, + onMounted, reactive, ref, render, @@ -405,4 +406,25 @@ describe('useCssVars', () => { ``, ) }) + + test('should set vars before child component onMount hook', () => { + const state = reactive({ color: 'red' }) + const root = document.createElement('div') + let colorInOnMount + + const App = { + setup() { + useCssVars(() => state) + onMounted(() => { + colorInOnMount = ( + root.children[0] as HTMLElement + ).style.getPropertyValue(`--color`) + }) + return () => h('div') + }, + } + + render(h(App), root) + expect(colorInOnMount).toBe(`red`) + }) }) diff --git a/packages/runtime-dom/src/helpers/useCssVars.ts b/packages/runtime-dom/src/helpers/useCssVars.ts index e57705304..6331208c5 100644 --- a/packages/runtime-dom/src/helpers/useCssVars.ts +++ b/packages/runtime-dom/src/helpers/useCssVars.ts @@ -3,13 +3,12 @@ import { Static, type VNode, getCurrentInstance, - onBeforeMount, onMounted, onUnmounted, warn, - watchPostEffect, + watch, } from '@vue/runtime-core' -import { ShapeFlags } from '@vue/shared' +import { NOOP, ShapeFlags } from '@vue/shared' export const CSS_VAR_TEXT: unique symbol = Symbol(__DEV__ ? 'CSS_VAR_TEXT' : '') /** @@ -48,11 +47,9 @@ export function useCssVars(getter: (ctx: any) => Record): void { updateTeleports(vars) } - onBeforeMount(() => { - watchPostEffect(setVars) - }) - onMounted(() => { + // run setVars synchronously here, but run as post-effect on changes + watch(setVars, NOOP, { flush: 'post' }) const ob = new MutationObserver(setVars) ob.observe(instance.subTree.el!.parentNode, { childList: true }) onUnmounted(() => ob.disconnect()) From c4312f9c715c131a09e552ba46e9beb4b36d55e6 Mon Sep 17 00:00:00 2001 From: linzhe <40790268+linzhe141@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:58:28 +0800 Subject: [PATCH 10/28] fix(runtime-dom): set css vars on update to handle child forcing reflow in onMount (#11561) --- .../__tests__/helpers/useCssVars.spec.ts | 38 +++++++++++++++++++ .../runtime-dom/src/helpers/useCssVars.ts | 8 ++++ 2 files changed, 46 insertions(+) diff --git a/packages/runtime-dom/__tests__/helpers/useCssVars.spec.ts b/packages/runtime-dom/__tests__/helpers/useCssVars.spec.ts index acba33158..1fb4cc65f 100644 --- a/packages/runtime-dom/__tests__/helpers/useCssVars.spec.ts +++ b/packages/runtime-dom/__tests__/helpers/useCssVars.spec.ts @@ -385,6 +385,44 @@ describe('useCssVars', () => { } }) + test('with delay mount child', async () => { + const state = reactive({ color: 'red' }) + const value = ref(false) + const root = document.createElement('div') + + const Child = { + setup() { + onMounted(() => { + const childEl = root.children[0] + expect(getComputedStyle(childEl!).getPropertyValue(`--color`)).toBe( + `red`, + ) + }) + return () => h('div', { id: 'childId' }) + }, + } + const App = { + setup() { + useCssVars(() => state) + return () => (value.value ? h(Child) : [h('span')]) + }, + } + + render(h(App), root) + await nextTick() + // css vars use with fallback tree + for (const c of [].slice.call(root.children as any)) { + expect((c as HTMLElement).style.getPropertyValue(`--color`)).toBe(`red`) + } + + // mount child + value.value = true + await nextTick() + for (const c of [].slice.call(root.children as any)) { + expect((c as HTMLElement).style.getPropertyValue(`--color`)).toBe(`red`) + } + }) + // #8826 test('with custom element', async () => { const state = reactive({ color: 'red' }) diff --git a/packages/runtime-dom/src/helpers/useCssVars.ts b/packages/runtime-dom/src/helpers/useCssVars.ts index 6331208c5..e2bc6de92 100644 --- a/packages/runtime-dom/src/helpers/useCssVars.ts +++ b/packages/runtime-dom/src/helpers/useCssVars.ts @@ -3,8 +3,10 @@ import { Static, type VNode, getCurrentInstance, + onBeforeUpdate, onMounted, onUnmounted, + queuePostFlushCb, warn, watch, } from '@vue/runtime-core' @@ -47,6 +49,12 @@ export function useCssVars(getter: (ctx: any) => Record): void { updateTeleports(vars) } + // handle cases where child component root is affected + // and triggers reflow in onMounted + onBeforeUpdate(() => { + queuePostFlushCb(setVars) + }) + onMounted(() => { // run setVars synchronously here, but run as post-effect on changes watch(setVars, NOOP, { flush: 'post' }) From 8bff142f99b646e9dd15897ec75368fbf34f1534 Mon Sep 17 00:00:00 2001 From: edison Date: Thu, 14 Nov 2024 20:55:18 +0800 Subject: [PATCH 11/28] 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 5dc333ad6..79125cd04 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 5def1b2d7..fe6fa36c1 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 12/28] 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 1d6f0da4c..da7de554b 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 13/28] 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 e045d30e8..cb4e057c4 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 14/28] 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 bd9224e65..df16e4663 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 b0224cf20..d0aaddd76 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 74a863972..83a347d88 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 15/28] 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 6ce06d282..2b58bc3fc 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 16/28] 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 7b5d6f147..656f1da79 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 54712c680..2ddaeb509 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 17/28] 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 c0863a759..60274ad13 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 18/28] 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 a863eb32e..2ed15ef5e 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