Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

types(runtime-core): enable plugin option types #3969

Merged
merged 7 commits into from
Nov 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions packages/runtime-core/src/apiCreateApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ import { ObjectEmitsOptions } from './componentEmits'
export interface App<HostElement = any> {
version: string
config: AppConfig
use(plugin: Plugin, ...options: any[]): this

use<Options extends unknown[]>(
plugin: Plugin<Options>,
...options: Options
): this
use<Options>(plugin: Plugin<Options>, options: Options): this

mixin(mixin: ComponentOptions): this
component(name: string): Component | undefined
component(name: string, component: Component): this
Expand Down Expand Up @@ -137,12 +143,16 @@ export interface AppContext {
filters?: Record<string, Function>
}

type PluginInstallFunction = (app: App, ...options: any[]) => any
type PluginInstallFunction<Options> = Options extends unknown[]
? (app: App, ...options: Options) => any
: (app: App, options: Options) => any

export type Plugin =
| (PluginInstallFunction & { install?: PluginInstallFunction })
export type Plugin<Options = any[]> =
| (PluginInstallFunction<Options> & {
install?: PluginInstallFunction<Options>
})
| {
install: PluginInstallFunction
install: PluginInstallFunction<Options>
}

export function createAppContext(): AppContext {
Expand Down
95 changes: 95 additions & 0 deletions test-dts/appUse.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { createApp, App, Plugin } from './index'

const app = createApp({})

// Plugin without types accept anything
const PluginWithoutType: Plugin = {
install(app: App) {}
}

app.use(PluginWithoutType)
app.use(PluginWithoutType, 2)
app.use(PluginWithoutType, { anything: 'goes' }, true)

type PluginOptions = {
option1?: string
option2: number
option3: boolean
}

const PluginWithObjectOptions = {
install(app: App, options: PluginOptions) {
options.option1
options.option2
options.option3
}
}

for (const Plugin of [
PluginWithObjectOptions,
PluginWithObjectOptions.install
]) {
// @ts-expect-error: no params
app.use(Plugin)

// @ts-expect-error option2 and option3 (required) missing
app.use(Plugin, {})
// @ts-expect-error type mismatch
app.use(Plugin, undefined)
// valid options
app.use(Plugin, { option2: 1, option3: true })
app.use(Plugin, { option1: 'foo', option2: 1, option3: true })
}

const PluginNoOptions = {
install(app: App) {}
}

for (const Plugin of [PluginNoOptions, PluginNoOptions.install]) {
// no args
app.use(Plugin)
// @ts-expect-error unexpected plugin option
app.use(Plugin, {})
// @ts-expect-error only no options is valid
app.use(Plugin, undefined)
}

const PluginMultipleArgs = {
install: (app: App, a: string, b: number) => {}
}

for (const Plugin of [PluginMultipleArgs, PluginMultipleArgs.install]) {
// @ts-expect-error: 2 arguments expected
app.use(Plugin, 'hey')
app.use(Plugin, 'hey', 2)
}

const PluginOptionalOptions = {
install(
app: App,
options: PluginOptions = { option2: 2, option3: true, option1: 'foo' }
) {
options.option1
options.option2
options.option3
}
}

for (const Plugin of [PluginOptionalOptions, PluginOptionalOptions.install]) {
// both version are valid
app.use(Plugin)
app.use(Plugin, undefined)

// @ts-expect-error option2 and option3 (required) missing
app.use(Plugin, {})
// valid options
app.use(Plugin, { option2: 1, option3: true })
app.use(Plugin, { option1: 'foo', option2: 1, option3: true })
}

// still valid but it's better to use the regular function because this one can accept an optional param
const PluginTyped: Plugin<PluginOptions> = (app, options) => {}

// @ts-expect-error: needs options
app.use(PluginTyped)
app.use(PluginTyped, { option2: 2, option3: true })