Merge remote-tracking branch 'upstream/minor'

This commit is contained in:
三咲智子 Kevin Deng 2024-04-29 17:49:04 +09:00
commit fb58e65d3d
No known key found for this signature in database
47 changed files with 1653 additions and 558 deletions

View File

@ -1,3 +1,26 @@
# [3.5.0-alpha.1](https://github.com/vuejs/core/compare/v3.4.25...v3.5.0-alpha.1) (2024-04-29)
### Bug Fixes
* **reactivity:** fix call sequence of ontrigger in effect ([#10501](https://github.com/vuejs/core/issues/10501)) ([28841fe](https://github.com/vuejs/core/commit/28841fee43a45c37905c2c1ed9ace23067539045))
### Features
* **compiler-sfc:** enable reactive props destructure by default ([d2dac0e](https://github.com/vuejs/core/commit/d2dac0e359c47d1ed0aa77eda488e76fd6466d2d))
* **reactivity:** `onEffectCleanup` API ([2cc5615](https://github.com/vuejs/core/commit/2cc5615590de77126e8df46136de0240dbde5004)), closes [#10173](https://github.com/vuejs/core/issues/10173)
* **reactivity:** add failSilently argument for onScopeDispose ([9a936aa](https://github.com/vuejs/core/commit/9a936aaec489c79433a32791ecf5ddb1739a62bd))
* **transition:** support directly nesting Teleport inside Transition ([#6548](https://github.com/vuejs/core/issues/6548)) ([0e6e3c7](https://github.com/vuejs/core/commit/0e6e3c7eb0e5320b7c1818e025cb4a490fede9c0)), closes [#5836](https://github.com/vuejs/core/issues/5836)
* **types:** provide internal options for directly using user types in language tools ([#10801](https://github.com/vuejs/core/issues/10801)) ([75c8cf6](https://github.com/vuejs/core/commit/75c8cf63a1ef30ac84f91282d66ad3f57c6612e9))
### Performance Improvements
* **reactivity:** optimize array tracking ([#9511](https://github.com/vuejs/core/issues/9511)) ([70196a4](https://github.com/vuejs/core/commit/70196a40cc078f50fcc1110c38c06fbcc70b205e)), closes [#4318](https://github.com/vuejs/core/issues/4318)
## [3.4.25](https://github.com/vuejs/core/compare/v3.4.24...v3.4.25) (2024-04-24) ## [3.4.25](https://github.com/vuejs/core/compare/v3.4.24...v3.4.25) (2024-04-24)

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compiler-core", "name": "@vue/compiler-core",
"version": "3.4.25", "version": "3.5.0-alpha.1",
"description": "@vue/compiler-core", "description": "@vue/compiler-core",
"main": "index.js", "main": "index.js",
"module": "dist/compiler-core.esm-bundler.js", "module": "dist/compiler-core.esm-bundler.js",

View File

@ -0,0 +1,20 @@
import { type CompilerError, compile } from '../../src'
describe('validate html nesting', () => {
it('should warn with p > div', () => {
let err: CompilerError | undefined
compile(`<p><div></div></p>`, {
onWarn: e => (err = e),
})
expect(err).toBeDefined()
expect(err!.message).toMatch(`<div> cannot be child of <p>`)
})
it('should not warn with select > hr', () => {
let err: CompilerError | undefined
compile(`<select><hr></select>`, {
onWarn: e => (err = e),
})
expect(err).toBeUndefined()
})
})

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compiler-dom", "name": "@vue/compiler-dom",
"version": "3.4.25", "version": "3.5.0-alpha.1",
"description": "@vue/compiler-dom", "description": "@vue/compiler-dom",
"main": "index.js", "main": "index.js",
"module": "dist/compiler-dom.esm-bundler.js", "module": "dist/compiler-dom.esm-bundler.js",

View File

@ -0,0 +1,195 @@
/**
* Copied from https://github.com/MananTank/validate-html-nesting
* with ISC license
*
* To avoid runtime dependency on validate-html-nesting
* This file should not change very often in the original repo
* but we may need to keep it up-to-date from time to time.
*/
/**
* returns true if given parent-child nesting is valid HTML
*/
export function isValidHTMLNesting(parent: string, child: string): boolean {
// if we know the list of children that are the only valid children for the given parent
if (parent in onlyValidChildren) {
return onlyValidChildren[parent].has(child)
}
// if we know the list of parents that are the only valid parents for the given child
if (child in onlyValidParents) {
return onlyValidParents[child].has(parent)
}
// if we know the list of children that are NOT valid for the given parent
if (parent in knownInvalidChildren) {
// check if the child is in the list of invalid children
// if so, return false
if (knownInvalidChildren[parent].has(child)) return false
}
// if we know the list of parents that are NOT valid for the given child
if (child in knownInvalidParents) {
// check if the parent is in the list of invalid parents
// if so, return false
if (knownInvalidParents[child].has(parent)) return false
}
return true
}
const headings = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'])
const emptySet = new Set([])
/**
* maps element to set of elements that can be it's children, no other */
const onlyValidChildren: Record<string, Set<string>> = {
head: new Set([
'base',
'basefront',
'bgsound',
'link',
'meta',
'title',
'noscript',
'noframes',
'style',
'script',
'template',
]),
optgroup: new Set(['option']),
select: new Set(['optgroup', 'option', 'hr']),
// table
table: new Set(['caption', 'colgroup', 'tbody', 'tfoot', 'thead']),
tr: new Set(['td', 'th']),
colgroup: new Set(['col']),
tbody: new Set(['tr']),
thead: new Set(['tr']),
tfoot: new Set(['tr']),
// these elements can not have any children elements
script: emptySet,
iframe: emptySet,
option: emptySet,
textarea: emptySet,
style: emptySet,
title: emptySet,
}
/** maps elements to set of elements which can be it's parent, no other */
const onlyValidParents: Record<string, Set<string>> = {
// sections
html: emptySet,
body: new Set(['html']),
head: new Set(['html']),
// table
td: new Set(['tr']),
colgroup: new Set(['table']),
caption: new Set(['table']),
tbody: new Set(['table']),
tfoot: new Set(['table']),
col: new Set(['colgroup']),
th: new Set(['tr']),
thead: new Set(['table']),
tr: new Set(['tbody', 'thead', 'tfoot']),
// data list
dd: new Set(['dl', 'div']),
dt: new Set(['dl', 'div']),
// other
figcaption: new Set(['figure']),
// li: new Set(["ul", "ol"]),
summary: new Set(['details']),
area: new Set(['map']),
} as const
/** maps element to set of elements that can not be it's children, others can */
const knownInvalidChildren: Record<string, Set<string>> = {
p: new Set([
'address',
'article',
'aside',
'blockquote',
'center',
'details',
'dialog',
'dir',
'div',
'dl',
'fieldset',
'figure',
'footer',
'form',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'header',
'hgroup',
'hr',
'li',
'main',
'nav',
'menu',
'ol',
'p',
'pre',
'section',
'table',
'ul',
]),
svg: new Set([
'b',
'blockquote',
'br',
'code',
'dd',
'div',
'dl',
'dt',
'em',
'embed',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'hr',
'i',
'img',
'li',
'menu',
'meta',
'ol',
'p',
'pre',
'ruby',
's',
'small',
'span',
'strong',
'sub',
'sup',
'table',
'u',
'ul',
'var',
]),
} as const
/** maps element to set of elements that can not be it's parent, others can */
const knownInvalidParents: Record<string, Set<string>> = {
a: new Set(['a']),
button: new Set(['button']),
dd: new Set(['dd', 'dt']),
dt: new Set(['dd', 'dt']),
form: new Set(['form']),
li: new Set(['li']),
h1: headings,
h2: headings,
h3: headings,
h4: headings,
h5: headings,
h6: headings,
}

View File

@ -19,13 +19,14 @@ import { transformShow } from './transforms/vShow'
import { transformTransition } from './transforms/Transition' import { transformTransition } from './transforms/Transition'
import { stringifyStatic } from './transforms/stringifyStatic' import { stringifyStatic } from './transforms/stringifyStatic'
import { ignoreSideEffectTags } from './transforms/ignoreSideEffectTags' import { ignoreSideEffectTags } from './transforms/ignoreSideEffectTags'
import { validateHtmlNesting } from './transforms/validateHtmlNesting'
import { extend } from '@vue/shared' import { extend } from '@vue/shared'
export { parserOptions } export { parserOptions }
export const DOMNodeTransforms: NodeTransform[] = [ export const DOMNodeTransforms: NodeTransform[] = [
transformStyle, transformStyle,
...(__DEV__ ? [transformTransition] : []), ...(__DEV__ ? [transformTransition, validateHtmlNesting] : []),
] ]
export const DOMDirectiveTransforms: Record<string, DirectiveTransform> = { export const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {

View File

@ -0,0 +1,27 @@
import {
type CompilerError,
ElementTypes,
type NodeTransform,
NodeTypes,
} from '@vue/compiler-core'
import { isValidHTMLNesting } from '../htmlNesting'
export const validateHtmlNesting: NodeTransform = (node, context) => {
if (
node.type === NodeTypes.ELEMENT &&
node.tagType === ElementTypes.ELEMENT &&
context.parent &&
context.parent.type === NodeTypes.ELEMENT &&
context.parent.tagType === ElementTypes.ELEMENT &&
!isValidHTMLNesting(context.parent.tag, node.tag)
) {
const error = new SyntaxError(
`<${node.tag}> cannot be child of <${context.parent.tag}>, ` +
'according to HTML specifications. ' +
'This can cause hydration errors or ' +
'potentially disrupt future functionality.',
) as CompilerError
error.loc = node.loc
context.onWarn(error)
}
}

View File

@ -597,11 +597,29 @@ const props = defineProps({ foo: String })
foo: Foo foo: Foo
}>() }>()
</script>`, </script>`,
{
propsDestructure: false,
},
) )
expect(content).toMatch(`const { foo } = __props`) expect(content).toMatch(`const { foo } = __props`)
assertCode(content) assertCode(content)
}) })
test('prohibiting reactive destructure', () => {
expect(() =>
compile(
`<script setup lang="ts">
const { foo } = defineProps<{
foo: Foo
}>()
</script>`,
{
propsDestructure: 'error',
},
),
).toThrow()
})
describe('errors', () => { describe('errors', () => {
test('w/ both type and non-type args', () => { test('w/ both type and non-type args', () => {
expect(() => { expect(() => {

View File

@ -6,7 +6,6 @@ describe('sfc reactive props destructure', () => {
function compile(src: string, options?: Partial<SFCScriptCompileOptions>) { function compile(src: string, options?: Partial<SFCScriptCompileOptions>) {
return compileSFCScript(src, { return compileSFCScript(src, {
inlineTemplate: true, inlineTemplate: true,
propsDestructure: true,
...options, ...options,
}) })
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compiler-sfc", "name": "@vue/compiler-sfc",
"version": "3.4.25", "version": "3.5.0-alpha.1",
"description": "@vue/compiler-sfc", "description": "@vue/compiler-sfc",
"main": "dist/compiler-sfc.cjs.js", "main": "dist/compiler-sfc.cjs.js",
"module": "dist/compiler-sfc.esm-browser.js", "module": "dist/compiler-sfc.esm-browser.js",

View File

@ -106,10 +106,11 @@ export interface SFCScriptCompileOptions {
*/ */
hoistStatic?: boolean hoistStatic?: boolean
/** /**
* (**Experimental**) Enable reactive destructure for `defineProps` * Set to `false` to disable reactive destructure for `defineProps` (pre-3.5
* @default false * behavior), or set to `'error'` to throw hard error on props destructures.
* @default true
*/ */
propsDestructure?: boolean propsDestructure?: boolean | 'error'
/** /**
* File system access methods to be used when resolving types * File system access methods to be used when resolving types
* imported in SFC macros. Defaults to ts.sys in Node.js, can be overwritten * imported in SFC macros. Defaults to ts.sys in Node.js, can be overwritten

View File

@ -22,23 +22,17 @@ import { genPropsAccessExp } from '@vue/shared'
import { isCallOf, resolveObjectKey } from './utils' import { isCallOf, resolveObjectKey } from './utils'
import type { ScriptCompileContext } from './context' import type { ScriptCompileContext } from './context'
import { DEFINE_PROPS } from './defineProps' import { DEFINE_PROPS } from './defineProps'
import { warnOnce } from '../warn'
export function processPropsDestructure( export function processPropsDestructure(
ctx: ScriptCompileContext, ctx: ScriptCompileContext,
declId: ObjectPattern, declId: ObjectPattern,
) { ) {
if (!ctx.options.propsDestructure) { if (ctx.options.propsDestructure === 'error') {
ctx.error(`Props destructure is explicitly prohibited via config.`, declId)
} else if (ctx.options.propsDestructure === false) {
return return
} }
warnOnce(
`This project is using reactive props destructure, which is an experimental ` +
`feature. It may receive breaking changes or be removed in the future, so ` +
`use at your own risk.\n` +
`To stay updated, follow the RFC at https://github.com/vuejs/rfcs/discussions/502.`,
)
ctx.propsDestructureDecl = declId ctx.propsDestructureDecl = declId
const registerBinding = ( const registerBinding = (
@ -104,7 +98,7 @@ export function transformDestructuredProps(
ctx: ScriptCompileContext, ctx: ScriptCompileContext,
vueImportAliases: Record<string, string>, vueImportAliases: Record<string, string>,
) { ) {
if (!ctx.options.propsDestructure) { if (ctx.options.propsDestructure === false) {
return return
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compiler-ssr", "name": "@vue/compiler-ssr",
"version": "3.4.25", "version": "3.5.0-alpha.1",
"description": "@vue/compiler-ssr", "description": "@vue/compiler-ssr",
"main": "dist/compiler-ssr.cjs.js", "main": "dist/compiler-ssr.cjs.js",
"types": "dist/compiler-ssr.d.ts", "types": "dist/compiler-ssr.d.ts",

View File

@ -1,4 +1,4 @@
import { defineComponent } from 'vue' import { type DefineComponent, type Directive, defineComponent } from 'vue'
import { expectType } from './utils' import { expectType } from './utils'
declare module 'vue' { declare module 'vue' {
@ -6,6 +6,14 @@ declare module 'vue' {
test?(n: number): void test?(n: number): void
} }
interface GlobalDirectives {
test: Directive
}
interface GlobalComponents {
RouterView: DefineComponent<{}>
}
interface ComponentCustomProperties { interface ComponentCustomProperties {
state?: 'stopped' | 'running' state?: 'stopped' | 'running'
} }
@ -46,6 +54,8 @@ export const Custom = defineComponent({
}, },
}) })
expectType<Directive>(Custom.directives!.test)
expectType<DefineComponent<{}>>(Custom.components!.RouterView)
expectType<JSX.Element>(<Custom baz={1} />) expectType<JSX.Element>(<Custom baz={1} />)
expectType<JSX.Element>(<Custom custom={1} baz={1} />) expectType<JSX.Element>(<Custom custom={1} baz={1} />)
expectType<JSX.Element>(<Custom bar="bar" baz={1} />) expectType<JSX.Element>(<Custom bar="bar" baz={1} />)

View File

@ -15,7 +15,7 @@ import {
withKeys, withKeys,
withModifiers, withModifiers,
} from 'vue' } from 'vue'
import { type IsUnion, describe, expectType } from './utils' import { type IsAny, type IsUnion, describe, expectType } from './utils'
describe('with object props', () => { describe('with object props', () => {
interface ExpectedProps { interface ExpectedProps {
@ -1501,18 +1501,108 @@ describe('should work when props type is incompatible with setup returned type '
describe('withKeys and withModifiers as pro', () => { describe('withKeys and withModifiers as pro', () => {
const onKeydown = withKeys(e => {}, ['']) const onKeydown = withKeys(e => {}, [''])
// @ts-expect-error invalid modifiers
const onClick = withModifiers(e => {}, ['']) const onClick = withModifiers(e => {}, [''])
;<input onKeydown={onKeydown} onClick={onClick} /> ;<input onKeydown={onKeydown} onClick={onClick} />
}) })
// #3367 expose components types
describe('expose component types', () => {
const child = defineComponent({
props: {
a: String,
},
})
const parent = defineComponent({
components: {
child,
child2: {
template: `<div></div>`,
},
},
})
expectType<typeof child>(parent.components!.child)
expectType<Component>(parent.components!.child2)
// global components
expectType<Readonly<KeepAliveProps>>(
new parent.components!.KeepAlive().$props,
)
expectType<Readonly<KeepAliveProps>>(new child.components!.KeepAlive().$props)
// runtime-dom components
expectType<Readonly<TransitionProps>>(
new parent.components!.Transition().$props,
)
expectType<Readonly<TransitionProps>>(
new child.components!.Transition().$props,
)
})
describe('directive typing', () => {
const customDirective: Directive = {
created(_) {},
}
const comp = defineComponent({
props: {
a: String,
},
directives: {
customDirective,
localDirective: {
created(_, { arg }) {
expectType<string | undefined>(arg)
},
},
},
})
expectType<typeof customDirective>(comp.directives!.customDirective)
expectType<Directive>(comp.directives!.localDirective)
// global directive
expectType<typeof vShow>(comp.directives!.vShow)
})
describe('expose typing', () => {
const Comp = defineComponent({
expose: ['a', 'b'],
props: {
some: String,
},
data() {
return { a: 1, b: '2', c: 1 }
},
})
expectType<Array<'a' | 'b'>>(Comp.expose!)
const vm = new Comp()
// internal should still be exposed
vm.$props
expectType<number>(vm.a)
expectType<string>(vm.b)
// @ts-expect-error shouldn't be exposed
vm.c
})
import type { import type {
AllowedComponentProps, AllowedComponentProps,
ComponentCustomProps, ComponentCustomProps,
ComponentOptionsMixin, ComponentOptionsMixin,
DefineComponent, DefineComponent,
Directive,
EmitsOptions, EmitsOptions,
ExtractPropTypes, ExtractPropTypes,
KeepAliveProps,
TransitionProps,
VNodeProps, VNodeProps,
vShow,
} from 'vue' } from 'vue'
// code generated by tsc / vue-tsc, make sure this continues to work // code generated by tsc / vue-tsc, make sure this continues to work
@ -1533,3 +1623,146 @@ declare const MyButton: DefineComponent<
{} {}
> >
;<MyButton class="x" /> ;<MyButton class="x" />
describe('__typeProps backdoor for union type for conditional props', () => {
interface CommonProps {
size?: 'xl' | 'l' | 'm' | 's' | 'xs'
}
type ConditionalProps =
| {
color?: 'normal' | 'primary' | 'secondary'
appearance?: 'normal' | 'outline' | 'text'
}
| {
color: 'white'
appearance: 'outline'
}
type Props = CommonProps & ConditionalProps
const Comp = defineComponent({
__typeProps: {} as Props,
})
// @ts-expect-error
;<Comp color="white" />
// @ts-expect-error
;<Comp color="white" appearance="normal" />
;<Comp color="white" appearance="outline" />
const c = new Comp()
// @ts-expect-error
c.$props = { color: 'white' }
// @ts-expect-error
c.$props = { color: 'white', appearance: 'text' }
c.$props = { color: 'white', appearance: 'outline' }
})
describe('__typeEmits backdoor, 3.3+ object syntax', () => {
type Emits = {
change: [id: number]
update: [value: string]
}
const Comp = defineComponent({
__typeEmits: {} as Emits,
mounted() {
this.$props.onChange?.(123)
// @ts-expect-error
this.$props.onChange?.('123')
this.$props.onUpdate?.('foo')
// @ts-expect-error
this.$props.onUpdate?.(123)
// @ts-expect-error
this.$emit('foo')
this.$emit('change', 123)
// @ts-expect-error
this.$emit('change', '123')
this.$emit('update', 'test')
// @ts-expect-error
this.$emit('update', 123)
},
})
;<Comp onChange={id => id.toFixed(2)} />
;<Comp onUpdate={id => id.toUpperCase()} />
// @ts-expect-error
;<Comp onChange={id => id.slice(1)} />
// @ts-expect-error
;<Comp onUpdate={id => id.toFixed(2)} />
const c = new Comp()
// @ts-expect-error
c.$emit('foo')
c.$emit('change', 123)
// @ts-expect-error
c.$emit('change', '123')
c.$emit('update', 'test')
// @ts-expect-error
c.$emit('update', 123)
})
describe('__typeEmits backdoor, call signature syntax', () => {
type Emits = {
(e: 'change', id: number): void
(e: 'update', value: string): void
}
const Comp = defineComponent({
__typeEmits: {} as Emits,
mounted() {
this.$props.onChange?.(123)
// @ts-expect-error
this.$props.onChange?.('123')
this.$props.onUpdate?.('foo')
// @ts-expect-error
this.$props.onUpdate?.(123)
// @ts-expect-error
this.$emit('foo')
this.$emit('change', 123)
// @ts-expect-error
this.$emit('change', '123')
this.$emit('update', 'test')
// @ts-expect-error
this.$emit('update', 123)
},
})
;<Comp onChange={id => id.toFixed(2)} />
;<Comp onUpdate={id => id.toUpperCase()} />
// @ts-expect-error
;<Comp onChange={id => id.slice(1)} />
// @ts-expect-error
;<Comp onUpdate={id => id.toFixed(2)} />
const c = new Comp()
// @ts-expect-error
c.$emit('foo')
c.$emit('change', 123)
// @ts-expect-error
c.$emit('change', '123')
c.$emit('update', 'test')
// @ts-expect-error
c.$emit('update', 123)
})
defineComponent({
props: {
foo: [String, null],
},
setup(props) {
expectType<IsAny<typeof props.foo>>(false)
expectType<string | null | undefined>(props.foo)
},
})

View File

@ -0,0 +1,58 @@
import { type Directive, type ObjectDirective, vModelText } from 'vue'
import { describe, expectType } from './utils'
type ExtractBinding<T> = T extends (
el: any,
binding: infer B,
vnode: any,
prev: any,
) => any
? B
: never
declare function testDirective<
Value,
Modifiers extends string = string,
Arg extends string = string,
>(): ExtractBinding<Directive<any, Value, Modifiers, Arg>>
describe('vmodel', () => {
expectType<ObjectDirective<any, any, 'trim' | 'number' | 'lazy', string>>(
vModelText,
)
// @ts-expect-error
expectType<ObjectDirective<any, any, 'not-valid', string>>(vModelText)
})
describe('custom', () => {
expectType<{
value: number
oldValue: number | null
arg?: 'Arg'
modifiers: Record<'a' | 'b', boolean>
}>(testDirective<number, 'a' | 'b', 'Arg'>())
expectType<{
value: number
oldValue: number | null
arg?: 'Arg'
modifiers: Record<'a' | 'b', boolean>
// @ts-expect-error
}>(testDirective<number, 'a', 'Arg'>())
expectType<{
value: number
oldValue: number | null
arg?: 'Arg'
modifiers: Record<'a' | 'b', boolean>
// @ts-expect-error
}>(testDirective<number, 'a' | 'b', 'Argx'>())
expectType<{
value: number
oldValue: number | null
arg?: 'Arg'
modifiers: Record<'a' | 'b', boolean>
// @ts-expect-error
}>(testDirective<string, 'a' | 'b', 'Arg'>())
})

View File

@ -769,6 +769,32 @@ describe('reactivity/effect', () => {
]) ])
}) })
it('debug: the call sequence of onTrack', () => {
const seq: number[] = []
const s = ref(0)
const track1 = () => seq.push(1)
const track2 = () => seq.push(2)
effect(
() => {
s.value
},
{
onTrack: track1,
},
)
effect(
() => {
s.value
},
{
onTrack: track2,
},
)
expect(seq.toString()).toBe('1,2')
})
it('events: onTrigger', () => { it('events: onTrigger', () => {
let events: DebuggerEvent[] = [] let events: DebuggerEvent[] = []
let dummy let dummy
@ -807,6 +833,51 @@ describe('reactivity/effect', () => {
}) })
}) })
it('debug: the call sequence of onTrigger', () => {
const seq: number[] = []
const s = ref(0)
const trigger1 = () => seq.push(1)
const trigger2 = () => seq.push(2)
const trigger3 = () => seq.push(3)
const trigger4 = () => seq.push(4)
effect(
() => {
s.value
},
{
onTrigger: trigger1,
},
)
effect(
() => {
s.value
effect(
() => {
s.value
effect(
() => {
s.value
},
{
onTrigger: trigger4,
},
)
},
{
onTrigger: trigger3,
},
)
},
{
onTrigger: trigger2,
},
)
s.value++
expect(seq.toString()).toBe('1,2,3,4')
})
it('stop', () => { it('stop', () => {
let dummy let dummy
const obj = reactive({ prop: 1 }) const obj = reactive({ prop: 1 })

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/reactivity", "name": "@vue/reactivity",
"version": "3.4.25", "version": "3.5.0-alpha.1",
"description": "@vue/reactivity", "description": "@vue/reactivity",
"main": "index.js", "main": "index.js",
"module": "dist/reactivity.esm-bundler.js", "module": "dist/reactivity.esm-bundler.js",

View File

@ -27,12 +27,23 @@ export class Dep {
* Link between this dep and the current active effect * Link between this dep and the current active effect
*/ */
activeLink?: Link = undefined activeLink?: Link = undefined
/** /**
* Doubly linked list representing the subscribing effects (tail) * Doubly linked list representing the subscribing effects (tail)
*/ */
subs?: Link = undefined subs?: Link = undefined
constructor(public computed?: ComputedRefImpl) {} /**
* Doubly linked list representing the subscribing effects (head)
* DEV only, for invoking onTrigger hooks in correct order
*/
subsHead?: Link
constructor(public computed?: ComputedRefImpl) {
if (__DEV__) {
this.subsHead = undefined
}
}
track(debugInfo?: DebuggerEventExtraInfo): Link | undefined { track(debugInfo?: DebuggerEventExtraInfo): Link | undefined {
if (!activeSub || !shouldTrack) { if (!activeSub || !shouldTrack) {
@ -113,21 +124,28 @@ export class Dep {
notify(debugInfo?: DebuggerEventExtraInfo) { notify(debugInfo?: DebuggerEventExtraInfo) {
startBatch() startBatch()
try { try {
for (let link = this.subs; link; link = link.prevSub) { if (__DEV__) {
// subs are notified and batched in reverse-order and then invoked in
// original order at the end of the batch, but onTrigger hooks should
// be invoked in original order here.
for (let head = this.subsHead; head; head = head.nextSub) {
if ( if (
__DEV__ && __DEV__ &&
link.sub.onTrigger && head.sub.onTrigger &&
!(link.sub.flags & EffectFlags.NOTIFIED) !(head.sub.flags & EffectFlags.NOTIFIED)
) { ) {
link.sub.onTrigger( head.sub.onTrigger(
extend( extend(
{ {
effect: link.sub, effect: head.sub,
}, },
debugInfo, debugInfo,
), ),
) )
} }
}
}
for (let link = this.subs; link; link = link.prevSub) {
link.sub.notify() link.sub.notify()
} }
} finally { } finally {
@ -152,6 +170,11 @@ function addSub(link: Link) {
link.prevSub = currentTail link.prevSub = currentTail
if (currentTail) currentTail.nextSub = link if (currentTail) currentTail.nextSub = link
} }
if (__DEV__ && link.dep.subsHead === undefined) {
link.dep.subsHead = link
}
link.dep.subs = link link.dep.subs = link
} }

View File

@ -375,13 +375,14 @@ export function refreshComputed(computed: ComputedRefImpl) {
} }
} catch (err) { } catch (err) {
dep.version++ dep.version++
} throw err
} finally {
activeSub = prevSub activeSub = prevSub
shouldTrack = prevShouldTrack shouldTrack = prevShouldTrack
cleanupDeps(computed) cleanupDeps(computed)
computed.flags &= ~EffectFlags.RUNNING computed.flags &= ~EffectFlags.RUNNING
} }
}
function removeSub(link: Link) { function removeSub(link: Link) {
const { dep, prevSub, nextSub } = link const { dep, prevSub, nextSub } = link

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/runtime-core", "name": "@vue/runtime-core",
"version": "3.4.25", "version": "3.5.0-alpha.1",
"description": "@vue/runtime-core", "description": "@vue/runtime-core",
"main": "index.js", "main": "index.js",
"module": "dist/runtime-core.esm-bundler.js", "module": "dist/runtime-core.esm-bundler.js",

View File

@ -3,16 +3,17 @@ import type {
ComponentOptions, ComponentOptions,
ComponentOptionsBase, ComponentOptionsBase,
ComponentOptionsMixin, ComponentOptionsMixin,
ComponentOptionsWithArrayProps, ComponentProvideOptions,
ComponentOptionsWithObjectProps,
ComponentOptionsWithoutProps,
ComputedOptions, ComputedOptions,
MethodOptions, MethodOptions,
RenderFunction, RenderFunction,
} from './componentOptions' } from './componentOptions'
import type { import type {
AllowedComponentProps, AllowedComponentProps,
Component,
ComponentCustomProps, ComponentCustomProps,
GlobalComponents,
GlobalDirectives,
SetupContext, SetupContext,
} from './component' } from './component'
import type { import type {
@ -21,7 +22,11 @@ import type {
ExtractDefaultPropTypes, ExtractDefaultPropTypes,
ExtractPropTypes, ExtractPropTypes,
} from './componentProps' } from './componentProps'
import type { EmitsOptions, EmitsToProps } from './componentEmits' import type {
EmitsOptions,
EmitsToProps,
TypeEmitsToOptions,
} from './componentEmits'
import { extend, isFunction } from '@vue/shared' import { extend, isFunction } from '@vue/shared'
import type { VNodeProps } from './vnode' import type { VNodeProps } from './vnode'
import type { import type {
@ -29,6 +34,8 @@ import type {
CreateComponentPublicInstance, CreateComponentPublicInstance,
} from './componentPublicInstance' } from './componentPublicInstance'
import type { SlotsType } from './componentSlots' import type { SlotsType } from './componentSlots'
import type { Directive } from './directives'
import type { ComponentTypeEmits } from './apiSetupHelpers'
export type PublicProps = VNodeProps & export type PublicProps = VNodeProps &
AllowedComponentProps & AllowedComponentProps &
@ -55,6 +62,11 @@ export type DefineComponent<
Props = ResolveProps<PropsOrPropOptions, E>, Props = ResolveProps<PropsOrPropOptions, E>,
Defaults = ExtractDefaultPropTypes<PropsOrPropOptions>, Defaults = ExtractDefaultPropTypes<PropsOrPropOptions>,
S extends SlotsType = {}, S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
MakeDefaultsOptional extends boolean = true,
> = ComponentPublicInstanceConstructor< > = ComponentPublicInstanceConstructor<
CreateComponentPublicInstance< CreateComponentPublicInstance<
Props, Props,
@ -67,9 +79,12 @@ export type DefineComponent<
E, E,
PP & Props, PP & Props,
Defaults, Defaults,
true, MakeDefaultsOptional,
{}, {},
S S,
LC & GlobalComponents,
Directives & GlobalDirectives,
Exposed
> >
> & > &
ComponentOptionsBase< ComponentOptionsBase<
@ -85,7 +100,11 @@ export type DefineComponent<
Defaults, Defaults,
{}, {},
string, string,
S S,
LC & GlobalComponents,
Directives & GlobalDirectives,
Exposed,
Provide
> & > &
PP PP
@ -153,147 +172,114 @@ export function defineComponent<
}, },
): DefineSetupFnComponent<Props, E, S> ): DefineSetupFnComponent<Props, E, S>
// overload 2: object format with no props // overload 2: defineComponent with options object, infer props from options
// (uses user defined props interface)
// return type is for Vetur and TSX support
export function defineComponent< export function defineComponent<
Props = {}, // props
RawBindings = {}, TypeProps,
D = {}, RuntimePropsOptions extends
C extends ComputedOptions = {}, ComponentObjectPropsOptions = ComponentObjectPropsOptions,
M extends MethodOptions = {}, RuntimePropsKeys extends string = string,
// emits
TypeEmits extends ComponentTypeEmits = {},
RuntimeEmitsOptions extends EmitsOptions = {},
RuntimeEmitsKeys extends string = string,
// other options
Data = {},
SetupBindings = {},
Computed extends ComputedOptions = {},
Methods extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {}, InjectOptions extends ComponentInjectOptions = {},
EE extends string = string, InjectKeys extends string = string,
S extends SlotsType = {}, Slots extends SlotsType = {},
I extends ComponentInjectOptions = {}, LocalComponents extends Record<string, Component> = {},
II extends string = string, Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
// resolved types
ResolvedEmits extends EmitsOptions = {} extends RuntimeEmitsOptions
? TypeEmitsToOptions<TypeEmits>
: RuntimeEmitsOptions,
InferredProps = unknown extends TypeProps
? string extends RuntimePropsKeys
? ComponentObjectPropsOptions extends RuntimePropsOptions
? {}
: ExtractPropTypes<RuntimePropsOptions>
: { [key in RuntimePropsKeys]?: any }
: TypeProps,
ResolvedProps = Readonly<InferredProps & EmitsToProps<ResolvedEmits>>,
>( >(
options: ComponentOptionsWithoutProps< options: {
Props, props?: (RuntimePropsOptions & ThisType<void>) | RuntimePropsKeys[]
RawBindings, /**
D, * @private for language-tools use only
C, */
M, __typeProps?: TypeProps
/**
* @private for language-tools use only
*/
__typeEmits?: TypeEmits
} & ComponentOptionsBase<
ResolvedProps,
SetupBindings,
Data,
Computed,
Methods,
Mixin, Mixin,
Extends, Extends,
E, RuntimeEmitsOptions,
EE, RuntimeEmitsKeys,
I, {}, // Defaults
II, InjectOptions,
S InjectKeys,
>, Slots,
): DefineComponent< LocalComponents,
Props, Directives,
RawBindings, Exposed,
D, Provide
C, > &
M, ThisType<
CreateComponentPublicInstance<
ResolvedProps,
SetupBindings,
Data,
Computed,
Methods,
Mixin, Mixin,
Extends, Extends,
E, ResolvedEmits,
EE, RuntimeEmitsKeys,
PublicProps, {},
ResolveProps<Props, E>, false,
ExtractDefaultPropTypes<Props>, InjectOptions,
S Slots,
LocalComponents,
Directives,
Exposed
> >
// overload 3: object format with array props declaration
// props inferred as { [key in PropNames]?: any }
// return type is for Vetur and TSX support
export function defineComponent<
PropNames extends string,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {},
II extends string = string,
Props = Readonly<{ [key in PropNames]?: any }>,
>(
options: ComponentOptionsWithArrayProps<
PropNames,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
I,
II,
S
>, >,
): DefineComponent< ): DefineComponent<
Props, InferredProps,
RawBindings, SetupBindings,
D, Data,
C, Computed,
M, Methods,
Mixin, Mixin,
Extends, Extends,
E, ResolvedEmits,
EE, RuntimeEmitsKeys,
PublicProps, PublicProps,
ResolveProps<Props, E>, ResolvedProps,
ExtractDefaultPropTypes<Props>, ExtractDefaultPropTypes<RuntimePropsOptions>,
S Slots,
> LocalComponents,
Directives,
// overload 4: object format with object props declaration Exposed,
// see `ExtractPropTypes` in ./componentProps.ts Provide,
export function defineComponent< // MakeDefaultsOptional - if TypeProps is provided, set to false to use
// the Readonly constraint allows TS to treat the type of { required: true } // user props types verbatim
// as constant instead of boolean. unknown extends TypeProps ? true : false
PropsOptions extends Readonly<ComponentPropsOptions>,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {},
II extends string = string,
>(
options: ComponentOptionsWithObjectProps<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
I,
II,
S
>,
): DefineComponent<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
PublicProps,
ResolveProps<PropsOptions, E>,
ExtractDefaultPropTypes<PropsOptions>,
S
> >
// implementation, close to no-op // implementation, close to no-op

View File

@ -16,8 +16,8 @@ import {
} from './component' } from './component'
import type { EmitFn, EmitsOptions, ObjectEmitsOptions } from './componentEmits' import type { EmitFn, EmitsOptions, ObjectEmitsOptions } from './componentEmits'
import type { import type {
ComponentOptionsBase,
ComponentOptionsMixin, ComponentOptionsMixin,
ComponentOptionsWithoutProps,
ComputedOptions, ComputedOptions,
MethodOptions, MethodOptions,
} from './componentOptions' } from './componentOptions'
@ -135,9 +135,11 @@ export function defineEmits<EE extends string = string>(
export function defineEmits<E extends EmitsOptions = EmitsOptions>( export function defineEmits<E extends EmitsOptions = EmitsOptions>(
emitOptions: E, emitOptions: E,
): EmitFn<E> ): EmitFn<E>
export function defineEmits< export function defineEmits<T extends ComponentTypeEmits>(): T extends (
T extends ((...args: any[]) => any) | Record<string, any[]>, ...args: any[]
>(): T extends (...args: any[]) => any ? T : ShortEmits<T> ) => any
? T
: ShortEmits<T>
// implementation // implementation
export function defineEmits() { export function defineEmits() {
if (__DEV__) { if (__DEV__) {
@ -146,6 +148,10 @@ export function defineEmits() {
return null as any return null as any
} }
export type ComponentTypeEmits =
| ((...args: any[]) => any)
| Record<string, any[]>
type RecordToUnion<T extends Record<string, any>> = T[keyof T] type RecordToUnion<T extends Record<string, any>> = T[keyof T]
type ShortEmits<T extends Record<string, any>> = UnionToIntersection< type ShortEmits<T extends Record<string, any>> = UnionToIntersection<
@ -191,15 +197,33 @@ export function defineOptions<
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
>( >(
options?: ComponentOptionsWithoutProps< options?: ComponentOptionsBase<
{}, {},
RawBindings, RawBindings,
D, D,
C, C,
M, M,
Mixin, Mixin,
Extends Extends,
> & { emits?: undefined; expose?: undefined; slots?: undefined }, {}
> & {
/**
* props should be defined via defineProps().
*/
props: never
/**
* emits should be defined via defineEmits().
*/
emits?: never
/**
* expose should be defined via defineExpose().
*/
expose?: never
/**
* slots should be defined via defineSlots().
*/
slots?: never
},
): void { ): void {
if (__DEV__) { if (__DEV__) {
warnRuntimeUsage(`defineOptions`) warnRuntimeUsage(`defineOptions`)

View File

@ -87,6 +87,13 @@ import {
import type { SchedulerJob } from './scheduler' import type { SchedulerJob } from './scheduler'
import type { LifecycleHooks } from './enums' import type { LifecycleHooks } from './enums'
// Augment GlobalComponents
import type { TeleportProps } from './components/Teleport'
import type { SuspenseProps } from './components/Suspense'
import type { KeepAliveProps } from './components/KeepAlive'
import type { BaseTransitionProps } from './components/BaseTransition'
import type { DefineComponent } from './apiDefineComponent'
/** /**
* Public utility type for extracting the instance type of a component. * Public utility type for extracting the instance type of a component.
* Works with all valid component definition types. This is intended to replace * Works with all valid component definition types. This is intended to replace
@ -125,6 +132,45 @@ export type ComponentInstance<T> = T extends { new (): ComponentPublicInstance }
*/ */
export interface ComponentCustomProps {} export interface ComponentCustomProps {}
/**
* For globally defined Directives
* Here is an example of adding a directive `VTooltip` as global directive:
*
* @example
* ```ts
* import VTooltip from 'v-tooltip'
*
* declare module '@vue/runtime-core' {
* interface GlobalDirectives {
* VTooltip
* }
* }
* ```
*/
export interface GlobalDirectives extends Record<string, Directive> {}
/**
* For globally defined Components
* Here is an example of adding a component `RouterView` as global component:
*
* @example
* ```ts
* import { RouterView } from 'vue-router'
*
* declare module '@vue/runtime-core' {
* interface GlobalComponents {
* RouterView
* }
* }
* ```
*/
export interface GlobalComponents extends Record<string, Component> {
Teleport: DefineComponent<TeleportProps>
Suspense: DefineComponent<SuspenseProps>
KeepAlive: DefineComponent<KeepAliveProps>
BaseTransition: DefineComponent<BaseTransitionProps>
}
/** /**
* Default allowed non-declared props on component in TSX * Default allowed non-declared props on component in TSX
*/ */

View File

@ -1,5 +1,6 @@
import { import {
EMPTY_OBJ, EMPTY_OBJ,
type OverloadParameters,
type UnionToIntersection, type UnionToIntersection,
camelize, camelize,
extend, extend,
@ -28,6 +29,7 @@ import {
compatModelEmit, compatModelEmit,
compatModelEventPrefix, compatModelEventPrefix,
} from './compat/componentVModel' } from './compat/componentVModel'
import type { ComponentTypeEmits } from './apiSetupHelpers'
export type ObjectEmitsOptions = Record< export type ObjectEmitsOptions = Record<
string, string,
@ -36,7 +38,8 @@ export type ObjectEmitsOptions = Record<
export type EmitsOptions = ObjectEmitsOptions | string[] export type EmitsOptions = ObjectEmitsOptions | string[]
export type EmitsToProps<T extends EmitsOptions> = T extends string[] export type EmitsToProps<T extends EmitsOptions | ComponentTypeEmits> =
T extends string[]
? { ? {
[K in `on${Capitalize<T[number]>}`]?: (...args: any[]) => any [K in `on${Capitalize<T[number]>}`]?: (...args: any[]) => any
} }
@ -54,6 +57,23 @@ export type EmitsToProps<T extends EmitsOptions> = T extends string[]
} }
: {} : {}
export type TypeEmitsToOptions<T extends ComponentTypeEmits> =
T extends Record<string, any[]>
? {
[K in keyof T]: T[K] extends [...args: infer Args]
? (...args: Args) => any
: () => any
}
: T extends (...args: any[]) => any
? ParametersToFns<OverloadParameters<T>>
: {}
type ParametersToFns<T extends any[]> = {
[K in T[0]]: K extends `${infer C}`
? (...args: T extends [C, ...infer Args] ? Args : never) => any
: never
}
export type ShortEmitsToObject<E> = export type ShortEmitsToObject<E> =
E extends Record<string, any[]> E extends Record<string, any[]>
? { ? {

View File

@ -55,7 +55,11 @@ import type {
ExtractDefaultPropTypes, ExtractDefaultPropTypes,
ExtractPropTypes, ExtractPropTypes,
} from './componentProps' } from './componentProps'
import type { EmitsOptions, EmitsToProps } from './componentEmits' import type {
EmitsOptions,
EmitsToProps,
TypeEmitsToOptions,
} from './componentEmits'
import type { Directive } from './directives' import type { Directive } from './directives'
import { import {
type ComponentPublicInstance, type ComponentPublicInstance,
@ -77,7 +81,10 @@ import {
import type { OptionMergeFunction } from './apiCreateApp' import type { OptionMergeFunction } from './apiCreateApp'
import { LifecycleHooks } from './enums' import { LifecycleHooks } from './enums'
import type { SlotsType } from './componentSlots' import type { SlotsType } from './componentSlots'
import { normalizePropsOrEmits } from './apiSetupHelpers' import {
type ComponentTypeEmits,
normalizePropsOrEmits,
} from './apiSetupHelpers'
/** /**
* Interface for declaring custom options. * Interface for declaring custom options.
@ -113,7 +120,11 @@ export interface ComponentOptionsBase<
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
II extends string = string, II extends string = string,
S extends SlotsType = {}, S extends SlotsType = {},
> extends LegacyOptions<Props, D, C, M, Mixin, Extends, I, II>, LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
> extends LegacyOptions<Props, D, C, M, Mixin, Extends, I, II, Provide>,
ComponentInternalOptions, ComponentInternalOptions,
ComponentCustomOptions { ComponentCustomOptions {
setup?: ( setup?: (
@ -137,13 +148,16 @@ export interface ComponentOptionsBase<
// Luckily `render()` doesn't need any arguments nor does it care about return // Luckily `render()` doesn't need any arguments nor does it care about return
// type. // type.
render?: Function render?: Function
components?: Record<string, Component> // NOTE: extending both LC and Record<string, Component> allows objects to be forced
directives?: Record<string, Directive> // to be of type Component, while still inferring LC generic
components?: LC & Record<string, Component>
// NOTE: extending both Directives and Record<string, Directive> allows objects to be forced
// to be of type Directive, while still inferring Directives generic
directives?: Directives & Record<string, Directive>
inheritAttrs?: boolean inheritAttrs?: boolean
emits?: (E | EE[]) & ThisType<void> emits?: (E | EE[]) & ThisType<void>
slots?: S slots?: S
// TODO infer public instance type based on exposed keys expose?: Exposed[]
expose?: string[]
serverPrefetch?(): void | Promise<any> serverPrefetch?(): void | Promise<any>
// Runtime compiler only ----------------------------------------------------- // Runtime compiler only -----------------------------------------------------
@ -212,151 +226,6 @@ export interface RuntimeCompilerOptions {
delimiters?: [string, string] delimiters?: [string, string]
} }
export type ComponentOptionsWithoutProps<
Props = {},
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
PE = Props & EmitsToProps<E>,
> = ComponentOptionsBase<
PE,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
{},
I,
II,
S
> & {
props?: undefined
} & ThisType<
CreateComponentPublicInstance<
PE,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
PE,
{},
false,
I,
S
>
>
export type ComponentOptionsWithArrayProps<
PropNames extends string = string,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
Props = Prettify<Readonly<{ [key in PropNames]?: any } & EmitsToProps<E>>>,
> = ComponentOptionsBase<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
{},
I,
II,
S
> & {
props: PropNames[]
} & ThisType<
CreateComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
Props,
{},
false,
I,
S
>
>
export type ComponentOptionsWithObjectProps<
PropsOptions = ComponentObjectPropsOptions,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
Props = Prettify<Readonly<ExtractPropTypes<PropsOptions> & EmitsToProps<E>>>,
Defaults = ExtractDefaultPropTypes<PropsOptions>,
> = ComponentOptionsBase<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
Defaults,
I,
II,
S
> & {
props: PropsOptions & ThisType<void>
} & ThisType<
CreateComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
Props,
Defaults,
false,
I,
S
>
>
export type ComponentOptions< export type ComponentOptions<
Props = {}, Props = {},
RawBindings = any, RawBindings = any,
@ -366,7 +235,15 @@ export type ComponentOptions<
Mixin extends ComponentOptionsMixin = any, Mixin extends ComponentOptionsMixin = any,
Extends extends ComponentOptionsMixin = any, Extends extends ComponentOptionsMixin = any,
E extends EmitsOptions = any, E extends EmitsOptions = any,
S extends SlotsType = any, EE extends string = string,
Defaults = {},
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
> = ComponentOptionsBase< > = ComponentOptionsBase<
Props, Props,
RawBindings, RawBindings,
@ -376,8 +253,15 @@ export type ComponentOptions<
Mixin, Mixin,
Extends, Extends,
E, E,
string, EE,
S Defaults,
I,
II,
S,
LC,
Directives,
Exposed,
Provide
> & > &
ThisType< ThisType<
CreateComponentPublicInstance< CreateComponentPublicInstance<
@ -389,7 +273,13 @@ export type ComponentOptions<
Mixin, Mixin,
Extends, Extends,
E, E,
Readonly<Props> Readonly<Props>,
Defaults,
false,
I,
S,
LC,
Directives
> >
> >
@ -404,6 +294,12 @@ export type ComponentOptionsMixin = ComponentOptionsBase<
any, any,
any, any,
any, any,
any,
any,
any,
any,
any,
any,
any any
> >
@ -465,6 +361,7 @@ interface LegacyOptions<
Extends extends ComponentOptionsMixin, Extends extends ComponentOptionsMixin,
I extends ComponentInjectOptions, I extends ComponentInjectOptions,
II extends string, II extends string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
> { > {
compatConfig?: CompatConfig compatConfig?: CompatConfig
@ -498,7 +395,7 @@ interface LegacyOptions<
computed?: C computed?: C
methods?: M methods?: M
watch?: ComponentWatchOptions watch?: ComponentWatchOptions
provide?: ComponentProvideOptions provide?: Provide
inject?: I | II[] inject?: I | II[]
// assets // assets
@ -1199,3 +1096,203 @@ function mergeWatchOptions(
} }
return merged return merged
} }
// Deprecated legacy types, kept because they were previously exported ---------
/**
* @deprecated
*/
export type ComponentOptionsWithoutProps<
Props = {},
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
TE extends ComponentTypeEmits = {},
ResolvedEmits extends EmitsOptions = {} extends E
? TypeEmitsToOptions<TE>
: E,
PE = Props & EmitsToProps<ResolvedEmits>,
> = ComponentOptionsBase<
PE,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
{},
I,
II,
S,
LC,
Directives,
Exposed,
Provide
> & {
props?: never
/**
* @private for language-tools use only
*/
__typeProps?: Props
/**
* @private for language-tools use only
*/
__typeEmits?: TE
} & ThisType<
CreateComponentPublicInstance<
PE,
RawBindings,
D,
C,
M,
Mixin,
Extends,
ResolvedEmits,
EE,
{},
false,
I,
S,
LC,
Directives,
Exposed
>
>
/**
* @deprecated
*/
export type ComponentOptionsWithArrayProps<
PropNames extends string = string,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
Props = Prettify<Readonly<{ [key in PropNames]?: any } & EmitsToProps<E>>>,
> = ComponentOptionsBase<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
{},
I,
II,
S,
LC,
Directives,
Exposed,
Provide
> & {
props: PropNames[]
} & ThisType<
CreateComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
Props,
{},
false,
I,
S,
LC,
Directives,
Exposed
>
>
/**
* @deprecated
*/
export type ComponentOptionsWithObjectProps<
PropsOptions = ComponentObjectPropsOptions,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
Props = Prettify<Readonly<ExtractPropTypes<PropsOptions> & EmitsToProps<E>>>,
Defaults = ExtractDefaultPropTypes<PropsOptions>,
> = ComponentOptionsBase<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
Defaults,
I,
II,
S,
LC,
Directives,
Exposed,
Provide
> & {
props: PropsOptions & ThisType<void>
} & ThisType<
CreateComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
Props,
Defaults,
false,
I,
S,
LC,
Directives
>
>

View File

@ -67,7 +67,7 @@ export interface PropOptions<T = any, D = T> {
skipFactory?: boolean skipFactory?: boolean
} }
export type PropType<T> = PropConstructor<T> | PropConstructor<T>[] export type PropType<T> = PropConstructor<T> | (PropConstructor<T> | null)[]
type PropConstructor<T = any> = type PropConstructor<T = any> =
| { new (...args: any[]): T & {} } | { new (...args: any[]): T & {} }
@ -107,8 +107,10 @@ type DefaultKeys<T> = {
: never : never
}[keyof T] }[keyof T]
type InferPropType<T> = [T] extends [null] type InferPropType<T, NullAsAny = true> = [T] extends [null]
? any // null & true would fail to infer ? NullAsAny extends true
? any
: null
: [T] extends [{ type: null | true }] : [T] extends [{ type: null | true }]
? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean` ? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean`
: [T] extends [ObjectConstructor | { type: ObjectConstructor }] : [T] extends [ObjectConstructor | { type: ObjectConstructor }]
@ -119,8 +121,8 @@ type InferPropType<T> = [T] extends [null]
? Date ? Date
: [T] extends [(infer U)[] | { type: (infer U)[] }] : [T] extends [(infer U)[] | { type: (infer U)[] }]
? U extends DateConstructor ? U extends DateConstructor
? Date | InferPropType<U> ? Date | InferPropType<U, false>
: InferPropType<U> : InferPropType<U, false>
: [T] extends [Prop<infer V, infer D>] : [T] extends [Prop<infer V, infer D>]
? unknown extends V ? unknown extends V
? IfAny<V, V, D> ? IfAny<V, V, D>
@ -594,7 +596,7 @@ function validatePropName(key: string) {
// use function string name to check type constructors // use function string name to check type constructors
// so that it works across vms / iframes. // so that it works across vms / iframes.
function getType(ctor: Prop<any>): string { function getType(ctor: Prop<any> | null): string {
// Early return for null to avoid unnecessary computations // Early return for null to avoid unnecessary computations
if (ctor === null) { if (ctor === null) {
return 'null' return 'null'
@ -614,7 +616,7 @@ function getType(ctor: Prop<any>): string {
return '' return ''
} }
function isSameType(a: Prop<any>, b: Prop<any>): boolean { function isSameType(a: Prop<any> | null, b: Prop<any> | null): boolean {
return getType(a) === getType(b) return getType(a) === getType(b)
} }
@ -707,24 +709,27 @@ type AssertionResult = {
/** /**
* dev only * dev only
*/ */
function assertType(value: unknown, type: PropConstructor): AssertionResult { function assertType(
value: unknown,
type: PropConstructor | null,
): AssertionResult {
let valid let valid
const expectedType = getType(type) const expectedType = getType(type)
if (isSimpleType(expectedType)) { if (expectedType === 'null') {
valid = value === null
} else if (isSimpleType(expectedType)) {
const t = typeof value const t = typeof value
valid = t === expectedType.toLowerCase() valid = t === expectedType.toLowerCase()
// for primitive wrapper objects // for primitive wrapper objects
if (!valid && t === 'object') { if (!valid && t === 'object') {
valid = value instanceof type valid = value instanceof (type as PropConstructor)
} }
} else if (expectedType === 'Object') { } else if (expectedType === 'Object') {
valid = isObject(value) valid = isObject(value)
} else if (expectedType === 'Array') { } else if (expectedType === 'Array') {
valid = isArray(value) valid = isArray(value)
} else if (expectedType === 'null') {
valid = value === null
} else { } else {
valid = value instanceof type valid = value instanceof (type as PropConstructor)
} }
return { return {
valid, valid,

View File

@ -1,4 +1,5 @@
import { import {
type Component,
type ComponentInternalInstance, type ComponentInternalInstance,
getExposeProxy, getExposeProxy,
isStatefulComponent, isStatefulComponent,
@ -35,6 +36,7 @@ import {
type ComponentInjectOptions, type ComponentInjectOptions,
type ComponentOptionsBase, type ComponentOptionsBase,
type ComponentOptionsMixin, type ComponentOptionsMixin,
type ComponentProvideOptions,
type ComputedOptions, type ComputedOptions,
type ExtractComputedReturns, type ExtractComputedReturns,
type InjectToObject, type InjectToObject,
@ -51,6 +53,7 @@ import { markAttrsAccessed } from './componentRenderUtils'
import { currentRenderingInstance } from './componentRenderContext' import { currentRenderingInstance } from './componentRenderContext'
import { warn } from './warning' import { warn } from './warning'
import { installCompatInstanceProperties } from './compat/instance' import { installCompatInstanceProperties } from './compat/instance'
import type { Directive } from './directives'
/** /**
* Custom properties added to component instances in any way and can be accessed through `this` * Custom properties added to component instances in any way and can be accessed through `this`
@ -99,6 +102,10 @@ type MixinToOptionTypes<T> =
infer Defaults, infer Defaults,
any, any,
any, any,
any,
any,
any,
any,
any any
> >
? OptionTypesType<P & {}, B & {}, D & {}, C & {}, M & {}, Defaults & {}> & ? OptionTypesType<P & {}, B & {}, D & {}, C & {}, M & {}, Defaults & {}> &
@ -157,6 +164,9 @@ export type CreateComponentPublicInstance<
MakeDefaultsOptional extends boolean = false, MakeDefaultsOptional extends boolean = false,
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
S extends SlotsType = {}, S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
PublicMixin = IntersectionMixin<Mixin> & IntersectionMixin<Extends>, PublicMixin = IntersectionMixin<Mixin> & IntersectionMixin<Extends>,
PublicP = UnwrapMixinsType<PublicMixin, 'P'> & EnsureNonVoid<P>, PublicP = UnwrapMixinsType<PublicMixin, 'P'> & EnsureNonVoid<P>,
PublicB = UnwrapMixinsType<PublicMixin, 'B'> & EnsureNonVoid<B>, PublicB = UnwrapMixinsType<PublicMixin, 'B'> & EnsureNonVoid<B>,
@ -167,6 +177,7 @@ export type CreateComponentPublicInstance<
EnsureNonVoid<M>, EnsureNonVoid<M>,
PublicDefaults = UnwrapMixinsType<PublicMixin, 'Defaults'> & PublicDefaults = UnwrapMixinsType<PublicMixin, 'Defaults'> &
EnsureNonVoid<Defaults>, EnsureNonVoid<Defaults>,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
> = ComponentPublicInstance< > = ComponentPublicInstance<
PublicP, PublicP,
PublicB, PublicB,
@ -190,11 +201,22 @@ export type CreateComponentPublicInstance<
Defaults, Defaults,
{}, {},
string, string,
S S,
LC,
Directives,
Exposed,
Provide
>, >,
I, I,
S S,
Exposed
> >
export type ExposedKeys<
T,
Exposed extends string & keyof T,
> = '' extends Exposed ? T : Pick<T, Exposed>
// public properties exposed on the proxy, which is used as the render context // public properties exposed on the proxy, which is used as the render context
// in templates (as `this` in the render option) // in templates (as `this` in the render option)
export type ComponentPublicInstance< export type ComponentPublicInstance<
@ -210,6 +232,7 @@ export type ComponentPublicInstance<
Options = ComponentOptionsBase<any, any, any, any, any, any, any, any, any>, Options = ComponentOptionsBase<any, any, any, any, any, any, any, any, any>,
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
S extends SlotsType = {}, S extends SlotsType = {},
Exposed extends string = '',
> = { > = {
$: ComponentInternalInstance $: ComponentInternalInstance
$data: D $data: D
@ -233,13 +256,16 @@ export type ComponentPublicInstance<
: (...args: any) => any, : (...args: any) => any,
options?: WatchOptions, options?: WatchOptions,
): WatchStopHandle ): WatchStopHandle
} & IfAny<P, P, Omit<P, keyof ShallowUnwrapRef<B>>> & } & ExposedKeys<
IfAny<P, P, Omit<P, keyof ShallowUnwrapRef<B>>> &
ShallowUnwrapRef<B> & ShallowUnwrapRef<B> &
UnwrapNestedRefs<D> & UnwrapNestedRefs<D> &
ExtractComputedReturns<C> & ExtractComputedReturns<C> &
M & M &
ComponentCustomProperties & ComponentCustomProperties &
InjectToObject<I> InjectToObject<I>,
Exposed
>
export type PublicPropertiesMap = Record< export type PublicPropertiesMap = Record<
string, string,

View File

@ -18,6 +18,7 @@ import { SchedulerJobFlags, toRaw } from '@vue/reactivity'
import { ErrorCodes, callWithAsyncErrorHandling } from '../errorHandling' import { ErrorCodes, callWithAsyncErrorHandling } from '../errorHandling'
import { PatchFlags, ShapeFlags, isArray, isFunction } from '@vue/shared' import { PatchFlags, ShapeFlags, isArray, isFunction } from '@vue/shared'
import { onBeforeUnmount, onMounted } from '../apiLifecycle' import { onBeforeUnmount, onMounted } from '../apiLifecycle'
import { isTeleport } from './Teleport'
import type { RendererElement } from '../renderer' import type { RendererElement } from '../renderer'
type Hook<T = () => void> = T | T[] type Hook<T = () => void> = T | T[]
@ -151,27 +152,7 @@ const BaseTransitionImpl: ComponentOptions = {
return return
} }
let child: VNode = children[0] const child: VNode = findNonCommentChild(children)
if (children.length > 1) {
let hasFound = false
// locate first non-comment child
for (const c of children) {
if (c.type !== Comment) {
if (__DEV__ && hasFound) {
// warn more than one non-comment child
warn(
'<transition> can only be used on a single element or component. ' +
'Use <transition-group> for lists.',
)
break
}
child = c
hasFound = true
if (!__DEV__) break
}
}
}
// there's no need to track reactivity for these props so use the raw // there's no need to track reactivity for these props so use the raw
// props for a bit better perf // props for a bit better perf
const rawProps = toRaw(props) const rawProps = toRaw(props)
@ -193,7 +174,7 @@ const BaseTransitionImpl: ComponentOptions = {
// in the case of <transition><keep-alive/></transition>, we need to // in the case of <transition><keep-alive/></transition>, we need to
// compare the type of the kept-alive children. // compare the type of the kept-alive children.
const innerChild = getKeepAliveChild(child) const innerChild = getInnerChild(child)
if (!innerChild) { if (!innerChild) {
return emptyPlaceholder(child) return emptyPlaceholder(child)
} }
@ -207,7 +188,7 @@ const BaseTransitionImpl: ComponentOptions = {
setTransitionHooks(innerChild, enterHooks) setTransitionHooks(innerChild, enterHooks)
const oldChild = instance.subTree const oldChild = instance.subTree
const oldInnerChild = oldChild && getKeepAliveChild(oldChild) const oldInnerChild = oldChild && getInnerChild(oldChild)
// handle mode // handle mode
if ( if (
@ -267,6 +248,30 @@ if (__COMPAT__) {
BaseTransitionImpl.__isBuiltIn = true BaseTransitionImpl.__isBuiltIn = true
} }
function findNonCommentChild(children: VNode[]): VNode {
let child: VNode = children[0]
if (children.length > 1) {
let hasFound = false
// locate first non-comment child
for (const c of children) {
if (c.type !== Comment) {
if (__DEV__ && hasFound) {
// warn more than one non-comment child
warn(
'<transition> can only be used on a single element or component. ' +
'Use <transition-group> for lists.',
)
break
}
child = c
hasFound = true
if (!__DEV__) break
}
}
}
return child
}
// export the public type for h/tsx inference // export the public type for h/tsx inference
// also to avoid inline import() in generated d.ts files // also to avoid inline import() in generated d.ts files
export const BaseTransition = BaseTransitionImpl as unknown as { export const BaseTransition = BaseTransitionImpl as unknown as {
@ -457,8 +462,12 @@ function emptyPlaceholder(vnode: VNode): VNode | undefined {
} }
} }
function getKeepAliveChild(vnode: VNode): VNode | undefined { function getInnerChild(vnode: VNode): VNode | undefined {
if (!isKeepAlive(vnode)) { if (!isKeepAlive(vnode)) {
if (isTeleport(vnode.type) && vnode.children) {
return findNonCommentChild(vnode.children as VNode[])
}
return vnode return vnode
} }
// #7121 ensure get the child component subtree in case // #7121 ensure get the child component subtree in case

View File

@ -26,19 +26,29 @@ import type { ComponentPublicInstance } from './componentPublicInstance'
import { mapCompatDirectiveHook } from './compat/customDirective' import { mapCompatDirectiveHook } from './compat/customDirective'
import { pauseTracking, resetTracking, traverse } from '@vue/reactivity' import { pauseTracking, resetTracking, traverse } from '@vue/reactivity'
export interface DirectiveBinding<V = any> { export interface DirectiveBinding<
Value = any,
Modifiers extends string = string,
Arg extends string = string,
> {
instance: ComponentPublicInstance | null instance: ComponentPublicInstance | null
value: V value: Value
oldValue: V | null oldValue: Value | null
arg?: string arg?: Arg
modifiers: DirectiveModifiers modifiers: DirectiveModifiers<Modifiers>
dir: ObjectDirective<any, V> dir: ObjectDirective<any, Value>
} }
export type DirectiveHook<T = any, Prev = VNode<any, T> | null, V = any> = ( export type DirectiveHook<
el: T, HostElement = any,
binding: DirectiveBinding<V>, Prev = VNode<any, HostElement> | null,
vnode: VNode<any, T>, Value = any,
Modifiers extends string = string,
Arg extends string = string,
> = (
el: HostElement,
binding: DirectiveBinding<Value, Modifiers, Arg>,
vnode: VNode<any, HostElement>,
prevVNode: Prev, prevVNode: Prev,
) => void ) => void
@ -47,25 +57,52 @@ export type SSRDirectiveHook = (
vnode: VNode, vnode: VNode,
) => Data | undefined ) => Data | undefined
export interface ObjectDirective<T = any, V = any> { export interface ObjectDirective<
created?: DirectiveHook<T, null, V> HostElement = any,
beforeMount?: DirectiveHook<T, null, V> Value = any,
mounted?: DirectiveHook<T, null, V> Modifiers extends string = string,
beforeUpdate?: DirectiveHook<T, VNode<any, T>, V> Arg extends string = string,
updated?: DirectiveHook<T, VNode<any, T>, V> > {
beforeUnmount?: DirectiveHook<T, null, V> created?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
unmounted?: DirectiveHook<T, null, V> beforeMount?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
mounted?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
beforeUpdate?: DirectiveHook<
HostElement,
VNode<any, HostElement>,
Value,
Modifiers,
Arg
>
updated?: DirectiveHook<
HostElement,
VNode<any, HostElement>,
Value,
Modifiers,
Arg
>
beforeUnmount?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
unmounted?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
getSSRProps?: SSRDirectiveHook getSSRProps?: SSRDirectiveHook
deep?: boolean deep?: boolean
} }
export type FunctionDirective<T = any, V = any> = DirectiveHook<T, any, V> export type FunctionDirective<
HostElement = any,
V = any,
Modifiers extends string = string,
Arg extends string = string,
> = DirectiveHook<HostElement, any, V, Modifiers, Arg>
export type Directive<T = any, V = any> = export type Directive<
| ObjectDirective<T, V> HostElement = any,
| FunctionDirective<T, V> Value = any,
Modifiers extends string = string,
Arg extends string = string,
> =
| ObjectDirective<HostElement, Value, Modifiers, Arg>
| FunctionDirective<HostElement, Value, Modifiers, Arg>
export type DirectiveModifiers = Record<string, boolean> export type DirectiveModifiers<K extends string = string> = Record<K, boolean>
export function validateDirectiveName(name: string) { export function validateDirectiveName(name: string) {
if (isBuiltInDirective(name)) { if (isBuiltInDirective(name)) {

View File

@ -77,6 +77,7 @@ export {
withDefaults, withDefaults,
type DefineProps, type DefineProps,
type ModelRef, type ModelRef,
type ComponentTypeEmits,
} from './apiSetupHelpers' } from './apiSetupHelpers'
/** /**
@ -249,6 +250,8 @@ export type {
SetupContext, SetupContext,
ComponentCustomProps, ComponentCustomProps,
AllowedComponentProps, AllowedComponentProps,
GlobalComponents,
GlobalDirectives,
ComponentInstance, ComponentInstance,
} from './component' } from './component'
export type { export type {
@ -259,9 +262,6 @@ export type {
export type { export type {
ComponentOptions, ComponentOptions,
ComponentOptionsMixin, ComponentOptionsMixin,
ComponentOptionsWithoutProps,
ComponentOptionsWithObjectProps,
ComponentOptionsWithArrayProps,
ComponentCustomOptions, ComponentCustomOptions,
ComponentOptionsBase, ComponentOptionsBase,
ComponentProvideOptions, ComponentProvideOptions,
@ -271,7 +271,11 @@ export type {
RuntimeCompilerOptions, RuntimeCompilerOptions,
ComponentInjectOptions, ComponentInjectOptions,
} from './componentOptions' } from './componentOptions'
export type { EmitsOptions, ObjectEmitsOptions } from './componentEmits' export type {
EmitsOptions,
ObjectEmitsOptions,
EmitsToProps,
} from './componentEmits'
export type { export type {
ComponentPublicInstance, ComponentPublicInstance,
ComponentCustomProperties, ComponentCustomProperties,

View File

@ -0,0 +1,11 @@
// Note: this file is auto concatenated to the end of the bundled d.ts during
// build.
declare module '@vue/runtime-core' {
export interface GlobalComponents {
Teleport: DefineComponent<TeleportProps>
Suspense: DefineComponent<SuspenseProps>
KeepAlive: DefineComponent<KeepAliveProps>
BaseTransition: DefineComponent<BaseTransitionProps>
}
}

View File

@ -88,10 +88,14 @@ describe('defineCustomElement', () => {
describe('props', () => { describe('props', () => {
const E = defineCustomElement({ const E = defineCustomElement({
props: ['foo', 'bar', 'bazQux'], props: {
foo: [String, null],
bar: Object,
bazQux: null,
},
render() { render() {
return [ return [
h('div', null, this.foo), h('div', null, this.foo || ''),
h('div', null, this.bazQux || (this.bar && this.bar.x)), h('div', null, this.bazQux || (this.bar && this.bar.x)),
] ]
}, },

View File

@ -43,7 +43,7 @@ describe('runtime-dom: v-on directive', () => {
}) })
test('it should support key modifiers and system modifiers', () => { test('it should support key modifiers and system modifiers', () => {
const keyNames = ['ctrl', 'shift', 'meta', 'alt'] const keyNames = ['ctrl', 'shift', 'meta', 'alt'] as const
keyNames.forEach(keyName => { keyNames.forEach(keyName => {
const el = document.createElement('div') const el = document.createElement('div')

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/runtime-dom", "name": "@vue/runtime-dom",
"version": "3.4.25", "version": "3.5.0-alpha.1",
"description": "@vue/runtime-dom", "description": "@vue/runtime-dom",
"main": "index.js", "main": "index.js",
"module": "dist/runtime-dom.esm-bundler.js", "module": "dist/runtime-dom.esm-bundler.js",

View File

@ -1,16 +1,19 @@
import { import {
type Component,
type ComponentInjectOptions, type ComponentInjectOptions,
type ComponentInternalInstance, type ComponentInternalInstance,
type ComponentObjectPropsOptions,
type ComponentOptions, type ComponentOptions,
type ComponentOptionsBase,
type ComponentOptionsMixin, type ComponentOptionsMixin,
type ComponentOptionsWithArrayProps, type ComponentProvideOptions,
type ComponentOptionsWithObjectProps,
type ComponentOptionsWithoutProps,
type ComponentPropsOptions,
type ComputedOptions, type ComputedOptions,
type ConcreteComponent, type ConcreteComponent,
type CreateComponentPublicInstance,
type DefineComponent, type DefineComponent,
type Directive,
type EmitsOptions, type EmitsOptions,
type EmitsToProps,
type ExtractPropTypes, type ExtractPropTypes,
type MethodOptions, type MethodOptions,
type RenderFunction, type RenderFunction,
@ -41,98 +44,79 @@ export function defineCustomElement<Props, RawBindings = object>(
) => RawBindings | RenderFunction, ) => RawBindings | RenderFunction,
): VueElementConstructor<Props> ): VueElementConstructor<Props>
// overload 2: object format with no props // overload 2: defineCustomElement with options object, infer props from options
export function defineCustomElement< export function defineCustomElement<
Props = {}, // props
RawBindings = {}, RuntimePropsOptions extends
D = {}, ComponentObjectPropsOptions = ComponentObjectPropsOptions,
C extends ComputedOptions = {}, PropsKeys extends string = string,
M extends MethodOptions = {}, // emits
RuntimeEmitsOptions extends EmitsOptions = {},
EmitsKeys extends string = string,
// other options
Data = {},
SetupBindings = {},
Computed extends ComputedOptions = {},
Methods extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions, InjectOptions extends ComponentInjectOptions = {},
EE extends string = string, InjectKeys extends string = string,
I extends ComponentInjectOptions = {}, Slots extends SlotsType = {},
II extends string = string, LocalComponents extends Record<string, Component> = {},
S extends SlotsType = {}, Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
// resolved types
InferredProps = string extends PropsKeys
? ComponentObjectPropsOptions extends RuntimePropsOptions
? {}
: ExtractPropTypes<RuntimePropsOptions>
: { [key in PropsKeys]?: any },
ResolvedProps = InferredProps & EmitsToProps<RuntimeEmitsOptions>,
>( >(
options: ComponentOptionsWithoutProps< options: {
Props, props?: (RuntimePropsOptions & ThisType<void>) | PropsKeys[]
RawBindings, } & ComponentOptionsBase<
D, ResolvedProps,
C, SetupBindings,
M, Data,
Computed,
Methods,
Mixin, Mixin,
Extends, Extends,
E, RuntimeEmitsOptions,
EE, EmitsKeys,
I, {}, // Defaults
II, InjectOptions,
S InjectKeys,
> & { styles?: string[] }, Slots,
): VueElementConstructor<Props> LocalComponents,
Directives,
// overload 3: object format with array props declaration Exposed,
export function defineCustomElement< Provide
PropNames extends string, > &
RawBindings, ThisType<
D, CreateComponentPublicInstance<
C extends ComputedOptions = {}, Readonly<ResolvedProps>,
M extends MethodOptions = {}, SetupBindings,
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Data,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Computed,
E extends EmitsOptions = Record<string, any>, Methods,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
>(
options: ComponentOptionsWithArrayProps<
PropNames,
RawBindings,
D,
C,
M,
Mixin, Mixin,
Extends, Extends,
E, RuntimeEmitsOptions,
EE, EmitsKeys,
I, {},
II, false,
S InjectOptions,
> & { styles?: string[] }, Slots,
): VueElementConstructor<{ [K in PropNames]: any }> LocalComponents,
Directives,
// overload 4: object format with object props declaration Exposed
export function defineCustomElement< >
PropsOptions extends Readonly<ComponentPropsOptions>, >,
RawBindings, ): VueElementConstructor<ResolvedProps>
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = Record<string, any>,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
>(
options: ComponentOptionsWithObjectProps<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
I,
II,
S
> & { styles?: string[] },
): VueElementConstructor<ExtractPropTypes<PropsOptions>>
// overload 5: defining a custom element from the returned value of // overload 5: defining a custom element from the returned value of
// `defineComponent` // `defineComponent`

View File

@ -39,14 +39,17 @@ function onCompositionEnd(e: Event) {
const assignKey = Symbol('_assign') const assignKey = Symbol('_assign')
type ModelDirective<T> = ObjectDirective< type ModelDirective<T, Modifiers extends string = string> = ObjectDirective<
T & { [assignKey]: AssignerFn; _assigning?: boolean } T & { [assignKey]: AssignerFn; _assigning?: boolean },
any,
Modifiers
> >
// We are exporting the v-model runtime directly as vnode hooks so that it can // We are exporting the v-model runtime directly as vnode hooks so that it can
// be tree-shaken in case v-model is never used. // be tree-shaken in case v-model is never used.
export const vModelText: ModelDirective< export const vModelText: ModelDirective<
HTMLInputElement | HTMLTextAreaElement HTMLInputElement | HTMLTextAreaElement,
'trim' | 'number' | 'lazy'
> = { > = {
created(el, { modifiers: { lazy, trim, number } }, vnode) { created(el, { modifiers: { lazy, trim, number } }, vnode) {
el[assignKey] = getModelAssigner(vnode) el[assignKey] = getModelAssigner(vnode)
@ -183,7 +186,7 @@ export const vModelRadio: ModelDirective<HTMLInputElement> = {
}, },
} }
export const vModelSelect: ModelDirective<HTMLSelectElement> = { export const vModelSelect: ModelDirective<HTMLSelectElement, 'number'> = {
// <select multiple> value need to be deep traversed // <select multiple> value need to be deep traversed
deep: true, deep: true,
created(el, { value, modifiers: { number } }, vnode) { created(el, { value, modifiers: { number } }, vnode) {
@ -363,3 +366,10 @@ export function initVModelForSSR() {
} }
} }
} }
export type VModelDirective =
| typeof vModelText
| typeof vModelCheckbox
| typeof vModelSelect
| typeof vModelRadio
| typeof vModelDynamic

View File

@ -1,33 +1,40 @@
import { import {
type ComponentInternalInstance, type ComponentInternalInstance,
DeprecationTypes, DeprecationTypes,
type Directive,
type LegacyConfig, type LegacyConfig,
compatUtils, compatUtils,
getCurrentInstance, getCurrentInstance,
} from '@vue/runtime-core' } from '@vue/runtime-core'
import { hyphenate, isArray } from '@vue/shared' import { hyphenate, isArray } from '@vue/shared'
const systemModifiers = ['ctrl', 'shift', 'alt', 'meta'] const systemModifiers = ['ctrl', 'shift', 'alt', 'meta'] as const
type SystemModifiers = (typeof systemModifiers)[number]
type CompatModifiers = keyof typeof keyNames
export type VOnModifiers = SystemModifiers | ModifierGuards | CompatModifiers
type KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent type KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent
const modifierGuards: Record< const modifierGuards = {
string, stop: (e: Event) => e.stopPropagation(),
(e: Event, modifiers: string[]) => void | boolean prevent: (e: Event) => e.preventDefault(),
> = { self: (e: Event) => e.target !== e.currentTarget,
stop: e => e.stopPropagation(), ctrl: (e: Event) => !(e as KeyedEvent).ctrlKey,
prevent: e => e.preventDefault(), shift: (e: Event) => !(e as KeyedEvent).shiftKey,
self: e => e.target !== e.currentTarget, alt: (e: Event) => !(e as KeyedEvent).altKey,
ctrl: e => !(e as KeyedEvent).ctrlKey, meta: (e: Event) => !(e as KeyedEvent).metaKey,
shift: e => !(e as KeyedEvent).shiftKey, left: (e: Event) => 'button' in e && (e as MouseEvent).button !== 0,
alt: e => !(e as KeyedEvent).altKey, middle: (e: Event) => 'button' in e && (e as MouseEvent).button !== 1,
meta: e => !(e as KeyedEvent).metaKey, right: (e: Event) => 'button' in e && (e as MouseEvent).button !== 2,
left: e => 'button' in e && (e as MouseEvent).button !== 0,
middle: e => 'button' in e && (e as MouseEvent).button !== 1,
right: e => 'button' in e && (e as MouseEvent).button !== 2,
exact: (e, modifiers) => exact: (e, modifiers) =>
systemModifiers.some(m => (e as any)[`${m}Key`] && !modifiers.includes(m)), systemModifiers.some(m => (e as any)[`${m}Key`] && !modifiers.includes(m)),
} } satisfies Record<
string,
| ((e: Event) => void | boolean)
| ((e: Event, modifiers: string[]) => void | boolean)
>
type ModifierGuards = keyof typeof modifierGuards
/** /**
* @private * @private
@ -36,7 +43,7 @@ export const withModifiers = <
T extends (event: Event, ...args: unknown[]) => any, T extends (event: Event, ...args: unknown[]) => any,
>( >(
fn: T & { _withMods?: { [key: string]: T } }, fn: T & { _withMods?: { [key: string]: T } },
modifiers: string[], modifiers: VOnModifiers[],
) => { ) => {
const cache = fn._withMods || (fn._withMods = {}) const cache = fn._withMods || (fn._withMods = {})
const cacheKey = modifiers.join('.') const cacheKey = modifiers.join('.')
@ -44,7 +51,7 @@ export const withModifiers = <
cache[cacheKey] || cache[cacheKey] ||
(cache[cacheKey] = ((event, ...args) => { (cache[cacheKey] = ((event, ...args) => {
for (let i = 0; i < modifiers.length; i++) { for (let i = 0; i < modifiers.length; i++) {
const guard = modifierGuards[modifiers[i]] const guard = modifierGuards[modifiers[i] as ModifierGuards]
if (guard && guard(event, modifiers)) return if (guard && guard(event, modifiers)) return
} }
return fn(event, ...args) return fn(event, ...args)
@ -54,7 +61,7 @@ export const withModifiers = <
// Kept for 2.x compat. // Kept for 2.x compat.
// Note: IE11 compat for `spacebar` and `del` is removed for now. // Note: IE11 compat for `spacebar` and `del` is removed for now.
const keyNames: Record<string, string | string[]> = { const keyNames = {
esc: 'escape', esc: 'escape',
space: ' ', space: ' ',
up: 'arrow-up', up: 'arrow-up',
@ -62,7 +69,7 @@ const keyNames: Record<string, string | string[]> = {
right: 'arrow-right', right: 'arrow-right',
down: 'arrow-down', down: 'arrow-down',
delete: 'backspace', delete: 'backspace',
} } satisfies Record<string, string | string[]>
/** /**
* @private * @private
@ -101,7 +108,13 @@ export const withKeys = <T extends (event: KeyboardEvent) => any>(
} }
const eventKey = hyphenate(event.key) const eventKey = hyphenate(event.key)
if (modifiers.some(k => k === eventKey || keyNames[k] === eventKey)) { if (
modifiers.some(
k =>
k === eventKey ||
keyNames[k as unknown as CompatModifiers] === eventKey,
)
) {
return fn(event) return fn(event)
} }
@ -133,3 +146,5 @@ export const withKeys = <T extends (event: KeyboardEvent) => any>(
}) as T) }) as T)
) )
} }
export type VOnDirective = Directive<any, any, VOnModifiers>

View File

@ -1,7 +1,9 @@
import { import {
type App, type App,
type CreateAppFunction, type CreateAppFunction,
type DefineComponent,
DeprecationTypes, DeprecationTypes,
type Directive,
type ElementNamespace, type ElementNamespace,
type HydrationRenderer, type HydrationRenderer,
type Renderer, type Renderer,
@ -25,6 +27,11 @@ import {
isSVGTag, isSVGTag,
isString, isString,
} from '@vue/shared' } from '@vue/shared'
import type { TransitionProps } from './components/Transition'
import type { TransitionGroupProps } from './components/TransitionGroup'
import type { vShow } from './directives/vShow'
import type { VOnDirective } from './directives/vOn'
import type { VModelDirective } from './directives/vModel'
declare module '@vue/reactivity' { declare module '@vue/reactivity' {
export interface RefUnwrapBailTypes { export interface RefUnwrapBailTypes {
@ -32,6 +39,22 @@ declare module '@vue/reactivity' {
} }
} }
declare module '@vue/runtime-core' {
interface GlobalComponents {
Transition: DefineComponent<TransitionProps>
TransitionGroup: DefineComponent<TransitionGroupProps>
}
interface GlobalDirectives {
vShow: typeof vShow
vOn: VOnDirective
vBind: VModelDirective
vIf: Directive<any, boolean>
VOnce: Directive
VSlot: Directive
}
}
const rendererOptions = /*#__PURE__*/ extend({ patchProp }, nodeOps) const rendererOptions = /*#__PURE__*/ extend({ patchProp }, nodeOps)
// lazy create the renderer - this makes core renderer logic tree-shakable // lazy create the renderer - this makes core renderer logic tree-shakable

View File

@ -74,7 +74,7 @@ function eventHandler(
let handler = getter() let handler = getter()
if (!handler) return if (!handler) return
if (modifiers) handler = withModifiers(handler, modifiers) if (modifiers) handler = withModifiers(handler, modifiers as any[])
if (keys) handler = withKeys(handler, keys) if (keys) handler = withKeys(handler, keys)
handler && handler(...args) handler && handler(...args)
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/server-renderer", "name": "@vue/server-renderer",
"version": "3.4.25", "version": "3.5.0-alpha.1",
"description": "@vue/server-renderer", "description": "@vue/server-renderer",
"main": "index.js", "main": "index.js",
"module": "dist/server-renderer.esm-bundler.js", "module": "dist/server-renderer.esm-bundler.js",

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/shared", "name": "@vue/shared",
"version": "3.4.25", "version": "3.5.0-alpha.1",
"description": "internal utils shared across @vue packages", "description": "internal utils shared across @vue packages",
"main": "index.js", "main": "index.js",
"module": "dist/shared.esm-bundler.js", "module": "dist/shared.esm-bundler.js",

View File

@ -23,3 +23,34 @@ export type Awaited<T> = T extends null | undefined
: T // non-object or non-thenable : T // non-object or non-thenable
export type Data = Record<string, unknown> export type Data = Record<string, unknown>
/**
* Utility for extracting the parameters from a function overload (for typed emits)
* https://github.com/microsoft/TypeScript/issues/32164#issuecomment-1146737709
*/
export type OverloadParameters<T extends (...args: any[]) => any> = Parameters<
OverloadUnion<T>
>
type OverloadProps<TOverload> = Pick<TOverload, keyof TOverload>
type OverloadUnionRecursive<
TOverload,
TPartialOverload = unknown,
> = TOverload extends (...args: infer TArgs) => infer TReturn
? TPartialOverload extends TOverload
? never
:
| OverloadUnionRecursive<
TPartialOverload & TOverload,
TPartialOverload &
((...args: TArgs) => TReturn) &
OverloadProps<TOverload>
>
| ((...args: TArgs) => TReturn)
: never
type OverloadUnion<TOverload extends (...args: any[]) => any> = Exclude<
OverloadUnionRecursive<(() => never) & TOverload>,
TOverload extends () => never ? never : () => never
>

View File

@ -292,7 +292,7 @@ describe('INSTANCE_SCOPED_SLOTS', () => {
components: { components: {
child: { child: {
compatConfig: { RENDER_FUNCTION: false }, compatConfig: { RENDER_FUNCTION: false },
render() { render(this: LegacyPublicInstance) {
normalSlots = this.$slots normalSlots = this.$slots
scopedSlots = this.$scopedSlots scopedSlots = this.$scopedSlots
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compat", "name": "@vue/compat",
"version": "3.4.25", "version": "3.5.0-alpha.1",
"description": "Vue 3 compatibility build for Vue 2", "description": "Vue 3 compatibility build for Vue 2",
"main": "index.js", "main": "index.js",
"module": "dist/vue.runtime.esm-bundler.js", "module": "dist/vue.runtime.esm-bundler.js",

View File

@ -1725,6 +1725,95 @@ describe('e2e: Transition', () => {
) )
}) })
describe('transition with Teleport', () => {
test(
'apply transition to teleport child',
async () => {
await page().evaluate(() => {
const { createApp, ref, h } = (window as any).Vue
createApp({
template: `
<div id="target"></div>
<div id="container">
<transition>
<Teleport to="#target">
<!-- comment -->
<Comp v-if="toggle" class="test">content</Comp>
</Teleport>
</transition>
</div>
<button id="toggleBtn" @click="click">button</button>
`,
components: {
Comp: {
setup() {
return () => h('div', { class: 'test' }, 'content')
},
},
},
setup: () => {
const toggle = ref(false)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
},
}).mount('#app')
})
expect(await html('#target')).toBe('<!-- comment --><!--v-if-->')
expect(await html('#container')).toBe(
'<!--teleport start--><!--teleport end-->',
)
const classWhenTransitionStart = () =>
page().evaluate(() => {
;(document.querySelector('#toggleBtn') as any)!.click()
return Promise.resolve().then(() => {
// find the class of teleported node
return document
.querySelector('#target div')!
.className.split(/\s+/g)
})
})
// enter
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'v-enter-from',
'v-enter-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'v-enter-active',
'v-enter-to',
])
await transitionFinish()
expect(await html('#target')).toBe(
'<!-- comment --><div class="test">content</div>',
)
// leave
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'v-leave-from',
'v-leave-active',
])
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'v-leave-active',
'v-leave-to',
])
await transitionFinish()
expect(await html('#target')).toBe('<!-- comment --><!--v-if-->')
expect(await html('#container')).toBe(
'<!--teleport start--><!--teleport end-->',
)
},
E2E_TIMEOUT,
)
})
describe('transition with v-show', () => { describe('transition with v-show', () => {
test( test(
'named transition with v-show', 'named transition with v-show',

View File

@ -1,6 +1,6 @@
{ {
"name": "vue", "name": "vue",
"version": "3.4.25", "version": "3.5.0-alpha.1",
"description": "The progressive JavaScript framework for building modern web UI.", "description": "The progressive JavaScript framework for building modern web UI.",
"main": "index.js", "main": "index.js",
"module": "dist/vue.runtime.esm-bundler.js", "module": "dist/vue.runtime.esm-bundler.js",