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)
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.5.6",
|
||||
"version": "3.5.7",
|
||||
"packageManager": "pnpm@9.10.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
|
|
@ -212,7 +212,8 @@ onMounted(() => {
|
|||
@keydown.ctrl.s.prevent
|
||||
@keydown.meta.s.prevent
|
||||
:ssr="useSSRMode"
|
||||
:autoSave="autoSave"
|
||||
:model-value="autoSave"
|
||||
:editorOptions="{ autoSaveText: false }"
|
||||
:store="store"
|
||||
:showCompileOutput="true"
|
||||
:autoResize="true"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-core",
|
||||
"version": "3.5.6",
|
||||
"version": "3.5.7",
|
||||
"description": "@vue/compiler-core",
|
||||
"main": "index.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
|
||||
// (not artificially created by a transform)
|
||||
const rawExp = exp.loc.source
|
||||
const rawExp = exp.loc.source.trim()
|
||||
const expString =
|
||||
exp.type === NodeTypes.SIMPLE_EXPRESSION ? exp.content : rawExp
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-dom",
|
||||
"version": "3.5.6",
|
||||
"version": "3.5.7",
|
||||
"description": "@vue/compiler-dom",
|
||||
"main": "index.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`] = `
|
||||
"
|
||||
export default {
|
||||
|
|
|
@ -472,6 +472,23 @@ describe('SFC compile <script setup>', () => {
|
|||
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', () => {
|
||||
const { content } = compile(
|
||||
`<script setup>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-sfc",
|
||||
"version": "3.5.6",
|
||||
"version": "3.5.7",
|
||||
"description": "@vue/compiler-sfc",
|
||||
"main": "dist/compiler-sfc.cjs.js",
|
||||
"module": "dist/compiler-sfc.esm-browser.js",
|
||||
|
|
|
@ -102,7 +102,7 @@ export function transformDestructuredProps(
|
|||
return
|
||||
}
|
||||
|
||||
const rootScope: Scope = {}
|
||||
const rootScope: Scope = Object.create(null)
|
||||
const scopeStack: Scope[] = [rootScope]
|
||||
let currentScope: Scope = rootScope
|
||||
const excludedIds = new WeakSet<Identifier>()
|
||||
|
|
|
@ -39,7 +39,7 @@ describe('transition-group', () => {
|
|||
})
|
||||
|
||||
// #11514
|
||||
test('with static tag + comment', () => {
|
||||
test('with static tag + v-if comment', () => {
|
||||
expect(
|
||||
compile(
|
||||
`<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', () => {
|
||||
expect(
|
||||
compile(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-ssr",
|
||||
"version": "3.5.6",
|
||||
"version": "3.5.7",
|
||||
"description": "@vue/compiler-ssr",
|
||||
"main": "dist/compiler-ssr.cjs.js",
|
||||
"types": "dist/compiler-ssr.d.ts",
|
||||
|
|
|
@ -156,7 +156,7 @@ export function processChildren(
|
|||
context: SSRTransformContext,
|
||||
asFragment = false,
|
||||
disableNestedFragments = false,
|
||||
disableCommentAsIfAlternate = false,
|
||||
disableComment = false,
|
||||
): void {
|
||||
if (asFragment) {
|
||||
context.pushStringPart(`<!--[-->`)
|
||||
|
@ -197,7 +197,9 @@ export function processChildren(
|
|||
case NodeTypes.COMMENT:
|
||||
// no need to escape comment here because the AST can only
|
||||
// contain valid comments.
|
||||
context.pushStringPart(`<!--${child.content}-->`)
|
||||
if (!disableComment) {
|
||||
context.pushStringPart(`<!--${child.content}-->`)
|
||||
}
|
||||
break
|
||||
case NodeTypes.INTERPOLATION:
|
||||
context.pushStringPart(
|
||||
|
@ -207,12 +209,7 @@ export function processChildren(
|
|||
)
|
||||
break
|
||||
case NodeTypes.IF:
|
||||
ssrProcessIf(
|
||||
child,
|
||||
context,
|
||||
disableNestedFragments,
|
||||
disableCommentAsIfAlternate,
|
||||
)
|
||||
ssrProcessIf(child, context, disableNestedFragments, disableComment)
|
||||
break
|
||||
case NodeTypes.FOR:
|
||||
ssrProcessFor(child, context, disableNestedFragments)
|
||||
|
|
|
@ -27,7 +27,7 @@ export function ssrProcessIf(
|
|||
node: IfNode,
|
||||
context: SSRTransformContext,
|
||||
disableNestedFragments = false,
|
||||
disableCommentAsIfAlternate = false,
|
||||
disableComment = false,
|
||||
): void {
|
||||
const [rootBranch] = node.branches
|
||||
const ifStatement = createIfStatement(
|
||||
|
@ -56,7 +56,7 @@ export function ssrProcessIf(
|
|||
}
|
||||
}
|
||||
|
||||
if (!currentIf.alternate && !disableCommentAsIfAlternate) {
|
||||
if (!currentIf.alternate && !disableComment) {
|
||||
currentIf.alternate = createBlockStatement([
|
||||
createCallExpression(`_push`, ['`<!---->`']),
|
||||
])
|
||||
|
|
|
@ -1006,9 +1006,27 @@ describe('reactivity/computed', () => {
|
|||
expect(serializeInner(root)).toBe(`<button>Step</button><p>Step 2</p>`)
|
||||
})
|
||||
|
||||
it('manual trigger computed', () => {
|
||||
test('manual trigger computed', () => {
|
||||
const cValue = computed(() => 1)
|
||||
triggerRef(cValue)
|
||||
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",
|
||||
"version": "3.5.6",
|
||||
"version": "3.5.7",
|
||||
"description": "@vue/reactivity",
|
||||
"main": "index.js",
|
||||
"module": "dist/reactivity.esm-bundler.js",
|
||||
|
|
|
@ -82,6 +82,13 @@ export class Dep {
|
|||
*/
|
||||
subsHead?: Link
|
||||
|
||||
/**
|
||||
* For object property deps cleanup
|
||||
*/
|
||||
target?: unknown = undefined
|
||||
map?: KeyToDepMap = undefined
|
||||
key?: unknown = undefined
|
||||
|
||||
constructor(public computed?: ComputedRefImpl | undefined) {
|
||||
if (__DEV__) {
|
||||
this.subsHead = undefined
|
||||
|
@ -218,7 +225,8 @@ function addSub(link: Link) {
|
|||
// which maintains a Set of subscribers, but we simply store them as
|
||||
// raw Maps to reduce memory overhead.
|
||||
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(
|
||||
__DEV__ ? 'Object iterate' : '',
|
||||
|
@ -249,6 +257,9 @@ export function track(target: object, type: TrackOpTypes, key: unknown): void {
|
|||
let dep = depsMap.get(key)
|
||||
if (!dep) {
|
||||
depsMap.set(key, (dep = new Dep()))
|
||||
dep.target = target
|
||||
dep.map = depsMap
|
||||
dep.key = key
|
||||
}
|
||||
if (__DEV__) {
|
||||
dep.track({
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { extend, hasChanged } from '@vue/shared'
|
||||
import type { ComputedRefImpl } from './computed'
|
||||
import type { TrackOpTypes, TriggerOpTypes } from './constants'
|
||||
import { type Link, globalVersion } from './dep'
|
||||
import { type Link, globalVersion, targetMap } from './dep'
|
||||
import { activeEffectScope } from './effectScope'
|
||||
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
|
||||
if (prevSub) {
|
||||
prevSub.nextSub = nextSub
|
||||
|
@ -413,14 +413,24 @@ function removeSub(link: Link) {
|
|||
// was previous tail, point new tail to prev
|
||||
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
|
||||
// if computed, unsubscribe it from all its deps so this computed and its
|
||||
// value can be GCed
|
||||
dep.computed.flags &= ~EffectFlags.TRACKING
|
||||
for (let l = dep.computed.deps; l; l = l.nextDep) {
|
||||
removeSub(l)
|
||||
if (dep.computed) {
|
||||
// if computed, unsubscribe it from all its deps so this computed and its
|
||||
// value can be GCed
|
||||
dep.computed.flags &= ~EffectFlags.TRACKING
|
||||
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}
|
||||
*/
|
||||
export function triggerRef(ref: Ref): void {
|
||||
if (__DEV__) {
|
||||
;(ref as unknown as RefImpl).dep.trigger({
|
||||
target: ref,
|
||||
type: TriggerOpTypes.SET,
|
||||
key: 'value',
|
||||
newValue: (ref as unknown as RefImpl)._value,
|
||||
})
|
||||
} else {
|
||||
;(ref as unknown as RefImpl).dep.trigger()
|
||||
// ref may be an instance of ObjectRefImpl
|
||||
if ((ref as unknown as RefImpl).dep) {
|
||||
if (__DEV__) {
|
||||
;(ref as unknown as RefImpl).dep.trigger({
|
||||
target: ref,
|
||||
type: TriggerOpTypes.SET,
|
||||
key: 'value',
|
||||
newValue: (ref as unknown as RefImpl)._value,
|
||||
})
|
||||
} else {
|
||||
;(ref as unknown as RefImpl).dep.trigger()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -517,6 +517,45 @@ describe('scheduler', () => {
|
|||
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 () => {
|
||||
let count = 0
|
||||
const job = () => {
|
||||
|
@ -558,6 +597,113 @@ describe('scheduler', () => {
|
|||
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
|
||||
// e.g. app.mount inside app.mount
|
||||
test('flushPostFlushCbs', async () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-core",
|
||||
"version": "3.5.6",
|
||||
"version": "3.5.7",
|
||||
"description": "@vue/runtime-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-core.esm-bundler.js",
|
||||
|
|
|
@ -209,11 +209,13 @@ export function defineComponent<
|
|||
? TypeEmitsToOptions<TypeEmits>
|
||||
: RuntimeEmitsOptions,
|
||||
InferredProps = unknown extends TypeProps
|
||||
? string extends RuntimePropsKeys
|
||||
? ComponentObjectPropsOptions extends RuntimePropsOptions
|
||||
? {}
|
||||
: ExtractPropTypes<RuntimePropsOptions>
|
||||
: { [key in RuntimePropsKeys]?: any }
|
||||
? keyof TypeProps extends never
|
||||
? string extends RuntimePropsKeys
|
||||
? ComponentObjectPropsOptions extends RuntimePropsOptions
|
||||
? {}
|
||||
: ExtractPropTypes<RuntimePropsOptions>
|
||||
: { [key in RuntimePropsKeys]?: any }
|
||||
: TypeProps
|
||||
: TypeProps,
|
||||
TypeRefs extends Record<string, unknown> = {},
|
||||
TypeEl extends Element = any,
|
||||
|
|
|
@ -125,7 +125,9 @@ type InferPropType<T, NullAsAny = true> = [T] extends [null]
|
|||
: InferPropType<U, false>
|
||||
: [T] extends [Prop<infer V, infer D>]
|
||||
? unknown extends V
|
||||
? IfAny<V, V, D>
|
||||
? keyof V extends never
|
||||
? IfAny<V, V, D>
|
||||
: V
|
||||
: V
|
||||
: T
|
||||
|
||||
|
|
|
@ -26,6 +26,16 @@ export const hydrateOnIdle: HydrationStrategyFactory<number> =
|
|||
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<
|
||||
IntersectionObserverInit
|
||||
> = opts => (hydrate, forEach) => {
|
||||
|
@ -37,7 +47,15 @@ export const hydrateOnVisible: HydrationStrategyFactory<
|
|||
break
|
||||
}
|
||||
}, 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()
|
||||
}
|
||||
|
||||
|
@ -85,14 +103,20 @@ export const hydrateOnInteraction: HydrationStrategyFactory<
|
|||
return teardown
|
||||
}
|
||||
|
||||
export function forEachElement(node: Node, cb: (el: Element) => void): void {
|
||||
export function forEachElement(
|
||||
node: Node,
|
||||
cb: (el: Element) => void | false,
|
||||
): void {
|
||||
// fragment
|
||||
if (isComment(node) && node.data === '[') {
|
||||
let depth = 1
|
||||
let next = node.nextSibling
|
||||
while (next) {
|
||||
if (next.nodeType === DOMNodeTypes.ELEMENT) {
|
||||
cb(next as Element)
|
||||
const result = cb(next as Element)
|
||||
if (result === false) {
|
||||
break
|
||||
}
|
||||
} else if (isComment(next)) {
|
||||
if (next.data === ']') {
|
||||
if (--depth === 0) break
|
||||
|
|
|
@ -162,7 +162,9 @@ export function flushPreFlushCbs(
|
|||
cb.flags! &= ~SchedulerJobFlags.QUEUED
|
||||
}
|
||||
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 ? ErrorCodes.COMPONENT_UPDATE : ErrorCodes.SCHEDULER,
|
||||
)
|
||||
job.flags! &= ~SchedulerJobFlags.QUEUED
|
||||
if (!(job.flags! & SchedulerJobFlags.ALLOW_RECURSE)) {
|
||||
job.flags! &= ~SchedulerJobFlags.QUEUED
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-dom",
|
||||
"version": "3.5.6",
|
||||
"version": "3.5.7",
|
||||
"description": "@vue/runtime-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-dom.esm-bundler.js",
|
||||
|
|
|
@ -344,7 +344,7 @@ function whenTransitionEnds(
|
|||
}
|
||||
}
|
||||
|
||||
if (explicitTimeout) {
|
||||
if (explicitTimeout != null) {
|
||||
return setTimeout(resolveIfNotStale, explicitTimeout)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/server-renderer",
|
||||
"version": "3.5.6",
|
||||
"version": "3.5.7",
|
||||
"description": "@vue/server-renderer",
|
||||
"main": "index.js",
|
||||
"module": "dist/server-renderer.esm-bundler.js",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/shared",
|
||||
"version": "3.5.6",
|
||||
"version": "3.5.7",
|
||||
"description": "internal utils shared across @vue packages",
|
||||
"main": "index.js",
|
||||
"module": "dist/shared.esm-bundler.js",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compat",
|
||||
"version": "3.5.6",
|
||||
"version": "3.5.7",
|
||||
"description": "Vue 3 compatibility build for Vue 2",
|
||||
"main": "index.js",
|
||||
"module": "dist/vue.runtime.esm-bundler.js",
|
||||
|
|
|
@ -11,9 +11,12 @@
|
|||
<script>
|
||||
const rootMargin = location.search.match(/rootMargin=(\d+)/)?.[1] ?? 0
|
||||
const isFragment = location.search.includes('?fragment')
|
||||
const isVIf = location.search.includes('?v-if')
|
||||
if (isFragment) {
|
||||
document.getElementById('app').innerHTML =
|
||||
`<!--[--><!--[--><span>one</span><!--]--><button>0</button><span>two</span><!--]-->`
|
||||
} else if (isVIf) {
|
||||
document.getElementById('app').innerHTML = `<!---->`
|
||||
}
|
||||
|
||||
window.isHydrated = false
|
||||
|
@ -24,6 +27,7 @@
|
|||
ref,
|
||||
onMounted,
|
||||
hydrateOnVisible,
|
||||
createCommentVNode,
|
||||
} = Vue
|
||||
|
||||
const Comp = {
|
||||
|
@ -39,7 +43,9 @@
|
|||
{ onClick: () => count.value++ },
|
||||
count.value,
|
||||
)
|
||||
if (isFragment) {
|
||||
if (isVIf) {
|
||||
return createCommentVNode('v-if', true)
|
||||
} else if (isFragment) {
|
||||
return [[h('span', 'one')], button, h('span', 'two')]
|
||||
} else {
|
||||
return button
|
||||
|
|
|
@ -65,6 +65,17 @@ describe('async component hydration strategies', () => {
|
|||
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 () => {
|
||||
await goToCase('media')
|
||||
await page().waitForFunction(() => window.isRootMounted)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "vue",
|
||||
"version": "3.5.6",
|
||||
"version": "3.5.7",
|
||||
"description": "The progressive JavaScript framework for building modern web UI.",
|
||||
"main": "index.js",
|
||||
"module": "dist/vue.runtime.esm-bundler.js",
|
||||
|
|
Loading…
Reference in New Issue