mirror of https://github.com/vuejs/core.git
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
b8713589de
24
CHANGELOG.md
24
CHANGELOG.md
|
@ -1,3 +1,27 @@
|
||||||
|
## [3.5.7](https://github.com/vuejs/core/compare/v3.5.6...v3.5.7) (2024-09-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compile-core:** fix v-model with newlines edge case ([#11960](https://github.com/vuejs/core/issues/11960)) ([6224288](https://github.com/vuejs/core/commit/62242886d705ece88dbcad45bb78072ecccad0ca)), closes [#8306](https://github.com/vuejs/core/issues/8306)
|
||||||
|
* **compiler-sfc:** initialize scope with null prototype object ([#11963](https://github.com/vuejs/core/issues/11963)) ([215e154](https://github.com/vuejs/core/commit/215e15407294bf667261360218f975b88c99c2e5))
|
||||||
|
* **hydration:** avoid observing non-Element node ([#11954](https://github.com/vuejs/core/issues/11954)) ([7257e6a](https://github.com/vuejs/core/commit/7257e6a34200409b3fc347d3bb807e11e2785974)), closes [#11952](https://github.com/vuejs/core/issues/11952)
|
||||||
|
* **reactivity:** do not remove dep from depsMap when unsubbed by computed ([960706e](https://github.com/vuejs/core/commit/960706eebf73f08ebc9d5dd853a05def05e2c153))
|
||||||
|
* **reactivity:** fix dev-only memory leak by updating dep.subsHead on sub removal ([5c8b76e](https://github.com/vuejs/core/commit/5c8b76ed6cfbbcee4cbaac0b72beab7291044e4f)), closes [#11956](https://github.com/vuejs/core/issues/11956)
|
||||||
|
* **reactivity:** fix memory leak from dep instances of garbage collected objects ([235ea47](https://github.com/vuejs/core/commit/235ea4772ed2972914cf142da8b7ac1fb04f7585)), closes [#11979](https://github.com/vuejs/core/issues/11979) [#11971](https://github.com/vuejs/core/issues/11971)
|
||||||
|
* **reactivity:** fix triggerRef call on ObjectRefImpl returned by toRef ([#11986](https://github.com/vuejs/core/issues/11986)) ([b030c8b](https://github.com/vuejs/core/commit/b030c8bc7327877efb98aa3d9a58eb287a6ff07a)), closes [#11982](https://github.com/vuejs/core/issues/11982)
|
||||||
|
* **scheduler:** ensure recursive jobs can't be queued twice ([#11955](https://github.com/vuejs/core/issues/11955)) ([d18d6aa](https://github.com/vuejs/core/commit/d18d6aa1b20dc57a8103c51ec4d61e8e53ed936d))
|
||||||
|
* **ssr:** don't render comments in TransitionGroup ([#11961](https://github.com/vuejs/core/issues/11961)) ([a2f6ede](https://github.com/vuejs/core/commit/a2f6edeb02faedbb673c4bc5c6a59d9a79a37d07)), closes [#11958](https://github.com/vuejs/core/issues/11958)
|
||||||
|
* **transition:** respect `duration` setting even when it is `0` ([#11967](https://github.com/vuejs/core/issues/11967)) ([f927a4a](https://github.com/vuejs/core/commit/f927a4ae6f7c453f70ba89498ee0c737dc9866fd))
|
||||||
|
* **types:** correct type inference of all-optional props ([#11644](https://github.com/vuejs/core/issues/11644)) ([9eca65e](https://github.com/vuejs/core/commit/9eca65ee9871d1ac878755afa9a3eb1b02030350)), closes [#11733](https://github.com/vuejs/core/issues/11733) [vuejs/language-tools#4704](https://github.com/vuejs/language-tools/issues/4704)
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **hydration:** avoid observer if element is in viewport ([#11639](https://github.com/vuejs/core/issues/11639)) ([e075dfa](https://github.com/vuejs/core/commit/e075dfad5c7649c6045e3711687ec888e7aa1a39))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.5.6](https://github.com/vuejs/core/compare/v3.5.5...v3.5.6) (2024-09-16)
|
## [3.5.6](https://github.com/vuejs/core/compare/v3.5.5...v3.5.6) (2024-09-16)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "3.5.6",
|
"version": "3.5.7",
|
||||||
"packageManager": "pnpm@9.10.0",
|
"packageManager": "pnpm@9.10.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -212,7 +212,8 @@ onMounted(() => {
|
||||||
@keydown.ctrl.s.prevent
|
@keydown.ctrl.s.prevent
|
||||||
@keydown.meta.s.prevent
|
@keydown.meta.s.prevent
|
||||||
:ssr="useSSRMode"
|
:ssr="useSSRMode"
|
||||||
:autoSave="autoSave"
|
:model-value="autoSave"
|
||||||
|
:editorOptions="{ autoSaveText: false }"
|
||||||
:store="store"
|
:store="store"
|
||||||
:showCompileOutput="true"
|
:showCompileOutput="true"
|
||||||
:autoResize="true"
|
:autoResize="true"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-core",
|
"name": "@vue/compiler-core",
|
||||||
"version": "3.5.6",
|
"version": "3.5.7",
|
||||||
"description": "@vue/compiler-core",
|
"description": "@vue/compiler-core",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/compiler-core.esm-bundler.js",
|
"module": "dist/compiler-core.esm-bundler.js",
|
||||||
|
|
|
@ -31,7 +31,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
||||||
|
|
||||||
// we assume v-model directives are always parsed
|
// we assume v-model directives are always parsed
|
||||||
// (not artificially created by a transform)
|
// (not artificially created by a transform)
|
||||||
const rawExp = exp.loc.source
|
const rawExp = exp.loc.source.trim()
|
||||||
const expString =
|
const expString =
|
||||||
exp.type === NodeTypes.SIMPLE_EXPRESSION ? exp.content : rawExp
|
exp.type === NodeTypes.SIMPLE_EXPRESSION ? exp.content : rawExp
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-dom",
|
"name": "@vue/compiler-dom",
|
||||||
"version": "3.5.6",
|
"version": "3.5.7",
|
||||||
"description": "@vue/compiler-dom",
|
"description": "@vue/compiler-dom",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/compiler-dom.esm-bundler.js",
|
"module": "dist/compiler-dom.esm-bundler.js",
|
||||||
|
|
|
@ -1084,6 +1084,29 @@ return (_ctx, _cache) => {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> > inlineTemplate mode > v-model w/ newlines codegen 1`] = `
|
||||||
|
"import { unref as _unref, isRef as _isRef, vModelText as _vModelText, withDirectives as _withDirectives, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(__props) {
|
||||||
|
|
||||||
|
const count = ref(0)
|
||||||
|
|
||||||
|
return (_ctx, _cache) => {
|
||||||
|
return _withDirectives((_openBlock(), _createElementBlock("input", {
|
||||||
|
"onUpdate:modelValue": _cache[0] || (_cache[0] = $event => (_isRef(count) ? (count).value = $event : null))
|
||||||
|
}, null, 512 /* NEED_PATCH */)), [
|
||||||
|
[_vModelText,
|
||||||
|
_unref(count)
|
||||||
|
]
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> > inlineTemplate mode > with defineExpose() 1`] = `
|
exports[`SFC compile <script setup> > inlineTemplate mode > with defineExpose() 1`] = `
|
||||||
"
|
"
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -472,6 +472,23 @@ describe('SFC compile <script setup>', () => {
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('v-model w/ newlines codegen', () => {
|
||||||
|
const { content } = compile(
|
||||||
|
`<script setup>
|
||||||
|
const count = ref(0)
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<input v-model="
|
||||||
|
count
|
||||||
|
">
|
||||||
|
</template>
|
||||||
|
`,
|
||||||
|
{ inlineTemplate: true },
|
||||||
|
)
|
||||||
|
expect(content).toMatch(`_isRef(count) ? (count).value = $event : null`)
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
test('v-model should not generate ref assignment code for non-setup bindings', () => {
|
test('v-model should not generate ref assignment code for non-setup bindings', () => {
|
||||||
const { content } = compile(
|
const { content } = compile(
|
||||||
`<script setup>
|
`<script setup>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-sfc",
|
"name": "@vue/compiler-sfc",
|
||||||
"version": "3.5.6",
|
"version": "3.5.7",
|
||||||
"description": "@vue/compiler-sfc",
|
"description": "@vue/compiler-sfc",
|
||||||
"main": "dist/compiler-sfc.cjs.js",
|
"main": "dist/compiler-sfc.cjs.js",
|
||||||
"module": "dist/compiler-sfc.esm-browser.js",
|
"module": "dist/compiler-sfc.esm-browser.js",
|
||||||
|
|
|
@ -102,7 +102,7 @@ export function transformDestructuredProps(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootScope: Scope = {}
|
const rootScope: Scope = Object.create(null)
|
||||||
const scopeStack: Scope[] = [rootScope]
|
const scopeStack: Scope[] = [rootScope]
|
||||||
let currentScope: Scope = rootScope
|
let currentScope: Scope = rootScope
|
||||||
const excludedIds = new WeakSet<Identifier>()
|
const excludedIds = new WeakSet<Identifier>()
|
||||||
|
|
|
@ -39,7 +39,7 @@ describe('transition-group', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// #11514
|
// #11514
|
||||||
test('with static tag + comment', () => {
|
test('with static tag + v-if comment', () => {
|
||||||
expect(
|
expect(
|
||||||
compile(
|
compile(
|
||||||
`<transition-group tag="ul"><div v-for="i in list"/><div v-if="false"></div></transition-group>`,
|
`<transition-group tag="ul"><div v-for="i in list"/><div v-if="false"></div></transition-group>`,
|
||||||
|
@ -60,6 +60,25 @@ describe('transition-group', () => {
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #11958
|
||||||
|
test('with static tag + comment', () => {
|
||||||
|
expect(
|
||||||
|
compile(
|
||||||
|
`<transition-group tag="ul"><div v-for="i in list"/><!--test--></transition-group>`,
|
||||||
|
).code,
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"const { ssrRenderAttrs: _ssrRenderAttrs, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
|
||||||
|
|
||||||
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
|
_push(\`<ul\${_ssrRenderAttrs(_attrs)}>\`)
|
||||||
|
_ssrRenderList(_ctx.list, (i) => {
|
||||||
|
_push(\`<div></div>\`)
|
||||||
|
})
|
||||||
|
_push(\`</ul>\`)
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
test('with dynamic tag', () => {
|
test('with dynamic tag', () => {
|
||||||
expect(
|
expect(
|
||||||
compile(
|
compile(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-ssr",
|
"name": "@vue/compiler-ssr",
|
||||||
"version": "3.5.6",
|
"version": "3.5.7",
|
||||||
"description": "@vue/compiler-ssr",
|
"description": "@vue/compiler-ssr",
|
||||||
"main": "dist/compiler-ssr.cjs.js",
|
"main": "dist/compiler-ssr.cjs.js",
|
||||||
"types": "dist/compiler-ssr.d.ts",
|
"types": "dist/compiler-ssr.d.ts",
|
||||||
|
|
|
@ -156,7 +156,7 @@ export function processChildren(
|
||||||
context: SSRTransformContext,
|
context: SSRTransformContext,
|
||||||
asFragment = false,
|
asFragment = false,
|
||||||
disableNestedFragments = false,
|
disableNestedFragments = false,
|
||||||
disableCommentAsIfAlternate = false,
|
disableComment = false,
|
||||||
): void {
|
): void {
|
||||||
if (asFragment) {
|
if (asFragment) {
|
||||||
context.pushStringPart(`<!--[-->`)
|
context.pushStringPart(`<!--[-->`)
|
||||||
|
@ -197,7 +197,9 @@ export function processChildren(
|
||||||
case NodeTypes.COMMENT:
|
case NodeTypes.COMMENT:
|
||||||
// no need to escape comment here because the AST can only
|
// no need to escape comment here because the AST can only
|
||||||
// contain valid comments.
|
// contain valid comments.
|
||||||
context.pushStringPart(`<!--${child.content}-->`)
|
if (!disableComment) {
|
||||||
|
context.pushStringPart(`<!--${child.content}-->`)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
case NodeTypes.INTERPOLATION:
|
case NodeTypes.INTERPOLATION:
|
||||||
context.pushStringPart(
|
context.pushStringPart(
|
||||||
|
@ -207,12 +209,7 @@ export function processChildren(
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
case NodeTypes.IF:
|
case NodeTypes.IF:
|
||||||
ssrProcessIf(
|
ssrProcessIf(child, context, disableNestedFragments, disableComment)
|
||||||
child,
|
|
||||||
context,
|
|
||||||
disableNestedFragments,
|
|
||||||
disableCommentAsIfAlternate,
|
|
||||||
)
|
|
||||||
break
|
break
|
||||||
case NodeTypes.FOR:
|
case NodeTypes.FOR:
|
||||||
ssrProcessFor(child, context, disableNestedFragments)
|
ssrProcessFor(child, context, disableNestedFragments)
|
||||||
|
|
|
@ -27,7 +27,7 @@ export function ssrProcessIf(
|
||||||
node: IfNode,
|
node: IfNode,
|
||||||
context: SSRTransformContext,
|
context: SSRTransformContext,
|
||||||
disableNestedFragments = false,
|
disableNestedFragments = false,
|
||||||
disableCommentAsIfAlternate = false,
|
disableComment = false,
|
||||||
): void {
|
): void {
|
||||||
const [rootBranch] = node.branches
|
const [rootBranch] = node.branches
|
||||||
const ifStatement = createIfStatement(
|
const ifStatement = createIfStatement(
|
||||||
|
@ -56,7 +56,7 @@ export function ssrProcessIf(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentIf.alternate && !disableCommentAsIfAlternate) {
|
if (!currentIf.alternate && !disableComment) {
|
||||||
currentIf.alternate = createBlockStatement([
|
currentIf.alternate = createBlockStatement([
|
||||||
createCallExpression(`_push`, ['`<!---->`']),
|
createCallExpression(`_push`, ['`<!---->`']),
|
||||||
])
|
])
|
||||||
|
|
|
@ -1006,9 +1006,27 @@ describe('reactivity/computed', () => {
|
||||||
expect(serializeInner(root)).toBe(`<button>Step</button><p>Step 2</p>`)
|
expect(serializeInner(root)).toBe(`<button>Step</button><p>Step 2</p>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('manual trigger computed', () => {
|
test('manual trigger computed', () => {
|
||||||
const cValue = computed(() => 1)
|
const cValue = computed(() => 1)
|
||||||
triggerRef(cValue)
|
triggerRef(cValue)
|
||||||
expect(cValue.value).toBe(1)
|
expect(cValue.value).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('computed should remain live after losing all subscribers', () => {
|
||||||
|
const toggle = ref(true)
|
||||||
|
const state = reactive({
|
||||||
|
a: 1,
|
||||||
|
})
|
||||||
|
const p = computed(() => state.a + 1)
|
||||||
|
const pp = computed(() => {
|
||||||
|
return toggle.value ? p.value : 111
|
||||||
|
})
|
||||||
|
|
||||||
|
const { effect: e } = effect(() => pp.value)
|
||||||
|
e.stop()
|
||||||
|
|
||||||
|
expect(p.value).toBe(2)
|
||||||
|
state.a++
|
||||||
|
expect(p.value).toBe(3)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/reactivity",
|
"name": "@vue/reactivity",
|
||||||
"version": "3.5.6",
|
"version": "3.5.7",
|
||||||
"description": "@vue/reactivity",
|
"description": "@vue/reactivity",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/reactivity.esm-bundler.js",
|
"module": "dist/reactivity.esm-bundler.js",
|
||||||
|
|
|
@ -82,6 +82,13 @@ export class Dep {
|
||||||
*/
|
*/
|
||||||
subsHead?: Link
|
subsHead?: Link
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For object property deps cleanup
|
||||||
|
*/
|
||||||
|
target?: unknown = undefined
|
||||||
|
map?: KeyToDepMap = undefined
|
||||||
|
key?: unknown = undefined
|
||||||
|
|
||||||
constructor(public computed?: ComputedRefImpl | undefined) {
|
constructor(public computed?: ComputedRefImpl | undefined) {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
this.subsHead = undefined
|
this.subsHead = undefined
|
||||||
|
@ -218,7 +225,8 @@ function addSub(link: Link) {
|
||||||
// which maintains a Set of subscribers, but we simply store them as
|
// which maintains a Set of subscribers, but we simply store them as
|
||||||
// raw Maps to reduce memory overhead.
|
// raw Maps to reduce memory overhead.
|
||||||
type KeyToDepMap = Map<any, Dep>
|
type KeyToDepMap = Map<any, Dep>
|
||||||
const targetMap = new WeakMap<object, KeyToDepMap>()
|
|
||||||
|
export const targetMap: WeakMap<object, KeyToDepMap> = new WeakMap()
|
||||||
|
|
||||||
export const ITERATE_KEY: unique symbol = Symbol(
|
export const ITERATE_KEY: unique symbol = Symbol(
|
||||||
__DEV__ ? 'Object iterate' : '',
|
__DEV__ ? 'Object iterate' : '',
|
||||||
|
@ -249,6 +257,9 @@ export function track(target: object, type: TrackOpTypes, key: unknown): void {
|
||||||
let dep = depsMap.get(key)
|
let dep = depsMap.get(key)
|
||||||
if (!dep) {
|
if (!dep) {
|
||||||
depsMap.set(key, (dep = new Dep()))
|
depsMap.set(key, (dep = new Dep()))
|
||||||
|
dep.target = target
|
||||||
|
dep.map = depsMap
|
||||||
|
dep.key = key
|
||||||
}
|
}
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
dep.track({
|
dep.track({
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { extend, hasChanged } from '@vue/shared'
|
import { extend, hasChanged } from '@vue/shared'
|
||||||
import type { ComputedRefImpl } from './computed'
|
import type { ComputedRefImpl } from './computed'
|
||||||
import type { TrackOpTypes, TriggerOpTypes } from './constants'
|
import type { TrackOpTypes, TriggerOpTypes } from './constants'
|
||||||
import { type Link, globalVersion } from './dep'
|
import { type Link, globalVersion, targetMap } from './dep'
|
||||||
import { activeEffectScope } from './effectScope'
|
import { activeEffectScope } from './effectScope'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
|
|
||||||
|
@ -399,7 +399,7 @@ export function refreshComputed(computed: ComputedRefImpl): undefined {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeSub(link: Link) {
|
function removeSub(link: Link, fromComputed = false) {
|
||||||
const { dep, prevSub, nextSub } = link
|
const { dep, prevSub, nextSub } = link
|
||||||
if (prevSub) {
|
if (prevSub) {
|
||||||
prevSub.nextSub = nextSub
|
prevSub.nextSub = nextSub
|
||||||
|
@ -413,14 +413,24 @@ function removeSub(link: Link) {
|
||||||
// was previous tail, point new tail to prev
|
// was previous tail, point new tail to prev
|
||||||
dep.subs = prevSub
|
dep.subs = prevSub
|
||||||
}
|
}
|
||||||
|
if (__DEV__ && dep.subsHead === link) {
|
||||||
|
// was previous head, point new head to next
|
||||||
|
dep.subsHead = nextSub
|
||||||
|
}
|
||||||
|
|
||||||
if (!dep.subs && dep.computed) {
|
if (!dep.subs) {
|
||||||
// last subscriber removed
|
// last subscriber removed
|
||||||
// if computed, unsubscribe it from all its deps so this computed and its
|
if (dep.computed) {
|
||||||
// value can be GCed
|
// if computed, unsubscribe it from all its deps so this computed and its
|
||||||
dep.computed.flags &= ~EffectFlags.TRACKING
|
// value can be GCed
|
||||||
for (let l = dep.computed.deps; l; l = l.nextDep) {
|
dep.computed.flags &= ~EffectFlags.TRACKING
|
||||||
removeSub(l)
|
for (let l = dep.computed.deps; l; l = l.nextDep) {
|
||||||
|
removeSub(l, true)
|
||||||
|
}
|
||||||
|
} else if (dep.map && !fromComputed) {
|
||||||
|
// property dep, remove it from the owner depsMap
|
||||||
|
dep.map.delete(dep.key)
|
||||||
|
if (!dep.map.size) targetMap.delete(dep.target!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,15 +182,18 @@ class RefImpl<T = any> {
|
||||||
* @see {@link https://vuejs.org/api/reactivity-advanced.html#triggerref}
|
* @see {@link https://vuejs.org/api/reactivity-advanced.html#triggerref}
|
||||||
*/
|
*/
|
||||||
export function triggerRef(ref: Ref): void {
|
export function triggerRef(ref: Ref): void {
|
||||||
if (__DEV__) {
|
// ref may be an instance of ObjectRefImpl
|
||||||
;(ref as unknown as RefImpl).dep.trigger({
|
if ((ref as unknown as RefImpl).dep) {
|
||||||
target: ref,
|
if (__DEV__) {
|
||||||
type: TriggerOpTypes.SET,
|
;(ref as unknown as RefImpl).dep.trigger({
|
||||||
key: 'value',
|
target: ref,
|
||||||
newValue: (ref as unknown as RefImpl)._value,
|
type: TriggerOpTypes.SET,
|
||||||
})
|
key: 'value',
|
||||||
} else {
|
newValue: (ref as unknown as RefImpl)._value,
|
||||||
;(ref as unknown as RefImpl).dep.trigger()
|
})
|
||||||
|
} else {
|
||||||
|
;(ref as unknown as RefImpl).dep.trigger()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -517,6 +517,45 @@ describe('scheduler', () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('jobs can be re-queued after an error', async () => {
|
||||||
|
const err = new Error('test')
|
||||||
|
let shouldThrow = true
|
||||||
|
|
||||||
|
const job1: SchedulerJob = vi.fn(() => {
|
||||||
|
if (shouldThrow) {
|
||||||
|
shouldThrow = false
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
job1.id = 1
|
||||||
|
|
||||||
|
const job2: SchedulerJob = vi.fn()
|
||||||
|
job2.id = 2
|
||||||
|
|
||||||
|
queueJob(job1)
|
||||||
|
queueJob(job2)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await nextTick()
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e).toBe(err)
|
||||||
|
}
|
||||||
|
expect(
|
||||||
|
`Unhandled error during execution of scheduler flush`,
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
|
||||||
|
expect(job1).toHaveBeenCalledTimes(1)
|
||||||
|
expect(job2).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
queueJob(job1)
|
||||||
|
queueJob(job2)
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(job1).toHaveBeenCalledTimes(2)
|
||||||
|
expect(job2).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
test('should prevent self-triggering jobs by default', async () => {
|
test('should prevent self-triggering jobs by default', async () => {
|
||||||
let count = 0
|
let count = 0
|
||||||
const job = () => {
|
const job = () => {
|
||||||
|
@ -558,6 +597,113 @@ describe('scheduler', () => {
|
||||||
expect(count).toBe(5)
|
expect(count).toBe(5)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('recursive jobs can only be queued once non-recursively', async () => {
|
||||||
|
const job: SchedulerJob = vi.fn()
|
||||||
|
job.id = 1
|
||||||
|
job.flags = SchedulerJobFlags.ALLOW_RECURSE
|
||||||
|
|
||||||
|
queueJob(job)
|
||||||
|
queueJob(job)
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(job).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('recursive jobs can only be queued once recursively', async () => {
|
||||||
|
let recurse = true
|
||||||
|
|
||||||
|
const job: SchedulerJob = vi.fn(() => {
|
||||||
|
if (recurse) {
|
||||||
|
queueJob(job)
|
||||||
|
queueJob(job)
|
||||||
|
recurse = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
job.id = 1
|
||||||
|
job.flags = SchedulerJobFlags.ALLOW_RECURSE
|
||||||
|
|
||||||
|
queueJob(job)
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(job).toHaveBeenCalledTimes(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
test(`recursive jobs can't be re-queued by other jobs`, async () => {
|
||||||
|
let recurse = true
|
||||||
|
|
||||||
|
const job1: SchedulerJob = () => {
|
||||||
|
if (recurse) {
|
||||||
|
// job2 is already queued, so this shouldn't do anything
|
||||||
|
queueJob(job2)
|
||||||
|
recurse = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
job1.id = 1
|
||||||
|
|
||||||
|
const job2: SchedulerJob = vi.fn(() => {
|
||||||
|
if (recurse) {
|
||||||
|
queueJob(job1)
|
||||||
|
queueJob(job2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
job2.id = 2
|
||||||
|
job2.flags = SchedulerJobFlags.ALLOW_RECURSE
|
||||||
|
|
||||||
|
queueJob(job2)
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(job2).toHaveBeenCalledTimes(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('jobs are de-duplicated correctly when calling flushPreFlushCbs', async () => {
|
||||||
|
let recurse = true
|
||||||
|
|
||||||
|
const job1: SchedulerJob = vi.fn(() => {
|
||||||
|
queueJob(job3)
|
||||||
|
queueJob(job3)
|
||||||
|
flushPreFlushCbs()
|
||||||
|
})
|
||||||
|
job1.id = 1
|
||||||
|
job1.flags = SchedulerJobFlags.PRE
|
||||||
|
|
||||||
|
const job2: SchedulerJob = vi.fn(() => {
|
||||||
|
if (recurse) {
|
||||||
|
// job2 does not allow recurse, so this shouldn't do anything
|
||||||
|
queueJob(job2)
|
||||||
|
|
||||||
|
// job3 is already queued, so this shouldn't do anything
|
||||||
|
queueJob(job3)
|
||||||
|
recurse = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
job2.id = 2
|
||||||
|
job2.flags = SchedulerJobFlags.PRE
|
||||||
|
|
||||||
|
const job3: SchedulerJob = vi.fn(() => {
|
||||||
|
if (recurse) {
|
||||||
|
queueJob(job2)
|
||||||
|
queueJob(job3)
|
||||||
|
|
||||||
|
// The jobs are already queued, so these should have no effect
|
||||||
|
queueJob(job2)
|
||||||
|
queueJob(job3)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
job3.id = 3
|
||||||
|
job3.flags = SchedulerJobFlags.ALLOW_RECURSE | SchedulerJobFlags.PRE
|
||||||
|
|
||||||
|
queueJob(job1)
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(job1).toHaveBeenCalledTimes(1)
|
||||||
|
expect(job2).toHaveBeenCalledTimes(1)
|
||||||
|
expect(job3).toHaveBeenCalledTimes(2)
|
||||||
|
})
|
||||||
|
|
||||||
// #1947 flushPostFlushCbs should handle nested calls
|
// #1947 flushPostFlushCbs should handle nested calls
|
||||||
// e.g. app.mount inside app.mount
|
// e.g. app.mount inside app.mount
|
||||||
test('flushPostFlushCbs', async () => {
|
test('flushPostFlushCbs', async () => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/runtime-core",
|
"name": "@vue/runtime-core",
|
||||||
"version": "3.5.6",
|
"version": "3.5.7",
|
||||||
"description": "@vue/runtime-core",
|
"description": "@vue/runtime-core",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/runtime-core.esm-bundler.js",
|
"module": "dist/runtime-core.esm-bundler.js",
|
||||||
|
|
|
@ -209,11 +209,13 @@ export function defineComponent<
|
||||||
? TypeEmitsToOptions<TypeEmits>
|
? TypeEmitsToOptions<TypeEmits>
|
||||||
: RuntimeEmitsOptions,
|
: RuntimeEmitsOptions,
|
||||||
InferredProps = unknown extends TypeProps
|
InferredProps = unknown extends TypeProps
|
||||||
? string extends RuntimePropsKeys
|
? keyof TypeProps extends never
|
||||||
? ComponentObjectPropsOptions extends RuntimePropsOptions
|
? string extends RuntimePropsKeys
|
||||||
? {}
|
? ComponentObjectPropsOptions extends RuntimePropsOptions
|
||||||
: ExtractPropTypes<RuntimePropsOptions>
|
? {}
|
||||||
: { [key in RuntimePropsKeys]?: any }
|
: ExtractPropTypes<RuntimePropsOptions>
|
||||||
|
: { [key in RuntimePropsKeys]?: any }
|
||||||
|
: TypeProps
|
||||||
: TypeProps,
|
: TypeProps,
|
||||||
TypeRefs extends Record<string, unknown> = {},
|
TypeRefs extends Record<string, unknown> = {},
|
||||||
TypeEl extends Element = any,
|
TypeEl extends Element = any,
|
||||||
|
|
|
@ -125,7 +125,9 @@ type InferPropType<T, NullAsAny = true> = [T] extends [null]
|
||||||
: InferPropType<U, false>
|
: InferPropType<U, false>
|
||||||
: [T] extends [Prop<infer V, infer D>]
|
: [T] extends [Prop<infer V, infer D>]
|
||||||
? unknown extends V
|
? unknown extends V
|
||||||
? IfAny<V, V, D>
|
? keyof V extends never
|
||||||
|
? IfAny<V, V, D>
|
||||||
|
: V
|
||||||
: V
|
: V
|
||||||
: T
|
: T
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,16 @@ export const hydrateOnIdle: HydrationStrategyFactory<number> =
|
||||||
return () => cancelIdleCallback(id)
|
return () => cancelIdleCallback(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function elementIsVisibleInViewport(el: Element) {
|
||||||
|
const { top, left, bottom, right } = el.getBoundingClientRect()
|
||||||
|
// eslint-disable-next-line no-restricted-globals
|
||||||
|
const { innerHeight, innerWidth } = window
|
||||||
|
return (
|
||||||
|
((top > 0 && top < innerHeight) || (bottom > 0 && bottom < innerHeight)) &&
|
||||||
|
((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const hydrateOnVisible: HydrationStrategyFactory<
|
export const hydrateOnVisible: HydrationStrategyFactory<
|
||||||
IntersectionObserverInit
|
IntersectionObserverInit
|
||||||
> = opts => (hydrate, forEach) => {
|
> = opts => (hydrate, forEach) => {
|
||||||
|
@ -37,7 +47,15 @@ export const hydrateOnVisible: HydrationStrategyFactory<
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}, opts)
|
}, opts)
|
||||||
forEach(el => ob.observe(el))
|
forEach(el => {
|
||||||
|
if (!(el instanceof Element)) return
|
||||||
|
if (elementIsVisibleInViewport(el)) {
|
||||||
|
hydrate()
|
||||||
|
ob.disconnect()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ob.observe(el)
|
||||||
|
})
|
||||||
return () => ob.disconnect()
|
return () => ob.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,14 +103,20 @@ export const hydrateOnInteraction: HydrationStrategyFactory<
|
||||||
return teardown
|
return teardown
|
||||||
}
|
}
|
||||||
|
|
||||||
export function forEachElement(node: Node, cb: (el: Element) => void): void {
|
export function forEachElement(
|
||||||
|
node: Node,
|
||||||
|
cb: (el: Element) => void | false,
|
||||||
|
): void {
|
||||||
// fragment
|
// fragment
|
||||||
if (isComment(node) && node.data === '[') {
|
if (isComment(node) && node.data === '[') {
|
||||||
let depth = 1
|
let depth = 1
|
||||||
let next = node.nextSibling
|
let next = node.nextSibling
|
||||||
while (next) {
|
while (next) {
|
||||||
if (next.nodeType === DOMNodeTypes.ELEMENT) {
|
if (next.nodeType === DOMNodeTypes.ELEMENT) {
|
||||||
cb(next as Element)
|
const result = cb(next as Element)
|
||||||
|
if (result === false) {
|
||||||
|
break
|
||||||
|
}
|
||||||
} else if (isComment(next)) {
|
} else if (isComment(next)) {
|
||||||
if (next.data === ']') {
|
if (next.data === ']') {
|
||||||
if (--depth === 0) break
|
if (--depth === 0) break
|
||||||
|
|
|
@ -162,7 +162,9 @@ export function flushPreFlushCbs(
|
||||||
cb.flags! &= ~SchedulerJobFlags.QUEUED
|
cb.flags! &= ~SchedulerJobFlags.QUEUED
|
||||||
}
|
}
|
||||||
cb()
|
cb()
|
||||||
cb.flags! &= ~SchedulerJobFlags.QUEUED
|
if (!(cb.flags! & SchedulerJobFlags.ALLOW_RECURSE)) {
|
||||||
|
cb.flags! &= ~SchedulerJobFlags.QUEUED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,7 +241,9 @@ function flushJobs(seen?: CountMap) {
|
||||||
job.i,
|
job.i,
|
||||||
job.i ? ErrorCodes.COMPONENT_UPDATE : ErrorCodes.SCHEDULER,
|
job.i ? ErrorCodes.COMPONENT_UPDATE : ErrorCodes.SCHEDULER,
|
||||||
)
|
)
|
||||||
job.flags! &= ~SchedulerJobFlags.QUEUED
|
if (!(job.flags! & SchedulerJobFlags.ALLOW_RECURSE)) {
|
||||||
|
job.flags! &= ~SchedulerJobFlags.QUEUED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/runtime-dom",
|
"name": "@vue/runtime-dom",
|
||||||
"version": "3.5.6",
|
"version": "3.5.7",
|
||||||
"description": "@vue/runtime-dom",
|
"description": "@vue/runtime-dom",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/runtime-dom.esm-bundler.js",
|
"module": "dist/runtime-dom.esm-bundler.js",
|
||||||
|
|
|
@ -344,7 +344,7 @@ function whenTransitionEnds(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (explicitTimeout) {
|
if (explicitTimeout != null) {
|
||||||
return setTimeout(resolveIfNotStale, explicitTimeout)
|
return setTimeout(resolveIfNotStale, explicitTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/server-renderer",
|
"name": "@vue/server-renderer",
|
||||||
"version": "3.5.6",
|
"version": "3.5.7",
|
||||||
"description": "@vue/server-renderer",
|
"description": "@vue/server-renderer",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/server-renderer.esm-bundler.js",
|
"module": "dist/server-renderer.esm-bundler.js",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/shared",
|
"name": "@vue/shared",
|
||||||
"version": "3.5.6",
|
"version": "3.5.7",
|
||||||
"description": "internal utils shared across @vue packages",
|
"description": "internal utils shared across @vue packages",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/shared.esm-bundler.js",
|
"module": "dist/shared.esm-bundler.js",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compat",
|
"name": "@vue/compat",
|
||||||
"version": "3.5.6",
|
"version": "3.5.7",
|
||||||
"description": "Vue 3 compatibility build for Vue 2",
|
"description": "Vue 3 compatibility build for Vue 2",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/vue.runtime.esm-bundler.js",
|
"module": "dist/vue.runtime.esm-bundler.js",
|
||||||
|
|
|
@ -11,9 +11,12 @@
|
||||||
<script>
|
<script>
|
||||||
const rootMargin = location.search.match(/rootMargin=(\d+)/)?.[1] ?? 0
|
const rootMargin = location.search.match(/rootMargin=(\d+)/)?.[1] ?? 0
|
||||||
const isFragment = location.search.includes('?fragment')
|
const isFragment = location.search.includes('?fragment')
|
||||||
|
const isVIf = location.search.includes('?v-if')
|
||||||
if (isFragment) {
|
if (isFragment) {
|
||||||
document.getElementById('app').innerHTML =
|
document.getElementById('app').innerHTML =
|
||||||
`<!--[--><!--[--><span>one</span><!--]--><button>0</button><span>two</span><!--]-->`
|
`<!--[--><!--[--><span>one</span><!--]--><button>0</button><span>two</span><!--]-->`
|
||||||
|
} else if (isVIf) {
|
||||||
|
document.getElementById('app').innerHTML = `<!---->`
|
||||||
}
|
}
|
||||||
|
|
||||||
window.isHydrated = false
|
window.isHydrated = false
|
||||||
|
@ -24,6 +27,7 @@
|
||||||
ref,
|
ref,
|
||||||
onMounted,
|
onMounted,
|
||||||
hydrateOnVisible,
|
hydrateOnVisible,
|
||||||
|
createCommentVNode,
|
||||||
} = Vue
|
} = Vue
|
||||||
|
|
||||||
const Comp = {
|
const Comp = {
|
||||||
|
@ -39,7 +43,9 @@
|
||||||
{ onClick: () => count.value++ },
|
{ onClick: () => count.value++ },
|
||||||
count.value,
|
count.value,
|
||||||
)
|
)
|
||||||
if (isFragment) {
|
if (isVIf) {
|
||||||
|
return createCommentVNode('v-if', true)
|
||||||
|
} else if (isFragment) {
|
||||||
return [[h('span', 'one')], button, h('span', 'two')]
|
return [[h('span', 'one')], button, h('span', 'two')]
|
||||||
} else {
|
} else {
|
||||||
return button
|
return button
|
||||||
|
|
|
@ -65,6 +65,17 @@ describe('async component hydration strategies', () => {
|
||||||
await assertHydrationSuccess()
|
await assertHydrationSuccess()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('visible (root v-if) should not throw error', async () => {
|
||||||
|
const spy = vi.fn()
|
||||||
|
const currentPage = page()
|
||||||
|
currentPage.on('pageerror', spy)
|
||||||
|
await goToCase('visible', '?v-if')
|
||||||
|
await page().waitForFunction(() => window.isRootMounted)
|
||||||
|
expect(await page().evaluate(() => window.isHydrated)).toBe(false)
|
||||||
|
expect(spy).toBeCalledTimes(0)
|
||||||
|
currentPage.off('pageerror', spy)
|
||||||
|
})
|
||||||
|
|
||||||
test('media query', async () => {
|
test('media query', async () => {
|
||||||
await goToCase('media')
|
await goToCase('media')
|
||||||
await page().waitForFunction(() => window.isRootMounted)
|
await page().waitForFunction(() => window.isRootMounted)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "vue",
|
"name": "vue",
|
||||||
"version": "3.5.6",
|
"version": "3.5.7",
|
||||||
"description": "The progressive JavaScript framework for building modern web UI.",
|
"description": "The progressive JavaScript framework for building modern web UI.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/vue.runtime.esm-bundler.js",
|
"module": "dist/vue.runtime.esm-bundler.js",
|
||||||
|
|
Loading…
Reference in New Issue