mirror of https://github.com/vuejs/core.git
Merge remote-tracking branch 'upstream/minor'
This commit is contained in:
commit
7cd70505d5
30
CHANGELOG.md
30
CHANGELOG.md
|
|
@ -1,3 +1,33 @@
|
|||
# [3.5.0-alpha.2](https://github.com/vuejs/core/compare/v3.4.26...v3.5.0-alpha.2) (2024-05-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **types:** fix app.component() typing with inline defineComponent ([908f70a](https://github.com/vuejs/core/commit/908f70adc06038d1ea253d96f4024367f4a7545d)), closes [#10843](https://github.com/vuejs/core/issues/10843)
|
||||
* **types:** fix compat with generated types that rely on CreateComponentPublicInstance ([c146186](https://github.com/vuejs/core/commit/c146186396d0c1a65423b8c9a21251c5a6467336)), closes [#10842](https://github.com/vuejs/core/issues/10842)
|
||||
* **types:** props in defineOptions type should be optional ([124c4ca](https://github.com/vuejs/core/commit/124c4cac833a28ae9bc8edc576c1d0c7c41f5985)), closes [#10841](https://github.com/vuejs/core/issues/10841)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **runtime-core:** add app.onUnmount() for registering cleanup functions ([#4619](https://github.com/vuejs/core/issues/4619)) ([582a3a3](https://github.com/vuejs/core/commit/582a3a382b1adda565bac576b913a88d9e8d7a9e)), closes [#4516](https://github.com/vuejs/core/issues/4516)
|
||||
|
||||
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
|
|
|||
24
package.json
24
package.json
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.0.0-vapor",
|
||||
"packageManager": "pnpm@9.0.5",
|
||||
"packageManager": "pnpm@9.0.6",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js vue vue-vapor",
|
||||
|
|
@ -72,16 +72,16 @@
|
|||
"@types/minimist": "^1.2.5",
|
||||
"@types/node": "^20.12.7",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@vitest/coverage-istanbul": "^1.5.0",
|
||||
"@vitest/ui": "^1.5.0",
|
||||
"@vitest/coverage-istanbul": "^1.5.2",
|
||||
"@vitest/ui": "^1.5.2",
|
||||
"@vue/consolidate": "1.0.0",
|
||||
"conventional-changelog-cli": "^4.1.0",
|
||||
"enquirer": "^2.4.1",
|
||||
"esbuild": "^0.20.2",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint": "^9.1.1",
|
||||
"eslint-plugin-import-x": "^0.5.0",
|
||||
"eslint-plugin-vitest": "^0.5.3",
|
||||
"eslint-plugin-vitest": "^0.5.4",
|
||||
"estree-walker": "^2.0.2",
|
||||
"execa": "^8.0.1",
|
||||
"jsdom": "^24.0.0",
|
||||
|
|
@ -96,23 +96,23 @@
|
|||
"prettier": "^3.2.5",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.2",
|
||||
"puppeteer": "~22.6.5",
|
||||
"puppeteer": "~22.7.1",
|
||||
"rimraf": "^5.0.5",
|
||||
"rollup": "^4.16.1",
|
||||
"rollup": "^4.17.1",
|
||||
"rollup-plugin-dts": "^6.1.0",
|
||||
"rollup-plugin-esbuild": "^6.1.1",
|
||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||
"semver": "^7.6.0",
|
||||
"serve": "^14.2.1",
|
||||
"serve": "^14.2.3",
|
||||
"simple-git-hooks": "^2.11.1",
|
||||
"terser": "^5.30.3",
|
||||
"terser": "^5.30.4",
|
||||
"todomvc-app-css": "^2.4.3",
|
||||
"tslib": "^2.6.2",
|
||||
"tsx": "^4.7.2",
|
||||
"tsx": "^4.7.3",
|
||||
"typescript": "~5.4.5",
|
||||
"typescript-eslint": "^7.6.0",
|
||||
"typescript-eslint": "^7.7.1",
|
||||
"vite": "^5.2.10",
|
||||
"vitest": "^1.5.0"
|
||||
"vitest": "^1.5.2"
|
||||
},
|
||||
"pnpm": {
|
||||
"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', () => {
|
||||
test('bigInt', () => {
|
||||
const node = parseWithExpressionTransform(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-core",
|
||||
"version": "3.5.0-alpha.1",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"description": "@vue/compiler-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-core.esm-bundler.js",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import type {
|
|||
} from '@babel/types'
|
||||
import { walk } from 'estree-walker'
|
||||
|
||||
/**
|
||||
* Return value indicates whether the AST walked can be a constant
|
||||
*/
|
||||
export function walkIdentifiers(
|
||||
root: Node,
|
||||
onIdentifier: (
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ const tokenizer = new Tokenizer(stack, {
|
|||
const name = currentOpenTag!.tag
|
||||
currentOpenTag!.isSelfClosing = true
|
||||
endOpenTag(end)
|
||||
if (stack[0]?.tag === name) {
|
||||
if (stack[0] && stack[0].tag === name) {
|
||||
onCloseTag(stack.shift()!, end)
|
||||
}
|
||||
},
|
||||
|
|
@ -587,14 +587,14 @@ function endOpenTag(end: number) {
|
|||
|
||||
function onText(content: string, start: number, end: number) {
|
||||
if (__BROWSER__) {
|
||||
const tag = stack[0]?.tag
|
||||
const tag = stack[0] && stack[0].tag
|
||||
if (tag !== 'script' && tag !== 'style' && content.includes('&')) {
|
||||
content = currentOptions.decodeEntities!(content, false)
|
||||
}
|
||||
}
|
||||
const parent = stack[0] || currentRoot
|
||||
const lastNode = parent.children[parent.children.length - 1]
|
||||
if (lastNode?.type === NodeTypes.TEXT) {
|
||||
if (lastNode && lastNode.type === NodeTypes.TEXT) {
|
||||
// merge
|
||||
lastNode.content += content
|
||||
setLocEnd(lastNode.loc, end)
|
||||
|
|
@ -771,7 +771,8 @@ function isComponent({ tag, props }: ElementNode): boolean {
|
|||
tag === 'component' ||
|
||||
isUpperCase(tag.charCodeAt(0)) ||
|
||||
isCoreComponent(tag) ||
|
||||
currentOptions.isBuiltInComponent?.(tag) ||
|
||||
(currentOptions.isBuiltInComponent &&
|
||||
currentOptions.isBuiltInComponent(tag)) ||
|
||||
(currentOptions.isNativeTag && !currentOptions.isNativeTag(tag))
|
||||
) {
|
||||
return true
|
||||
|
|
@ -828,8 +829,8 @@ function condenseWhitespace(
|
|||
if (node.type === NodeTypes.TEXT) {
|
||||
if (!inPre) {
|
||||
if (isAllWhitespace(node.content)) {
|
||||
const prev = nodes[i - 1]?.type
|
||||
const next = nodes[i + 1]?.type
|
||||
const prev = nodes[i - 1] && nodes[i - 1].type
|
||||
const next = nodes[i + 1] && nodes[i + 1].type
|
||||
// Remove if:
|
||||
// - the whitespace is the first or last node, 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.MATH_ML
|
||||
|
||||
const delimiters = options?.delimiters
|
||||
const delimiters = options && options.delimiters
|
||||
if (delimiters) {
|
||||
tokenizer.delimiterOpen = toCharCodes(delimiters[0])
|
||||
tokenizer.delimiterClose = toCharCodes(delimiters[1])
|
||||
|
|
|
|||
|
|
@ -48,10 +48,6 @@ export 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) => {
|
||||
if (node.type === NodeTypes.INTERPOLATION) {
|
||||
node.content = processExpression(
|
||||
|
|
@ -228,8 +224,6 @@ export function processExpression(
|
|||
|
||||
// fast path if expression is a simple identifier.
|
||||
const rawExp = node.content
|
||||
// bail constant on parens (function invocation) and dot (member access)
|
||||
const bailConstant = constantBailRE.test(rawExp)
|
||||
|
||||
let ast = node.ast
|
||||
|
||||
|
|
@ -319,7 +313,12 @@ export function processExpression(
|
|||
} else {
|
||||
// The identifier is considered constant unless it's pointing to a
|
||||
// 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
|
||||
}
|
||||
// also generate sub-expressions for other identifiers for better
|
||||
|
|
@ -373,9 +372,7 @@ export function processExpression(
|
|||
ret.ast = ast
|
||||
} else {
|
||||
ret = node
|
||||
ret.constType = bailConstant
|
||||
? ConstantTypes.NOT_CONSTANT
|
||||
: ConstantTypes.CAN_STRINGIFY
|
||||
ret.constType = ConstantTypes.CAN_STRINGIFY
|
||||
}
|
||||
ret.identifiers = Object.keys(knownIds)
|
||||
return ret
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-dom",
|
||||
"version": "3.5.0-alpha.1",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"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.1",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"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.1",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"description": "@vue/compiler-ssr",
|
||||
"main": "dist/compiler-ssr.cjs.js",
|
||||
"types": "dist/compiler-ssr.d.ts",
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ Tests TypeScript types to ensure the types remain as expected.
|
|||
|
||||
- This directory is included in the root `tsconfig.json`, where package imports are aliased to `src` directories, so in IDEs and the `pnpm check` script the types are validated against source code.
|
||||
|
||||
- When running `tsc` with `packages/dts-test/tsconfig.test.json`, packages are resolved using normal `node` resolution, so the types are validated against actual **built** types. This requires the types to be built first via `pnpm build-types`.
|
||||
- When running `tsc` with `packages/dts-test/tsconfig.test.json`, packages are resolved using normal `node` resolution, so the types are validated against actual **built** types. This requires the types to be built first via `pnpm build-dts`.
|
||||
|
|
|
|||
|
|
@ -1766,3 +1766,210 @@ defineComponent({
|
|||
expectType<string | null | undefined>(props.foo)
|
||||
},
|
||||
})
|
||||
|
||||
import type * as vue from 'vue'
|
||||
|
||||
interface ErrorMessageSlotProps {
|
||||
message: string | undefined
|
||||
}
|
||||
/**
|
||||
* #10842
|
||||
* component types generated by vue-tsc
|
||||
* relying on legacy CreateComponentPublicInstance signature
|
||||
*/
|
||||
declare const ErrorMessage: {
|
||||
new (...args: any[]): vue.CreateComponentPublicInstance<
|
||||
Readonly<
|
||||
vue.ExtractPropTypes<{
|
||||
as: {
|
||||
type: StringConstructor
|
||||
default: any
|
||||
}
|
||||
name: {
|
||||
type: StringConstructor
|
||||
required: true
|
||||
}
|
||||
}>
|
||||
>,
|
||||
() =>
|
||||
| VNode<
|
||||
vue.RendererNode,
|
||||
vue.RendererElement,
|
||||
{
|
||||
[key: string]: any
|
||||
}
|
||||
>
|
||||
| vue.Slot<any>
|
||||
| VNode<
|
||||
vue.RendererNode,
|
||||
vue.RendererElement,
|
||||
{
|
||||
[key: string]: any
|
||||
}
|
||||
>[]
|
||||
| {
|
||||
default: () => VNode<
|
||||
vue.RendererNode,
|
||||
vue.RendererElement,
|
||||
{
|
||||
[key: string]: any
|
||||
}
|
||||
>[]
|
||||
},
|
||||
unknown,
|
||||
{},
|
||||
{},
|
||||
vue.ComponentOptionsMixin,
|
||||
vue.ComponentOptionsMixin,
|
||||
{},
|
||||
vue.VNodeProps &
|
||||
vue.AllowedComponentProps &
|
||||
vue.ComponentCustomProps &
|
||||
Readonly<
|
||||
vue.ExtractPropTypes<{
|
||||
as: {
|
||||
type: StringConstructor
|
||||
default: any
|
||||
}
|
||||
name: {
|
||||
type: StringConstructor
|
||||
required: true
|
||||
}
|
||||
}>
|
||||
>,
|
||||
{
|
||||
as: string
|
||||
},
|
||||
true,
|
||||
{},
|
||||
{},
|
||||
{
|
||||
P: {}
|
||||
B: {}
|
||||
D: {}
|
||||
C: {}
|
||||
M: {}
|
||||
Defaults: {}
|
||||
},
|
||||
Readonly<
|
||||
vue.ExtractPropTypes<{
|
||||
as: {
|
||||
type: StringConstructor
|
||||
default: any
|
||||
}
|
||||
name: {
|
||||
type: StringConstructor
|
||||
required: true
|
||||
}
|
||||
}>
|
||||
>,
|
||||
() =>
|
||||
| VNode<
|
||||
vue.RendererNode,
|
||||
vue.RendererElement,
|
||||
{
|
||||
[key: string]: any
|
||||
}
|
||||
>
|
||||
| vue.Slot<any>
|
||||
| VNode<
|
||||
vue.RendererNode,
|
||||
vue.RendererElement,
|
||||
{
|
||||
[key: string]: any
|
||||
}
|
||||
>[]
|
||||
| {
|
||||
default: () => VNode<
|
||||
vue.RendererNode,
|
||||
vue.RendererElement,
|
||||
{
|
||||
[key: string]: any
|
||||
}
|
||||
>[]
|
||||
},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{
|
||||
as: string
|
||||
}
|
||||
>
|
||||
__isFragment?: never
|
||||
__isTeleport?: never
|
||||
__isSuspense?: never
|
||||
} & vue.ComponentOptionsBase<
|
||||
Readonly<
|
||||
vue.ExtractPropTypes<{
|
||||
as: {
|
||||
type: StringConstructor
|
||||
default: any
|
||||
}
|
||||
name: {
|
||||
type: StringConstructor
|
||||
required: true
|
||||
}
|
||||
}>
|
||||
>,
|
||||
() =>
|
||||
| VNode<
|
||||
vue.RendererNode,
|
||||
vue.RendererElement,
|
||||
{
|
||||
[key: string]: any
|
||||
}
|
||||
>
|
||||
| vue.Slot<any>
|
||||
| VNode<
|
||||
vue.RendererNode,
|
||||
vue.RendererElement,
|
||||
{
|
||||
[key: string]: any
|
||||
}
|
||||
>[]
|
||||
| {
|
||||
default: () => VNode<
|
||||
vue.RendererNode,
|
||||
vue.RendererElement,
|
||||
{
|
||||
[key: string]: any
|
||||
}
|
||||
>[]
|
||||
},
|
||||
unknown,
|
||||
{},
|
||||
{},
|
||||
vue.ComponentOptionsMixin,
|
||||
vue.ComponentOptionsMixin,
|
||||
{},
|
||||
string,
|
||||
{
|
||||
as: string
|
||||
},
|
||||
{},
|
||||
string,
|
||||
{}
|
||||
> &
|
||||
vue.VNodeProps &
|
||||
vue.AllowedComponentProps &
|
||||
vue.ComponentCustomProps &
|
||||
(new () => {
|
||||
$slots: {
|
||||
default: (arg: ErrorMessageSlotProps) => VNode[]
|
||||
}
|
||||
})
|
||||
;<ErrorMessage name="password" class="error" />
|
||||
|
||||
// #10843
|
||||
createApp({}).component(
|
||||
'SomeComponent',
|
||||
defineComponent({
|
||||
props: {
|
||||
title: String,
|
||||
},
|
||||
setup(props) {
|
||||
expectType<string | undefined>(props.title)
|
||||
return {}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
defineComponent,
|
||||
defineEmits,
|
||||
defineModel,
|
||||
defineOptions,
|
||||
defineProps,
|
||||
defineSlots,
|
||||
toRefs,
|
||||
|
|
@ -501,3 +502,21 @@ describe('toRefs w/ type declaration', () => {
|
|||
}>()
|
||||
expectType<Ref<File | File[] | undefined>>(toRefs(props).file)
|
||||
})
|
||||
|
||||
describe('defineOptions', () => {
|
||||
defineOptions({
|
||||
name: 'MyComponent',
|
||||
inheritAttrs: true,
|
||||
})
|
||||
|
||||
defineOptions({
|
||||
// @ts-expect-error props should be defined via defineProps()
|
||||
props: ['props'],
|
||||
// @ts-expect-error emits should be defined via defineEmits()
|
||||
emits: ['emits'],
|
||||
// @ts-expect-error slots should be defined via defineSlots()
|
||||
slots: { default: 'default' },
|
||||
// @ts-expect-error expose should be defined via defineExpose()
|
||||
expose: ['expose'],
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/reactivity",
|
||||
"version": "3.5.0-alpha.1",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"description": "@vue/reactivity",
|
||||
"main": "index.js",
|
||||
"module": "dist/reactivity.esm-bundler.js",
|
||||
|
|
|
|||
|
|
@ -322,39 +322,32 @@ export function baseWatch(
|
|||
|
||||
export function traverse(
|
||||
value: unknown,
|
||||
depth?: number,
|
||||
currentDepth = 0,
|
||||
depth = Infinity,
|
||||
seen?: Set<unknown>,
|
||||
) {
|
||||
if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
|
||||
if (depth <= 0 || !isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
|
||||
return value
|
||||
}
|
||||
|
||||
if (depth && depth > 0) {
|
||||
if (currentDepth >= depth) {
|
||||
return value
|
||||
}
|
||||
currentDepth++
|
||||
}
|
||||
|
||||
seen = seen || new Set()
|
||||
if (seen.has(value)) {
|
||||
return value
|
||||
}
|
||||
seen.add(value)
|
||||
depth--
|
||||
if (isRef(value)) {
|
||||
traverse(value.value, depth, currentDepth, seen)
|
||||
traverse(value.value, depth, seen)
|
||||
} else if (isArray(value)) {
|
||||
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)) {
|
||||
value.forEach((v: any) => {
|
||||
traverse(v, depth, currentDepth, seen)
|
||||
traverse(v, depth, seen)
|
||||
})
|
||||
} else if (isPlainObject(value)) {
|
||||
for (const key in value) {
|
||||
traverse(value[key], depth, currentDepth, seen)
|
||||
traverse(value[key], depth, seen)
|
||||
}
|
||||
}
|
||||
return value
|
||||
|
|
|
|||
|
|
@ -344,6 +344,36 @@ describe('api: createApp', () => {
|
|||
).toHaveBeenWarnedTimes(1)
|
||||
})
|
||||
|
||||
test('onUnmount', () => {
|
||||
const cleanup = vi.fn().mockName('plugin cleanup')
|
||||
const PluginA: Plugin = app => {
|
||||
app.provide('foo', 1)
|
||||
app.onUnmount(cleanup)
|
||||
}
|
||||
const PluginB: Plugin = {
|
||||
install: (app, arg1, arg2) => {
|
||||
app.provide('bar', arg1 + arg2)
|
||||
app.onUnmount(cleanup)
|
||||
},
|
||||
}
|
||||
|
||||
const app = createApp({
|
||||
render: () => `Test`,
|
||||
})
|
||||
app.use(PluginA)
|
||||
app.use(PluginB)
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
app.mount(root)
|
||||
|
||||
//also can be added after mount
|
||||
app.onUnmount(cleanup)
|
||||
|
||||
app.unmount()
|
||||
|
||||
expect(cleanup).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
test('config.errorHandler', () => {
|
||||
const error = new Error()
|
||||
const count = ref(0)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import {
|
|||
ref,
|
||||
render,
|
||||
serializeInner,
|
||||
toRaw,
|
||||
toRefs,
|
||||
watch,
|
||||
} from '@vue/runtime-test'
|
||||
|
|
@ -129,12 +128,12 @@ describe('component props', () => {
|
|||
render(h(Comp, { foo: 1 }), root)
|
||||
expect(props).toEqual({ foo: 1 })
|
||||
expect(attrs).toEqual({ foo: 1 })
|
||||
expect(toRaw(props)).toBe(attrs)
|
||||
expect(props).toBe(attrs)
|
||||
|
||||
render(h(Comp, { bar: 2 }), root)
|
||||
expect(props).toEqual({ bar: 2 })
|
||||
expect(attrs).toEqual({ bar: 2 })
|
||||
expect(toRaw(props)).toBe(attrs)
|
||||
expect(props).toBe(attrs)
|
||||
})
|
||||
|
||||
test('boolean casting', () => {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import {
|
|||
h,
|
||||
nextTick,
|
||||
nodeOps,
|
||||
onUnmounted,
|
||||
ref,
|
||||
render,
|
||||
serialize,
|
||||
|
|
@ -769,42 +768,6 @@ describe('BaseTransition', () => {
|
|||
test('w/ KeepAlive', async () => {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -356,7 +356,7 @@ describe('hot module replacement', () => {
|
|||
triggerEvent(root.children[1] as TestElement, 'click')
|
||||
await nextTick()
|
||||
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(mountSpy).toHaveBeenCalledTimes(1)
|
||||
expect(activeSpy).toHaveBeenCalledTimes(1)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-core",
|
||||
"version": "3.5.0-alpha.1",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"description": "@vue/runtime-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-core.esm-bundler.js",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import { version } from '.'
|
|||
import { installAppCompatProperties } from './compat/global'
|
||||
import type { NormalizedPropsOptions } from './componentProps'
|
||||
import type { ObjectEmitsOptions } from './componentEmits'
|
||||
import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
||||
import type { DefineComponent } from './apiDefineComponent'
|
||||
|
||||
export interface App<HostElement = any> {
|
||||
|
|
@ -40,7 +41,10 @@ export interface App<HostElement = any> {
|
|||
|
||||
mixin(mixin: ComponentOptions): this
|
||||
component(name: string): Component | undefined
|
||||
component(name: string, component: Component | DefineComponent): this
|
||||
component<T extends Component | DefineComponent>(
|
||||
name: string,
|
||||
component: T,
|
||||
): this
|
||||
directive<T = any, V = any>(name: string): Directive<T, V> | undefined
|
||||
directive<T = any, V = any>(name: string, directive: Directive<T, V>): this
|
||||
mount(
|
||||
|
|
@ -49,6 +53,7 @@ export interface App<HostElement = any> {
|
|||
namespace?: boolean | ElementNamespace,
|
||||
): ComponentPublicInstance
|
||||
unmount(): void
|
||||
onUnmount(cb: () => void): void
|
||||
provide<T>(key: InjectionKey<T> | string, value: T): this
|
||||
|
||||
/**
|
||||
|
|
@ -213,6 +218,7 @@ export function createAppAPI<HostElement>(
|
|||
|
||||
const context = createAppContext()
|
||||
const installedPlugins = new WeakSet()
|
||||
const pluginCleanupFns: Array<() => any> = []
|
||||
|
||||
let isMounted = false
|
||||
|
||||
|
|
@ -365,8 +371,23 @@ export function createAppAPI<HostElement>(
|
|||
}
|
||||
},
|
||||
|
||||
onUnmount(cleanupFn: () => void) {
|
||||
if (__DEV__ && typeof cleanupFn !== 'function') {
|
||||
warn(
|
||||
`Expected function as first argument to app.onUnmount(), ` +
|
||||
`but got ${typeof cleanupFn}`,
|
||||
)
|
||||
}
|
||||
pluginCleanupFns.push(cleanupFn)
|
||||
},
|
||||
|
||||
unmount() {
|
||||
if (isMounted) {
|
||||
callWithAsyncErrorHandling(
|
||||
pluginCleanupFns,
|
||||
app._instance,
|
||||
ErrorCodes.APP_UNMOUNT_CLEANUP,
|
||||
)
|
||||
render(null, app._container)
|
||||
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
||||
app._instance = null
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import { extend, isFunction } from '@vue/shared'
|
|||
import type { VNodeProps } from './vnode'
|
||||
import type {
|
||||
ComponentPublicInstanceConstructor,
|
||||
CreateComponentPublicInstance,
|
||||
CreateComponentPublicInstanceWithMixins,
|
||||
} from './componentPublicInstance'
|
||||
import type { SlotsType } from './componentSlots'
|
||||
import type { Directive } from './directives'
|
||||
|
|
@ -68,7 +68,7 @@ export type DefineComponent<
|
|||
Provide extends ComponentProvideOptions = ComponentProvideOptions,
|
||||
MakeDefaultsOptional extends boolean = true,
|
||||
> = ComponentPublicInstanceConstructor<
|
||||
CreateComponentPublicInstance<
|
||||
CreateComponentPublicInstanceWithMixins<
|
||||
Props,
|
||||
RawBindings,
|
||||
D,
|
||||
|
|
@ -116,7 +116,7 @@ export type DefineSetupFnComponent<
|
|||
PP = PublicProps,
|
||||
> = new (
|
||||
props: Props & PP,
|
||||
) => CreateComponentPublicInstance<
|
||||
) => CreateComponentPublicInstanceWithMixins<
|
||||
Props,
|
||||
{},
|
||||
{},
|
||||
|
|
@ -240,7 +240,7 @@ export function defineComponent<
|
|||
Provide
|
||||
> &
|
||||
ThisType<
|
||||
CreateComponentPublicInstance<
|
||||
CreateComponentPublicInstanceWithMixins<
|
||||
ResolvedProps,
|
||||
SetupBindings,
|
||||
Data,
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ export function defineOptions<
|
|||
/**
|
||||
* props should be defined via defineProps().
|
||||
*/
|
||||
props: never
|
||||
props?: never
|
||||
/**
|
||||
* emits should be defined via defineEmits().
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -77,7 +77,12 @@ export type CompatVue = Pick<App, 'version' | 'component' | 'directive'> & {
|
|||
|
||||
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
|
||||
|
||||
component(name: string): Component | undefined
|
||||
|
|
@ -176,11 +181,11 @@ export function createCompatVue(
|
|||
Vue.version = `2.6.14-compat:${__VERSION__}`
|
||||
Vue.config = singletonApp.config
|
||||
|
||||
Vue.use = (p, ...options) => {
|
||||
if (p && isFunction(p.install)) {
|
||||
p.install(Vue as any, ...options)
|
||||
} else if (isFunction(p)) {
|
||||
p(Vue as any, ...options)
|
||||
Vue.use = (plugin: Plugin, ...options: any[]) => {
|
||||
if (plugin && isFunction(plugin.install)) {
|
||||
plugin.install(Vue as any, ...options)
|
||||
} else if (isFunction(plugin)) {
|
||||
plugin(Vue as any, ...options)
|
||||
}
|
||||
return Vue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ import type {
|
|||
import type { Directive } from './directives'
|
||||
import {
|
||||
type ComponentPublicInstance,
|
||||
type CreateComponentPublicInstance,
|
||||
type CreateComponentPublicInstanceWithMixins,
|
||||
type IntersectionMixin,
|
||||
type UnwrapMixinsType,
|
||||
isReservedPrefix,
|
||||
|
|
@ -264,7 +264,7 @@ export type ComponentOptions<
|
|||
Provide
|
||||
> &
|
||||
ThisType<
|
||||
CreateComponentPublicInstance<
|
||||
CreateComponentPublicInstanceWithMixins<
|
||||
{},
|
||||
RawBindings,
|
||||
D,
|
||||
|
|
@ -373,7 +373,7 @@ interface LegacyOptions<
|
|||
// since that leads to some sort of circular inference and breaks ThisType
|
||||
// for the entire component.
|
||||
data?: (
|
||||
this: CreateComponentPublicInstance<
|
||||
this: CreateComponentPublicInstanceWithMixins<
|
||||
Props,
|
||||
{},
|
||||
{},
|
||||
|
|
@ -382,7 +382,7 @@ interface LegacyOptions<
|
|||
Mixin,
|
||||
Extends
|
||||
>,
|
||||
vm: CreateComponentPublicInstance<
|
||||
vm: CreateComponentPublicInstanceWithMixins<
|
||||
Props,
|
||||
{},
|
||||
{},
|
||||
|
|
@ -1153,7 +1153,7 @@ export type ComponentOptionsWithoutProps<
|
|||
*/
|
||||
__typeEmits?: TE
|
||||
} & ThisType<
|
||||
CreateComponentPublicInstance<
|
||||
CreateComponentPublicInstanceWithMixins<
|
||||
PE,
|
||||
RawBindings,
|
||||
D,
|
||||
|
|
@ -1215,7 +1215,7 @@ export type ComponentOptionsWithArrayProps<
|
|||
> & {
|
||||
props: PropNames[]
|
||||
} & ThisType<
|
||||
CreateComponentPublicInstance<
|
||||
CreateComponentPublicInstanceWithMixins<
|
||||
Props,
|
||||
RawBindings,
|
||||
D,
|
||||
|
|
@ -1278,7 +1278,7 @@ export type ComponentOptionsWithObjectProps<
|
|||
> & {
|
||||
props: PropsOptions & ThisType<void>
|
||||
} & ThisType<
|
||||
CreateComponentPublicInstance<
|
||||
CreateComponentPublicInstanceWithMixins<
|
||||
Props,
|
||||
RawBindings,
|
||||
D,
|
||||
|
|
|
|||
|
|
@ -150,7 +150,71 @@ export type ComponentPublicInstanceConstructor<
|
|||
new (...args: any[]): T
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This is no longer used internally, but exported and relied on by
|
||||
* existing library types generated by vue-tsc.
|
||||
*/
|
||||
export type CreateComponentPublicInstance<
|
||||
P = {},
|
||||
B = {},
|
||||
D = {},
|
||||
C extends ComputedOptions = {},
|
||||
M extends MethodOptions = {},
|
||||
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
|
||||
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
|
||||
E extends EmitsOptions = {},
|
||||
PublicProps = P,
|
||||
Defaults = {},
|
||||
MakeDefaultsOptional extends boolean = false,
|
||||
I extends ComponentInjectOptions = {},
|
||||
S extends SlotsType = {},
|
||||
PublicMixin = IntersectionMixin<Mixin> & IntersectionMixin<Extends>,
|
||||
PublicP = UnwrapMixinsType<PublicMixin, 'P'> & EnsureNonVoid<P>,
|
||||
PublicB = UnwrapMixinsType<PublicMixin, 'B'> & EnsureNonVoid<B>,
|
||||
PublicD = UnwrapMixinsType<PublicMixin, 'D'> & EnsureNonVoid<D>,
|
||||
PublicC extends ComputedOptions = UnwrapMixinsType<PublicMixin, 'C'> &
|
||||
EnsureNonVoid<C>,
|
||||
PublicM extends MethodOptions = UnwrapMixinsType<PublicMixin, 'M'> &
|
||||
EnsureNonVoid<M>,
|
||||
PublicDefaults = UnwrapMixinsType<PublicMixin, 'Defaults'> &
|
||||
EnsureNonVoid<Defaults>,
|
||||
> = ComponentPublicInstance<
|
||||
PublicP,
|
||||
PublicB,
|
||||
PublicD,
|
||||
PublicC,
|
||||
PublicM,
|
||||
E,
|
||||
PublicProps,
|
||||
PublicDefaults,
|
||||
MakeDefaultsOptional,
|
||||
ComponentOptionsBase<
|
||||
P,
|
||||
B,
|
||||
D,
|
||||
C,
|
||||
M,
|
||||
Mixin,
|
||||
Extends,
|
||||
E,
|
||||
string,
|
||||
Defaults,
|
||||
{},
|
||||
string,
|
||||
S
|
||||
>,
|
||||
I,
|
||||
S
|
||||
>
|
||||
|
||||
/**
|
||||
* This is the same as `CreateComponentPublicInstance` but adds local components,
|
||||
* global directives, exposed, and provide inference.
|
||||
* It changes the arguments order so that we don't need to repeat mixin
|
||||
* inference everywhere internally, but it has to be a new type to avoid
|
||||
* breaking types that relies on previous arguments order (#10842)
|
||||
*/
|
||||
export type CreateComponentPublicInstanceWithMixins<
|
||||
P = {},
|
||||
B = {},
|
||||
D = {},
|
||||
|
|
@ -167,6 +231,8 @@ export type CreateComponentPublicInstance<
|
|||
LC extends Record<string, Component> = {},
|
||||
Directives extends Record<string, Directive> = {},
|
||||
Exposed extends string = string,
|
||||
Provide extends ComponentProvideOptions = ComponentProvideOptions,
|
||||
// mixin inference
|
||||
PublicMixin = IntersectionMixin<Mixin> & IntersectionMixin<Extends>,
|
||||
PublicP = UnwrapMixinsType<PublicMixin, 'P'> & EnsureNonVoid<P>,
|
||||
PublicB = UnwrapMixinsType<PublicMixin, 'B'> & EnsureNonVoid<B>,
|
||||
|
|
@ -177,7 +243,6 @@ export type CreateComponentPublicInstance<
|
|||
EnsureNonVoid<M>,
|
||||
PublicDefaults = UnwrapMixinsType<PublicMixin, 'Defaults'> &
|
||||
EnsureNonVoid<Defaults>,
|
||||
Provide extends ComponentProvideOptions = ComponentProvideOptions,
|
||||
> = ComponentPublicInstance<
|
||||
PublicP,
|
||||
PublicB,
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ export function renderComponentRoot(
|
|||
? {
|
||||
get attrs() {
|
||||
markAttrsAccessed()
|
||||
return attrs
|
||||
return shallowReadonly(attrs)
|
||||
},
|
||||
slots,
|
||||
emit,
|
||||
|
|
@ -171,7 +171,7 @@ export function renderComponentRoot(
|
|||
propsOptions,
|
||||
)
|
||||
}
|
||||
root = cloneVNode(root, fallthroughAttrs)
|
||||
root = cloneVNode(root, fallthroughAttrs, false, true)
|
||||
} else if (__DEV__ && !accessedAttrs && root.type !== Comment) {
|
||||
const allAttrs = Object.keys(attrs)
|
||||
const eventAttrs: string[] = []
|
||||
|
|
@ -226,10 +226,15 @@ export function renderComponentRoot(
|
|||
getComponentName(instance.type),
|
||||
)
|
||||
}
|
||||
root = cloneVNode(root, {
|
||||
root = cloneVNode(
|
||||
root,
|
||||
{
|
||||
class: cls,
|
||||
style: style,
|
||||
})
|
||||
},
|
||||
false,
|
||||
true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -242,7 +247,7 @@ export function renderComponentRoot(
|
|||
)
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// inherit transition data
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ export const initSlots = (
|
|||
if (type) {
|
||||
extend(slots, children as InternalSlots)
|
||||
// make compiler marker non-enumerable
|
||||
def(slots, '_', type)
|
||||
def(slots, '_', type, true)
|
||||
} else {
|
||||
normalizeObjectSlots(children as RawSlots, slots, instance)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ const BaseTransitionImpl: ComponentOptions = {
|
|||
// update old tree's hooks in case of dynamic transition
|
||||
setTransitionHooks(oldInnerChild, leavingHooks)
|
||||
// switching between different views
|
||||
if (mode === 'out-in') {
|
||||
if (mode === 'out-in' && innerChild.type !== Comment) {
|
||||
state.isLeaving = true
|
||||
// return placeholder node and queue update when leave finishes
|
||||
leavingHooks.afterLeave = () => {
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ const KeepAliveImpl: ComponentOptions = {
|
|||
pendingCacheKey = null
|
||||
|
||||
if (!slots.default) {
|
||||
return (current = null)
|
||||
return null
|
||||
}
|
||||
|
||||
const children = slots.default()
|
||||
|
|
|
|||
|
|
@ -479,7 +479,7 @@ function createSuspenseBoundary(
|
|||
let parentSuspenseId: number | undefined
|
||||
const isSuspensible = isVNodeSuspensible(vnode)
|
||||
if (isSuspensible) {
|
||||
if (parentSuspense?.pendingBranch) {
|
||||
if (parentSuspense && parentSuspense.pendingBranch) {
|
||||
parentSuspenseId = parentSuspense.pendingId
|
||||
parentSuspense.deps++
|
||||
}
|
||||
|
|
@ -898,5 +898,6 @@ function setActiveBranch(suspense: SuspenseBoundary, branch: 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,8 +52,12 @@ export type DirectiveHook<
|
|||
prevVNode: Prev,
|
||||
) => void
|
||||
|
||||
export type SSRDirectiveHook = (
|
||||
binding: DirectiveBinding,
|
||||
export type SSRDirectiveHook<
|
||||
Value = any,
|
||||
Modifiers extends string = string,
|
||||
Arg extends string = string,
|
||||
> = (
|
||||
binding: DirectiveBinding<Value, Modifiers, Arg>,
|
||||
vnode: VNode,
|
||||
) => Data | undefined
|
||||
|
||||
|
|
@ -63,6 +67,12 @@ export interface ObjectDirective<
|
|||
Modifiers extends string = string,
|
||||
Arg extends string = string,
|
||||
> {
|
||||
/**
|
||||
* @internal without this, ts-expect-error in directives.test-d.ts somehow
|
||||
* fails when running tsc, but passes in IDE and when testing against built
|
||||
* dts. Could be a TS bug.
|
||||
*/
|
||||
__mod?: Modifiers
|
||||
created?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
|
||||
beforeMount?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
|
||||
mounted?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
|
||||
|
|
@ -82,7 +92,7 @@ export interface ObjectDirective<
|
|||
>
|
||||
beforeUnmount?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
|
||||
unmounted?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
|
||||
getSSRProps?: SSRDirectiveHook
|
||||
getSSRProps?: SSRDirectiveHook<Value, Modifiers, Arg>
|
||||
deep?: boolean
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ export enum ErrorCodes {
|
|||
FUNCTION_REF,
|
||||
ASYNC_COMPONENT_LOADER,
|
||||
SCHEDULER,
|
||||
APP_UNMOUNT_CLEANUP,
|
||||
}
|
||||
|
||||
export type ErrorTypes = LifecycleHooks | ErrorCodes | BaseWatchErrorCodes
|
||||
|
|
@ -63,6 +64,7 @@ export const ErrorTypeStrings: Record<ErrorTypes, string> = {
|
|||
[ErrorCodes.SCHEDULER]:
|
||||
'scheduler flush. This is likely a Vue internals bug. ' +
|
||||
'Please open an issue at https://github.com/vuejs/core .',
|
||||
[ErrorCodes.APP_UNMOUNT_CLEANUP]: 'app unmount cleanup function',
|
||||
}
|
||||
|
||||
export function callWithErrorHandling(
|
||||
|
|
|
|||
|
|
@ -270,6 +270,10 @@ export type {
|
|||
ComputedOptions,
|
||||
RuntimeCompilerOptions,
|
||||
ComponentInjectOptions,
|
||||
// deprecated
|
||||
ComponentOptionsWithoutProps,
|
||||
ComponentOptionsWithArrayProps,
|
||||
ComponentOptionsWithObjectProps,
|
||||
} from './componentOptions'
|
||||
export type {
|
||||
EmitsOptions,
|
||||
|
|
@ -280,6 +284,7 @@ export type {
|
|||
ComponentPublicInstance,
|
||||
ComponentCustomProperties,
|
||||
CreateComponentPublicInstance,
|
||||
CreateComponentPublicInstanceWithMixins,
|
||||
} from './componentPublicInstance'
|
||||
export type {
|
||||
Renderer,
|
||||
|
|
|
|||
|
|
@ -624,10 +624,11 @@ export function cloneVNode<T, U>(
|
|||
vnode: VNode<T, U>,
|
||||
extraProps?: (Data & VNodeProps) | null,
|
||||
mergeRef = false,
|
||||
cloneTransition = false,
|
||||
): VNode<T, U> {
|
||||
// This is intentionally NOT using spread or extend to avoid the runtime
|
||||
// key enumeration cost.
|
||||
const { props, ref, patchFlag, children } = vnode
|
||||
const { props, ref, patchFlag, children, transition } = vnode
|
||||
const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
|
||||
const cloned: VNode<T, U> = {
|
||||
__v_isVNode: true,
|
||||
|
|
@ -670,7 +671,7 @@ export function cloneVNode<T, U>(
|
|||
dynamicChildren: vnode.dynamicChildren,
|
||||
appContext: vnode.appContext,
|
||||
dirs: vnode.dirs,
|
||||
transition: vnode.transition,
|
||||
transition,
|
||||
|
||||
// These should technically only be non-null on mounted VNodes. However,
|
||||
// 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,
|
||||
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__) {
|
||||
defineLegacyVNodeProperties(cloned as VNode)
|
||||
}
|
||||
|
||||
return cloned
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-dom",
|
||||
"version": "3.5.0-alpha.1",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"description": "@vue/runtime-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-dom.esm-bundler.js",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
type ComponentProvideOptions,
|
||||
type ComputedOptions,
|
||||
type ConcreteComponent,
|
||||
type CreateComponentPublicInstance,
|
||||
type CreateComponentPublicInstanceWithMixins,
|
||||
type DefineComponent,
|
||||
type Directive,
|
||||
type EmitsOptions,
|
||||
|
|
@ -97,7 +97,7 @@ export function defineCustomElement<
|
|||
Provide
|
||||
> &
|
||||
ThisType<
|
||||
CreateComponentPublicInstance<
|
||||
CreateComponentPublicInstanceWithMixins<
|
||||
Readonly<ResolvedProps>,
|
||||
SetupBindings,
|
||||
Data,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/server-renderer",
|
||||
"version": "3.5.0-alpha.1",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"description": "@vue/server-renderer",
|
||||
"main": "index.js",
|
||||
"module": "dist/server-renderer.esm-bundler.js",
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
"vite": "^5.2.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/repl": "^4.1.1",
|
||||
"@vue/repl": "^4.1.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"jszip": "^3.10.1",
|
||||
"vue": "workspace:*"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/shared",
|
||||
"version": "3.5.0-alpha.1",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"description": "internal utils shared across @vue packages",
|
||||
"main": "index.js",
|
||||
"module": "dist/shared.esm-bundler.js",
|
||||
|
|
|
|||
|
|
@ -146,10 +146,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, {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable,
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compat",
|
||||
"version": "3.5.0-alpha.1",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"description": "Vue 3 compatibility build for Vue 2",
|
||||
"main": "index.js",
|
||||
"module": "dist/vue.runtime.esm-bundler.js",
|
||||
|
|
|
|||
|
|
@ -1214,6 +1214,111 @@ describe('e2e: Transition', () => {
|
|||
},
|
||||
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', () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "vue",
|
||||
"version": "3.5.0-alpha.1",
|
||||
"version": "3.5.0-alpha.2",
|
||||
"description": "The progressive JavaScript framework for building modern web UI.",
|
||||
"main": "index.js",
|
||||
"module": "dist/vue.runtime.esm-bundler.js",
|
||||
|
|
|
|||
727
pnpm-lock.yaml
727
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue