Skip to content

Commit 4376685

Browse files
committed
feat(types): infer attrs in defineCustomElement
1 parent 20e2364 commit 4376685

File tree

4 files changed

+142
-25
lines changed

4 files changed

+142
-25
lines changed

packages/dts-test/defineCustomElement.test-d.ts

Lines changed: 117 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
import { defineCustomElement } from 'vue'
2-
import { expectType, describe } from './utils'
1+
import {
2+
defineCustomElement,
3+
expectType,
4+
expectError,
5+
SetupContext
6+
} from './index'
37

48
describe('inject', () => {
59
// with object inject
@@ -15,7 +19,7 @@ describe('inject', () => {
1519
expectType<unknown>(this.foo)
1620
expectType<unknown>(this.bar)
1721
// @ts-expect-error
18-
this.foobar = 1
22+
expectError((this.foobar = 1))
1923
}
2024
})
2125

@@ -27,7 +31,7 @@ describe('inject', () => {
2731
expectType<unknown>(this.foo)
2832
expectType<unknown>(this.bar)
2933
// @ts-expect-error
30-
this.foobar = 1
34+
expectError((this.foobar = 1))
3135
}
3236
})
3337

@@ -47,7 +51,7 @@ describe('inject', () => {
4751
expectType<unknown>(this.foo)
4852
expectType<unknown>(this.bar)
4953
// @ts-expect-error
50-
this.foobar = 1
54+
expectError((this.foobar = 1))
5155
}
5256
})
5357

@@ -56,9 +60,115 @@ describe('inject', () => {
5660
props: ['a', 'b'],
5761
created() {
5862
// @ts-expect-error
59-
this.foo = 1
63+
expectError((this.foo = 1))
6064
// @ts-expect-error
61-
this.bar = 1
65+
expectError((this.bar = 1))
6266
}
6367
})
6468
})
69+
70+
describe('define attrs', () => {
71+
test('define attrs w/ object props', () => {
72+
type CompAttrs = {
73+
bar: number
74+
baz?: string
75+
}
76+
defineCustomElement(
77+
{
78+
props: {
79+
foo: String
80+
},
81+
created() {
82+
expectType<number>(this.$attrs.bar)
83+
expectType<string | undefined>(this.$attrs.baz)
84+
}
85+
},
86+
{
87+
attrs: {} as CompAttrs
88+
}
89+
)
90+
})
91+
92+
test('define attrs w/ array props', () => {
93+
type CompAttrs = {
94+
bar: number
95+
baz?: string
96+
}
97+
defineCustomElement(
98+
{
99+
props: ['foo'],
100+
created() {
101+
expectType<number>(this.$attrs.bar)
102+
expectType<string | undefined>(this.$attrs.baz)
103+
}
104+
},
105+
{
106+
attrs: {} as CompAttrs
107+
}
108+
)
109+
})
110+
111+
test('define attrs w/ no props', () => {
112+
type CompAttrs = {
113+
bar: number
114+
baz?: string
115+
}
116+
defineCustomElement(
117+
{
118+
created() {
119+
expectType<number>(this.$attrs.bar)
120+
expectType<string | undefined>(this.$attrs.baz)
121+
}
122+
},
123+
{
124+
attrs: {} as CompAttrs
125+
}
126+
)
127+
})
128+
129+
test('define attrs w/ function component', () => {
130+
type CompAttrs = {
131+
bar: number
132+
baz?: string
133+
}
134+
defineCustomElement(
135+
(_props: { foo: string }, ctx: SetupContext<{}, CompAttrs>) => {
136+
expectType<number>(ctx.attrs.bar)
137+
expectType<number>(ctx.attrs.bar)
138+
expectType<string | undefined>(ctx.attrs.baz)
139+
}
140+
)
141+
})
142+
143+
test('define attrs as low priority', () => {
144+
type CompAttrs = {
145+
foo: number
146+
}
147+
defineCustomElement(
148+
{
149+
props: {
150+
foo: String
151+
},
152+
created() {
153+
// @ts-expect-error
154+
console.log(this.$attrs.foo)
155+
}
156+
},
157+
{
158+
attrs: {} as CompAttrs
159+
}
160+
)
161+
})
162+
163+
test('define attrs w/ default attrs such as class, style', () => {
164+
defineCustomElement({
165+
props: {
166+
foo: String
167+
},
168+
created() {
169+
expectType<unknown>(this.$attrs.class)
170+
expectType<unknown>(this.$attrs.style)
171+
}
172+
})
173+
})
174+
})

