mirror of https://github.com/vuejs/core.git
chore: Merge branch 'main' into minor
This commit is contained in:
commit
801b8dea3b
|
@ -24,4 +24,5 @@ jobs:
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ github.ref }}
|
tag_name: ${{ github.ref }}
|
||||||
body: |
|
body: |
|
||||||
Please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/main/CHANGELOG.md) for details.
|
For stable releases, please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/main/CHANGELOG.md) for details.
|
||||||
|
For pre-releases, please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/minor/CHANGELOG.md) of the `minor` branch.
|
||||||
|
|
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -1,3 +1,17 @@
|
||||||
|
## [3.4.26](https://github.com/vuejs/core/compare/v3.4.25...v3.4.26) (2024-04-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compiler-core:** fix bail constant for globals ([fefce06](https://github.com/vuejs/core/commit/fefce06b41e3b75de3d748dc6399628ec5056e78))
|
||||||
|
* **compiler-core:** remove unnecessary constant bail check ([09b4df8](https://github.com/vuejs/core/commit/09b4df809e59ef5f4bc91acfc56dc8f82a8e243a)), closes [#10807](https://github.com/vuejs/core/issues/10807)
|
||||||
|
* **runtime-core:** attrs should be readonly in functional components ([#10767](https://github.com/vuejs/core/issues/10767)) ([e8fd644](https://github.com/vuejs/core/commit/e8fd6446d14a6899e5e8ab1ee394d90088e01844))
|
||||||
|
* **runtime-core:** ensure slot compiler marker writable ([#10825](https://github.com/vuejs/core/issues/10825)) ([9c2de62](https://github.com/vuejs/core/commit/9c2de6244cd44bc5fbfd82b5850c710ce725044f)), closes [#10818](https://github.com/vuejs/core/issues/10818)
|
||||||
|
* **runtime-core:** properly handle inherit transition during clone VNode ([#10809](https://github.com/vuejs/core/issues/10809)) ([638a79f](https://github.com/vuejs/core/commit/638a79f64a7e184f2a2c65e21d764703f4bda561)), closes [#3716](https://github.com/vuejs/core/issues/3716) [#10497](https://github.com/vuejs/core/issues/10497) [#4091](https://github.com/vuejs/core/issues/4091)
|
||||||
|
* **Transition:** re-fix [#10620](https://github.com/vuejs/core/issues/10620) ([#10832](https://github.com/vuejs/core/issues/10832)) ([accf839](https://github.com/vuejs/core/commit/accf8396ae1c9dd49759ba0546483f1d2c70c9bc)), closes [#10632](https://github.com/vuejs/core/issues/10632) [#10827](https://github.com/vuejs/core/issues/10827)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [3.5.0-alpha.1](https://github.com/vuejs/core/compare/v3.4.25...v3.5.0-alpha.1) (2024-04-29)
|
# [3.5.0-alpha.1](https://github.com/vuejs/core/compare/v3.4.25...v3.5.0-alpha.1) (2024-04-29)
|
||||||
|
|
||||||
|
|
||||||
|
|
22
package.json
22
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "3.5.0-alpha.1",
|
"version": "3.5.0-alpha.1",
|
||||||
"packageManager": "pnpm@9.0.5",
|
"packageManager": "pnpm@9.0.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/dev.js",
|
"dev": "node scripts/dev.js",
|
||||||
|
@ -72,15 +72,15 @@
|
||||||
"@types/minimist": "^1.2.5",
|
"@types/minimist": "^1.2.5",
|
||||||
"@types/node": "^20.12.7",
|
"@types/node": "^20.12.7",
|
||||||
"@types/semver": "^7.5.8",
|
"@types/semver": "^7.5.8",
|
||||||
"@vitest/coverage-istanbul": "^1.5.0",
|
"@vitest/coverage-istanbul": "^1.5.2",
|
||||||
"@vue/consolidate": "1.0.0",
|
"@vue/consolidate": "1.0.0",
|
||||||
"conventional-changelog-cli": "^4.1.0",
|
"conventional-changelog-cli": "^4.1.0",
|
||||||
"enquirer": "^2.4.1",
|
"enquirer": "^2.4.1",
|
||||||
"esbuild": "^0.20.2",
|
"esbuild": "^0.20.2",
|
||||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.1.1",
|
||||||
"eslint-plugin-import-x": "^0.5.0",
|
"eslint-plugin-import-x": "^0.5.0",
|
||||||
"eslint-plugin-vitest": "^0.5.3",
|
"eslint-plugin-vitest": "^0.5.4",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"execa": "^8.0.1",
|
"execa": "^8.0.1",
|
||||||
"jsdom": "^24.0.0",
|
"jsdom": "^24.0.0",
|
||||||
|
@ -95,23 +95,23 @@
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"pug": "^3.0.2",
|
"pug": "^3.0.2",
|
||||||
"puppeteer": "~22.6.5",
|
"puppeteer": "~22.7.1",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"rollup": "^4.16.1",
|
"rollup": "^4.17.1",
|
||||||
"rollup-plugin-dts": "^6.1.0",
|
"rollup-plugin-dts": "^6.1.0",
|
||||||
"rollup-plugin-esbuild": "^6.1.1",
|
"rollup-plugin-esbuild": "^6.1.1",
|
||||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||||
"semver": "^7.6.0",
|
"semver": "^7.6.0",
|
||||||
"serve": "^14.2.1",
|
"serve": "^14.2.3",
|
||||||
"simple-git-hooks": "^2.11.1",
|
"simple-git-hooks": "^2.11.1",
|
||||||
"terser": "^5.30.3",
|
"terser": "^5.30.4",
|
||||||
"todomvc-app-css": "^2.4.3",
|
"todomvc-app-css": "^2.4.3",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"tsx": "^4.7.2",
|
"tsx": "^4.7.3",
|
||||||
"typescript": "~5.4.5",
|
"typescript": "~5.4.5",
|
||||||
"typescript-eslint": "^7.6.0",
|
"typescript-eslint": "^7.7.1",
|
||||||
"vite": "^5.2.10",
|
"vite": "^5.2.10",
|
||||||
"vitest": "^1.5.0"
|
"vitest": "^1.5.2"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"peerDependencyRules": {
|
"peerDependencyRules": {
|
||||||
|
|
|
@ -421,6 +421,31 @@ describe('compiler: expression transform', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #10807
|
||||||
|
test('should not bail constant on strings w/ ()', () => {
|
||||||
|
const node = parseWithExpressionTransform(
|
||||||
|
`{{ { foo: 'ok()' } }}`,
|
||||||
|
) as InterpolationNode
|
||||||
|
expect(node.content).toMatchObject({
|
||||||
|
constType: ConstantTypes.CAN_STRINGIFY,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should bail constant for global identifiers w/ new or call expressions', () => {
|
||||||
|
const node = parseWithExpressionTransform(
|
||||||
|
`{{ new Date().getFullYear() }}`,
|
||||||
|
) as InterpolationNode
|
||||||
|
expect(node.content).toMatchObject({
|
||||||
|
children: [
|
||||||
|
'new ',
|
||||||
|
{ constType: ConstantTypes.NOT_CONSTANT },
|
||||||
|
'().',
|
||||||
|
{ constType: ConstantTypes.NOT_CONSTANT },
|
||||||
|
'()',
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('ES Proposals support', () => {
|
describe('ES Proposals support', () => {
|
||||||
test('bigInt', () => {
|
test('bigInt', () => {
|
||||||
const node = parseWithExpressionTransform(
|
const node = parseWithExpressionTransform(
|
||||||
|
|
|
@ -10,6 +10,9 @@ import type {
|
||||||
} from '@babel/types'
|
} from '@babel/types'
|
||||||
import { walk } from 'estree-walker'
|
import { walk } from 'estree-walker'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return value indicates whether the AST walked can be a constant
|
||||||
|
*/
|
||||||
export function walkIdentifiers(
|
export function walkIdentifiers(
|
||||||
root: Node,
|
root: Node,
|
||||||
onIdentifier: (
|
onIdentifier: (
|
||||||
|
|
|
@ -179,7 +179,7 @@ const tokenizer = new Tokenizer(stack, {
|
||||||
const name = currentOpenTag!.tag
|
const name = currentOpenTag!.tag
|
||||||
currentOpenTag!.isSelfClosing = true
|
currentOpenTag!.isSelfClosing = true
|
||||||
endOpenTag(end)
|
endOpenTag(end)
|
||||||
if (stack[0]?.tag === name) {
|
if (stack[0] && stack[0].tag === name) {
|
||||||
onCloseTag(stack.shift()!, end)
|
onCloseTag(stack.shift()!, end)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -587,14 +587,14 @@ function endOpenTag(end: number) {
|
||||||
|
|
||||||
function onText(content: string, start: number, end: number) {
|
function onText(content: string, start: number, end: number) {
|
||||||
if (__BROWSER__) {
|
if (__BROWSER__) {
|
||||||
const tag = stack[0]?.tag
|
const tag = stack[0] && stack[0].tag
|
||||||
if (tag !== 'script' && tag !== 'style' && content.includes('&')) {
|
if (tag !== 'script' && tag !== 'style' && content.includes('&')) {
|
||||||
content = currentOptions.decodeEntities!(content, false)
|
content = currentOptions.decodeEntities!(content, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const parent = stack[0] || currentRoot
|
const parent = stack[0] || currentRoot
|
||||||
const lastNode = parent.children[parent.children.length - 1]
|
const lastNode = parent.children[parent.children.length - 1]
|
||||||
if (lastNode?.type === NodeTypes.TEXT) {
|
if (lastNode && lastNode.type === NodeTypes.TEXT) {
|
||||||
// merge
|
// merge
|
||||||
lastNode.content += content
|
lastNode.content += content
|
||||||
setLocEnd(lastNode.loc, end)
|
setLocEnd(lastNode.loc, end)
|
||||||
|
@ -771,7 +771,8 @@ function isComponent({ tag, props }: ElementNode): boolean {
|
||||||
tag === 'component' ||
|
tag === 'component' ||
|
||||||
isUpperCase(tag.charCodeAt(0)) ||
|
isUpperCase(tag.charCodeAt(0)) ||
|
||||||
isCoreComponent(tag) ||
|
isCoreComponent(tag) ||
|
||||||
currentOptions.isBuiltInComponent?.(tag) ||
|
(currentOptions.isBuiltInComponent &&
|
||||||
|
currentOptions.isBuiltInComponent(tag)) ||
|
||||||
(currentOptions.isNativeTag && !currentOptions.isNativeTag(tag))
|
(currentOptions.isNativeTag && !currentOptions.isNativeTag(tag))
|
||||||
) {
|
) {
|
||||||
return true
|
return true
|
||||||
|
@ -828,8 +829,8 @@ function condenseWhitespace(
|
||||||
if (node.type === NodeTypes.TEXT) {
|
if (node.type === NodeTypes.TEXT) {
|
||||||
if (!inPre) {
|
if (!inPre) {
|
||||||
if (isAllWhitespace(node.content)) {
|
if (isAllWhitespace(node.content)) {
|
||||||
const prev = nodes[i - 1]?.type
|
const prev = nodes[i - 1] && nodes[i - 1].type
|
||||||
const next = nodes[i + 1]?.type
|
const next = nodes[i + 1] && nodes[i + 1].type
|
||||||
// Remove if:
|
// Remove if:
|
||||||
// - the whitespace is the first or last node, or:
|
// - the whitespace is the first or last node, or:
|
||||||
// - (condense mode) the whitespace is between two comments, or:
|
// - (condense mode) the whitespace is between two comments, or:
|
||||||
|
@ -1063,7 +1064,7 @@ export function baseParse(input: string, options?: ParserOptions): RootNode {
|
||||||
currentOptions.ns === Namespaces.SVG ||
|
currentOptions.ns === Namespaces.SVG ||
|
||||||
currentOptions.ns === Namespaces.MATH_ML
|
currentOptions.ns === Namespaces.MATH_ML
|
||||||
|
|
||||||
const delimiters = options?.delimiters
|
const delimiters = options && options.delimiters
|
||||||
if (delimiters) {
|
if (delimiters) {
|
||||||
tokenizer.delimiterOpen = toCharCodes(delimiters[0])
|
tokenizer.delimiterOpen = toCharCodes(delimiters[0])
|
||||||
tokenizer.delimiterClose = toCharCodes(delimiters[1])
|
tokenizer.delimiterClose = toCharCodes(delimiters[1])
|
||||||
|
|
|
@ -46,10 +46,6 @@ import { BindingTypes } from '../options'
|
||||||
|
|
||||||
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
|
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
|
||||||
|
|
||||||
// a heuristic safeguard to bail constant expressions on presence of
|
|
||||||
// likely function invocation and member access
|
|
||||||
const constantBailRE = /\w\s*\(|\.[^\d]/
|
|
||||||
|
|
||||||
export const transformExpression: NodeTransform = (node, context) => {
|
export const transformExpression: NodeTransform = (node, context) => {
|
||||||
if (node.type === NodeTypes.INTERPOLATION) {
|
if (node.type === NodeTypes.INTERPOLATION) {
|
||||||
node.content = processExpression(
|
node.content = processExpression(
|
||||||
|
@ -226,8 +222,6 @@ export function processExpression(
|
||||||
|
|
||||||
// fast path if expression is a simple identifier.
|
// fast path if expression is a simple identifier.
|
||||||
const rawExp = node.content
|
const rawExp = node.content
|
||||||
// bail constant on parens (function invocation) and dot (member access)
|
|
||||||
const bailConstant = constantBailRE.test(rawExp)
|
|
||||||
|
|
||||||
let ast = node.ast
|
let ast = node.ast
|
||||||
|
|
||||||
|
@ -317,7 +311,12 @@ export function processExpression(
|
||||||
} else {
|
} else {
|
||||||
// The identifier is considered constant unless it's pointing to a
|
// The identifier is considered constant unless it's pointing to a
|
||||||
// local scope variable (a v-for alias, or a v-slot prop)
|
// local scope variable (a v-for alias, or a v-slot prop)
|
||||||
if (!(needPrefix && isLocal) && !bailConstant) {
|
if (
|
||||||
|
!(needPrefix && isLocal) &&
|
||||||
|
parent.type !== 'CallExpression' &&
|
||||||
|
parent.type !== 'NewExpression' &&
|
||||||
|
parent.type !== 'MemberExpression'
|
||||||
|
) {
|
||||||
;(node as QualifiedId).isConstant = true
|
;(node as QualifiedId).isConstant = true
|
||||||
}
|
}
|
||||||
// also generate sub-expressions for other identifiers for better
|
// also generate sub-expressions for other identifiers for better
|
||||||
|
@ -371,9 +370,7 @@ export function processExpression(
|
||||||
ret.ast = ast
|
ret.ast = ast
|
||||||
} else {
|
} else {
|
||||||
ret = node
|
ret = node
|
||||||
ret.constType = bailConstant
|
ret.constType = ConstantTypes.CAN_STRINGIFY
|
||||||
? ConstantTypes.NOT_CONSTANT
|
|
||||||
: ConstantTypes.CAN_STRINGIFY
|
|
||||||
}
|
}
|
||||||
ret.identifiers = Object.keys(knownIds)
|
ret.identifiers = Object.keys(knownIds)
|
||||||
return ret
|
return ret
|
||||||
|
|
|
@ -17,7 +17,6 @@ import {
|
||||||
ref,
|
ref,
|
||||||
render,
|
render,
|
||||||
serializeInner,
|
serializeInner,
|
||||||
toRaw,
|
|
||||||
toRefs,
|
toRefs,
|
||||||
watch,
|
watch,
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
|
@ -129,12 +128,12 @@ describe('component props', () => {
|
||||||
render(h(Comp, { foo: 1 }), root)
|
render(h(Comp, { foo: 1 }), root)
|
||||||
expect(props).toEqual({ foo: 1 })
|
expect(props).toEqual({ foo: 1 })
|
||||||
expect(attrs).toEqual({ foo: 1 })
|
expect(attrs).toEqual({ foo: 1 })
|
||||||
expect(toRaw(props)).toBe(attrs)
|
expect(props).toBe(attrs)
|
||||||
|
|
||||||
render(h(Comp, { bar: 2 }), root)
|
render(h(Comp, { bar: 2 }), root)
|
||||||
expect(props).toEqual({ bar: 2 })
|
expect(props).toEqual({ bar: 2 })
|
||||||
expect(attrs).toEqual({ bar: 2 })
|
expect(attrs).toEqual({ bar: 2 })
|
||||||
expect(toRaw(props)).toBe(attrs)
|
expect(props).toBe(attrs)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('boolean casting', () => {
|
test('boolean casting', () => {
|
||||||
|
|
|
@ -7,7 +7,6 @@ import {
|
||||||
h,
|
h,
|
||||||
nextTick,
|
nextTick,
|
||||||
nodeOps,
|
nodeOps,
|
||||||
onUnmounted,
|
|
||||||
ref,
|
ref,
|
||||||
render,
|
render,
|
||||||
serialize,
|
serialize,
|
||||||
|
@ -769,42 +768,6 @@ describe('BaseTransition', () => {
|
||||||
test('w/ KeepAlive', async () => {
|
test('w/ KeepAlive', async () => {
|
||||||
await runTestWithKeepAlive(testOutIn)
|
await runTestWithKeepAlive(testOutIn)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('w/ KeepAlive + unmount innerChild', async () => {
|
|
||||||
const unmountSpy = vi.fn()
|
|
||||||
const includeRef = ref(['TrueBranch'])
|
|
||||||
const trueComp = {
|
|
||||||
name: 'TrueBranch',
|
|
||||||
setup() {
|
|
||||||
onUnmounted(unmountSpy)
|
|
||||||
const count = ref(0)
|
|
||||||
return () => h('div', count.value)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggle = ref(true)
|
|
||||||
const { props } = mockProps({ mode: 'out-in' }, true /*withKeepAlive*/)
|
|
||||||
const root = nodeOps.createElement('div')
|
|
||||||
const App = {
|
|
||||||
render() {
|
|
||||||
return h(BaseTransition, props, () => {
|
|
||||||
return h(
|
|
||||||
KeepAlive,
|
|
||||||
{ include: includeRef.value },
|
|
||||||
toggle.value ? h(trueComp) : h('div'),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
render(h(App), root)
|
|
||||||
|
|
||||||
// trigger toggle
|
|
||||||
toggle.value = false
|
|
||||||
includeRef.value = []
|
|
||||||
|
|
||||||
await nextTick()
|
|
||||||
expect(unmountSpy).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// #6835
|
// #6835
|
||||||
|
|
|
@ -356,7 +356,7 @@ describe('hot module replacement', () => {
|
||||||
triggerEvent(root.children[1] as TestElement, 'click')
|
triggerEvent(root.children[1] as TestElement, 'click')
|
||||||
await nextTick()
|
await nextTick()
|
||||||
await new Promise(r => setTimeout(r, 0))
|
await new Promise(r => setTimeout(r, 0))
|
||||||
expect(serializeInner(root)).toBe(`<button></button><!---->`)
|
expect(serializeInner(root)).toBe(`<button></button><!--v-if-->`)
|
||||||
expect(unmountSpy).toHaveBeenCalledTimes(1)
|
expect(unmountSpy).toHaveBeenCalledTimes(1)
|
||||||
expect(mountSpy).toHaveBeenCalledTimes(1)
|
expect(mountSpy).toHaveBeenCalledTimes(1)
|
||||||
expect(activeSpy).toHaveBeenCalledTimes(1)
|
expect(activeSpy).toHaveBeenCalledTimes(1)
|
||||||
|
|
|
@ -472,39 +472,32 @@ export function createPathGetter(ctx: any, path: string) {
|
||||||
|
|
||||||
export function traverse(
|
export function traverse(
|
||||||
value: unknown,
|
value: unknown,
|
||||||
depth?: number,
|
depth = Infinity,
|
||||||
currentDepth = 0,
|
|
||||||
seen?: Set<unknown>,
|
seen?: Set<unknown>,
|
||||||
) {
|
) {
|
||||||
if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
|
if (depth <= 0 || !isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
if (depth && depth > 0) {
|
|
||||||
if (currentDepth >= depth) {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
currentDepth++
|
|
||||||
}
|
|
||||||
|
|
||||||
seen = seen || new Set()
|
seen = seen || new Set()
|
||||||
if (seen.has(value)) {
|
if (seen.has(value)) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
seen.add(value)
|
seen.add(value)
|
||||||
|
depth--
|
||||||
if (isRef(value)) {
|
if (isRef(value)) {
|
||||||
traverse(value.value, depth, currentDepth, seen)
|
traverse(value.value, depth, seen)
|
||||||
} else if (isArray(value)) {
|
} else if (isArray(value)) {
|
||||||
for (let i = 0; i < value.length; i++) {
|
for (let i = 0; i < value.length; i++) {
|
||||||
traverse(value[i], depth, currentDepth, seen)
|
traverse(value[i], depth, seen)
|
||||||
}
|
}
|
||||||
} else if (isSet(value) || isMap(value)) {
|
} else if (isSet(value) || isMap(value)) {
|
||||||
value.forEach((v: any) => {
|
value.forEach((v: any) => {
|
||||||
traverse(v, depth, currentDepth, seen)
|
traverse(v, depth, seen)
|
||||||
})
|
})
|
||||||
} else if (isPlainObject(value)) {
|
} else if (isPlainObject(value)) {
|
||||||
for (const key in value) {
|
for (const key in value) {
|
||||||
traverse(value[key], depth, currentDepth, seen)
|
traverse(value[key], depth, seen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
|
|
|
@ -77,7 +77,12 @@ export type CompatVue = Pick<App, 'version' | 'component' | 'directive'> & {
|
||||||
|
|
||||||
nextTick: typeof nextTick
|
nextTick: typeof nextTick
|
||||||
|
|
||||||
use(plugin: Plugin, ...options: any[]): CompatVue
|
use<Options extends unknown[]>(
|
||||||
|
plugin: Plugin<Options>,
|
||||||
|
...options: Options
|
||||||
|
): CompatVue
|
||||||
|
use<Options>(plugin: Plugin<Options>, options: Options): CompatVue
|
||||||
|
|
||||||
mixin(mixin: ComponentOptions): CompatVue
|
mixin(mixin: ComponentOptions): CompatVue
|
||||||
|
|
||||||
component(name: string): Component | undefined
|
component(name: string): Component | undefined
|
||||||
|
@ -176,11 +181,11 @@ export function createCompatVue(
|
||||||
Vue.version = `2.6.14-compat:${__VERSION__}`
|
Vue.version = `2.6.14-compat:${__VERSION__}`
|
||||||
Vue.config = singletonApp.config
|
Vue.config = singletonApp.config
|
||||||
|
|
||||||
Vue.use = (p, ...options) => {
|
Vue.use = (plugin: Plugin, ...options: any[]) => {
|
||||||
if (p && isFunction(p.install)) {
|
if (plugin && isFunction(plugin.install)) {
|
||||||
p.install(Vue as any, ...options)
|
plugin.install(Vue as any, ...options)
|
||||||
} else if (isFunction(p)) {
|
} else if (isFunction(plugin)) {
|
||||||
p(Vue as any, ...options)
|
plugin(Vue as any, ...options)
|
||||||
}
|
}
|
||||||
return Vue
|
return Vue
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ export function renderComponentRoot(
|
||||||
? {
|
? {
|
||||||
get attrs() {
|
get attrs() {
|
||||||
markAttrsAccessed()
|
markAttrsAccessed()
|
||||||
return attrs
|
return shallowReadonly(attrs)
|
||||||
},
|
},
|
||||||
slots,
|
slots,
|
||||||
emit,
|
emit,
|
||||||
|
@ -166,7 +166,7 @@ export function renderComponentRoot(
|
||||||
propsOptions,
|
propsOptions,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
root = cloneVNode(root, fallthroughAttrs)
|
root = cloneVNode(root, fallthroughAttrs, false, true)
|
||||||
} else if (__DEV__ && !accessedAttrs && root.type !== Comment) {
|
} else if (__DEV__ && !accessedAttrs && root.type !== Comment) {
|
||||||
const allAttrs = Object.keys(attrs)
|
const allAttrs = Object.keys(attrs)
|
||||||
const eventAttrs: string[] = []
|
const eventAttrs: string[] = []
|
||||||
|
@ -221,10 +221,15 @@ export function renderComponentRoot(
|
||||||
getComponentName(instance.type),
|
getComponentName(instance.type),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
root = cloneVNode(root, {
|
root = cloneVNode(
|
||||||
class: cls,
|
root,
|
||||||
style: style,
|
{
|
||||||
})
|
class: cls,
|
||||||
|
style: style,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,7 +242,7 @@ export function renderComponentRoot(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// clone before mutating since the root may be a hoisted vnode
|
// clone before mutating since the root may be a hoisted vnode
|
||||||
root = cloneVNode(root)
|
root = cloneVNode(root, null, false, true)
|
||||||
root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs
|
root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs
|
||||||
}
|
}
|
||||||
// inherit transition data
|
// inherit transition data
|
||||||
|
|
|
@ -171,7 +171,7 @@ export const initSlots = (
|
||||||
if (type) {
|
if (type) {
|
||||||
extend(slots, children as InternalSlots)
|
extend(slots, children as InternalSlots)
|
||||||
// make compiler marker non-enumerable
|
// make compiler marker non-enumerable
|
||||||
def(slots, '_', type)
|
def(slots, '_', type, true)
|
||||||
} else {
|
} else {
|
||||||
normalizeObjectSlots(children as RawSlots, slots, instance)
|
normalizeObjectSlots(children as RawSlots, slots, instance)
|
||||||
}
|
}
|
||||||
|
|
|
@ -206,7 +206,7 @@ const BaseTransitionImpl: ComponentOptions = {
|
||||||
// update old tree's hooks in case of dynamic transition
|
// update old tree's hooks in case of dynamic transition
|
||||||
setTransitionHooks(oldInnerChild, leavingHooks)
|
setTransitionHooks(oldInnerChild, leavingHooks)
|
||||||
// switching between different views
|
// switching between different views
|
||||||
if (mode === 'out-in') {
|
if (mode === 'out-in' && innerChild.type !== Comment) {
|
||||||
state.isLeaving = true
|
state.isLeaving = true
|
||||||
// return placeholder node and queue update when leave finishes
|
// return placeholder node and queue update when leave finishes
|
||||||
leavingHooks.afterLeave = () => {
|
leavingHooks.afterLeave = () => {
|
||||||
|
|
|
@ -254,7 +254,7 @@ const KeepAliveImpl: ComponentOptions = {
|
||||||
pendingCacheKey = null
|
pendingCacheKey = null
|
||||||
|
|
||||||
if (!slots.default) {
|
if (!slots.default) {
|
||||||
return (current = null)
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const children = slots.default()
|
const children = slots.default()
|
||||||
|
|
|
@ -479,7 +479,7 @@ function createSuspenseBoundary(
|
||||||
let parentSuspenseId: number | undefined
|
let parentSuspenseId: number | undefined
|
||||||
const isSuspensible = isVNodeSuspensible(vnode)
|
const isSuspensible = isVNodeSuspensible(vnode)
|
||||||
if (isSuspensible) {
|
if (isSuspensible) {
|
||||||
if (parentSuspense?.pendingBranch) {
|
if (parentSuspense && parentSuspense.pendingBranch) {
|
||||||
parentSuspenseId = parentSuspense.pendingId
|
parentSuspenseId = parentSuspense.pendingId
|
||||||
parentSuspense.deps++
|
parentSuspense.deps++
|
||||||
}
|
}
|
||||||
|
@ -898,5 +898,6 @@ function setActiveBranch(suspense: SuspenseBoundary, branch: VNode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function isVNodeSuspensible(vnode: VNode) {
|
function isVNodeSuspensible(vnode: VNode) {
|
||||||
return vnode.props?.suspensible != null && vnode.props.suspensible !== false
|
const suspensible = vnode.props && vnode.props.suspensible
|
||||||
|
return suspensible != null && suspensible !== false
|
||||||
}
|
}
|
||||||
|
|
|
@ -624,10 +624,11 @@ export function cloneVNode<T, U>(
|
||||||
vnode: VNode<T, U>,
|
vnode: VNode<T, U>,
|
||||||
extraProps?: (Data & VNodeProps) | null,
|
extraProps?: (Data & VNodeProps) | null,
|
||||||
mergeRef = false,
|
mergeRef = false,
|
||||||
|
cloneTransition = false,
|
||||||
): VNode<T, U> {
|
): VNode<T, U> {
|
||||||
// This is intentionally NOT using spread or extend to avoid the runtime
|
// This is intentionally NOT using spread or extend to avoid the runtime
|
||||||
// key enumeration cost.
|
// key enumeration cost.
|
||||||
const { props, ref, patchFlag, children } = vnode
|
const { props, ref, patchFlag, children, transition } = vnode
|
||||||
const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
|
const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
|
||||||
const cloned: VNode<T, U> = {
|
const cloned: VNode<T, U> = {
|
||||||
__v_isVNode: true,
|
__v_isVNode: true,
|
||||||
|
@ -670,7 +671,7 @@ export function cloneVNode<T, U>(
|
||||||
dynamicChildren: vnode.dynamicChildren,
|
dynamicChildren: vnode.dynamicChildren,
|
||||||
appContext: vnode.appContext,
|
appContext: vnode.appContext,
|
||||||
dirs: vnode.dirs,
|
dirs: vnode.dirs,
|
||||||
transition: vnode.transition,
|
transition,
|
||||||
|
|
||||||
// These should technically only be non-null on mounted VNodes. However,
|
// These should technically only be non-null on mounted VNodes. However,
|
||||||
// they *should* be copied for kept-alive vnodes. So we just always copy
|
// they *should* be copied for kept-alive vnodes. So we just always copy
|
||||||
|
@ -685,9 +686,18 @@ export function cloneVNode<T, U>(
|
||||||
ctx: vnode.ctx,
|
ctx: vnode.ctx,
|
||||||
ce: vnode.ce,
|
ce: vnode.ce,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the vnode will be replaced by the cloned one, it is necessary
|
||||||
|
// to clone the transition to ensure that the vnode referenced within
|
||||||
|
// the transition hooks is fresh.
|
||||||
|
if (transition && cloneTransition) {
|
||||||
|
cloned.transition = transition.clone(cloned as VNode)
|
||||||
|
}
|
||||||
|
|
||||||
if (__COMPAT__) {
|
if (__COMPAT__) {
|
||||||
defineLegacyVNodeProperties(cloned as VNode)
|
defineLegacyVNodeProperties(cloned as VNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cloned
|
return cloned
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
"vite": "^5.2.10"
|
"vite": "^5.2.10"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/repl": "^4.1.1",
|
"@vue/repl": "^4.1.2",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"vue": "workspace:*"
|
"vue": "workspace:*"
|
||||||
|
|
|
@ -139,10 +139,16 @@ export const invokeArrayFns = (fns: Function[], arg?: any) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const def = (obj: object, key: string | symbol, value: any) => {
|
export const def = (
|
||||||
|
obj: object,
|
||||||
|
key: string | symbol,
|
||||||
|
value: any,
|
||||||
|
writable = false,
|
||||||
|
) => {
|
||||||
Object.defineProperty(obj, key, {
|
Object.defineProperty(obj, key, {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
|
writable,
|
||||||
value,
|
value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1214,6 +1214,111 @@ describe('e2e: Transition', () => {
|
||||||
},
|
},
|
||||||
E2E_TIMEOUT,
|
E2E_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// #3716
|
||||||
|
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({
|
||||||
|
components: {
|
||||||
|
'my-transition': {
|
||||||
|
template: `
|
||||||
|
<transition foo="1" name="test">
|
||||||
|
<slot></slot>
|
||||||
|
</transition>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div id="container">
|
||||||
|
<my-transition>
|
||||||
|
<div v-if="toggle">content</div>
|
||||||
|
</my-transition>
|
||||||
|
</div>
|
||||||
|
<button id="toggleBtn" @click="click">button</button>
|
||||||
|
`,
|
||||||
|
setup: () => {
|
||||||
|
const toggle = ref(true)
|
||||||
|
const click = () => (toggle.value = !toggle.value)
|
||||||
|
return { toggle, click }
|
||||||
|
},
|
||||||
|
}).mount('#app')
|
||||||
|
})
|
||||||
|
expect(await html('#container')).toBe('<div foo="1">content</div>')
|
||||||
|
|
||||||
|
await click('#toggleBtn')
|
||||||
|
// toggle again before leave finishes
|
||||||
|
await nextTick()
|
||||||
|
await click('#toggleBtn')
|
||||||
|
|
||||||
|
await transitionFinish()
|
||||||
|
expect(await html('#container')).toBe(
|
||||||
|
'<div foo="1" class="">content</div>',
|
||||||
|
)
|
||||||
|
},
|
||||||
|
E2E_TIMEOUT,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'w/ KeepAlive + unmount innerChild',
|
||||||
|
async () => {
|
||||||
|
const unmountSpy = vi.fn()
|
||||||
|
await page().exposeFunction('unmountSpy', unmountSpy)
|
||||||
|
await page().evaluate(() => {
|
||||||
|
const { unmountSpy } = window as any
|
||||||
|
const { createApp, ref, h, onUnmounted } = (window as any).Vue
|
||||||
|
createApp({
|
||||||
|
template: `
|
||||||
|
<div id="container">
|
||||||
|
<transition mode="out-in">
|
||||||
|
<KeepAlive :include="includeRef">
|
||||||
|
<TrueBranch v-if="toggle"></TrueBranch>
|
||||||
|
</KeepAlive>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
<button id="toggleBtn" @click="click">button</button>
|
||||||
|
`,
|
||||||
|
components: {
|
||||||
|
TrueBranch: {
|
||||||
|
name: 'TrueBranch',
|
||||||
|
setup() {
|
||||||
|
onUnmounted(unmountSpy)
|
||||||
|
const count = ref(0)
|
||||||
|
return () => h('div', count.value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup: () => {
|
||||||
|
const includeRef = ref(['TrueBranch'])
|
||||||
|
const toggle = ref(true)
|
||||||
|
const click = () => {
|
||||||
|
toggle.value = !toggle.value
|
||||||
|
if (toggle.value) {
|
||||||
|
includeRef.value = ['TrueBranch']
|
||||||
|
} else {
|
||||||
|
includeRef.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { toggle, click, unmountSpy, includeRef }
|
||||||
|
},
|
||||||
|
}).mount('#app')
|
||||||
|
})
|
||||||
|
|
||||||
|
await transitionFinish()
|
||||||
|
expect(await html('#container')).toBe('<div>0</div>')
|
||||||
|
|
||||||
|
await click('#toggleBtn')
|
||||||
|
|
||||||
|
await transitionFinish()
|
||||||
|
expect(await html('#container')).toBe('<!--v-if-->')
|
||||||
|
expect(unmountSpy).toBeCalledTimes(1)
|
||||||
|
},
|
||||||
|
E2E_TIMEOUT,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('transition with Suspense', () => {
|
describe('transition with Suspense', () => {
|
||||||
|
|
653
pnpm-lock.yaml
653
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue