Skip to content

Commit

Permalink
feat(types): infer attrs in defineCustomElement
Browse files Browse the repository at this point in the history
  • Loading branch information
rudyxu1102 committed Jan 1, 2023
1 parent 4f7d0fd commit 12bf718
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 37 deletions.
9 changes: 5 additions & 4 deletions packages/runtime-core/src/vnode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {
ConcreteComponent,
ClassComponent,
Component,
isClassComponent
isClassComponent,
AllowedComponentProps
} from './component'
import { RawSlots } from './componentSlots'
import { isProxy, Ref, toRaw, ReactiveFlags, isRef } from '@vue/reactivity'
Expand Down Expand Up @@ -813,7 +814,7 @@ export function normalizeChildren(vnode: VNode, children: unknown) {
vnode.shapeFlag |= type
}

export function mergeProps(...args: (Data & VNodeProps)[]) {
export function mergeProps(...args: (AllowedComponentProps | Data)[]) {
const ret: Data = {}
for (let i = 0; i < args.length; i++) {
const toMerge = args[i]
Expand All @@ -826,7 +827,7 @@ export function mergeProps(...args: (Data & VNodeProps)[]) {
ret.style = normalizeStyle([ret.style, toMerge.style])
} else if (isOn(key)) {
const existing = ret[key]
const incoming = toMerge[key]
const incoming = (toMerge as Data)[key]
if (
incoming &&
existing !== incoming &&
Expand All @@ -837,7 +838,7 @@ export function mergeProps(...args: (Data & VNodeProps)[]) {
: incoming
}
} else if (key !== '') {
ret[key] = toMerge[key]
ret[key] = (toMerge as Data)[key]
}
}
}
Expand Down
13 changes: 8 additions & 5 deletions packages/runtime-dom/__tests__/customElement.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,11 +286,14 @@ describe('defineCustomElement', () => {
})

describe('attrs', () => {
const E = defineCustomElement({
render() {
return [h('div', null, this.$attrs.foo as string)]
}
})
const E = defineCustomElement(
{
render() {
return [h('div', null, this.$attrs.foo)]
}
},
{ attrs: {} as { foo: string } }
)
customElements.define('my-el-attrs', E)

test('attrs via attribute', async () => {
Expand Down
56 changes: 37 additions & 19 deletions packages/runtime-dom/src/apiCustomElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ export type VueElementConstructor<P = {}> = {
// so most of the following overloads should be kept in sync w/ defineComponent.

// overload 1: direct setup function
export function defineCustomElement<Props, RawBindings = object>(
export function defineCustomElement<Props, RawBindings = object, Attrs = {}>(
setup: (
props: Readonly<Props>,
ctx: SetupContext
ctx: SetupContext<{}, Attrs>
) => RawBindings | RenderFunction
): VueElementConstructor<Props>

Expand All @@ -52,9 +52,10 @@ export function defineCustomElement<
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string
II extends string = string,
Attrs = {}
>(
options: ComponentOptionsWithoutProps<
comp: ComponentOptionsWithoutProps<
Props,
RawBindings,
D,
Expand All @@ -65,8 +66,12 @@ export function defineCustomElement<
E,
EE,
I,
II
> & { styles?: string[] }
II,
Attrs
> & { styles?: string[] },
options?: {
attrs: Attrs
}
): VueElementConstructor<Props>

// overload 3: object format with array props declaration
Expand All @@ -81,9 +86,10 @@ export function defineCustomElement<
E extends EmitsOptions = Record<string, any>,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string
II extends string = string,
Attrs = {}
>(
options: ComponentOptionsWithArrayProps<
comp: ComponentOptionsWithArrayProps<
PropNames,
RawBindings,
D,
Expand All @@ -94,8 +100,12 @@ export function defineCustomElement<
E,
EE,
I,
II
> & { styles?: string[] }
II,
Attrs
> & { styles?: string[] },
options?: {
attrs: Attrs
}
): VueElementConstructor<{ [K in PropNames]: any }>

// overload 4: object format with object props declaration
Expand All @@ -110,9 +120,10 @@ export function defineCustomElement<
E extends EmitsOptions = Record<string, any>,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string
II extends string = string,
Attrs = {}
>(
options: ComponentOptionsWithObjectProps<
comp: ComponentOptionsWithObjectProps<
PropsOptions,
RawBindings,
D,
Expand All @@ -123,8 +134,12 @@ export function defineCustomElement<
E,
EE,
I,
II
> & { styles?: string[] }
II,
Attrs
> & { styles?: string[] },
options?: {
attrs: Attrs
}
): VueElementConstructor<ExtractPropTypes<PropsOptions>>

// overload 5: defining a custom element from the returned value of
Expand All @@ -134,14 +149,17 @@ export function defineCustomElement(options: {
}): VueElementConstructor

export function defineCustomElement(
options: any,
hydrate?: RootHydrateFunction
comp: any,
options?: {
hydrate?: RootHydrateFunction
attrs?: any
}
): VueElementConstructor {
const Comp = defineComponent(options as any)
const Comp = defineComponent(comp as any)
class VueCustomElement extends VueElement {
static def = Comp
constructor(initialProps?: Record<string, any>) {
super(Comp, initialProps, hydrate)
super(Comp, initialProps, options?.hydrate)
}
}

Expand All @@ -150,7 +168,7 @@ export function defineCustomElement(

export const defineSSRCustomElement = ((options: any) => {
// @ts-ignore
return defineCustomElement(options, hydrate)
return defineCustomElement(options, { hydrate })
}) as typeof defineCustomElement

const BaseClass = (
Expand Down
129 changes: 120 additions & 9 deletions test-dts/defineCustomElement.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { defineCustomElement, expectType, expectError } from './index'
import {
defineCustomElement,
expectType,
expectError,
SetupContext
} from './index'

describe('inject', () => {
// with object inject
Expand All @@ -8,13 +13,13 @@ describe('inject', () => {
},
inject: {
foo: 'foo',
bar: 'bar',
bar: 'bar'
},
created() {
expectType<unknown>(this.foo)
expectType<unknown>(this.bar)
// @ts-expect-error
expectError(this.foobar = 1)
expectError((this.foobar = 1))
}
})

Expand All @@ -26,7 +31,7 @@ describe('inject', () => {
expectType<unknown>(this.foo)
expectType<unknown>(this.bar)
// @ts-expect-error
expectError(this.foobar = 1)
expectError((this.foobar = 1))
}
})

Expand All @@ -40,13 +45,13 @@ describe('inject', () => {
bar: {
from: 'pfoo',
default: 'bar'
},
}
},
created() {
expectType<unknown>(this.foo)
expectType<unknown>(this.bar)
// @ts-expect-error
expectError(this.foobar = 1)
expectError((this.foobar = 1))
}
})

Expand All @@ -55,9 +60,115 @@ describe('inject', () => {
props: ['a', 'b'],
created() {
// @ts-expect-error
expectError(this.foo = 1)
expectError((this.foo = 1))
// @ts-expect-error
expectError(this.bar = 1)
expectError((this.bar = 1))
}
})
})

describe('define attrs', () => {
test('define attrs w/ object props', () => {
type CompAttrs = {
bar: number
baz?: string
}
defineCustomElement(
{
props: {
foo: String
},
created() {
expectType<number>(this.$attrs.bar)
expectType<string | undefined>(this.$attrs.baz)
}
},
{
attrs: {} as CompAttrs
}
)
})

test('define attrs w/ array props', () => {
type CompAttrs = {
bar: number
baz?: string
}
defineCustomElement(
{
props: ['foo'],
created() {
expectType<number>(this.$attrs.bar)
expectType<string | undefined>(this.$attrs.baz)
}
},
{
attrs: {} as CompAttrs
}
)
})

test('define attrs w/ no props', () => {
type CompAttrs = {
bar: number
baz?: string
}
defineCustomElement(
{
created() {
expectType<number>(this.$attrs.bar)
expectType<string | undefined>(this.$attrs.baz)
}
},
{
attrs: {} as CompAttrs
}
)
})

test('define attrs w/ function component', () => {
type CompAttrs = {
bar: number
baz?: string
}
defineCustomElement(
(_props: { foo: string }, ctx: SetupContext<{}, CompAttrs>) => {
expectType<number>(ctx.attrs.bar)
expectType<number>(ctx.attrs.bar)
expectType<string | undefined>(ctx.attrs.baz)
}
)
})

test('define attrs as low priority', () => {
type CompAttrs = {
foo: number
}
defineCustomElement(
{
props: {
foo: String
},
created() {
// @ts-expect-error
console.log(this.$attrs.foo)
}
},
{
attrs: {} as CompAttrs
}
)
})

test('define attrs w/ default attrs such as class, style', () => {
defineCustomElement({
props: {
foo: String
},
created() {
expectType<unknown>(this.$attrs.class)
expectType<unknown>(this.$attrs.style)
}
})
})
})
})

0 comments on commit 12bf718

Please sign in to comment.