Skip to content

Commit fd96c63

Browse files
committed
feat: allow to define props by class
1 parent 8bdce38 commit fd96c63

14 files changed

+221
-403
lines changed

Diff for: example/babel.config.js

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
module.exports = {
22
presets: [['@babel/env', { modules: false }]],
3+
plugins: [
4+
['@babel/proposal-decorators', { legacy: true }],
5+
['@babel/proposal-class-properties', { loose: true }],
6+
],
37
}

Diff for: example/src/App.vue

+5-5
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212
</template>
1313

1414
<script lang="ts">
15-
import { props } from '../../src'
15+
import { Vue } from '../../src'
1616
17-
const Props = props({
18-
propMessage: String
19-
})
17+
class Props {
18+
propMessage!: string
19+
}
2020
21-
export default class App extends Props {
21+
export default class App extends Vue.props(Props) {
2222
// inital data
2323
msg: number = 123
2424

Diff for: example/tsconfig.json

+3-7
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
{
22
"compilerOptions": {
33
"target": "esnext",
4-
"lib": [
5-
"dom",
6-
"esnext"
7-
],
4+
"lib": ["dom", "esnext"],
85
"module": "es2015",
96
"moduleResolution": "node",
107
"experimentalDecorators": true,
8+
"useDefineForClassFields": true,
119
"strict": true,
1210
"noUnusedLocals": true,
1311
"noUnusedParameters": true,
1412
"sourceMap": true
1513
},
16-
"include": [
17-
"./**/*.ts"
18-
]
14+
"include": ["./**/*.ts"]
1915
}

Diff for: example/webpack.config.js

+6-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const { VueLoaderPlugin } = require('vue-loader')
2+
const babelConfig = require('./babel.config')
23

34
module.exports = {
45
mode: 'development',
@@ -20,7 +21,10 @@ module.exports = {
2021
test: /\.tsx?$/,
2122
exclude: /node_modules/,
2223
use: [
23-
'babel-loader',
24+
{
25+
loader: 'babel-loader',
26+
options: babelConfig,
27+
},
2428
{
2529
loader: 'ts-loader',
2630
options: {
@@ -32,18 +36,7 @@ module.exports = {
3236
},
3337
{
3438
test: /\.vue$/,
35-
use: [
36-
{
37-
loader: 'vue-loader',
38-
options: {
39-
babelParserPlugins: [
40-
'jsx',
41-
'classProperties',
42-
'decorators-legacy',
43-
],
44-
},
45-
},
46-
],
39+
use: ['vue-loader'],
4740
},
4841
],
4942
},

Diff for: src/helpers.ts

+3-76
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,6 @@
1-
import {
2-
ComponentOptions,
3-
ComponentObjectPropsOptions,
4-
ExtractPropTypes,
5-
ExtractDefaultPropTypes,
6-
ShallowUnwrapRef,
7-
Ref,
8-
} from 'vue'
1+
import { ComponentOptions, ShallowUnwrapRef, Ref } from 'vue'
92

10-
import {
11-
ClassComponentHooks,
12-
EmitsOptions,
13-
ObjectEmitsOptions,
14-
Vue,
15-
VueBase,
16-
VueConstructor,
17-
VueMixin,
18-
} from './vue'
3+
import { Vue, VueConstructor, VueMixin } from './vue'
194

205
export function Options<V extends Vue>(
216
options: ComponentOptions & ThisType<V>
@@ -63,25 +48,8 @@ export type UnionToIntersection<U> = (
6348

6449
export type ExtractInstance<T> = T extends VueMixin<infer V> ? V : never
6550

66-
export type NarrowEmit<T extends VueBase> = Omit<
67-
T,
68-
'$emit' | keyof ClassComponentHooks
69-
> &
70-
// Reassign class component hooks as mapped types makes prototype function (`mounted(): void`) instance function (`mounted: () => void`).
71-
ClassComponentHooks & {
72-
// Exclude generic $emit type (`$emit: (event: string, ...args: any[]) => void`) if there are another intersected type.
73-
$emit: T['$emit'] extends ((event: string, ...args: any[]) => void) &
74-
infer R
75-
? unknown extends R
76-
? T['$emit']
77-
: R
78-
: T['$emit']
79-
}
80-
8151
export type MixedVueBase<Mixins extends VueMixin[]> = Mixins extends (infer T)[]
82-
? VueConstructor<
83-
NarrowEmit<UnionToIntersection<ExtractInstance<T>> & Vue> & VueBase
84-
>
52+
? VueConstructor<UnionToIntersection<ExtractInstance<T>> & Vue>
8553
: never
8654

8755
export function mixins<T extends VueMixin[]>(...Ctors: T): MixedVueBase<T>
@@ -104,47 +72,6 @@ export function mixins(...Ctors: VueMixin[]): VueConstructor {
10472
}
10573
}
10674

107-
export function props<
108-
PropNames extends string,
109-
Props = Readonly<{ [key in PropNames]?: any }>
110-
>(propNames: PropNames[]): VueConstructor<Vue<Props> & Props>
111-
112-
export function props<
113-
PropsOptions extends ComponentObjectPropsOptions,
114-
Props = Readonly<ExtractPropTypes<PropsOptions>>,
115-
DefaultProps = ExtractDefaultPropTypes<PropsOptions>
116-
>(
117-
propsOptions: PropsOptions
118-
): VueConstructor<Vue<Props, {}, DefaultProps> & Props>
119-
120-
export function props(
121-
propsOptions: string[] | ComponentObjectPropsOptions
122-
): VueConstructor {
123-
class PropsMixin extends Vue {
124-
static __vccExtend(options: ComponentOptions) {
125-
options.props = propsOptions
126-
}
127-
}
128-
return PropsMixin
129-
}
130-
131-
export function emits<EmitNames extends string>(
132-
emitNames: EmitNames[]
133-
): VueConstructor<Vue<unknown, EmitNames[]>>
134-
135-
export function emits<EmitsOptions extends ObjectEmitsOptions>(
136-
emitsOptions: EmitsOptions
137-
): VueConstructor<Vue<unknown, EmitsOptions>>
138-
139-
export function emits(emitsOptions: EmitsOptions): VueConstructor {
140-
class EmitsMixin extends Vue {
141-
static __vccExtend(options: ComponentOptions) {
142-
options.emits = emitsOptions
143-
}
144-
}
145-
return EmitsMixin
146-
}
147-
14875
export type UnwrapSetupValue<T> = T extends Ref<infer R>
14976
? R
15077
: ShallowUnwrapRef<T>

Diff for: src/index.ts

+15-9
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,9 @@
44

55
export { Vue, ClassComponentHooks } from './vue'
66

7-
export {
8-
Options,
9-
createDecorator,
10-
mixins,
11-
props,
12-
emits,
13-
setup,
14-
} from './helpers'
7+
export { Options, createDecorator, mixins, setup } from './helpers'
8+
9+
export { prop } from './props'
1510

1611
/**
1712
* Other types
@@ -27,11 +22,22 @@ export {
2722
PublicProps,
2823
} from './vue'
2924

25+
export {
26+
PropOptions,
27+
PropOptionsWithDefault,
28+
PropOptionsWithRequired,
29+
WithDefault,
30+
VueWithProps,
31+
DefaultFactory,
32+
DefaultKeys,
33+
ExtractDefaultProps,
34+
ExtractProps,
35+
} from './props'
36+
3037
export {
3138
VueDecorator,
3239
MixedVueBase,
3340
UnionToIntersection,
3441
ExtractInstance,
35-
NarrowEmit,
3642
UnwrapSetupValue,
3743
} from './helpers'

Diff for: src/props.ts

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Prop, PropType } from 'vue'
2+
import { Vue } from './vue'
3+
4+
declare const withDefaultSymbol: unique symbol
5+
6+
export interface WithDefault<T> {
7+
[withDefaultSymbol]: T
8+
}
9+
10+
export type DefaultFactory<T> = (
11+
props: Record<string, unknown>
12+
) => T | null | undefined
13+
14+
export interface PropOptions<T = any, D = T> {
15+
type?: PropType<T> | true | null
16+
required?: boolean
17+
default?: D | DefaultFactory<D> | null | undefined | object
18+
validator?(value: unknown): boolean
19+
}
20+
21+
export interface PropOptionsWithDefault<T, D = T> extends PropOptions<T, D> {
22+
default: PropOptions<T, D>['default']
23+
}
24+
25+
export interface PropOptionsWithRequired<T, D = T> extends PropOptions<T, D> {
26+
required: true
27+
}
28+
29+
export type VueWithProps<P> = Vue<ExtractProps<P>, {}, ExtractDefaultProps<P>> &
30+
ExtractProps<P>
31+
32+
export type ExtractProps<P> = {
33+
[K in keyof P]: P[K] extends WithDefault<infer T> ? T : P[K]
34+
}
35+
36+
export type DefaultKeys<P> = {
37+
[K in keyof P]: P[K] extends WithDefault<any> ? K : never
38+
}[keyof P]
39+
40+
export type ExtractDefaultProps<P> = {
41+
[K in DefaultKeys<P>]: P[K] extends WithDefault<infer T> ? T : never
42+
}
43+
44+
// With default
45+
export function prop<T>(options: PropOptionsWithDefault<T>): WithDefault<T>
46+
47+
// With required
48+
export function prop<T>(options: PropOptionsWithRequired<T>): T
49+
50+
// Others
51+
export function prop<T>(options: Prop<T>): T
52+
53+
// Actual implementation
54+
export function prop(options: Prop<unknown>): unknown {
55+
return options
56+
}

Diff for: src/vue.ts

+31-5
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import {
88
AllowedComponentProps,
99
ComponentCustomProps,
1010
proxyRefs,
11+
Prop,
12+
ComponentObjectPropsOptions,
1113
} from 'vue'
14+
import { VueWithProps } from './props'
1215

1316
function defineGetter<T, K extends keyof T>(
1417
obj: T,
@@ -82,10 +85,6 @@ export interface VueStatic {
8285

8386
/** @internal */
8487
__hmrId?: string
85-
86-
// --- Public APIs
87-
88-
registerHooks(keys: string[]): void
8988
}
9089

9190
export type PublicProps = VNodeProps &
@@ -94,7 +93,9 @@ export type PublicProps = VNodeProps &
9493

9594
export type VueBase = Vue<unknown, never[]>
9695

97-
export type VueMixin<V extends VueBase = VueBase> = VueStatic & { prototype: V }
96+
export type VueMixin<V extends VueBase = VueBase> = VueStatic & {
97+
prototype: V
98+
}
9899

99100
export interface ClassComponentHooks {
100101
// To be extended on user land
@@ -140,6 +141,14 @@ export type Vue<
140141

141142
export interface VueConstructor<V extends VueBase = Vue> extends VueMixin<V> {
142143
new (...args: any[]): V
144+
145+
// --- Public APIs
146+
147+
registerHooks(keys: string[]): void
148+
149+
props<P extends { new (): unknown }>(
150+
Props: P
151+
): VueConstructor<V & VueWithProps<InstanceType<P>>>
143152
}
144153

145154
class VueImpl {
@@ -291,6 +300,23 @@ class VueImpl {
291300
this.__vccHooks.push(...keys)
292301
}
293302

303+
static props(Props: { new (): unknown }): VueConstructor {
304+
const propsMeta = new Props() as Record<string, Prop<any> | undefined>
305+
const props: ComponentObjectPropsOptions = {}
306+
307+
Object.keys(propsMeta).forEach((key) => {
308+
const meta = propsMeta[key]
309+
props[key] = meta ?? null
310+
})
311+
312+
class PropsMixin extends this {
313+
static __vccExtend(options: ComponentOptions) {
314+
options.props = props
315+
}
316+
}
317+
return PropsMixin as VueConstructor
318+
}
319+
294320
$props!: Record<string, any>
295321
$emit!: (event: string, ...args: any[]) => void
296322
$attrs!: ComponentPublicInstance['$attrs']

0 commit comments

Comments
 (0)