Skip to content

Commit c513126

Browse files
authored
types(runtime-core): support plugin options type inference (#3969)
1 parent 584eae6 commit c513126

File tree

2 files changed

+110
-5
lines changed

2 files changed

+110
-5
lines changed

packages/runtime-core/src/apiCreateApp.ts

+15-5
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,13 @@ import { ObjectEmitsOptions } from './componentEmits'
3131
export interface App<HostElement = any> {
3232
version: string
3333
config: AppConfig
34-
use(plugin: Plugin, ...options: any[]): this
34+
35+
use<Options extends unknown[]>(
36+
plugin: Plugin<Options>,
37+
...options: Options
38+
): this
39+
use<Options>(plugin: Plugin<Options>, options: Options): this
40+
3541
mixin(mixin: ComponentOptions): this
3642
component(name: string): Component | undefined
3743
component(name: string, component: Component): this
@@ -140,12 +146,16 @@ export interface AppContext {
140146
filters?: Record<string, Function>
141147
}
142148

143-
type PluginInstallFunction = (app: App, ...options: any[]) => any
149+
type PluginInstallFunction<Options> = Options extends unknown[]
150+
? (app: App, ...options: Options) => any
151+
: (app: App, options: Options) => any
144152

145-
export type Plugin =
146-
| (PluginInstallFunction & { install?: PluginInstallFunction })
153+
export type Plugin<Options = any[]> =
154+
| (PluginInstallFunction<Options> & {
155+
install?: PluginInstallFunction<Options>
156+
})
147157
| {
148-
install: PluginInstallFunction
158+
install: PluginInstallFunction<Options>
149159
}
150160

151161
export function createAppContext(): AppContext {

test-dts/appUse.test-d.ts

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { createApp, App, Plugin } from './index'
2+
3+
const app = createApp({})
4+
5+
// Plugin without types accept anything
6+
const PluginWithoutType: Plugin = {
7+
install(app: App) {}
8+
}
9+
10+
app.use(PluginWithoutType)
11+
app.use(PluginWithoutType, 2)
12+
app.use(PluginWithoutType, { anything: 'goes' }, true)
13+
14+
type PluginOptions = {
15+
option1?: string
16+
option2: number
17+
option3: boolean
18+
}
19+
20+
const PluginWithObjectOptions = {
21+
install(app: App, options: PluginOptions) {
22+
options.option1
23+
options.option2
24+
options.option3
25+
}
26+
}
27+
28+
for (const Plugin of [
29+
PluginWithObjectOptions,
30+
PluginWithObjectOptions.install
31+
]) {
32+
// @ts-expect-error: no params
33+
app.use(Plugin)
34+
35+
// @ts-expect-error option2 and option3 (required) missing
36+
app.use(Plugin, {})
37+
// @ts-expect-error type mismatch
38+
app.use(Plugin, undefined)
39+
// valid options
40+
app.use(Plugin, { option2: 1, option3: true })
41+
app.use(Plugin, { option1: 'foo', option2: 1, option3: true })
42+
}
43+
44+
const PluginNoOptions = {
45+
install(app: App) {}
46+
}
47+
48+
for (const Plugin of [PluginNoOptions, PluginNoOptions.install]) {
49+
// no args
50+
app.use(Plugin)
51+
// @ts-expect-error unexpected plugin option
52+
app.use(Plugin, {})
53+
// @ts-expect-error only no options is valid
54+
app.use(Plugin, undefined)
55+
}
56+
57+
const PluginMultipleArgs = {
58+
install: (app: App, a: string, b: number) => {}
59+
}
60+
61+
for (const Plugin of [PluginMultipleArgs, PluginMultipleArgs.install]) {
62+
// @ts-expect-error: 2 arguments expected
63+
app.use(Plugin, 'hey')
64+
app.use(Plugin, 'hey', 2)
65+
}
66+
67+
const PluginOptionalOptions = {
68+
install(
69+
app: App,
70+
options: PluginOptions = { option2: 2, option3: true, option1: 'foo' }
71+
) {
72+
options.option1
73+
options.option2
74+
options.option3
75+
}
76+
}
77+
78+
for (const Plugin of [PluginOptionalOptions, PluginOptionalOptions.install]) {
79+
// both version are valid
80+
app.use(Plugin)
81+
app.use(Plugin, undefined)
82+
83+
// @ts-expect-error option2 and option3 (required) missing
84+
app.use(Plugin, {})
85+
// valid options
86+
app.use(Plugin, { option2: 1, option3: true })
87+
app.use(Plugin, { option1: 'foo', option2: 1, option3: true })
88+
}
89+
90+
// still valid but it's better to use the regular function because this one can accept an optional param
91+
const PluginTyped: Plugin<PluginOptions> = (app, options) => {}
92+
93+
// @ts-expect-error: needs options
94+
app.use(PluginTyped)
95+
app.use(PluginTyped, { option2: 2, option3: true })

0 commit comments

Comments
 (0)