mirror of https://github.com/vuejs/core.git
chore: Merge branch 'main' into minor
This commit is contained in:
commit
f35080daf5
|
|
@ -82,7 +82,7 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
|
|||
|
||||
## Development Setup
|
||||
|
||||
You will need [Node.js](https://nodejs.org) **version 18.12+**, and [PNPM](https://pnpm.io) **version 8+**.
|
||||
You will need [Node.js](https://nodejs.org) with minimum version as specified in the [`.node-version`](https://github.com/vuejs/core/blob/main/.node-version) file, and [PNPM](https://pnpm.io) with minimum version as specified in the [`"packageManager"` field in `package.json`](https://github.com/vuejs/core/blob/main/package.json#L4).
|
||||
|
||||
We also recommend installing [@antfu/ni](https://github.com/antfu/ni) to help switching between repos using different package managers. `ni` also provides the handy `nr` command which running npm scripts easier.
|
||||
|
||||
|
|
|
|||
|
|
@ -31,4 +31,4 @@ jobs:
|
|||
- name: Run prettier
|
||||
run: pnpm run format
|
||||
|
||||
- uses: autofix-ci/action@2891949f3779a1cafafae1523058501de3d4e944
|
||||
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c
|
||||
|
|
|
|||
17
CHANGELOG.md
17
CHANGELOG.md
|
|
@ -1,6 +1,21 @@
|
|||
# [3.5.0-alpha.4](https://github.com/vuejs/core/compare/v3.4.34...v3.5.0-alpha.4) (2024-07-24)
|
||||
## [3.4.35](https://github.com/vuejs/core/compare/v3.4.34...v3.4.35) (2024-07-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **teleport/ssr:** fix Teleport hydration regression due to targetStart anchor addition ([7b18cdb](https://github.com/vuejs/core/commit/7b18cdb0b53a94007ca6a3675bf41b5d3153fec6))
|
||||
* **teleport/ssr:** ensure targetAnchor and targetStart not null during hydration ([#11456](https://github.com/vuejs/core/issues/11456)) ([12667da](https://github.com/vuejs/core/commit/12667da4879f980dcf2c50e36f3642d085a87d71)), closes [#11400](https://github.com/vuejs/core/issues/11400)
|
||||
* **types/ref:** allow getter and setter types to be unrelated ([#11442](https://github.com/vuejs/core/issues/11442)) ([e0b2975](https://github.com/vuejs/core/commit/e0b2975ef65ae6a0be0aa0a0df43fb887c665251))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **runtime-core:** improve efficiency of normalizePropsOptions ([#11409](https://github.com/vuejs/core/issues/11409)) ([5680142](https://github.com/vuejs/core/commit/5680142e68096c42e66da9f4c6220d040d7c56ba)), closes [#9739](https://github.com/vuejs/core/issues/9739)
|
||||
|
||||
|
||||
|
||||
# [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)
|
||||
|
|
|
|||
24
package.json
24
package.json
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.5.0-alpha.4",
|
||||
"packageManager": "pnpm@9.5.0",
|
||||
"packageManager": "pnpm@9.6.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js",
|
||||
|
|
@ -66,21 +66,21 @@
|
|||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-replace": "5.0.4",
|
||||
"@swc/core": "^1.6.13",
|
||||
"@swc/core": "^1.7.3",
|
||||
"@types/hash-sum": "^1.0.2",
|
||||
"@types/node": "^20.14.10",
|
||||
"@types/node": "^20.14.13",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@vitest/coverage-istanbul": "^1.6.0",
|
||||
"@vue/consolidate": "1.0.0",
|
||||
"conventional-changelog-cli": "^4.1.0",
|
||||
"conventional-changelog-cli": "^5.0.0",
|
||||
"enquirer": "^2.4.1",
|
||||
"esbuild": "^0.23.0",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"eslint": "^9.6.0",
|
||||
"eslint-plugin-import-x": "^0.5.3",
|
||||
"eslint": "^9.8.0",
|
||||
"eslint-plugin-import-x": "^3.1.0",
|
||||
"eslint-plugin-vitest": "^0.5.4",
|
||||
"estree-walker": "catalog:",
|
||||
"jsdom": "^24.1.0",
|
||||
"jsdom": "^24.1.1",
|
||||
"lint-staged": "^15.2.7",
|
||||
"lodash": "^4.17.21",
|
||||
"magic-string": "^0.30.10",
|
||||
|
|
@ -88,23 +88,23 @@
|
|||
"marked": "^12.0.2",
|
||||
"npm-run-all2": "^6.2.2",
|
||||
"picocolors": "^1.0.1",
|
||||
"prettier": "^3.3.2",
|
||||
"prettier": "^3.3.3",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.3",
|
||||
"puppeteer": "~22.12.1",
|
||||
"puppeteer": "~22.14.0",
|
||||
"rimraf": "^5.0.9",
|
||||
"rollup": "^4.18.1",
|
||||
"rollup": "^4.19.1",
|
||||
"rollup-plugin-dts": "^6.1.1",
|
||||
"rollup-plugin-esbuild": "^6.1.1",
|
||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||
"semver": "^7.6.2",
|
||||
"semver": "^7.6.3",
|
||||
"serve": "^14.2.3",
|
||||
"simple-git-hooks": "^2.11.1",
|
||||
"todomvc-app-css": "^2.4.3",
|
||||
"tslib": "^2.6.3",
|
||||
"tsx": "^4.16.2",
|
||||
"typescript": "~5.4.5",
|
||||
"typescript-eslint": "^7.15.0",
|
||||
"typescript-eslint": "^7.17.0",
|
||||
"vite": "catalog:",
|
||||
"vitest": "^1.6.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@
|
|||
"@vue/shared": "workspace:*",
|
||||
"estree-walker": "catalog:",
|
||||
"magic-string": "catalog:",
|
||||
"postcss": "^8.4.39",
|
||||
"postcss": "^8.4.40",
|
||||
"source-map-js": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -60,7 +60,7 @@
|
|||
"merge-source-map": "^1.1.0",
|
||||
"minimatch": "^9.0.5",
|
||||
"postcss-modules": "^6.0.0",
|
||||
"postcss-selector-parser": "^6.1.0",
|
||||
"postcss-selector-parser": "^6.1.1",
|
||||
"pug": "^3.0.3",
|
||||
"sass": "^1.77.8"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,6 +173,16 @@ describe('ref with generic', <T extends { name: string }>() => {
|
|||
expectType<string>(ss.value.name)
|
||||
})
|
||||
|
||||
describe('allow getter and setter types to be unrelated', <T>() => {
|
||||
const a = { b: ref(0) }
|
||||
const c = ref(a)
|
||||
c.value = a
|
||||
|
||||
const d = {} as T
|
||||
const e = ref(d)
|
||||
e.value = d
|
||||
})
|
||||
|
||||
// shallowRef
|
||||
type Status = 'initial' | 'ready' | 'invalidating'
|
||||
const shallowStatus = shallowRef<Status>('initial')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
type ComputedRef,
|
||||
type MaybeRef,
|
||||
type Ref,
|
||||
computed,
|
||||
defineComponent,
|
||||
|
|
@ -203,3 +204,10 @@ defineComponent({
|
|||
expectType<{ foo: string }>(value)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const css: MaybeRef<string> = ''
|
||||
watch(ref(css), value => {
|
||||
expectType<string>(value)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,19 +169,6 @@ function createForEach(isReadonly: boolean, isShallow: boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
interface Iterable {
|
||||
[Symbol.iterator](): Iterator
|
||||
}
|
||||
|
||||
interface Iterator {
|
||||
next(value?: any): IterationResult
|
||||
}
|
||||
|
||||
interface IterationResult {
|
||||
value: any
|
||||
done: boolean
|
||||
}
|
||||
|
||||
function createIterableMethod(
|
||||
method: string | symbol,
|
||||
isReadonly: boolean,
|
||||
|
|
@ -190,7 +177,7 @@ function createIterableMethod(
|
|||
return function (
|
||||
this: IterableCollections,
|
||||
...args: unknown[]
|
||||
): Iterable & Iterator {
|
||||
): Iterable<unknown> & Iterator<unknown> {
|
||||
const target = this[ReactiveFlags.RAW]
|
||||
const rawTarget = toRaw(target)
|
||||
const targetIsMap = isMap(rawTarget)
|
||||
|
|
|
|||
|
|
@ -23,8 +23,9 @@ import { warn } from './warning'
|
|||
declare const RefSymbol: unique symbol
|
||||
export declare const RawSymbol: unique symbol
|
||||
|
||||
export interface Ref<T = any> {
|
||||
value: T
|
||||
export interface Ref<T = any, S = T> {
|
||||
get value(): T
|
||||
set value(_: S)
|
||||
/**
|
||||
* Type differentiator only.
|
||||
* We need this to be in public d.ts but don't want it to show up in IDE
|
||||
|
|
@ -51,7 +52,7 @@ export function isRef(r: any): r is Ref {
|
|||
* @param value - The object to wrap in the ref.
|
||||
* @see {@link https://vuejs.org/api/reactivity-core.html#ref}
|
||||
*/
|
||||
export function ref<T>(value: T): Ref<UnwrapRef<T>>
|
||||
export function ref<T>(value: T): Ref<UnwrapRef<T>, UnwrapRef<T> | T>
|
||||
export function ref<T = any>(): Ref<T | undefined>
|
||||
export function ref(value?: unknown) {
|
||||
return createRef(value, false)
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ describe('SSR hydration', () => {
|
|||
const fn = vi.fn()
|
||||
const teleportContainer = document.createElement('div')
|
||||
teleportContainer.id = 'teleport'
|
||||
teleportContainer.innerHTML = `<span>foo</span><span class="foo"></span><!--teleport anchor-->`
|
||||
teleportContainer.innerHTML = `<!--teleport start anchor--><span>foo</span><span class="foo"></span><!--teleport anchor-->`
|
||||
document.body.appendChild(teleportContainer)
|
||||
|
||||
const { vnode, container } = mountWithHydration(
|
||||
|
|
@ -281,13 +281,14 @@ describe('SSR hydration', () => {
|
|||
expect(vnode.anchor).toBe(container.lastChild)
|
||||
|
||||
expect(vnode.target).toBe(teleportContainer)
|
||||
expect(vnode.targetStart).toBe(teleportContainer.childNodes[0])
|
||||
expect((vnode.children as VNode[])[0].el).toBe(
|
||||
teleportContainer.childNodes[0],
|
||||
)
|
||||
expect((vnode.children as VNode[])[1].el).toBe(
|
||||
teleportContainer.childNodes[1],
|
||||
)
|
||||
expect(vnode.targetAnchor).toBe(teleportContainer.childNodes[2])
|
||||
expect((vnode.children as VNode[])[1].el).toBe(
|
||||
teleportContainer.childNodes[2],
|
||||
)
|
||||
expect(vnode.targetAnchor).toBe(teleportContainer.childNodes[3])
|
||||
|
||||
// event handler
|
||||
triggerEvent('click', teleportContainer.querySelector('.foo')!)
|
||||
|
|
@ -296,7 +297,7 @@ describe('SSR hydration', () => {
|
|||
msg.value = 'bar'
|
||||
await nextTick()
|
||||
expect(teleportContainer.innerHTML).toBe(
|
||||
`<span>bar</span><span class="bar"></span><!--teleport anchor-->`,
|
||||
`<!--teleport start anchor--><span>bar</span><span class="bar"></span><!--teleport anchor-->`,
|
||||
)
|
||||
})
|
||||
|
||||
|
|
@ -326,7 +327,7 @@ describe('SSR hydration', () => {
|
|||
|
||||
const teleportHtml = ctx.teleports!['#teleport2']
|
||||
expect(teleportHtml).toMatchInlineSnapshot(
|
||||
`"<span>foo</span><span class="foo"></span><!--teleport anchor--><span>foo2</span><span class="foo2"></span><!--teleport anchor-->"`,
|
||||
`"<!--teleport start anchor--><span>foo</span><span class="foo"></span><!--teleport anchor--><!--teleport start anchor--><span>foo2</span><span class="foo2"></span><!--teleport anchor-->"`,
|
||||
)
|
||||
|
||||
teleportContainer.innerHTML = teleportHtml
|
||||
|
|
@ -342,16 +343,18 @@ describe('SSR hydration', () => {
|
|||
expect(teleportVnode2.anchor).toBe(container.childNodes[4])
|
||||
|
||||
expect(teleportVnode1.target).toBe(teleportContainer)
|
||||
expect(teleportVnode1.targetStart).toBe(teleportContainer.childNodes[0])
|
||||
expect((teleportVnode1 as any).children[0].el).toBe(
|
||||
teleportContainer.childNodes[0],
|
||||
teleportContainer.childNodes[1],
|
||||
)
|
||||
expect(teleportVnode1.targetAnchor).toBe(teleportContainer.childNodes[2])
|
||||
expect(teleportVnode1.targetAnchor).toBe(teleportContainer.childNodes[3])
|
||||
|
||||
expect(teleportVnode2.target).toBe(teleportContainer)
|
||||
expect(teleportVnode2.targetStart).toBe(teleportContainer.childNodes[4])
|
||||
expect((teleportVnode2 as any).children[0].el).toBe(
|
||||
teleportContainer.childNodes[3],
|
||||
teleportContainer.childNodes[5],
|
||||
)
|
||||
expect(teleportVnode2.targetAnchor).toBe(teleportContainer.childNodes[5])
|
||||
expect(teleportVnode2.targetAnchor).toBe(teleportContainer.childNodes[7])
|
||||
|
||||
// // event handler
|
||||
triggerEvent('click', teleportContainer.querySelector('.foo')!)
|
||||
|
|
@ -363,7 +366,7 @@ describe('SSR hydration', () => {
|
|||
msg.value = 'bar'
|
||||
await nextTick()
|
||||
expect(teleportContainer.innerHTML).toMatchInlineSnapshot(
|
||||
`"<span>bar</span><span class="bar"></span><!--teleport anchor--><span>bar2</span><span class="bar2"></span><!--teleport anchor-->"`,
|
||||
`"<!--teleport start anchor--><span>bar</span><span class="bar"></span><!--teleport anchor--><!--teleport start anchor--><span>bar2</span><span class="bar2"></span><!--teleport anchor-->"`,
|
||||
)
|
||||
})
|
||||
|
||||
|
|
@ -390,7 +393,9 @@ describe('SSR hydration', () => {
|
|||
)
|
||||
|
||||
const teleportHtml = ctx.teleports!['#teleport3']
|
||||
expect(teleportHtml).toMatchInlineSnapshot(`"<!--teleport anchor-->"`)
|
||||
expect(teleportHtml).toMatchInlineSnapshot(
|
||||
`"<!--teleport start anchor--><!--teleport anchor-->"`,
|
||||
)
|
||||
|
||||
teleportContainer.innerHTML = teleportHtml
|
||||
document.body.appendChild(teleportContainer)
|
||||
|
|
@ -413,7 +418,8 @@ describe('SSR hydration', () => {
|
|||
expect(children[2].el).toBe(container.childNodes[6])
|
||||
|
||||
expect(teleportVnode.target).toBe(teleportContainer)
|
||||
expect(teleportVnode.targetAnchor).toBe(teleportContainer.childNodes[0])
|
||||
expect(teleportVnode.targetStart).toBe(teleportContainer.childNodes[0])
|
||||
expect(teleportVnode.targetAnchor).toBe(teleportContainer.childNodes[1])
|
||||
|
||||
// // event handler
|
||||
triggerEvent('click', container.querySelector('.foo')!)
|
||||
|
|
@ -454,7 +460,7 @@ describe('SSR hydration', () => {
|
|||
test('Teleport (as component root)', () => {
|
||||
const teleportContainer = document.createElement('div')
|
||||
teleportContainer.id = 'teleport4'
|
||||
teleportContainer.innerHTML = `hello<!--teleport anchor-->`
|
||||
teleportContainer.innerHTML = `<!--teleport start anchor-->hello<!--teleport anchor-->`
|
||||
document.body.appendChild(teleportContainer)
|
||||
|
||||
const wrapper = {
|
||||
|
|
@ -483,7 +489,7 @@ describe('SSR hydration', () => {
|
|||
test('Teleport (nested)', () => {
|
||||
const teleportContainer = document.createElement('div')
|
||||
teleportContainer.id = 'teleport5'
|
||||
teleportContainer.innerHTML = `<div><!--teleport start--><!--teleport end--></div><!--teleport anchor--><div>child</div><!--teleport anchor-->`
|
||||
teleportContainer.innerHTML = `<!--teleport start anchor--><div><!--teleport start--><!--teleport end--></div><!--teleport anchor--><!--teleport start anchor--><div>child</div><!--teleport anchor-->`
|
||||
document.body.appendChild(teleportContainer)
|
||||
|
||||
const { vnode, container } = mountWithHydration(
|
||||
|
|
@ -498,7 +504,7 @@ describe('SSR hydration', () => {
|
|||
expect(vnode.anchor).toBe(container.lastChild)
|
||||
|
||||
const childDivVNode = (vnode as any).children[0]
|
||||
const div = teleportContainer.firstChild
|
||||
const div = teleportContainer.childNodes[1]
|
||||
expect(childDivVNode.el).toBe(div)
|
||||
expect(vnode.targetAnchor).toBe(div?.nextSibling)
|
||||
|
||||
|
|
@ -512,6 +518,178 @@ describe('SSR hydration', () => {
|
|||
)
|
||||
})
|
||||
|
||||
test('Teleport unmount (full integration)', async () => {
|
||||
const Comp1 = {
|
||||
template: `
|
||||
<Teleport to="#target">
|
||||
<span>Teleported Comp1</span>
|
||||
</Teleport>
|
||||
`,
|
||||
}
|
||||
const Comp2 = {
|
||||
template: `
|
||||
<div>Comp2</div>
|
||||
`,
|
||||
}
|
||||
|
||||
const toggle = ref(true)
|
||||
const App = {
|
||||
template: `
|
||||
<div>
|
||||
<Comp1 v-if="toggle"/>
|
||||
<Comp2 v-else/>
|
||||
</div>
|
||||
`,
|
||||
components: {
|
||||
Comp1,
|
||||
Comp2,
|
||||
},
|
||||
setup() {
|
||||
return { toggle }
|
||||
},
|
||||
}
|
||||
|
||||
const container = document.createElement('div')
|
||||
const teleportContainer = document.createElement('div')
|
||||
teleportContainer.id = 'target'
|
||||
document.body.appendChild(teleportContainer)
|
||||
|
||||
// server render
|
||||
const ctx: SSRContext = {}
|
||||
container.innerHTML = await renderToString(h(App), ctx)
|
||||
expect(container.innerHTML).toBe(
|
||||
'<div><!--teleport start--><!--teleport end--></div>',
|
||||
)
|
||||
teleportContainer.innerHTML = ctx.teleports!['#target']
|
||||
|
||||
// hydrate
|
||||
createSSRApp(App).mount(container)
|
||||
expect(container.innerHTML).toBe(
|
||||
'<div><!--teleport start--><!--teleport end--></div>',
|
||||
)
|
||||
expect(teleportContainer.innerHTML).toBe(
|
||||
'<!--teleport start anchor--><span>Teleported Comp1</span><!--teleport anchor-->',
|
||||
)
|
||||
expect(`Hydration children mismatch`).not.toHaveBeenWarned()
|
||||
|
||||
toggle.value = false
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe('<div><div>Comp2</div></div>')
|
||||
expect(teleportContainer.innerHTML).toBe('')
|
||||
})
|
||||
|
||||
test('Teleport unmount (mismatch + full integration)', async () => {
|
||||
const Comp1 = {
|
||||
template: `
|
||||
<Teleport to="#target">
|
||||
<span>Teleported Comp1</span>
|
||||
</Teleport>
|
||||
`,
|
||||
}
|
||||
const Comp2 = {
|
||||
template: `
|
||||
<div>Comp2</div>
|
||||
`,
|
||||
}
|
||||
|
||||
const toggle = ref(true)
|
||||
const App = {
|
||||
template: `
|
||||
<div>
|
||||
<Comp1 v-if="toggle"/>
|
||||
<Comp2 v-else/>
|
||||
</div>
|
||||
`,
|
||||
components: {
|
||||
Comp1,
|
||||
Comp2,
|
||||
},
|
||||
setup() {
|
||||
return { toggle }
|
||||
},
|
||||
}
|
||||
|
||||
const container = document.createElement('div')
|
||||
const teleportContainer = document.createElement('div')
|
||||
teleportContainer.id = 'target'
|
||||
document.body.appendChild(teleportContainer)
|
||||
|
||||
// server render
|
||||
container.innerHTML = await renderToString(h(App))
|
||||
expect(container.innerHTML).toBe(
|
||||
'<div><!--teleport start--><!--teleport end--></div>',
|
||||
)
|
||||
expect(teleportContainer.innerHTML).toBe('')
|
||||
|
||||
// hydrate
|
||||
createSSRApp(App).mount(container)
|
||||
expect(container.innerHTML).toBe(
|
||||
'<div><!--teleport start--><!--teleport end--></div>',
|
||||
)
|
||||
expect(teleportContainer.innerHTML).toBe('<span>Teleported Comp1</span>')
|
||||
expect(`Hydration children mismatch`).toHaveBeenWarned()
|
||||
|
||||
toggle.value = false
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe('<div><div>Comp2</div></div>')
|
||||
expect(teleportContainer.innerHTML).toBe('')
|
||||
})
|
||||
|
||||
test('Teleport target change (mismatch + full integration)', async () => {
|
||||
const target = ref('#target1')
|
||||
const Comp = {
|
||||
template: `
|
||||
<Teleport :to="target">
|
||||
<span>Teleported</span>
|
||||
</Teleport>
|
||||
`,
|
||||
setup() {
|
||||
return { target }
|
||||
},
|
||||
}
|
||||
|
||||
const App = {
|
||||
template: `
|
||||
<div>
|
||||
<Comp />
|
||||
</div>
|
||||
`,
|
||||
components: {
|
||||
Comp,
|
||||
},
|
||||
}
|
||||
|
||||
const container = document.createElement('div')
|
||||
const teleportContainer1 = document.createElement('div')
|
||||
teleportContainer1.id = 'target1'
|
||||
const teleportContainer2 = document.createElement('div')
|
||||
teleportContainer2.id = 'target2'
|
||||
document.body.appendChild(teleportContainer1)
|
||||
document.body.appendChild(teleportContainer2)
|
||||
|
||||
// server render
|
||||
container.innerHTML = await renderToString(h(App))
|
||||
expect(container.innerHTML).toBe(
|
||||
'<div><!--teleport start--><!--teleport end--></div>',
|
||||
)
|
||||
expect(teleportContainer1.innerHTML).toBe('')
|
||||
expect(teleportContainer2.innerHTML).toBe('')
|
||||
|
||||
// hydrate
|
||||
createSSRApp(App).mount(container)
|
||||
expect(container.innerHTML).toBe(
|
||||
'<div><!--teleport start--><!--teleport end--></div>',
|
||||
)
|
||||
expect(teleportContainer1.innerHTML).toBe('<span>Teleported</span>')
|
||||
expect(teleportContainer2.innerHTML).toBe('')
|
||||
expect(`Hydration children mismatch`).toHaveBeenWarned()
|
||||
|
||||
target.value = '#target2'
|
||||
await nextTick()
|
||||
expect(teleportContainer1.innerHTML).toBe('')
|
||||
expect(teleportContainer2.innerHTML).toBe('<span>Teleported</span>')
|
||||
})
|
||||
|
||||
// compile SSR + client render fn from the same template & hydrate
|
||||
test('full compiler integration', async () => {
|
||||
const mounted: string[] = []
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ import { useSSRContext } from './helpers/useSsrContext'
|
|||
|
||||
export type WatchEffect = (onCleanup: OnCleanup) => void
|
||||
|
||||
export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
|
||||
export type WatchSource<T = any> = Ref<T, any> | ComputedRef<T> | (() => T)
|
||||
|
||||
export type WatchCallback<V = any, OV = any> = (
|
||||
value: V,
|
||||
|
|
|
|||
|
|
@ -176,12 +176,10 @@ export type ExtractDefaultPropTypes<O> = O extends object
|
|||
{ [K in keyof Pick<O, DefaultKeys<O>>]: InferPropType<O[K]> }
|
||||
: {}
|
||||
|
||||
type NormalizedProp =
|
||||
| null
|
||||
| (PropOptions & {
|
||||
[BooleanFlags.shouldCast]?: boolean
|
||||
[BooleanFlags.shouldCastTrue]?: boolean
|
||||
})
|
||||
type NormalizedProp = PropOptions & {
|
||||
[BooleanFlags.shouldCast]?: boolean
|
||||
[BooleanFlags.shouldCastTrue]?: boolean
|
||||
}
|
||||
|
||||
// normalized value is a tuple of the actual normalized options
|
||||
// and an array of prop keys that need value casting (booleans and defaults)
|
||||
|
|
@ -566,16 +564,36 @@ export function normalizePropsOptions(
|
|||
const opt = raw[key]
|
||||
const prop: NormalizedProp = (normalized[normalizedKey] =
|
||||
isArray(opt) || isFunction(opt) ? { type: opt } : extend({}, opt))
|
||||
if (prop) {
|
||||
const booleanIndex = getTypeIndex(Boolean, prop.type)
|
||||
const stringIndex = getTypeIndex(String, prop.type)
|
||||
prop[BooleanFlags.shouldCast] = booleanIndex > -1
|
||||
prop[BooleanFlags.shouldCastTrue] =
|
||||
stringIndex < 0 || booleanIndex < stringIndex
|
||||
// if the prop needs boolean casting or default value
|
||||
if (booleanIndex > -1 || hasOwn(prop, 'default')) {
|
||||
needCastKeys.push(normalizedKey)
|
||||
const propType = prop.type
|
||||
let shouldCast = false
|
||||
let shouldCastTrue = true
|
||||
|
||||
if (isArray(propType)) {
|
||||
for (let index = 0; index < propType.length; ++index) {
|
||||
const type = propType[index]
|
||||
const typeName = isFunction(type) && type.name
|
||||
|
||||
if (typeName === 'Boolean') {
|
||||
shouldCast = true
|
||||
break
|
||||
} else if (typeName === 'String') {
|
||||
// If we find `String` before `Boolean`, e.g. `[String, Boolean]`,
|
||||
// we need to handle the casting slightly differently. Props
|
||||
// passed as `<Comp checked="">` or `<Comp checked="checked">`
|
||||
// will either be treated as strings or converted to a boolean
|
||||
// `true`, depending on the order of the types.
|
||||
shouldCastTrue = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
shouldCast = isFunction(propType) && propType.name === 'Boolean'
|
||||
}
|
||||
|
||||
prop[BooleanFlags.shouldCast] = shouldCast
|
||||
prop[BooleanFlags.shouldCastTrue] = shouldCastTrue
|
||||
// if the prop needs boolean casting or default value
|
||||
if (shouldCast || hasOwn(prop, 'default')) {
|
||||
needCastKeys.push(normalizedKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -597,6 +615,7 @@ function validatePropName(key: string) {
|
|||
return false
|
||||
}
|
||||
|
||||
// dev only
|
||||
// use function string name to check type constructors
|
||||
// so that it works across vms / iframes.
|
||||
function getType(ctor: Prop<any> | null): string {
|
||||
|
|
@ -619,22 +638,6 @@ function getType(ctor: Prop<any> | null): string {
|
|||
return ''
|
||||
}
|
||||
|
||||
function isSameType(a: Prop<any> | null, b: Prop<any> | null): boolean {
|
||||
return getType(a) === getType(b)
|
||||
}
|
||||
|
||||
function getTypeIndex(
|
||||
type: Prop<any>,
|
||||
expectedTypes: PropType<any> | void | null | true,
|
||||
): number {
|
||||
if (isArray(expectedTypes)) {
|
||||
return expectedTypes.findIndex(t => isSameType(t, type))
|
||||
} else if (isFunction(expectedTypes)) {
|
||||
return isSameType(expectedTypes, type) ? 0 : -1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* dev only
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -112,13 +112,8 @@ export const TeleportImpl = {
|
|||
const mainAnchor = (n2.anchor = __DEV__
|
||||
? createComment('teleport end')
|
||||
: createText(''))
|
||||
const targetStart = (n2.targetStart = createText(''))
|
||||
const targetAnchor = (n2.targetAnchor = createText(''))
|
||||
insert(placeholder, container, anchor)
|
||||
insert(mainAnchor, container, anchor)
|
||||
// attach a special property so we can skip teleported content in
|
||||
// renderer's nextSibling search
|
||||
targetStart[TeleportEndKey] = targetAnchor
|
||||
|
||||
const mount = (container: RendererElement, anchor: RendererNode) => {
|
||||
// Teleport *always* has Array children. This is enforced in both the
|
||||
|
|
@ -139,9 +134,8 @@ export const TeleportImpl = {
|
|||
|
||||
const mountToTarget = () => {
|
||||
const target = (n2.target = resolveTarget(n2.props, querySelector))
|
||||
const targetAnchor = prepareAnchor(target, n2, createText, insert)
|
||||
if (target) {
|
||||
insert(targetStart, target)
|
||||
insert(targetAnchor, target)
|
||||
// #2652 we could be teleporting from a non-SVG tree into an SVG tree
|
||||
if (namespace !== 'svg' && isTargetSVG(target)) {
|
||||
namespace = 'svg'
|
||||
|
|
@ -375,7 +369,7 @@ function hydrateTeleport(
|
|||
slotScopeIds: string[] | null,
|
||||
optimized: boolean,
|
||||
{
|
||||
o: { nextSibling, parentNode, querySelector },
|
||||
o: { nextSibling, parentNode, querySelector, insert, createText },
|
||||
}: RendererInternals<Node, Element>,
|
||||
hydrateChildren: (
|
||||
node: Node | null,
|
||||
|
|
@ -407,7 +401,8 @@ function hydrateTeleport(
|
|||
slotScopeIds,
|
||||
optimized,
|
||||
)
|
||||
vnode.targetAnchor = targetNode
|
||||
vnode.targetStart = targetNode
|
||||
vnode.targetAnchor = targetNode && nextSibling(targetNode)
|
||||
} else {
|
||||
vnode.anchor = nextSibling(node)
|
||||
|
||||
|
|
@ -416,21 +411,29 @@ function hydrateTeleport(
|
|||
// could be nested teleports
|
||||
let targetAnchor = targetNode
|
||||
while (targetAnchor) {
|
||||
targetAnchor = nextSibling(targetAnchor)
|
||||
if (
|
||||
targetAnchor &&
|
||||
targetAnchor.nodeType === 8 &&
|
||||
(targetAnchor as Comment).data === 'teleport anchor'
|
||||
) {
|
||||
vnode.targetAnchor = targetAnchor
|
||||
;(target as TeleportTargetElement)._lpa =
|
||||
vnode.targetAnchor && nextSibling(vnode.targetAnchor as Node)
|
||||
break
|
||||
if (targetAnchor && targetAnchor.nodeType === 8) {
|
||||
if ((targetAnchor as Comment).data === 'teleport start anchor') {
|
||||
vnode.targetStart = targetAnchor
|
||||
} else if ((targetAnchor as Comment).data === 'teleport anchor') {
|
||||
vnode.targetAnchor = targetAnchor
|
||||
;(target as TeleportTargetElement)._lpa =
|
||||
vnode.targetAnchor && nextSibling(vnode.targetAnchor as Node)
|
||||
break
|
||||
}
|
||||
}
|
||||
targetAnchor = nextSibling(targetAnchor)
|
||||
}
|
||||
|
||||
// #11400 if the HTML corresponding to Teleport is not embedded in the
|
||||
// correct position on the final page during SSR. the targetAnchor will
|
||||
// always be null, we need to manually add targetAnchor to ensure
|
||||
// Teleport it can properly unmount or move
|
||||
if (!vnode.targetAnchor) {
|
||||
prepareAnchor(target, vnode, createText, insert)
|
||||
}
|
||||
|
||||
hydrateChildren(
|
||||
targetNode,
|
||||
targetNode && nextSibling(targetNode),
|
||||
vnode,
|
||||
target,
|
||||
parentComponent,
|
||||
|
|
@ -469,3 +472,24 @@ function updateCssVars(vnode: VNode) {
|
|||
ctx.ut()
|
||||
}
|
||||
}
|
||||
|
||||
function prepareAnchor(
|
||||
target: RendererElement | null,
|
||||
vnode: TeleportVNode,
|
||||
createText: RendererOptions['createText'],
|
||||
insert: RendererOptions['insert'],
|
||||
) {
|
||||
const targetStart = (vnode.targetStart = createText(''))
|
||||
const targetAnchor = (vnode.targetAnchor = createText(''))
|
||||
|
||||
// attach a special property, so we can skip teleported content in
|
||||
// renderer's nextSibling search
|
||||
targetStart[TeleportEndKey] = targetAnchor
|
||||
|
||||
if (target) {
|
||||
insert(targetStart, target)
|
||||
insert(targetAnchor, target)
|
||||
}
|
||||
|
||||
return targetAnchor
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ describe('ssrRenderTeleport', () => {
|
|||
)
|
||||
expect(html).toBe('<!--teleport start--><!--teleport end-->')
|
||||
expect(ctx.teleports!['#target']).toBe(
|
||||
`<div>content</div><!--teleport anchor-->`,
|
||||
`<!--teleport start anchor--><div>content</div><!--teleport anchor-->`,
|
||||
)
|
||||
})
|
||||
|
||||
|
|
@ -56,7 +56,9 @@ describe('ssrRenderTeleport', () => {
|
|||
expect(html).toBe(
|
||||
'<!--teleport start--><div>content</div><!--teleport end-->',
|
||||
)
|
||||
expect(ctx.teleports!['#target']).toBe(`<!--teleport anchor-->`)
|
||||
expect(ctx.teleports!['#target']).toBe(
|
||||
`<!--teleport start anchor--><!--teleport anchor-->`,
|
||||
)
|
||||
})
|
||||
|
||||
test('teleport rendering (vnode)', async () => {
|
||||
|
|
@ -73,7 +75,7 @@ describe('ssrRenderTeleport', () => {
|
|||
)
|
||||
expect(html).toBe('<!--teleport start--><!--teleport end-->')
|
||||
expect(ctx.teleports!['#target']).toBe(
|
||||
'<span>hello</span><!--teleport anchor-->',
|
||||
'<!--teleport start anchor--><span>hello</span><!--teleport anchor-->',
|
||||
)
|
||||
})
|
||||
|
||||
|
|
@ -93,7 +95,9 @@ describe('ssrRenderTeleport', () => {
|
|||
expect(html).toBe(
|
||||
'<!--teleport start--><span>hello</span><!--teleport end-->',
|
||||
)
|
||||
expect(ctx.teleports!['#target']).toBe(`<!--teleport anchor-->`)
|
||||
expect(ctx.teleports!['#target']).toBe(
|
||||
`<!--teleport start anchor--><!--teleport anchor-->`,
|
||||
)
|
||||
})
|
||||
|
||||
test('multiple teleports with same target', async () => {
|
||||
|
|
@ -115,7 +119,8 @@ describe('ssrRenderTeleport', () => {
|
|||
'<div><!--teleport start--><!--teleport end--><!--teleport start--><!--teleport end--></div>',
|
||||
)
|
||||
expect(ctx.teleports!['#target']).toBe(
|
||||
'<span>hello</span><!--teleport anchor-->world<!--teleport anchor-->',
|
||||
'<!--teleport start anchor--><span>hello</span><!--teleport anchor-->' +
|
||||
'<!--teleport start anchor-->world<!--teleport anchor-->',
|
||||
)
|
||||
})
|
||||
|
||||
|
|
@ -134,7 +139,7 @@ describe('ssrRenderTeleport', () => {
|
|||
)
|
||||
expect(html).toBe('<!--teleport start--><!--teleport end-->')
|
||||
expect(ctx.teleports!['#target']).toBe(
|
||||
`<div>content</div><!--teleport anchor-->`,
|
||||
`<!--teleport start anchor--><div>content</div><!--teleport anchor-->`,
|
||||
)
|
||||
})
|
||||
|
||||
|
|
@ -169,7 +174,7 @@ describe('ssrRenderTeleport', () => {
|
|||
await p
|
||||
expect(html).toBe('<!--teleport start--><!--teleport end-->')
|
||||
expect(ctx.teleports!['#target']).toBe(
|
||||
`<div>content</div><!--teleport anchor-->`,
|
||||
`<!--teleport start anchor--><div>content</div><!--teleport anchor-->`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -29,9 +29,10 @@ export function ssrRenderTeleport(
|
|||
|
||||
if (disabled) {
|
||||
contentRenderFn(parentPush)
|
||||
teleportContent = `<!--teleport anchor-->`
|
||||
teleportContent = `<!--teleport start anchor--><!--teleport anchor-->`
|
||||
} else {
|
||||
const { getBuffer, push } = createBuffer()
|
||||
push(`<!--teleport start anchor-->`)
|
||||
contentRenderFn(push)
|
||||
push(`<!--teleport anchor-->`)
|
||||
teleportContent = getBuffer()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"serve": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"@vitejs/plugin-vue": "^5.1.1",
|
||||
"vite": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ function toggleDark() {
|
|||
pkg="vue"
|
||||
label="Vue Version"
|
||||
>
|
||||
<li>
|
||||
<li :class="{ active: vueVersion === `@${currentCommit}` }">
|
||||
<a @click="resetVueVersion">This Commit ({{ currentCommit }})</a>
|
||||
</li>
|
||||
<li>
|
||||
|
|
|
|||
|
|
@ -74,7 +74,12 @@ onMounted(() => {
|
|||
|
||||
<ul class="versions" :class="{ expanded }">
|
||||
<li v-if="!versions"><a>loading versions...</a></li>
|
||||
<li v-for="ver of versions" :class="{ active: ver === version }">
|
||||
<li
|
||||
v-for="(ver, index) of versions"
|
||||
:class="{
|
||||
active: ver === version || (version === 'latest' && index === 0),
|
||||
}"
|
||||
>
|
||||
<a @click="setVersion(ver)">v{{ ver }}</a>
|
||||
</li>
|
||||
<div @click="expanded = false">
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"vue": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"vite": "^5.3.3"
|
||||
"@vitejs/plugin-vue": "^5.1.1",
|
||||
"vite": "^5.3.5"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ describe('e2e: Transition', () => {
|
|||
const baseUrl = `file://${path.resolve(__dirname, './transition.html')}`
|
||||
|
||||
const duration = process.env.CI ? 200 : 50
|
||||
const buffer = process.env.CI ? 20 : 5
|
||||
const buffer = 20
|
||||
|
||||
const transitionFinish = (time = duration) => timeout(time + buffer)
|
||||
|
||||
|
|
@ -29,8 +29,6 @@ describe('e2e: Transition', () => {
|
|||
test(
|
||||
'basic transition',
|
||||
async () => {
|
||||
await page().goto(baseUrl)
|
||||
await page().waitForSelector('#app')
|
||||
await page().evaluate(() => {
|
||||
const { createApp, ref } = (window as any).Vue
|
||||
createApp({
|
||||
|
|
@ -1296,8 +1294,6 @@ describe('e2e: Transition', () => {
|
|||
test(
|
||||
'wrapping transition + fallthrough attrs',
|
||||
async () => {
|
||||
await page().goto(baseUrl)
|
||||
await page().waitForSelector('#app')
|
||||
await page().evaluate(() => {
|
||||
const { createApp, ref } = (window as any).Vue
|
||||
createApp({
|
||||
|
|
|
|||
1169
pnpm-lock.yaml
1169
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue