Skip to content

Commit 4b6e904

Browse files
authored
Merge branch 'main' into edison/fix/12790
2 parents 6eba8ea + 258f78b commit 4b6e904

File tree

23 files changed

+1380
-820
lines changed

23 files changed

+1380
-820
lines changed

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* **custom-element:** avoid triggering mutationObserver when relecting props ([352bc88](https://github.com/vuejs/core/commit/352bc88c1bd2fda09c61ab17ea1a5967ffcd7bc0)), closes [#12214](https://github.com/vuejs/core/issues/12214) [#12215](https://github.com/vuejs/core/issues/12215)
99
* **deps:** update dependency postcss to ^8.4.48 ([#12356](https://github.com/vuejs/core/issues/12356)) ([b5ff930](https://github.com/vuejs/core/commit/b5ff930089985a58c3553977ef999cec2a6708a4))
1010
* **hydration:** the component vnode's el should be updated when a mismatch occurs. ([#12255](https://github.com/vuejs/core/issues/12255)) ([a20a4cb](https://github.com/vuejs/core/commit/a20a4cb36a3e717d1f8f259d0d59f133f508ff0a)), closes [#12253](https://github.com/vuejs/core/issues/12253)
11-
* **reactiivty:** avoid unnecessary watcher effect removal from inactive scope ([2193284](https://github.com/vuejs/core/commit/21932840eae72ffcd357a62ec596aaecc7ec224a)), closes [#5783](https://github.com/vuejs/core/issues/5783) [#5806](https://github.com/vuejs/core/issues/5806)
11+
* **reactivity:** avoid unnecessary watcher effect removal from inactive scope ([2193284](https://github.com/vuejs/core/commit/21932840eae72ffcd357a62ec596aaecc7ec224a)), closes [#5783](https://github.com/vuejs/core/issues/5783) [#5806](https://github.com/vuejs/core/issues/5806)
1212
* **reactivity:** release nested effects/scopes on effect scope stop ([#12373](https://github.com/vuejs/core/issues/12373)) ([bee2f5e](https://github.com/vuejs/core/commit/bee2f5ee62dc0cd04123b737779550726374dd0a)), closes [#12370](https://github.com/vuejs/core/issues/12370)
1313
* **runtime-dom:** set css vars before user onMounted hooks ([2d5c5e2](https://github.com/vuejs/core/commit/2d5c5e25e9b7a56e883674fb434135ac514429b5)), closes [#11533](https://github.com/vuejs/core/issues/11533)
1414
* **runtime-dom:** set css vars on update to handle child forcing reflow in onMount ([#11561](https://github.com/vuejs/core/issues/11561)) ([c4312f9](https://github.com/vuejs/core/commit/c4312f9c715c131a09e552ba46e9beb4b36d55e6))

package.json

+15-32
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"private": true,
33
"version": "3.5.13",
4-
"packageManager": "pnpm@10.8.0",
4+
"packageManager": "pnpm@10.9.0",
55
"type": "module",
66
"scripts": {
77
"dev": "node scripts/dev.js",
@@ -69,23 +69,23 @@
6969
"@rollup/plugin-json": "^6.1.0",
7070
"@rollup/plugin-node-resolve": "^16.0.1",
7171
"@rollup/plugin-replace": "5.0.4",
72-
"@swc/core": "^1.11.13",
72+
"@swc/core": "^1.11.24",
7373
"@types/hash-sum": "^1.0.2",
74-
"@types/node": "^22.14.0",
74+
"@types/node": "^22.14.1",
7575
"@types/semver": "^7.7.0",
7676
"@types/serve-handler": "^6.1.4",
77-
"@vitest/coverage-v8": "^3.0.9",
78-
"@vitest/eslint-plugin": "^1.1.38",
77+
"@vitest/coverage-v8": "^3.1.3",
78+
"@vitest/eslint-plugin": "^1.1.44",
7979
"@vue/consolidate": "1.0.0",
8080
"conventional-changelog-cli": "^5.0.0",
8181
"enquirer": "^2.4.1",
82-
"esbuild": "^0.25.2",
82+
"esbuild": "^0.25.4",
8383
"esbuild-plugin-polyfill-node": "^0.3.0",
84-
"eslint": "^9.23.0",
85-
"eslint-plugin-import-x": "^4.9.4",
84+
"eslint": "^9.25.1",
85+
"eslint-plugin-import-x": "^4.11.0",
8686
"estree-walker": "catalog:",
87-
"jsdom": "^26.0.0",
88-
"lint-staged": "^15.5.0",
87+
"jsdom": "^26.1.0",
88+
"lint-staged": "^15.5.1",
8989
"lodash": "^4.17.21",
9090
"magic-string": "^0.30.17",
9191
"markdown-table": "^3.0.4",
@@ -95,38 +95,21 @@
9595
"prettier": "^3.5.3",
9696
"pretty-bytes": "^6.1.1",
9797
"pug": "^3.0.3",
98-
"puppeteer": "~24.4.0",
98+
"puppeteer": "~24.8.2",
9999
"rimraf": "^6.0.1",
100-
"rollup": "^4.38.0",
100+
"rollup": "^4.40.2",
101101
"rollup-plugin-dts": "^6.2.1",
102102
"rollup-plugin-esbuild": "^6.2.1",
103103
"rollup-plugin-polyfill-node": "^0.13.0",
104104
"semver": "^7.7.1",
105105
"serve": "^14.2.4",
106106
"serve-handler": "^6.1.6",
107-
"simple-git-hooks": "^2.12.1",
107+
"simple-git-hooks": "^2.13.0",
108108
"todomvc-app-css": "^2.4.3",
109109
"tslib": "^2.8.1",
110110
"typescript": "~5.6.2",
111-
"typescript-eslint": "^8.28.0",
111+
"typescript-eslint": "^8.31.1",
112112
"vite": "catalog:",
113-
"vitest": "^3.0.9"
114-
},
115-
"pnpm": {
116-
"peerDependencyRules": {
117-
"allowedVersions": {
118-
"typescript-eslint>eslint": "^9.0.0",
119-
"@typescript-eslint/eslint-plugin>eslint": "^9.0.0",
120-
"@typescript-eslint/parser>eslint": "^9.0.0",
121-
"@typescript-eslint/type-utils>eslint": "^9.0.0",
122-
"@typescript-eslint/utils>eslint": "^9.0.0"
123-
}
124-
},
125-
"onlyBuiltDependencies": [
126-
"@swc/core",
127-
"esbuild",
128-
"puppeteer",
129-
"simple-git-hooks"
130-
]
113+
"vitest": "^3.1.3"
131114
}
132115
}

packages-private/sfc-playground/src/download/template/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"vue": "latest"
1212
},
1313
"devDependencies": {
14-
"@vitejs/plugin-vue": "^5.2.3",
15-
"vite": "^6.2.3"
14+
"@vitejs/plugin-vue": "^5.2.4",
15+
"vite": "^6.3.5"
1616
}
1717
}

packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts

+10
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ describe('compiler: cacheStatic transform', () => {
170170
{
171171
/* _ slot flag */
172172
},
173+
{
174+
type: NodeTypes.JS_PROPERTY,
175+
key: { content: '__' },
176+
value: { content: '[0]' },
177+
},
173178
],
174179
})
175180
})
@@ -197,6 +202,11 @@ describe('compiler: cacheStatic transform', () => {
197202
{
198203
/* _ slot flag */
199204
},
205+
{
206+
type: NodeTypes.JS_PROPERTY,
207+
key: { content: '__' },
208+
value: { content: '[0]' },
209+
},
200210
],
201211
})
202212
})

packages/compiler-core/src/transforms/cacheStatic.ts

+27
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ import {
1212
type RootNode,
1313
type SimpleExpressionNode,
1414
type SlotFunctionExpression,
15+
type SlotsObjectProperty,
1516
type TemplateChildNode,
1617
type TemplateNode,
1718
type TextCallNode,
1819
type VNodeCall,
1920
createArrayExpression,
21+
createObjectProperty,
22+
createSimpleExpression,
2023
getVNodeBlockHelper,
2124
getVNodeHelper,
2225
} from '../ast'
@@ -140,6 +143,7 @@ function walk(
140143
}
141144

142145
let cachedAsArray = false
146+
const slotCacheKeys = []
143147
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
144148
if (
145149
node.tagType === ElementTypes.ELEMENT &&
@@ -163,6 +167,7 @@ function walk(
163167
// default slot
164168
const slot = getSlotNode(node.codegenNode, 'default')
165169
if (slot) {
170+
slotCacheKeys.push(context.cached.length)
166171
slot.returns = getCacheExpression(
167172
createArrayExpression(slot.returns as TemplateChildNode[]),
168173
)
@@ -186,6 +191,7 @@ function walk(
186191
slotName.arg &&
187192
getSlotNode(parent.codegenNode, slotName.arg)
188193
if (slot) {
194+
slotCacheKeys.push(context.cached.length)
189195
slot.returns = getCacheExpression(
190196
createArrayExpression(slot.returns as TemplateChildNode[]),
191197
)
@@ -196,10 +202,31 @@ function walk(
196202

197203
if (!cachedAsArray) {
198204
for (const child of toCache) {
205+
slotCacheKeys.push(context.cached.length)
199206
child.codegenNode = context.cache(child.codegenNode!)
200207
}
201208
}
202209

210+
// put the slot cached keys on the slot object, so that the cache
211+
// can be removed when component unmounting to prevent memory leaks
212+
if (
213+
slotCacheKeys.length &&
214+
node.type === NodeTypes.ELEMENT &&
215+
node.tagType === ElementTypes.COMPONENT &&
216+
node.codegenNode &&
217+
node.codegenNode.type === NodeTypes.VNODE_CALL &&
218+
node.codegenNode.children &&
219+
!isArray(node.codegenNode.children) &&
220+
node.codegenNode.children.type === NodeTypes.JS_OBJECT_EXPRESSION
221+
) {
222+
node.codegenNode.children.properties.push(
223+
createObjectProperty(
224+
`__`,
225+
createSimpleExpression(JSON.stringify(slotCacheKeys), false),
226+
) as SlotsObjectProperty,
227+
)
228+
}
229+
203230
function getCacheExpression(value: JSChildNode): CacheExpression {
204231
const exp = context.cache(value)
205232
// #6978, #7138, #7114

packages/compiler-core/src/transforms/vSlot.ts

-1
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,6 @@ export function buildSlots(
342342
: hasForwardedSlots(node.children)
343343
? SlotFlags.FORWARDED
344344
: SlotFlags.STABLE
345-
346345
let slots = createObjectExpression(
347346
slotsProperties.concat(
348347
createObjectProperty(

packages/compiler-sfc/__tests__/compileStyle.spec.ts

+20-16
Original file line numberDiff line numberDiff line change
@@ -211,38 +211,42 @@ color: red
211211
expect(
212212
compileScoped(`.div { color: red; } .div:where(:hover) { color: blue; }`),
213213
).toMatchInlineSnapshot(`
214-
".div[data-v-test] { color: red;
215-
}
216-
.div[data-v-test]:where(:hover) { color: blue;
217-
}"`)
214+
".div[data-v-test] { color: red;
215+
}
216+
.div[data-v-test]:where(:hover) { color: blue;
217+
}"
218+
`)
218219

219220
expect(
220221
compileScoped(`.div { color: red; } .div:is(:hover) { color: blue; }`),
221222
).toMatchInlineSnapshot(`
222-
".div[data-v-test] { color: red;
223-
}
224-
.div[data-v-test]:is(:hover) { color: blue;
225-
}"`)
223+
".div[data-v-test] { color: red;
224+
}
225+
.div[data-v-test]:is(:hover) { color: blue;
226+
}"
227+
`)
226228

227229
expect(
228230
compileScoped(
229231
`.div { color: red; } .div:where(.foo:hover) { color: blue; }`,
230232
),
231233
).toMatchInlineSnapshot(`
232-
".div[data-v-test] { color: red;
233-
}
234-
.div[data-v-test]:where(.foo:hover) { color: blue;
235-
}"`)
234+
".div[data-v-test] { color: red;
235+
}
236+
.div[data-v-test]:where(.foo:hover) { color: blue;
237+
}"
238+
`)
236239

237240
expect(
238241
compileScoped(
239242
`.div { color: red; } .div:is(.foo:hover) { color: blue; }`,
240243
),
241244
).toMatchInlineSnapshot(`
242-
".div[data-v-test] { color: red;
243-
}
244-
.div[data-v-test]:is(.foo:hover) { color: blue;
245-
}"`)
245+
".div[data-v-test] { color: red;
246+
}
247+
.div[data-v-test]:is(.foo:hover) { color: blue;
248+
}"
249+
`)
246250
})
247251

248252
test('media query', () => {

packages/compiler-sfc/src/compileScript.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,7 @@ function walkDeclaration(
11041104
m === userImportAliases['shallowRef'] ||
11051105
m === userImportAliases['customRef'] ||
11061106
m === userImportAliases['toRef'] ||
1107+
m === userImportAliases['useTemplateRef'] ||
11071108
m === DEFINE_MODEL,
11081109
)
11091110
) {

packages/reactivity/__tests__/computed.spec.ts

+11
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,17 @@ describe('reactivity/computed', () => {
10121012
expect(cValue.value).toBe(1)
10131013
})
10141014

1015+
test('should not recompute if computed does not track reactive data', async () => {
1016+
const spy = vi.fn()
1017+
const c1 = computed(() => spy())
1018+
1019+
c1.value
1020+
ref(0).value++ // update globalVersion
1021+
c1.value
1022+
1023+
expect(spy).toBeCalledTimes(1)
1024+
})
1025+
10151026
test('computed should remain live after losing all subscribers', () => {
10161027
const state = reactive({ a: 1 })
10171028
const p = computed(() => state.a + 1)

packages/reactivity/src/effect.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export enum EffectFlags {
4949
DIRTY = 1 << 4,
5050
ALLOW_RECURSE = 1 << 5,
5151
PAUSED = 1 << 6,
52+
EVALUATED = 1 << 7,
5253
}
5354

5455
/**
@@ -377,22 +378,22 @@ export function refreshComputed(computed: ComputedRefImpl): undefined {
377378
}
378379
computed.globalVersion = globalVersion
379380

380-
const dep = computed.dep
381-
computed.flags |= EffectFlags.RUNNING
382381
// In SSR there will be no render effect, so the computed has no subscriber
383382
// and therefore tracks no deps, thus we cannot rely on the dirty check.
384383
// Instead, computed always re-evaluate and relies on the globalVersion
385384
// fast path above for caching.
385+
// #12337 if computed has no deps (does not rely on any reactive data) and evaluated,
386+
// there is no need to re-evaluate.
386387
if (
387-
dep.version > 0 &&
388388
!computed.isSSR &&
389-
computed.deps &&
390-
!isDirty(computed)
389+
computed.flags & EffectFlags.EVALUATED &&
390+
((!computed.deps && !(computed as any)._dirty) || !isDirty(computed))
391391
) {
392-
computed.flags &= ~EffectFlags.RUNNING
393392
return
394393
}
394+
computed.flags |= EffectFlags.RUNNING
395395

396+
const dep = computed.dep
396397
const prevSub = activeSub
397398
const prevShouldTrack = shouldTrack
398399
activeSub = computed
@@ -402,6 +403,7 @@ export function refreshComputed(computed: ComputedRefImpl): undefined {
402403
prepareDeps(computed)
403404
const value = computed.fn(computed._value)
404405
if (dep.version === 0 || hasChanged(value, computed._value)) {
406+
computed.flags |= EffectFlags.EVALUATED
405407
computed._value = value
406408
dep.version++
407409
}

0 commit comments

Comments
 (0)