packages/runtime-core/src/vnode.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import {
1818
ConcreteComponent,
1919
ClassComponent,
2020
Component,
21-
isClassComponent
21+
isClassComponent,
22+
AllowedComponentProps
2223
} from './component'
2324
import { RawSlots } from './componentSlots'
2425
import { isProxy, Ref, toRaw, ReactiveFlags, isRef } from '@vue/reactivity'
@@ -816,7 +817,7 @@ export function normalizeChildren(vnode: VNode, children: unknown) {
816817
vnode.shapeFlag |= type
817818
}
818819

819-
export function mergeProps(...args: (Data & VNodeProps)[]) {
820+
export function mergeProps(...args: (AllowedComponentProps | Data)[]) {
820821
const ret: Data = {}
821822
for (let i = 0; i < args.length; i++) {
822823
const toMerge = args[i]
@@ -829,7 +830,7 @@ export function mergeProps(...args: (Data & VNodeProps)[]) {
829830
ret.style = normalizeStyle([ret.style, toMerge.style])
830831
} else if (isOn(key)) {
831832
const existing = ret[key]
832-
const incoming = toMerge[key]
833+
const incoming = (toMerge as Data)[key]
833834
if (
834835
incoming &&
835836
existing !== incoming &&
@@ -840,7 +841,7 @@ export function mergeProps(...args: (Data & VNodeProps)[]) {
840841
: incoming
841842
}
842843
} else if (key !== '') {
843-
ret[key] = toMerge[key]
844+
ret[key] = (toMerge as Data)[key]
844845
}
845846
}
846847
}

packages/runtime-dom/__tests__/customElement.spec.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,14 @@ describe('defineCustomElement', () => {
286286
})
287287

288288
describe('attrs', () => {
289-
const E = defineCustomElement({
290-
render() {
291-
return [h('div', null, this.$attrs.foo as string)]
292-
}
293-
})
289+
const E = defineCustomElement(
290+
{
291+
render() {
292+
return [h('div', null, this.$attrs.foo)]
293+
}
294+
},
295+
{ attrs: {} as { foo: string } }
296+
)
294297
customElements.define('my-el-attrs', E)
295298

296299
test('attrs via attribute', async () => {

packages/runtime-dom/src/apiCustomElement.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ export type VueElementConstructor<P = {}> = {
3434
// so most of the following overloads should be kept in sync w/ defineComponent.
3535

3636
// overload 1: direct setup function
37-
export function defineCustomElement<Props, RawBindings = object>(
37+
export function defineCustomElement<Props, RawBindings = object, Attrs = {}>(
3838
setup: (
3939
props: Readonly<Props>,
40-
ctx: SetupContext
40+
ctx: SetupContext<{}, Attrs>
4141
) => RawBindings | RenderFunction
4242
): VueElementConstructor<Props>
4343

@@ -56,7 +56,7 @@ export function defineCustomElement<
5656
II extends string = string,
5757
S extends SlotsType = {}
5858
>(
59-
options: ComponentOptionsWithoutProps<
59+
comp: ComponentOptionsWithoutProps<
6060
Props,
6161
RawBindings,
6262
D,
@@ -87,7 +87,7 @@ export function defineCustomElement<
8787
II extends string = string,
8888
S extends SlotsType = {}
8989
>(
90-
options: ComponentOptionsWithArrayProps<
90+
comp: ComponentOptionsWithArrayProps<
9191
PropNames,
9292
RawBindings,
9393
D,
@@ -118,7 +118,7 @@ export function defineCustomElement<
118118
II extends string = string,
119119
S extends SlotsType = {}
120120
>(
121-
options: ComponentOptionsWithObjectProps<
121+
comp: ComponentOptionsWithObjectProps<
122122
PropsOptions,
123123
RawBindings,
124124
D,
@@ -142,14 +142,17 @@ export function defineCustomElement(options: {
142142

143143
/*! #__NO_SIDE_EFFECTS__ */
144144
export function defineCustomElement(
145-
options: any,
146-
hydrate?: RootHydrateFunction
145+
comp: any,
146+
options?: {
147+
hydrate?: RootHydrateFunction
148+
attrs?: any
149+
}
147150
): VueElementConstructor {
148151
const Comp = defineComponent(options) as any
149152
class VueCustomElement extends VueElement {
150153
static def = Comp
151154
constructor(initialProps?: Record<string, any>) {
152-
super(Comp, initialProps, hydrate)
155+
super(Comp, initialProps, options?.hydrate)
153156
}
154157
}
155158

@@ -159,7 +162,7 @@ export function defineCustomElement(
159162
/*! #__NO_SIDE_EFFECTS__ */
160163
export const defineSSRCustomElement = ((options: any) => {
161164
// @ts-ignore
162-
return defineCustomElement(options, hydrate)
165+
return defineCustomElement(options, { hydrate })
163166
}) as typeof defineCustomElement
164167

165168
const BaseClass = (

0 commit comments

Comments
 (0)