Skip to content

Commit 3d9e1f2

Browse files
committed
Merge branch 'props-class' into next
2 parents c3ccae0 + 19880a7 commit 3d9e1f2

16 files changed

+288
-466
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: package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
"@microsoft/api-extractor": "^7.9.9",
5959
"@types/jest": "^26.0.10",
6060
"@types/node": "^14.6.0",
61-
"@vue/compiler-sfc": "^3.0.0-rc.12",
61+
"@vue/compiler-sfc": "^3.0.0",
6262
"babel-jest": "^26.3.0",
6363
"babel-loader": "^8.1.0",
6464
"conventional-changelog-cli": "^2.1.0",
@@ -74,7 +74,7 @@
7474
"ts-loader": "^8.0.2",
7575
"typescript": "^4.0.2",
7676
"uglify-es": "^3.3.9",
77-
"vue": "^3.0.0-rc.12",
77+
"vue": "^3.0.0",
7878
"vue-loader": "^16.0.0-beta.5",
7979
"webpack": "^4.44.1",
8080
"webpack-cli": "^3.3.12"

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>
@@ -67,25 +52,8 @@ export type UnionToIntersection<U> = (
6752

6853
export type ExtractInstance<T> = T extends VueMixin<infer V> ? V : never
6954

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

9159
export function mixins<T extends VueMixin[]>(...Ctors: T): MixedVueBase<T>
@@ -108,47 +76,6 @@ export function mixins(...Ctors: VueMixin[]): VueConstructor {
10876
}
10977
}
11078

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

Diff for: src/index.ts

+15-11
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
@@ -22,16 +17,25 @@ export {
2217
VueMixin,
2318
VueStatic,
2419
VueConstructor,
25-
EmitsOptions,
26-
ObjectEmitsOptions,
2720
PublicProps,
2821
} from './vue'
2922

23+
export {
24+
PropOptions,
25+
PropOptionsWithDefault,
26+
PropOptionsWithRequired,
27+
WithDefault,
28+
VueWithProps,
29+
DefaultFactory,
30+
DefaultKeys,
31+
ExtractDefaultProps,
32+
ExtractProps,
33+
} from './props'
34+
3035
export {
3136
VueDecorator,
3237
MixedVueBase,
3338
UnionToIntersection,
3439
ExtractInstance,
35-
NarrowEmit,
3640
UnwrapSetupValue,
3741
} 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 | undefined
52+
53+
// Actual implementation
54+
export function prop(options: Prop<unknown>): unknown {
55+
return options
56+
}

Diff for: src/vue.ts

+32-11
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import {
88
AllowedComponentProps,
99
ComponentCustomProps,
1010
proxyRefs,
11+
Prop,
12+
ComponentObjectPropsOptions,
13+
EmitsOptions,
1114
} from 'vue'
15+
import { VueWithProps } from './props'
1216

1317
function defineGetter<T, K extends keyof T>(
1418
obj: T,
@@ -82,10 +86,6 @@ export interface VueStatic {
8286

8387
/** @internal */
8488
__hmrId?: string
85-
86-
// --- Public APIs
87-
88-
registerHooks(keys: string[]): void
8989
}
9090

9191
export type PublicProps = VNodeProps &
@@ -94,7 +94,9 @@ export type PublicProps = VNodeProps &
9494

9595
export type VueBase = Vue<unknown, never[]>
9696

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

99101
export interface ClassComponentHooks {
100102
// To be extended on user land
@@ -115,12 +117,6 @@ export interface ClassComponentHooks {
115117
serverPrefetch?(): Promise<unknown>
116118
}
117119

118-
export type ObjectEmitsOptions = Record<
119-
string,
120-
((...args: any[]) => any) | null
121-
>
122-
export type EmitsOptions = ObjectEmitsOptions | string[]
123-
124120
export type Vue<
125121
Props = unknown,
126122
Emits extends EmitsOptions = {},
@@ -140,6 +136,14 @@ export type Vue<
140136

141137
export interface VueConstructor<V extends VueBase = Vue> extends VueMixin<V> {
142138
new (...args: any[]): V
139+
140+
// --- Public APIs
141+
142+
registerHooks(keys: string[]): void
143+
144+
props<P extends { new (): unknown }>(
145+
Props: P
146+
): VueConstructor<V & VueWithProps<InstanceType<P>>>
143147
}
144148

145149
class VueImpl {
@@ -291,6 +295,23 @@ class VueImpl {
291295
this.__vccHooks.push(...keys)
292296
}
293297

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

0 commit comments

Comments
 (0)