Merge tag 'v3.5.0-alpha.4'
This commit is contained in:
commit
f0405f1528
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -1,3 +1,19 @@
|
|||
# [3.5.0-alpha.4](https://github.com/vuejs/core/compare/v3.4.34...v3.5.0-alpha.4) (2024-07-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **suspense/hydration:** fix hydration timing of async component inside suspense ([1b8e197](https://github.com/vuejs/core/commit/1b8e197a5b65d67a9703b8511786fb81df9aa7cc)), closes [#6638](https://github.com/vuejs/core/issues/6638)
|
||||
* **useId:** properly mark async boundary for already resolved async component ([cd28172](https://github.com/vuejs/core/commit/cd281725781ada2ab279e919031ae307e146a9d9))
|
||||
|
||||
|
||||
|
||||
## [3.4.34](https://github.com/vuejs/core/compare/v3.4.33...v3.4.34) (2024-07-24)
|
||||
|
||||
* **defineModel:** correct update with multiple changes in same tick ([#11430](https://github.com/vuejs/core/issues/11430)) ([a18f1ec](https://github.com/vuejs/core/commit/a18f1ecf05842337f1eb39a6871adb8cb4024093)), closes [#11429](https://github.com/vuejs/core/issues/11429)
|
||||
|
||||
|
||||
|
||||
# [3.5.0-alpha.3](https://github.com/vuejs/core/compare/v3.4.33...v3.5.0-alpha.3) (2024-07-19)
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.0.0-vapor",
|
||||
"version": "3.5.0-alpha.4",
|
||||
"packageManager": "pnpm@9.5.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-core",
|
||||
"version": "3.5.0-alpha.3",
|
||||
"version": "3.5.0-alpha.4",
|
||||
"description": "@vue/compiler-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-core.esm-bundler.js",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-dom",
|
||||
"version": "3.5.0-alpha.3",
|
||||
"version": "3.5.0-alpha.4",
|
||||
"description": "@vue/compiler-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-dom.esm-bundler.js",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-sfc",
|
||||
"version": "3.5.0-alpha.3",
|
||||
"version": "3.5.0-alpha.4",
|
||||
"description": "@vue/compiler-sfc",
|
||||
"main": "dist/compiler-sfc.cjs.js",
|
||||
"module": "dist/compiler-sfc.esm-browser.js",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-ssr",
|
||||
"version": "3.5.0-alpha.3",
|
||||
"version": "3.5.0-alpha.4",
|
||||
"description": "@vue/compiler-ssr",
|
||||
"main": "dist/compiler-ssr.cjs.js",
|
||||
"types": "dist/compiler-ssr.d.ts",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/reactivity",
|
||||
"version": "3.5.0-alpha.3",
|
||||
"version": "3.5.0-alpha.4",
|
||||
"description": "@vue/reactivity",
|
||||
"main": "index.js",
|
||||
"module": "dist/reactivity.esm-bundler.js",
|
||||
|
|
|
@ -12,10 +12,11 @@ import {
|
|||
} from 'vue'
|
||||
import { renderToString } from '@vue/server-renderer'
|
||||
|
||||
type TestCaseFactory = () => [App, Promise<any>[]]
|
||||
type FactoryRes = [App, Promise<any>[]]
|
||||
type TestCaseFactory = () => FactoryRes | Promise<FactoryRes>
|
||||
|
||||
async function runOnClient(factory: TestCaseFactory) {
|
||||
const [app, deps] = factory()
|
||||
const [app, deps] = await factory()
|
||||
const root = document.createElement('div')
|
||||
app.mount(root)
|
||||
await Promise.all(deps)
|
||||
|
@ -24,7 +25,7 @@ async function runOnClient(factory: TestCaseFactory) {
|
|||
}
|
||||
|
||||
async function runOnServer(factory: TestCaseFactory) {
|
||||
const [app, _] = factory()
|
||||
const [app, _] = await factory()
|
||||
return (await renderToString(app))
|
||||
.replace(/<!--[\[\]]-->/g, '') // remove fragment wrappers
|
||||
.trim()
|
||||
|
@ -95,8 +96,8 @@ describe('useId', () => {
|
|||
'v:0-0 v:0-1 ' + // inside first async subtree
|
||||
'v:1-0 v:1-1' // inside second async subtree
|
||||
// assert different async resolution order does not affect id stable-ness
|
||||
expect(await getOutput(() => factory(10, 20))).toBe(expected)
|
||||
expect(await getOutput(() => factory(20, 10))).toBe(expected)
|
||||
expect(await getOutput(() => factory(0, 16))).toBe(expected)
|
||||
expect(await getOutput(() => factory(16, 0))).toBe(expected)
|
||||
})
|
||||
|
||||
test('serverPrefetch', async () => {
|
||||
|
@ -140,8 +141,8 @@ describe('useId', () => {
|
|||
'v:0-0 v:0-1 ' + // inside first async subtree
|
||||
'v:1-0 v:1-1' // inside second async subtree
|
||||
// assert different async resolution order does not affect id stable-ness
|
||||
expect(await getOutput(() => factory(10, 20))).toBe(expected)
|
||||
expect(await getOutput(() => factory(20, 10))).toBe(expected)
|
||||
expect(await getOutput(() => factory(0, 16))).toBe(expected)
|
||||
expect(await getOutput(() => factory(16, 0))).toBe(expected)
|
||||
})
|
||||
|
||||
test('async setup()', async () => {
|
||||
|
@ -192,8 +193,8 @@ describe('useId', () => {
|
|||
'v:1-0 v:1-1' + // inside second async subtree
|
||||
'</div>'
|
||||
// assert different async resolution order does not affect id stable-ness
|
||||
expect(await getOutput(() => factory(10, 20))).toBe(expected)
|
||||
expect(await getOutput(() => factory(20, 10))).toBe(expected)
|
||||
expect(await getOutput(() => factory(0, 16))).toBe(expected)
|
||||
expect(await getOutput(() => factory(16, 0))).toBe(expected)
|
||||
})
|
||||
|
||||
test('deep nested', async () => {
|
||||
|
@ -239,4 +240,49 @@ describe('useId', () => {
|
|||
expect(await getOutput(() => factory())).toBe(expected)
|
||||
expect(await getOutput(() => factory())).toBe(expected)
|
||||
})
|
||||
|
||||
test('async component inside async setup, already resolved', async () => {
|
||||
const factory = async (
|
||||
delay1: number,
|
||||
delay2: number,
|
||||
): Promise<FactoryRes> => {
|
||||
const p1 = promiseWithDelay(null, delay1)
|
||||
const p2 = promiseWithDelay(BasicComponentWithUseId, delay2)
|
||||
const AsyncInner = defineAsyncComponent(() => p2)
|
||||
|
||||
const AsyncSetup = defineComponent({
|
||||
async setup() {
|
||||
await p1
|
||||
return {}
|
||||
},
|
||||
render() {
|
||||
return h(AsyncInner)
|
||||
},
|
||||
})
|
||||
|
||||
const app = createApp({
|
||||
setup() {
|
||||
const id1 = useId()
|
||||
const id2 = useId()
|
||||
return () =>
|
||||
h(Suspense, null, {
|
||||
default: h('div', [id1, ' ', id2, ' ', h(AsyncSetup)]),
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
// the async component may have already been resolved
|
||||
await AsyncInner.__asyncLoader()
|
||||
return [app, [p1, p2]]
|
||||
}
|
||||
|
||||
const expected =
|
||||
'<div>' +
|
||||
'v:0 v:1 ' + // root
|
||||
'v:0-0-0 v:0-0-1' + // async component inside async setup
|
||||
'</div>'
|
||||
// assert different async resolution order does not affect id stable-ness
|
||||
expect(await getOutput(async () => factory(0, 16))).toBe(expected)
|
||||
expect(await getOutput(() => factory(16, 0))).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -614,24 +614,23 @@ describe('useModel', () => {
|
|||
})
|
||||
|
||||
test('set no change value', async () => {
|
||||
let changeChildMsg: (() => void) | null = null
|
||||
let changeChildMsg!: (val: string) => void
|
||||
|
||||
const compRender = vi.fn()
|
||||
const setValue = vi.fn()
|
||||
const Comp = defineComponent({
|
||||
props: ['msg'],
|
||||
emits: ['update:msg'],
|
||||
setup(props) {
|
||||
const childMsg = useModel(props, 'msg')
|
||||
changeChildMsg = () => {
|
||||
childMsg.value = childMsg.value
|
||||
}
|
||||
changeChildMsg = (val: string) => (childMsg.value = val)
|
||||
return () => {
|
||||
return childMsg.value
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const msg = ref('HI')
|
||||
const defaultVal = 'defaultVal'
|
||||
const msg = ref(defaultVal)
|
||||
const Parent = defineComponent({
|
||||
setup() {
|
||||
return () =>
|
||||
|
@ -639,7 +638,7 @@ describe('useModel', () => {
|
|||
msg: msg.value,
|
||||
'onUpdate:msg': val => {
|
||||
msg.value = val
|
||||
compRender()
|
||||
setValue()
|
||||
},
|
||||
})
|
||||
},
|
||||
|
@ -648,8 +647,14 @@ describe('useModel', () => {
|
|||
const root = nodeOps.createElement('div')
|
||||
render(h(Parent), root)
|
||||
|
||||
expect(compRender).toBeCalledTimes(0)
|
||||
changeChildMsg!()
|
||||
expect(compRender).toBeCalledTimes(0)
|
||||
expect(setValue).toBeCalledTimes(0)
|
||||
|
||||
changeChildMsg(defaultVal)
|
||||
expect(setValue).toBeCalledTimes(0)
|
||||
|
||||
changeChildMsg('changed')
|
||||
changeChildMsg(defaultVal)
|
||||
expect(setValue).toBeCalledTimes(2)
|
||||
expect(msg.value).toBe(defaultVal)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -688,6 +688,54 @@ describe('SSR hydration', () => {
|
|||
expect(container.innerHTML).toBe(`<span>1</span>`)
|
||||
})
|
||||
|
||||
// #6638
|
||||
test('Suspense + async component', async () => {
|
||||
let isSuspenseResolved = false
|
||||
let isSuspenseResolvedInChild: any
|
||||
const AsyncChild = defineAsyncComponent(() =>
|
||||
Promise.resolve(
|
||||
defineComponent({
|
||||
setup() {
|
||||
isSuspenseResolvedInChild = isSuspenseResolved
|
||||
const count = ref(0)
|
||||
return () =>
|
||||
h(
|
||||
'span',
|
||||
{
|
||||
onClick: () => {
|
||||
count.value++
|
||||
},
|
||||
},
|
||||
count.value,
|
||||
)
|
||||
},
|
||||
}),
|
||||
),
|
||||
)
|
||||
const { vnode, container } = mountWithHydration('<span>0</span>', () =>
|
||||
h(
|
||||
Suspense,
|
||||
{
|
||||
onResolve() {
|
||||
isSuspenseResolved = true
|
||||
},
|
||||
},
|
||||
() => h(AsyncChild),
|
||||
),
|
||||
)
|
||||
expect(vnode.el).toBe(container.firstChild)
|
||||
// wait for hydration to finish
|
||||
await new Promise(r => setTimeout(r))
|
||||
|
||||
expect(isSuspenseResolvedInChild).toBe(false)
|
||||
expect(isSuspenseResolved).toBe(true)
|
||||
|
||||
// assert interaction
|
||||
triggerEvent('click', container.querySelector('span')!)
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(`<span>1</span>`)
|
||||
})
|
||||
|
||||
test('Suspense (full integration)', async () => {
|
||||
const mountedCalls: number[] = []
|
||||
const asyncDeps: Promise<any>[] = []
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-core",
|
||||
"version": "3.5.0-alpha.3",
|
||||
"version": "3.5.0-alpha.4",
|
||||
"description": "@vue/runtime-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-core.esm-bundler.js",
|
||||
|
|
|
@ -124,6 +124,7 @@ export function defineAsyncComponent<
|
|||
|
||||
setup() {
|
||||
const instance = currentInstance!
|
||||
markAsyncBoundary(instance)
|
||||
|
||||
// already resolved
|
||||
if (resolvedComp) {
|
||||
|
@ -158,8 +159,6 @@ export function defineAsyncComponent<
|
|||
})
|
||||
: null
|
||||
})
|
||||
} else {
|
||||
markAsyncBoundary(instance)
|
||||
}
|
||||
|
||||
const loaded = ref(false)
|
||||
|
|
|
@ -94,6 +94,7 @@ import type { KeepAliveProps } from './components/KeepAlive'
|
|||
import type { BaseTransitionProps } from './components/BaseTransition'
|
||||
import type { DefineComponent } from './apiDefineComponent'
|
||||
import { markAsyncBoundary } from './helpers/useId'
|
||||
import { isAsyncWrapper } from './apiAsyncComponent'
|
||||
|
||||
/**
|
||||
* Public utility type for extracting the instance type of a component.
|
||||
|
@ -863,7 +864,7 @@ function setupStatefulComponent(
|
|||
|
||||
if (isPromise(setupResult)) {
|
||||
// async setup, mark as async boundary for useId()
|
||||
markAsyncBoundary(instance)
|
||||
if (!isAsyncWrapper(instance)) markAsyncBoundary(instance)
|
||||
setupResult.then(unsetCurrentInstance, unsetCurrentInstance)
|
||||
if (isSSR) {
|
||||
// return the promise so server-renderer can wait on it
|
||||
|
|
|
@ -33,7 +33,7 @@ export function useModel(
|
|||
|
||||
const res = customRef((track, trigger) => {
|
||||
let localValue: any
|
||||
let prevSetValue: any
|
||||
let prevSetValue: any = EMPTY_OBJ
|
||||
let prevEmittedValue: any
|
||||
|
||||
watchSyncEffect(() => {
|
||||
|
@ -51,7 +51,10 @@ export function useModel(
|
|||
},
|
||||
|
||||
set(value) {
|
||||
if (!hasChanged(value, localValue)) {
|
||||
if (
|
||||
!hasChanged(value, localValue) &&
|
||||
!(prevSetValue !== EMPTY_OBJ && hasChanged(value, prevSetValue))
|
||||
) {
|
||||
return
|
||||
}
|
||||
const rawProps = i.vnode!.props
|
||||
|
@ -78,9 +81,9 @@ export function useModel(
|
|||
// updates and there will be no prop sync. However the local input state
|
||||
// may be out of sync, so we need to force an update here.
|
||||
if (
|
||||
value !== emittedValue &&
|
||||
value !== prevSetValue &&
|
||||
emittedValue === prevEmittedValue
|
||||
hasChanged(value, emittedValue) &&
|
||||
hasChanged(value, prevSetValue) &&
|
||||
!hasChanged(emittedValue, prevEmittedValue)
|
||||
) {
|
||||
trigger()
|
||||
}
|
||||
|
|
|
@ -1289,7 +1289,7 @@ function baseCreateRenderer(
|
|||
const componentUpdateFn = () => {
|
||||
if (!instance.isMounted) {
|
||||
let vnodeHook: VNodeHook | null | undefined
|
||||
const { el, props } = initialVNode
|
||||
const { el, props, type } = initialVNode
|
||||
const { bm, m, parent } = instance
|
||||
const isAsyncWrapperVNode = isAsyncWrapper(initialVNode)
|
||||
|
||||
|
@ -1338,8 +1338,11 @@ function baseCreateRenderer(
|
|||
}
|
||||
}
|
||||
|
||||
if (isAsyncWrapperVNode) {
|
||||
;(initialVNode.type as ComponentOptions).__asyncLoader!().then(
|
||||
if (
|
||||
isAsyncWrapperVNode &&
|
||||
!(type as ComponentOptions).__asyncResolved
|
||||
) {
|
||||
;(type as ComponentOptions).__asyncLoader!().then(
|
||||
// note: we are moving the render call into an async callback,
|
||||
// which means it won't track dependencies - but it's ok because
|
||||
// a server-rendered async wrapper is already in resolved state
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-dom",
|
||||
"version": "3.5.0-alpha.3",
|
||||
"version": "3.5.0-alpha.4",
|
||||
"description": "@vue/runtime-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-dom.esm-bundler.js",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/server-renderer",
|
||||
"version": "3.5.0-alpha.3",
|
||||
"version": "3.5.0-alpha.4",
|
||||
"description": "@vue/server-renderer",
|
||||
"main": "index.js",
|
||||
"module": "dist/server-renderer.esm-bundler.js",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/shared",
|
||||
"version": "3.5.0-alpha.3",
|
||||
"version": "3.5.0-alpha.4",
|
||||
"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.0-alpha.3",
|
||||
"version": "3.5.0-alpha.4",
|
||||
"description": "Vue 3 compatibility build for Vue 2",
|
||||
"main": "index.js",
|
||||
"module": "dist/vue.runtime.esm-bundler.js",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "vue",
|
||||
"version": "3.5.0-alpha.3",
|
||||
"version": "3.5.0-alpha.4",
|
||||
"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