diff --git a/CHANGELOG.md b/CHANGELOG.md
index fe351268a..032a5f3a0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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)
diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json
index 9ae397813..6861dc2b1 100644
--- a/packages/compiler-core/package.json
+++ b/packages/compiler-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-core",
- "version": "3.4.25",
+ "version": "3.5.0-alpha.1",
"description": "@vue/compiler-core",
"main": "index.js",
"module": "dist/compiler-core.esm-bundler.js",
diff --git a/packages/compiler-dom/__tests__/transforms/validateHtmlNesting.spec.ts b/packages/compiler-dom/__tests__/transforms/validateHtmlNesting.spec.ts
new file mode 100644
index 000000000..ad9f91713
--- /dev/null
+++ b/packages/compiler-dom/__tests__/transforms/validateHtmlNesting.spec.ts
@@ -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(`
cannot be child of
`)
+ })
+
+ it('should not warn with select > hr', () => {
+ let err: CompilerError | undefined
+ compile(``, {
+ onWarn: e => (err = e),
+ })
+ expect(err).toBeUndefined()
+ })
+})
diff --git a/packages/compiler-dom/package.json b/packages/compiler-dom/package.json
index 3cc86ddd0..d82b11093 100644
--- a/packages/compiler-dom/package.json
+++ b/packages/compiler-dom/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-dom",
- "version": "3.4.25",
+ "version": "3.5.0-alpha.1",
"description": "@vue/compiler-dom",
"main": "index.js",
"module": "dist/compiler-dom.esm-bundler.js",
diff --git a/packages/compiler-dom/src/htmlNesting.ts b/packages/compiler-dom/src/htmlNesting.ts
new file mode 100644
index 000000000..cb0a7626d
--- /dev/null
+++ b/packages/compiler-dom/src/htmlNesting.ts
@@ -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> = {
+ 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> = {
+ // 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> = {
+ 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> = {
+ 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,
+}
diff --git a/packages/compiler-dom/src/index.ts b/packages/compiler-dom/src/index.ts
index 01d3eebe9..9c5489900 100644
--- a/packages/compiler-dom/src/index.ts
+++ b/packages/compiler-dom/src/index.ts
@@ -19,13 +19,14 @@ import { transformShow } from './transforms/vShow'
import { transformTransition } from './transforms/Transition'
import { stringifyStatic } from './transforms/stringifyStatic'
import { ignoreSideEffectTags } from './transforms/ignoreSideEffectTags'
+import { validateHtmlNesting } from './transforms/validateHtmlNesting'
import { extend } from '@vue/shared'
export { parserOptions }
export const DOMNodeTransforms: NodeTransform[] = [
transformStyle,
- ...(__DEV__ ? [transformTransition] : []),
+ ...(__DEV__ ? [transformTransition, validateHtmlNesting] : []),
]
export const DOMDirectiveTransforms: Record = {
diff --git a/packages/compiler-dom/src/transforms/validateHtmlNesting.ts b/packages/compiler-dom/src/transforms/validateHtmlNesting.ts
new file mode 100644
index 000000000..540c0c258
--- /dev/null
+++ b/packages/compiler-dom/src/transforms/validateHtmlNesting.ts
@@ -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)
+ }
+}
diff --git a/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts
index c139a3d7b..d5374ae89 100644
--- a/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts
@@ -597,11 +597,29 @@ const props = defineProps({ foo: String })
foo: Foo
}>()
`,
+ {
+ propsDestructure: false,
+ },
)
expect(content).toMatch(`const { foo } = __props`)
assertCode(content)
})
+ test('prohibiting reactive destructure', () => {
+ expect(() =>
+ compile(
+ ``,
+ {
+ propsDestructure: 'error',
+ },
+ ),
+ ).toThrow()
+ })
+
describe('errors', () => {
test('w/ both type and non-type args', () => {
expect(() => {
diff --git a/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts b/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts
index 3843ef921..20f2c432d 100644
--- a/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts
@@ -6,7 +6,6 @@ describe('sfc reactive props destructure', () => {
function compile(src: string, options?: Partial) {
return compileSFCScript(src, {
inlineTemplate: true,
- propsDestructure: true,
...options,
})
}
diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json
index 273aa3116..cb574181f 100644
--- a/packages/compiler-sfc/package.json
+++ b/packages/compiler-sfc/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-sfc",
- "version": "3.4.25",
+ "version": "3.5.0-alpha.1",
"description": "@vue/compiler-sfc",
"main": "dist/compiler-sfc.cjs.js",
"module": "dist/compiler-sfc.esm-browser.js",
diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts
index d1794484a..989e1cf2b 100644
--- a/packages/compiler-sfc/src/compileScript.ts
+++ b/packages/compiler-sfc/src/compileScript.ts
@@ -106,10 +106,11 @@ export interface SFCScriptCompileOptions {
*/
hoistStatic?: boolean
/**
- * (**Experimental**) Enable reactive destructure for `defineProps`
- * @default false
+ * Set to `false` to disable reactive destructure for `defineProps` (pre-3.5
+ * 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
* imported in SFC macros. Defaults to ts.sys in Node.js, can be overwritten
diff --git a/packages/compiler-sfc/src/script/definePropsDestructure.ts b/packages/compiler-sfc/src/script/definePropsDestructure.ts
index e4a59aca7..34bc7a428 100644
--- a/packages/compiler-sfc/src/script/definePropsDestructure.ts
+++ b/packages/compiler-sfc/src/script/definePropsDestructure.ts
@@ -22,23 +22,17 @@ import { genPropsAccessExp } from '@vue/shared'
import { isCallOf, resolveObjectKey } from './utils'
import type { ScriptCompileContext } from './context'
import { DEFINE_PROPS } from './defineProps'
-import { warnOnce } from '../warn'
export function processPropsDestructure(
ctx: ScriptCompileContext,
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
}
- 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
const registerBinding = (
@@ -104,7 +98,7 @@ export function transformDestructuredProps(
ctx: ScriptCompileContext,
vueImportAliases: Record,
) {
- if (!ctx.options.propsDestructure) {
+ if (ctx.options.propsDestructure === false) {
return
}
diff --git a/packages/compiler-ssr/package.json b/packages/compiler-ssr/package.json
index 637af44b6..377ed64f5 100644
--- a/packages/compiler-ssr/package.json
+++ b/packages/compiler-ssr/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-ssr",
- "version": "3.4.25",
+ "version": "3.5.0-alpha.1",
"description": "@vue/compiler-ssr",
"main": "dist/compiler-ssr.cjs.js",
"types": "dist/compiler-ssr.d.ts",
diff --git a/packages/dts-test/componentTypeExtensions.test-d.tsx b/packages/dts-test/componentTypeExtensions.test-d.tsx
index 683641919..fe58c0a64 100644
--- a/packages/dts-test/componentTypeExtensions.test-d.tsx
+++ b/packages/dts-test/componentTypeExtensions.test-d.tsx
@@ -1,4 +1,4 @@
-import { defineComponent } from 'vue'
+import { type DefineComponent, type Directive, defineComponent } from 'vue'
import { expectType } from './utils'
declare module 'vue' {
@@ -6,6 +6,14 @@ declare module 'vue' {
test?(n: number): void
}
+ interface GlobalDirectives {
+ test: Directive
+ }
+
+ interface GlobalComponents {
+ RouterView: DefineComponent<{}>
+ }
+
interface ComponentCustomProperties {
state?: 'stopped' | 'running'
}
@@ -46,6 +54,8 @@ export const Custom = defineComponent({
},
})
+expectType(Custom.directives!.test)
+expectType>(Custom.components!.RouterView)
expectType()
expectType()
expectType()
diff --git a/packages/dts-test/defineComponent.test-d.tsx b/packages/dts-test/defineComponent.test-d.tsx
index 41646751b..077f1abc0 100644
--- a/packages/dts-test/defineComponent.test-d.tsx
+++ b/packages/dts-test/defineComponent.test-d.tsx
@@ -15,7 +15,7 @@ import {
withKeys,
withModifiers,
} from 'vue'
-import { type IsUnion, describe, expectType } from './utils'
+import { type IsAny, type IsUnion, describe, expectType } from './utils'
describe('with object props', () => {
interface ExpectedProps {
@@ -1501,18 +1501,108 @@ describe('should work when props type is incompatible with setup returned type '
describe('withKeys and withModifiers as pro', () => {
const onKeydown = withKeys(e => {}, [''])
+ // @ts-expect-error invalid modifiers
const onClick = withModifiers(e => {}, [''])
;
})
+// #3367 expose components types
+describe('expose component types', () => {
+ const child = defineComponent({
+ props: {
+ a: String,
+ },
+ })
+
+ const parent = defineComponent({
+ components: {
+ child,
+ child2: {
+ template: ``,
+ },
+ },
+ })
+
+ expectType(parent.components!.child)
+ expectType(parent.components!.child2)
+
+ // global components
+ expectType>(
+ new parent.components!.KeepAlive().$props,
+ )
+ expectType>(new child.components!.KeepAlive().$props)
+
+ // runtime-dom components
+ expectType>(
+ new parent.components!.Transition().$props,
+ )
+ expectType>(
+ new child.components!.Transition().$props,
+ )
+})
+
+describe('directive typing', () => {
+ const customDirective: Directive = {
+ created(_) {},
+ }
+
+ const comp = defineComponent({
+ props: {
+ a: String,
+ },
+ directives: {
+ customDirective,
+ localDirective: {
+ created(_, { arg }) {
+ expectType(arg)
+ },
+ },
+ },
+ })
+
+ expectType(comp.directives!.customDirective)
+ expectType(comp.directives!.localDirective)
+
+ // global directive
+ expectType(comp.directives!.vShow)
+})
+
+describe('expose typing', () => {
+ const Comp = defineComponent({
+ expose: ['a', 'b'],
+ props: {
+ some: String,
+ },
+ data() {
+ return { a: 1, b: '2', c: 1 }
+ },
+ })
+
+ expectType>(Comp.expose!)
+
+ const vm = new Comp()
+ // internal should still be exposed
+ vm.$props
+
+ expectType(vm.a)
+ expectType(vm.b)
+
+ // @ts-expect-error shouldn't be exposed
+ vm.c
+})
+
import type {
AllowedComponentProps,
ComponentCustomProps,
ComponentOptionsMixin,
DefineComponent,
+ Directive,
EmitsOptions,
ExtractPropTypes,
+ KeepAliveProps,
+ TransitionProps,
VNodeProps,
+ vShow,
} from 'vue'
// code generated by tsc / vue-tsc, make sure this continues to work
@@ -1533,3 +1623,146 @@ declare const MyButton: DefineComponent<
{}
>
;
+
+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
+ ;
+ // @ts-expect-error
+ ;
+ ;
+
+ 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)
+ },
+ })
+
+ ; id.toFixed(2)} />
+ ; id.toUpperCase()} />
+ // @ts-expect-error
+ ; id.slice(1)} />
+ // @ts-expect-error
+ ; 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)
+ },
+ })
+
+ ; id.toFixed(2)} />
+ ; id.toUpperCase()} />
+ // @ts-expect-error
+ ; id.slice(1)} />
+ // @ts-expect-error
+ ; 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>(false)
+ expectType(props.foo)
+ },
+})
diff --git a/packages/dts-test/directives.test-d.ts b/packages/dts-test/directives.test-d.ts
new file mode 100644
index 000000000..5b87ebf71
--- /dev/null
+++ b/packages/dts-test/directives.test-d.ts
@@ -0,0 +1,58 @@
+import { type Directive, type ObjectDirective, vModelText } from 'vue'
+import { describe, expectType } from './utils'
+
+type ExtractBinding = 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>
+
+describe('vmodel', () => {
+ expectType>(
+ vModelText,
+ )
+ // @ts-expect-error
+ expectType>(vModelText)
+})
+
+describe('custom', () => {
+ expectType<{
+ value: number
+ oldValue: number | null
+ arg?: 'Arg'
+ modifiers: Record<'a' | 'b', boolean>
+ }>(testDirective())
+
+ expectType<{
+ value: number
+ oldValue: number | null
+ arg?: 'Arg'
+ modifiers: Record<'a' | 'b', boolean>
+ // @ts-expect-error
+ }>(testDirective())
+
+ expectType<{
+ value: number
+ oldValue: number | null
+ arg?: 'Arg'
+ modifiers: Record<'a' | 'b', boolean>
+ // @ts-expect-error
+ }>(testDirective())
+
+ expectType<{
+ value: number
+ oldValue: number | null
+ arg?: 'Arg'
+ modifiers: Record<'a' | 'b', boolean>
+ // @ts-expect-error
+ }>(testDirective())
+})
diff --git a/packages/reactivity/__tests__/effect.spec.ts b/packages/reactivity/__tests__/effect.spec.ts
index 3936216ae..fba816da0 100644
--- a/packages/reactivity/__tests__/effect.spec.ts
+++ b/packages/reactivity/__tests__/effect.spec.ts
@@ -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', () => {
let events: DebuggerEvent[] = []
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', () => {
let dummy
const obj = reactive({ prop: 1 })
diff --git a/packages/reactivity/package.json b/packages/reactivity/package.json
index a2540725b..2dda9fb4c 100644
--- a/packages/reactivity/package.json
+++ b/packages/reactivity/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/reactivity",
- "version": "3.4.25",
+ "version": "3.5.0-alpha.1",
"description": "@vue/reactivity",
"main": "index.js",
"module": "dist/reactivity.esm-bundler.js",
diff --git a/packages/reactivity/src/dep.ts b/packages/reactivity/src/dep.ts
index 0dccf40aa..f4e4fd971 100644
--- a/packages/reactivity/src/dep.ts
+++ b/packages/reactivity/src/dep.ts
@@ -27,12 +27,23 @@ export class Dep {
* Link between this dep and the current active effect
*/
activeLink?: Link = undefined
+
/**
* Doubly linked list representing the subscribing effects (tail)
*/
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 {
if (!activeSub || !shouldTrack) {
@@ -113,21 +124,28 @@ export class Dep {
notify(debugInfo?: DebuggerEventExtraInfo) {
startBatch()
try {
- for (let link = this.subs; link; link = link.prevSub) {
- if (
- __DEV__ &&
- link.sub.onTrigger &&
- !(link.sub.flags & EffectFlags.NOTIFIED)
- ) {
- link.sub.onTrigger(
- extend(
- {
- effect: link.sub,
- },
- debugInfo,
- ),
- )
+ 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 (
+ __DEV__ &&
+ head.sub.onTrigger &&
+ !(head.sub.flags & EffectFlags.NOTIFIED)
+ ) {
+ head.sub.onTrigger(
+ extend(
+ {
+ effect: head.sub,
+ },
+ debugInfo,
+ ),
+ )
+ }
}
+ }
+ for (let link = this.subs; link; link = link.prevSub) {
link.sub.notify()
}
} finally {
@@ -152,6 +170,11 @@ function addSub(link: Link) {
link.prevSub = currentTail
if (currentTail) currentTail.nextSub = link
}
+
+ if (__DEV__ && link.dep.subsHead === undefined) {
+ link.dep.subsHead = link
+ }
+
link.dep.subs = link
}
diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts
index 90db99cf7..e361e8540 100644
--- a/packages/reactivity/src/effect.ts
+++ b/packages/reactivity/src/effect.ts
@@ -375,12 +375,13 @@ export function refreshComputed(computed: ComputedRefImpl) {
}
} catch (err) {
dep.version++
+ throw err
+ } finally {
+ activeSub = prevSub
+ shouldTrack = prevShouldTrack
+ cleanupDeps(computed)
+ computed.flags &= ~EffectFlags.RUNNING
}
-
- activeSub = prevSub
- shouldTrack = prevShouldTrack
- cleanupDeps(computed)
- computed.flags &= ~EffectFlags.RUNNING
}
function removeSub(link: Link) {
diff --git a/packages/runtime-core/package.json b/packages/runtime-core/package.json
index 3dc61d024..66f99cd94 100644
--- a/packages/runtime-core/package.json
+++ b/packages/runtime-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/runtime-core",
- "version": "3.4.25",
+ "version": "3.5.0-alpha.1",
"description": "@vue/runtime-core",
"main": "index.js",
"module": "dist/runtime-core.esm-bundler.js",
diff --git a/packages/runtime-core/src/apiDefineComponent.ts b/packages/runtime-core/src/apiDefineComponent.ts
index 8c0492e1c..7fce96586 100644
--- a/packages/runtime-core/src/apiDefineComponent.ts
+++ b/packages/runtime-core/src/apiDefineComponent.ts
@@ -3,16 +3,17 @@ import type {
ComponentOptions,
ComponentOptionsBase,
ComponentOptionsMixin,
- ComponentOptionsWithArrayProps,
- ComponentOptionsWithObjectProps,
- ComponentOptionsWithoutProps,
+ ComponentProvideOptions,
ComputedOptions,
MethodOptions,
RenderFunction,
} from './componentOptions'
import type {
AllowedComponentProps,
+ Component,
ComponentCustomProps,
+ GlobalComponents,
+ GlobalDirectives,
SetupContext,
} from './component'
import type {
@@ -21,7 +22,11 @@ import type {
ExtractDefaultPropTypes,
ExtractPropTypes,
} from './componentProps'
-import type { EmitsOptions, EmitsToProps } from './componentEmits'
+import type {
+ EmitsOptions,
+ EmitsToProps,
+ TypeEmitsToOptions,
+} from './componentEmits'
import { extend, isFunction } from '@vue/shared'
import type { VNodeProps } from './vnode'
import type {
@@ -29,6 +34,8 @@ import type {
CreateComponentPublicInstance,
} from './componentPublicInstance'
import type { SlotsType } from './componentSlots'
+import type { Directive } from './directives'
+import type { ComponentTypeEmits } from './apiSetupHelpers'
export type PublicProps = VNodeProps &
AllowedComponentProps &
@@ -55,6 +62,11 @@ export type DefineComponent<
Props = ResolveProps,
Defaults = ExtractDefaultPropTypes,
S extends SlotsType = {},
+ LC extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
+ MakeDefaultsOptional extends boolean = true,
> = ComponentPublicInstanceConstructor<
CreateComponentPublicInstance<
Props,
@@ -67,9 +79,12 @@ export type DefineComponent<
E,
PP & Props,
Defaults,
- true,
+ MakeDefaultsOptional,
{},
- S
+ S,
+ LC & GlobalComponents,
+ Directives & GlobalDirectives,
+ Exposed
>
> &
ComponentOptionsBase<
@@ -85,7 +100,11 @@ export type DefineComponent<
Defaults,
{},
string,
- S
+ S,
+ LC & GlobalComponents,
+ Directives & GlobalDirectives,
+ Exposed,
+ Provide
> &
PP
@@ -153,147 +172,114 @@ export function defineComponent<
},
): DefineSetupFnComponent
-// overload 2: object format with no props
-// (uses user defined props interface)
-// return type is for Vetur and TSX support
+// overload 2: defineComponent with options object, infer props from options
export function defineComponent<
- Props = {},
- RawBindings = {},
- D = {},
- C extends ComputedOptions = {},
- M extends MethodOptions = {},
+ // props
+ TypeProps,
+ RuntimePropsOptions extends
+ ComponentObjectPropsOptions = ComponentObjectPropsOptions,
+ 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,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
- E extends EmitsOptions = {},
- EE extends string = string,
- S extends SlotsType = {},
- I extends ComponentInjectOptions = {},
- II extends string = string,
+ InjectOptions extends ComponentInjectOptions = {},
+ InjectKeys extends string = string,
+ Slots extends SlotsType = {},
+ LocalComponents extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
+ // resolved types
+ ResolvedEmits extends EmitsOptions = {} extends RuntimeEmitsOptions
+ ? TypeEmitsToOptions
+ : RuntimeEmitsOptions,
+ InferredProps = unknown extends TypeProps
+ ? string extends RuntimePropsKeys
+ ? ComponentObjectPropsOptions extends RuntimePropsOptions
+ ? {}
+ : ExtractPropTypes
+ : { [key in RuntimePropsKeys]?: any }
+ : TypeProps,
+ ResolvedProps = Readonly>,
>(
- options: ComponentOptionsWithoutProps<
- Props,
- RawBindings,
- D,
- C,
- M,
+ options: {
+ props?: (RuntimePropsOptions & ThisType) | RuntimePropsKeys[]
+ /**
+ * @private for language-tools use only
+ */
+ __typeProps?: TypeProps
+ /**
+ * @private for language-tools use only
+ */
+ __typeEmits?: TypeEmits
+ } & ComponentOptionsBase<
+ ResolvedProps,
+ SetupBindings,
+ Data,
+ Computed,
+ Methods,
Mixin,
Extends,
- E,
- EE,
- I,
- II,
- S
- >,
+ RuntimeEmitsOptions,
+ RuntimeEmitsKeys,
+ {}, // Defaults
+ InjectOptions,
+ InjectKeys,
+ Slots,
+ LocalComponents,
+ Directives,
+ Exposed,
+ Provide
+ > &
+ ThisType<
+ CreateComponentPublicInstance<
+ ResolvedProps,
+ SetupBindings,
+ Data,
+ Computed,
+ Methods,
+ Mixin,
+ Extends,
+ ResolvedEmits,
+ RuntimeEmitsKeys,
+ {},
+ false,
+ InjectOptions,
+ Slots,
+ LocalComponents,
+ Directives,
+ Exposed
+ >
+ >,
): DefineComponent<
- Props,
- RawBindings,
- D,
- C,
- M,
+ InferredProps,
+ SetupBindings,
+ Data,
+ Computed,
+ Methods,
Mixin,
Extends,
- E,
- EE,
+ ResolvedEmits,
+ RuntimeEmitsKeys,
PublicProps,
- ResolveProps,
- ExtractDefaultPropTypes,
- S
->
-
-// 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<
- Props,
- RawBindings,
- D,
- C,
- M,
- Mixin,
- Extends,
- E,
- EE,
- PublicProps,
- ResolveProps,
- ExtractDefaultPropTypes,
- S
->
-
-// overload 4: object format with object props declaration
-// see `ExtractPropTypes` in ./componentProps.ts
-export function defineComponent<
- // the Readonly constraint allows TS to treat the type of { required: true }
- // as constant instead of boolean.
- PropsOptions extends Readonly,
- 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,
- ExtractDefaultPropTypes,
- S
+ ResolvedProps,
+ ExtractDefaultPropTypes,
+ Slots,
+ LocalComponents,
+ Directives,
+ Exposed,
+ Provide,
+ // MakeDefaultsOptional - if TypeProps is provided, set to false to use
+ // user props types verbatim
+ unknown extends TypeProps ? true : false
>
// implementation, close to no-op
diff --git a/packages/runtime-core/src/apiSetupHelpers.ts b/packages/runtime-core/src/apiSetupHelpers.ts
index 382bb30b3..dbe27dde4 100644
--- a/packages/runtime-core/src/apiSetupHelpers.ts
+++ b/packages/runtime-core/src/apiSetupHelpers.ts
@@ -16,8 +16,8 @@ import {
} from './component'
import type { EmitFn, EmitsOptions, ObjectEmitsOptions } from './componentEmits'
import type {
+ ComponentOptionsBase,
ComponentOptionsMixin,
- ComponentOptionsWithoutProps,
ComputedOptions,
MethodOptions,
} from './componentOptions'
@@ -135,9 +135,11 @@ export function defineEmits(
export function defineEmits(
emitOptions: E,
): EmitFn
-export function defineEmits<
- T extends ((...args: any[]) => any) | Record,
->(): T extends (...args: any[]) => any ? T : ShortEmits
+export function defineEmits(): T extends (
+ ...args: any[]
+) => any
+ ? T
+ : ShortEmits
// implementation
export function defineEmits() {
if (__DEV__) {
@@ -146,6 +148,10 @@ export function defineEmits() {
return null as any
}
+export type ComponentTypeEmits =
+ | ((...args: any[]) => any)
+ | Record
+
type RecordToUnion> = T[keyof T]
type ShortEmits> = UnionToIntersection<
@@ -191,15 +197,33 @@ export function defineOptions<
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
>(
- options?: ComponentOptionsWithoutProps<
+ options?: ComponentOptionsBase<
{},
RawBindings,
D,
C,
M,
Mixin,
- Extends
- > & { emits?: undefined; expose?: undefined; slots?: undefined },
+ Extends,
+ {}
+ > & {
+ /**
+ * 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 {
if (__DEV__) {
warnRuntimeUsage(`defineOptions`)
diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts
index 1011e1756..a60434ad4 100644
--- a/packages/runtime-core/src/component.ts
+++ b/packages/runtime-core/src/component.ts
@@ -87,6 +87,13 @@ import {
import type { SchedulerJob } from './scheduler'
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.
* Works with all valid component definition types. This is intended to replace
@@ -125,6 +132,45 @@ export type ComponentInstance = T extends { new (): ComponentPublicInstance }
*/
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 {}
+
+/**
+ * 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 {
+ Teleport: DefineComponent
+ Suspense: DefineComponent
+ KeepAlive: DefineComponent
+ BaseTransition: DefineComponent
+}
+
/**
* Default allowed non-declared props on component in TSX
*/
diff --git a/packages/runtime-core/src/componentEmits.ts b/packages/runtime-core/src/componentEmits.ts
index 4551235bc..eb80d02af 100644
--- a/packages/runtime-core/src/componentEmits.ts
+++ b/packages/runtime-core/src/componentEmits.ts
@@ -1,5 +1,6 @@
import {
EMPTY_OBJ,
+ type OverloadParameters,
type UnionToIntersection,
camelize,
extend,
@@ -28,6 +29,7 @@ import {
compatModelEmit,
compatModelEventPrefix,
} from './compat/componentVModel'
+import type { ComponentTypeEmits } from './apiSetupHelpers'
export type ObjectEmitsOptions = Record<
string,
@@ -36,23 +38,41 @@ export type ObjectEmitsOptions = Record<
export type EmitsOptions = ObjectEmitsOptions | string[]
-export type EmitsToProps = T extends string[]
- ? {
- [K in `on${Capitalize}`]?: (...args: any[]) => any
- }
- : T extends ObjectEmitsOptions
+export type EmitsToProps =
+ T extends string[]
? {
- [K in `on${Capitalize}`]?: K extends `on${infer C}`
- ? (
- ...args: T[Uncapitalize] extends (...args: infer P) => any
- ? P
- : T[Uncapitalize] extends null
- ? any[]
- : never
- ) => any
- : never
+ [K in `on${Capitalize}`]?: (...args: any[]) => any
}
- : {}
+ : T extends ObjectEmitsOptions
+ ? {
+ [K in `on${Capitalize}`]?: K extends `on${infer C}`
+ ? (
+ ...args: T[Uncapitalize] extends (...args: infer P) => any
+ ? P
+ : T[Uncapitalize] extends null
+ ? any[]
+ : never
+ ) => any
+ : never
+ }
+ : {}
+
+export type TypeEmitsToOptions =
+ T extends Record
+ ? {
+ [K in keyof T]: T[K] extends [...args: infer Args]
+ ? (...args: Args) => any
+ : () => any
+ }
+ : T extends (...args: any[]) => any
+ ? ParametersToFns>
+ : {}
+
+type ParametersToFns = {
+ [K in T[0]]: K extends `${infer C}`
+ ? (...args: T extends [C, ...infer Args] ? Args : never) => any
+ : never
+}
export type ShortEmitsToObject =
E extends Record
diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts
index 1017f8b64..786e09d4d 100644
--- a/packages/runtime-core/src/componentOptions.ts
+++ b/packages/runtime-core/src/componentOptions.ts
@@ -55,7 +55,11 @@ import type {
ExtractDefaultPropTypes,
ExtractPropTypes,
} from './componentProps'
-import type { EmitsOptions, EmitsToProps } from './componentEmits'
+import type {
+ EmitsOptions,
+ EmitsToProps,
+ TypeEmitsToOptions,
+} from './componentEmits'
import type { Directive } from './directives'
import {
type ComponentPublicInstance,
@@ -77,7 +81,10 @@ import {
import type { OptionMergeFunction } from './apiCreateApp'
import { LifecycleHooks } from './enums'
import type { SlotsType } from './componentSlots'
-import { normalizePropsOrEmits } from './apiSetupHelpers'
+import {
+ type ComponentTypeEmits,
+ normalizePropsOrEmits,
+} from './apiSetupHelpers'
/**
* Interface for declaring custom options.
@@ -113,7 +120,11 @@ export interface ComponentOptionsBase<
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
-> extends LegacyOptions,
+ LC extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
+> extends LegacyOptions,
ComponentInternalOptions,
ComponentCustomOptions {
setup?: (
@@ -137,13 +148,16 @@ export interface ComponentOptionsBase<
// Luckily `render()` doesn't need any arguments nor does it care about return
// type.
render?: Function
- components?: Record
- directives?: Record
+ // NOTE: extending both LC and Record allows objects to be forced
+ // to be of type Component, while still inferring LC generic
+ components?: LC & Record
+ // NOTE: extending both Directives and Record allows objects to be forced
+ // to be of type Directive, while still inferring Directives generic
+ directives?: Directives & Record
inheritAttrs?: boolean
emits?: (E | EE[]) & ThisType
slots?: S
- // TODO infer public instance type based on exposed keys
- expose?: string[]
+ expose?: Exposed[]
serverPrefetch?(): void | Promise
// Runtime compiler only -----------------------------------------------------
@@ -212,117 +226,24 @@ export interface RuntimeCompilerOptions {
delimiters?: [string, string]
}
-export type ComponentOptionsWithoutProps<
+export type ComponentOptions<
Props = {},
- RawBindings = {},
- D = {},
- C extends ComputedOptions = {},
- M extends MethodOptions = {},
- Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
- Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
- E extends EmitsOptions = EmitsOptions,
+ RawBindings = any,
+ D = any,
+ C extends ComputedOptions = any,
+ M extends MethodOptions = any,
+ Mixin extends ComponentOptionsMixin = any,
+ Extends extends ComponentOptionsMixin = any,
+ E extends EmitsOptions = any,
EE extends string = string,
+ Defaults = {},
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
- PE = Props & EmitsToProps,
-> = 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>>,
-> = 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 & EmitsToProps>>,
- Defaults = ExtractDefaultPropTypes,
+ LC extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
> = ComponentOptionsBase<
Props,
RawBindings,
@@ -336,48 +257,11 @@ export type ComponentOptionsWithObjectProps<
Defaults,
I,
II,
- S
-> & {
- props: PropsOptions & ThisType
-} & ThisType<
- CreateComponentPublicInstance<
- Props,
- RawBindings,
- D,
- C,
- M,
- Mixin,
- Extends,
- E,
- Props,
- Defaults,
- false,
- I,
- S
- >
- >
-
-export type ComponentOptions<
- Props = {},
- RawBindings = any,
- D = any,
- C extends ComputedOptions = any,
- M extends MethodOptions = any,
- Mixin extends ComponentOptionsMixin = any,
- Extends extends ComponentOptionsMixin = any,
- E extends EmitsOptions = any,
- S extends SlotsType = any,
-> = ComponentOptionsBase<
- Props,
- RawBindings,
- D,
- C,
- M,
- Mixin,
- Extends,
- E,
- string,
- S
+ S,
+ LC,
+ Directives,
+ Exposed,
+ Provide
> &
ThisType<
CreateComponentPublicInstance<
@@ -389,7 +273,13 @@ export type ComponentOptions<
Mixin,
Extends,
E,
- Readonly
+ Readonly,
+ Defaults,
+ false,
+ I,
+ S,
+ LC,
+ Directives
>
>
@@ -404,6 +294,12 @@ export type ComponentOptionsMixin = ComponentOptionsBase<
any,
any,
any,
+ any,
+ any,
+ any,
+ any,
+ any,
+ any,
any
>
@@ -465,6 +361,7 @@ interface LegacyOptions<
Extends extends ComponentOptionsMixin,
I extends ComponentInjectOptions,
II extends string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
> {
compatConfig?: CompatConfig
@@ -498,7 +395,7 @@ interface LegacyOptions<
computed?: C
methods?: M
watch?: ComponentWatchOptions
- provide?: ComponentProvideOptions
+ provide?: Provide
inject?: I | II[]
// assets
@@ -1199,3 +1096,203 @@ function mergeWatchOptions(
}
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 = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
+ TE extends ComponentTypeEmits = {},
+ ResolvedEmits extends EmitsOptions = {} extends E
+ ? TypeEmitsToOptions
+ : E,
+ PE = Props & EmitsToProps,
+> = 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 = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
+ Props = Prettify>>,
+> = 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 = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
+ Props = Prettify & EmitsToProps>>,
+ Defaults = ExtractDefaultPropTypes,
+> = ComponentOptionsBase<
+ Props,
+ RawBindings,
+ D,
+ C,
+ M,
+ Mixin,
+ Extends,
+ E,
+ EE,
+ Defaults,
+ I,
+ II,
+ S,
+ LC,
+ Directives,
+ Exposed,
+ Provide
+> & {
+ props: PropsOptions & ThisType
+} & ThisType<
+ CreateComponentPublicInstance<
+ Props,
+ RawBindings,
+ D,
+ C,
+ M,
+ Mixin,
+ Extends,
+ E,
+ Props,
+ Defaults,
+ false,
+ I,
+ S,
+ LC,
+ Directives
+ >
+ >
diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts
index 74f77db0b..7bd0feb7f 100644
--- a/packages/runtime-core/src/componentProps.ts
+++ b/packages/runtime-core/src/componentProps.ts
@@ -67,7 +67,7 @@ export interface PropOptions {
skipFactory?: boolean
}
-export type PropType = PropConstructor | PropConstructor[]
+export type PropType = PropConstructor | (PropConstructor | null)[]
type PropConstructor =
| { new (...args: any[]): T & {} }
@@ -107,8 +107,10 @@ type DefaultKeys = {
: never
}[keyof T]
-type InferPropType = [T] extends [null]
- ? any // null & true would fail to infer
+type InferPropType = [T] extends [null]
+ ? NullAsAny extends true
+ ? any
+ : null
: [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`
: [T] extends [ObjectConstructor | { type: ObjectConstructor }]
@@ -119,8 +121,8 @@ type InferPropType = [T] extends [null]
? Date
: [T] extends [(infer U)[] | { type: (infer U)[] }]
? U extends DateConstructor
- ? Date | InferPropType
- : InferPropType
+ ? Date | InferPropType
+ : InferPropType
: [T] extends [Prop]
? unknown extends V
? IfAny
@@ -594,7 +596,7 @@ function validatePropName(key: string) {
// use function string name to check type constructors
// so that it works across vms / iframes.
-function getType(ctor: Prop): string {
+function getType(ctor: Prop | null): string {
// Early return for null to avoid unnecessary computations
if (ctor === null) {
return 'null'
@@ -614,7 +616,7 @@ function getType(ctor: Prop): string {
return ''
}
-function isSameType(a: Prop, b: Prop): boolean {
+function isSameType(a: Prop | null, b: Prop | null): boolean {
return getType(a) === getType(b)
}
@@ -707,24 +709,27 @@ type AssertionResult = {
/**
* dev only
*/
-function assertType(value: unknown, type: PropConstructor): AssertionResult {
+function assertType(
+ value: unknown,
+ type: PropConstructor | null,
+): AssertionResult {
let valid
const expectedType = getType(type)
- if (isSimpleType(expectedType)) {
+ if (expectedType === 'null') {
+ valid = value === null
+ } else if (isSimpleType(expectedType)) {
const t = typeof value
valid = t === expectedType.toLowerCase()
// for primitive wrapper objects
if (!valid && t === 'object') {
- valid = value instanceof type
+ valid = value instanceof (type as PropConstructor)
}
} else if (expectedType === 'Object') {
valid = isObject(value)
} else if (expectedType === 'Array') {
valid = isArray(value)
- } else if (expectedType === 'null') {
- valid = value === null
} else {
- valid = value instanceof type
+ valid = value instanceof (type as PropConstructor)
}
return {
valid,
diff --git a/packages/runtime-core/src/componentPublicInstance.ts b/packages/runtime-core/src/componentPublicInstance.ts
index 3266299c5..538f6279a 100644
--- a/packages/runtime-core/src/componentPublicInstance.ts
+++ b/packages/runtime-core/src/componentPublicInstance.ts
@@ -1,4 +1,5 @@
import {
+ type Component,
type ComponentInternalInstance,
getExposeProxy,
isStatefulComponent,
@@ -35,6 +36,7 @@ import {
type ComponentInjectOptions,
type ComponentOptionsBase,
type ComponentOptionsMixin,
+ type ComponentProvideOptions,
type ComputedOptions,
type ExtractComputedReturns,
type InjectToObject,
@@ -51,6 +53,7 @@ import { markAttrsAccessed } from './componentRenderUtils'
import { currentRenderingInstance } from './componentRenderContext'
import { warn } from './warning'
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`
@@ -99,6 +102,10 @@ type MixinToOptionTypes =
infer Defaults,
any,
any,
+ any,
+ any,
+ any,
+ any,
any
>
? OptionTypesType &
@@ -157,6 +164,9 @@ export type CreateComponentPublicInstance<
MakeDefaultsOptional extends boolean = false,
I extends ComponentInjectOptions = {},
S extends SlotsType = {},
+ LC extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
PublicMixin = IntersectionMixin & IntersectionMixin,
PublicP = UnwrapMixinsType & EnsureNonVoid,
PublicB = UnwrapMixinsType & EnsureNonVoid,
@@ -167,6 +177,7 @@ export type CreateComponentPublicInstance<
EnsureNonVoid,
PublicDefaults = UnwrapMixinsType &
EnsureNonVoid,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
> = ComponentPublicInstance<
PublicP,
PublicB,
@@ -190,11 +201,22 @@ export type CreateComponentPublicInstance<
Defaults,
{},
string,
- S
+ S,
+ LC,
+ Directives,
+ Exposed,
+ Provide
>,
I,
- S
+ S,
+ Exposed
>
+
+export type ExposedKeys<
+ T,
+ Exposed extends string & keyof T,
+> = '' extends Exposed ? T : Pick
+
// public properties exposed on the proxy, which is used as the render context
// in templates (as `this` in the render option)
export type ComponentPublicInstance<
@@ -210,6 +232,7 @@ export type ComponentPublicInstance<
Options = ComponentOptionsBase,
I extends ComponentInjectOptions = {},
S extends SlotsType = {},
+ Exposed extends string = '',
> = {
$: ComponentInternalInstance
$data: D
@@ -233,13 +256,16 @@ export type ComponentPublicInstance<
: (...args: any) => any,
options?: WatchOptions,
): WatchStopHandle
-} & IfAny>> &
- ShallowUnwrapRef &
- UnwrapNestedRefs &
- ExtractComputedReturns &
- M &
- ComponentCustomProperties &
- InjectToObject
+} & ExposedKeys<
+ IfAny>> &
+ ShallowUnwrapRef &
+ UnwrapNestedRefs &
+ ExtractComputedReturns &
+ M &
+ ComponentCustomProperties &
+ InjectToObject,
+ Exposed
+>
export type PublicPropertiesMap = Record<
string,
diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts
index 86bc0b53a..ff2dcb7c9 100644
--- a/packages/runtime-core/src/components/BaseTransition.ts
+++ b/packages/runtime-core/src/components/BaseTransition.ts
@@ -18,6 +18,7 @@ import { SchedulerJobFlags, toRaw } from '@vue/reactivity'
import { ErrorCodes, callWithAsyncErrorHandling } from '../errorHandling'
import { PatchFlags, ShapeFlags, isArray, isFunction } from '@vue/shared'
import { onBeforeUnmount, onMounted } from '../apiLifecycle'
+import { isTeleport } from './Teleport'
import type { RendererElement } from '../renderer'
type Hook void> = T | T[]
@@ -151,27 +152,7 @@ const BaseTransitionImpl: ComponentOptions = {
return
}
- 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(
- ' can only be used on a single element or component. ' +
- 'Use for lists.',
- )
- break
- }
- child = c
- hasFound = true
- if (!__DEV__) break
- }
- }
- }
-
+ const child: VNode = findNonCommentChild(children)
// there's no need to track reactivity for these props so use the raw
// props for a bit better perf
const rawProps = toRaw(props)
@@ -193,7 +174,7 @@ const BaseTransitionImpl: ComponentOptions = {
// in the case of , we need to
// compare the type of the kept-alive children.
- const innerChild = getKeepAliveChild(child)
+ const innerChild = getInnerChild(child)
if (!innerChild) {
return emptyPlaceholder(child)
}
@@ -207,7 +188,7 @@ const BaseTransitionImpl: ComponentOptions = {
setTransitionHooks(innerChild, enterHooks)
const oldChild = instance.subTree
- const oldInnerChild = oldChild && getKeepAliveChild(oldChild)
+ const oldInnerChild = oldChild && getInnerChild(oldChild)
// handle mode
if (
@@ -267,6 +248,30 @@ if (__COMPAT__) {
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(
+ ' can only be used on a single element or component. ' +
+ 'Use for lists.',
+ )
+ break
+ }
+ child = c
+ hasFound = true
+ if (!__DEV__) break
+ }
+ }
+ }
+ return child
+}
+
// export the public type for h/tsx inference
// also to avoid inline import() in generated d.ts files
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 (isTeleport(vnode.type) && vnode.children) {
+ return findNonCommentChild(vnode.children as VNode[])
+ }
+
return vnode
}
// #7121 ensure get the child component subtree in case
diff --git a/packages/runtime-core/src/directives.ts b/packages/runtime-core/src/directives.ts
index 475c495e9..3d130bde4 100644
--- a/packages/runtime-core/src/directives.ts
+++ b/packages/runtime-core/src/directives.ts
@@ -26,19 +26,29 @@ import type { ComponentPublicInstance } from './componentPublicInstance'
import { mapCompatDirectiveHook } from './compat/customDirective'
import { pauseTracking, resetTracking, traverse } from '@vue/reactivity'
-export interface DirectiveBinding {
+export interface DirectiveBinding<
+ Value = any,
+ Modifiers extends string = string,
+ Arg extends string = string,
+> {
instance: ComponentPublicInstance | null
- value: V
- oldValue: V | null
- arg?: string
- modifiers: DirectiveModifiers
- dir: ObjectDirective
+ value: Value
+ oldValue: Value | null
+ arg?: Arg
+ modifiers: DirectiveModifiers
+ dir: ObjectDirective
}
-export type DirectiveHook | null, V = any> = (
- el: T,
- binding: DirectiveBinding,
- vnode: VNode,
+export type DirectiveHook<
+ HostElement = any,
+ Prev = VNode | null,
+ Value = any,
+ Modifiers extends string = string,
+ Arg extends string = string,
+> = (
+ el: HostElement,
+ binding: DirectiveBinding,
+ vnode: VNode,
prevVNode: Prev,
) => void
@@ -47,25 +57,52 @@ export type SSRDirectiveHook = (
vnode: VNode,
) => Data | undefined
-export interface ObjectDirective {
- created?: DirectiveHook
- beforeMount?: DirectiveHook
- mounted?: DirectiveHook
- beforeUpdate?: DirectiveHook, V>
- updated?: DirectiveHook, V>
- beforeUnmount?: DirectiveHook
- unmounted?: DirectiveHook
+export interface ObjectDirective<
+ HostElement = any,
+ Value = any,
+ Modifiers extends string = string,
+ Arg extends string = string,
+> {
+ created?: DirectiveHook
+ beforeMount?: DirectiveHook
+ mounted?: DirectiveHook
+ beforeUpdate?: DirectiveHook<
+ HostElement,
+ VNode,
+ Value,
+ Modifiers,
+ Arg
+ >
+ updated?: DirectiveHook<
+ HostElement,
+ VNode,
+ Value,
+ Modifiers,
+ Arg
+ >
+ beforeUnmount?: DirectiveHook
+ unmounted?: DirectiveHook
getSSRProps?: SSRDirectiveHook
deep?: boolean
}
-export type FunctionDirective = DirectiveHook
+export type FunctionDirective<
+ HostElement = any,
+ V = any,
+ Modifiers extends string = string,
+ Arg extends string = string,
+> = DirectiveHook
-export type Directive =
- | ObjectDirective
- | FunctionDirective
+export type Directive<
+ HostElement = any,
+ Value = any,
+ Modifiers extends string = string,
+ Arg extends string = string,
+> =
+ | ObjectDirective
+ | FunctionDirective
-export type DirectiveModifiers = Record
+export type DirectiveModifiers = Record
export function validateDirectiveName(name: string) {
if (isBuiltInDirective(name)) {
diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts
index d314cdbc2..e9347faa9 100644
--- a/packages/runtime-core/src/index.ts
+++ b/packages/runtime-core/src/index.ts
@@ -77,6 +77,7 @@ export {
withDefaults,
type DefineProps,
type ModelRef,
+ type ComponentTypeEmits,
} from './apiSetupHelpers'
/**
@@ -249,6 +250,8 @@ export type {
SetupContext,
ComponentCustomProps,
AllowedComponentProps,
+ GlobalComponents,
+ GlobalDirectives,
ComponentInstance,
} from './component'
export type {
@@ -259,9 +262,6 @@ export type {
export type {
ComponentOptions,
ComponentOptionsMixin,
- ComponentOptionsWithoutProps,
- ComponentOptionsWithObjectProps,
- ComponentOptionsWithArrayProps,
ComponentCustomOptions,
ComponentOptionsBase,
ComponentProvideOptions,
@@ -271,7 +271,11 @@ export type {
RuntimeCompilerOptions,
ComponentInjectOptions,
} from './componentOptions'
-export type { EmitsOptions, ObjectEmitsOptions } from './componentEmits'
+export type {
+ EmitsOptions,
+ ObjectEmitsOptions,
+ EmitsToProps,
+} from './componentEmits'
export type {
ComponentPublicInstance,
ComponentCustomProperties,
diff --git a/packages/runtime-core/types/globalComponents.d.ts b/packages/runtime-core/types/globalComponents.d.ts
new file mode 100644
index 000000000..a4abd6d1f
--- /dev/null
+++ b/packages/runtime-core/types/globalComponents.d.ts
@@ -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
+ Suspense: DefineComponent
+ KeepAlive: DefineComponent
+ BaseTransition: DefineComponent
+ }
+}
diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts
index fb746f72c..cc56de2d6 100644
--- a/packages/runtime-dom/__tests__/customElement.spec.ts
+++ b/packages/runtime-dom/__tests__/customElement.spec.ts
@@ -88,10 +88,14 @@ describe('defineCustomElement', () => {
describe('props', () => {
const E = defineCustomElement({
- props: ['foo', 'bar', 'bazQux'],
+ props: {
+ foo: [String, null],
+ bar: Object,
+ bazQux: null,
+ },
render() {
return [
- h('div', null, this.foo),
+ h('div', null, this.foo || ''),
h('div', null, this.bazQux || (this.bar && this.bar.x)),
]
},
diff --git a/packages/runtime-dom/__tests__/directives/vOn.spec.ts b/packages/runtime-dom/__tests__/directives/vOn.spec.ts
index 03620f747..ef7ee346b 100644
--- a/packages/runtime-dom/__tests__/directives/vOn.spec.ts
+++ b/packages/runtime-dom/__tests__/directives/vOn.spec.ts
@@ -43,7 +43,7 @@ describe('runtime-dom: v-on directive', () => {
})
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 => {
const el = document.createElement('div')
diff --git a/packages/runtime-dom/package.json b/packages/runtime-dom/package.json
index 0b9f25d06..bf567a8ef 100644
--- a/packages/runtime-dom/package.json
+++ b/packages/runtime-dom/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/runtime-dom",
- "version": "3.4.25",
+ "version": "3.5.0-alpha.1",
"description": "@vue/runtime-dom",
"main": "index.js",
"module": "dist/runtime-dom.esm-bundler.js",
diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts
index 01ce2bad4..017284662 100644
--- a/packages/runtime-dom/src/apiCustomElement.ts
+++ b/packages/runtime-dom/src/apiCustomElement.ts
@@ -1,16 +1,19 @@
import {
+ type Component,
type ComponentInjectOptions,
type ComponentInternalInstance,
+ type ComponentObjectPropsOptions,
type ComponentOptions,
+ type ComponentOptionsBase,
type ComponentOptionsMixin,
- type ComponentOptionsWithArrayProps,
- type ComponentOptionsWithObjectProps,
- type ComponentOptionsWithoutProps,
- type ComponentPropsOptions,
+ type ComponentProvideOptions,
type ComputedOptions,
type ConcreteComponent,
+ type CreateComponentPublicInstance,
type DefineComponent,
+ type Directive,
type EmitsOptions,
+ type EmitsToProps,
type ExtractPropTypes,
type MethodOptions,
type RenderFunction,
@@ -41,98 +44,79 @@ export function defineCustomElement(
) => RawBindings | RenderFunction,
): VueElementConstructor
-// overload 2: object format with no props
+// overload 2: defineCustomElement with options object, infer props from options
export function defineCustomElement<
- Props = {},
- RawBindings = {},
- D = {},
- C extends ComputedOptions = {},
- M extends MethodOptions = {},
+ // props
+ RuntimePropsOptions extends
+ ComponentObjectPropsOptions = ComponentObjectPropsOptions,
+ PropsKeys extends string = string,
+ // emits
+ RuntimeEmitsOptions extends EmitsOptions = {},
+ EmitsKeys extends string = string,
+ // other options
+ Data = {},
+ SetupBindings = {},
+ Computed extends ComputedOptions = {},
+ Methods 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 = {},
+ InjectOptions extends ComponentInjectOptions = {},
+ InjectKeys extends string = string,
+ Slots extends SlotsType = {},
+ LocalComponents extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
+ // resolved types
+ InferredProps = string extends PropsKeys
+ ? ComponentObjectPropsOptions extends RuntimePropsOptions
+ ? {}
+ : ExtractPropTypes
+ : { [key in PropsKeys]?: any },
+ ResolvedProps = InferredProps & EmitsToProps,
>(
- options: ComponentOptionsWithoutProps<
- Props,
- RawBindings,
- D,
- C,
- M,
+ options: {
+ props?: (RuntimePropsOptions & ThisType) | PropsKeys[]
+ } & ComponentOptionsBase<
+ ResolvedProps,
+ SetupBindings,
+ Data,
+ Computed,
+ Methods,
Mixin,
Extends,
- E,
- EE,
- I,
- II,
- S
- > & { styles?: string[] },
-): VueElementConstructor
-
-// overload 3: object format with array props declaration
-export function defineCustomElement<
- PropNames extends string,
- RawBindings,
- D,
- C extends ComputedOptions = {},
- M extends MethodOptions = {},
- Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
- Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
- E extends EmitsOptions = Record,
- EE extends string = string,
- I extends ComponentInjectOptions = {},
- II extends string = string,
- S extends SlotsType = {},
->(
- options: ComponentOptionsWithArrayProps<
- PropNames,
- RawBindings,
- D,
- C,
- M,
- Mixin,
- Extends,
- E,
- EE,
- I,
- II,
- S
- > & { styles?: string[] },
-): VueElementConstructor<{ [K in PropNames]: any }>
-
-// overload 4: object format with object props declaration
-export function defineCustomElement<
- PropsOptions extends Readonly,
- RawBindings,
- D,
- C extends ComputedOptions = {},
- M extends MethodOptions = {},
- Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
- Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
- E extends EmitsOptions = Record,
- 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>
+ RuntimeEmitsOptions,
+ EmitsKeys,
+ {}, // Defaults
+ InjectOptions,
+ InjectKeys,
+ Slots,
+ LocalComponents,
+ Directives,
+ Exposed,
+ Provide
+ > &
+ ThisType<
+ CreateComponentPublicInstance<
+ Readonly,
+ SetupBindings,
+ Data,
+ Computed,
+ Methods,
+ Mixin,
+ Extends,
+ RuntimeEmitsOptions,
+ EmitsKeys,
+ {},
+ false,
+ InjectOptions,
+ Slots,
+ LocalComponents,
+ Directives,
+ Exposed
+ >
+ >,
+): VueElementConstructor
// overload 5: defining a custom element from the returned value of
// `defineComponent`
diff --git a/packages/runtime-dom/src/directives/vModel.ts b/packages/runtime-dom/src/directives/vModel.ts
index b0ea41728..cb599656f 100644
--- a/packages/runtime-dom/src/directives/vModel.ts
+++ b/packages/runtime-dom/src/directives/vModel.ts
@@ -39,14 +39,17 @@ function onCompositionEnd(e: Event) {
const assignKey = Symbol('_assign')
-type ModelDirective = ObjectDirective<
- T & { [assignKey]: AssignerFn; _assigning?: boolean }
+type ModelDirective = ObjectDirective<
+ T & { [assignKey]: AssignerFn; _assigning?: boolean },
+ any,
+ Modifiers
>
// 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.
export const vModelText: ModelDirective<
- HTMLInputElement | HTMLTextAreaElement
+ HTMLInputElement | HTMLTextAreaElement,
+ 'trim' | 'number' | 'lazy'
> = {
created(el, { modifiers: { lazy, trim, number } }, vnode) {
el[assignKey] = getModelAssigner(vnode)
@@ -183,7 +186,7 @@ export const vModelRadio: ModelDirective = {
},
}
-export const vModelSelect: ModelDirective = {
+export const vModelSelect: ModelDirective = {
//