Skip to content

Commit fd96c63

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

File tree

14 files changed

+221
-403
lines changed

14 files changed

+221
-403
lines changed

example/babel.config.js

Lines changed: 4 additions & 0 deletions
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
}

example/src/App.vue

Lines changed: 5 additions & 5 deletions
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

example/tsconfig.json

Lines changed: 3 additions & 7 deletions
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
}

example/webpack.config.js

Lines changed: 6 additions & 13 deletions
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
},

src/helpers.ts

Lines changed: 3 additions & 76 deletions
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>

src/index.ts

Lines changed: 15 additions & 9 deletions
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'

src/props.ts

Lines changed: 56 additions & 0 deletions
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+
}

src/vue.ts

Lines changed: 31 additions & 5 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy