mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 13:45:18 +00:00
feat(kit,schema): add .with
for better module options types (#27520)
This commit is contained in:
parent
3a59c02cfd
commit
e374474df3
@ -1,7 +1,7 @@
|
|||||||
import { performance } from 'node:perf_hooks'
|
import { performance } from 'node:perf_hooks'
|
||||||
import { defu } from 'defu'
|
import { defu } from 'defu'
|
||||||
import { applyDefaults } from 'untyped'
|
import { applyDefaults } from 'untyped'
|
||||||
import type { ModuleDefinition, ModuleOptions, ModuleSetupReturn, Nuxt, NuxtModule, NuxtOptions } from '@nuxt/schema'
|
import type { ModuleDefinition, ModuleOptions, ModuleSetupInstallResult, ModuleSetupReturn, Nuxt, NuxtModule, NuxtOptions, ResolvedModuleOptions } from '@nuxt/schema'
|
||||||
import { logger } from '../logger'
|
import { logger } from '../logger'
|
||||||
import { tryUseNuxt, useNuxt } from '../context'
|
import { tryUseNuxt, useNuxt } from '../context'
|
||||||
import { checkNuxtCompatibility } from '../compatibility'
|
import { checkNuxtCompatibility } from '../compatibility'
|
||||||
@ -10,28 +10,76 @@ import { checkNuxtCompatibility } from '../compatibility'
|
|||||||
* Define a Nuxt module, automatically merging defaults with user provided options, installing
|
* Define a Nuxt module, automatically merging defaults with user provided options, installing
|
||||||
* any hooks that are provided, and calling an optional setup function for full control.
|
* any hooks that are provided, and calling an optional setup function for full control.
|
||||||
*/
|
*/
|
||||||
export function defineNuxtModule<OptionsT extends ModuleOptions> (definition: ModuleDefinition<OptionsT> | NuxtModule<OptionsT>): NuxtModule<OptionsT> {
|
export function defineNuxtModule<TOptions extends ModuleOptions> (
|
||||||
if (typeof definition === 'function') { return defineNuxtModule({ setup: definition }) }
|
definition: ModuleDefinition<TOptions, Partial<TOptions>, false> | NuxtModule<TOptions, Partial<TOptions>, false>
|
||||||
|
): NuxtModule<TOptions, TOptions, false>
|
||||||
|
|
||||||
|
export function defineNuxtModule<TOptions extends ModuleOptions> (): {
|
||||||
|
with: <TOptionsDefaults extends Partial<TOptions>> (
|
||||||
|
definition: ModuleDefinition<TOptions, TOptionsDefaults, true> | NuxtModule<TOptions, TOptionsDefaults, true>
|
||||||
|
) => NuxtModule<TOptions, TOptionsDefaults, true>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defineNuxtModule<TOptions extends ModuleOptions> (
|
||||||
|
definition?: ModuleDefinition<TOptions, Partial<TOptions>, false> | NuxtModule<TOptions, Partial<TOptions>, false>,
|
||||||
|
) {
|
||||||
|
if (definition) {
|
||||||
|
return _defineNuxtModule(definition)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
with: <TOptionsDefaults extends Partial<TOptions>>(
|
||||||
|
definition: ModuleDefinition<TOptions, TOptionsDefaults, true> | NuxtModule<TOptions, TOptionsDefaults, true>,
|
||||||
|
) => _defineNuxtModule(definition),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _defineNuxtModule<
|
||||||
|
TOptions extends ModuleOptions,
|
||||||
|
TOptionsDefaults extends Partial<TOptions>,
|
||||||
|
TWith extends boolean,
|
||||||
|
> (
|
||||||
|
definition: ModuleDefinition<TOptions, TOptionsDefaults, TWith> | NuxtModule<TOptions, TOptionsDefaults, TWith>,
|
||||||
|
): NuxtModule<TOptions, TOptionsDefaults, TWith> {
|
||||||
|
if (typeof definition === 'function') {
|
||||||
|
return _defineNuxtModule<TOptions, TOptionsDefaults, TWith>({ setup: definition })
|
||||||
|
}
|
||||||
|
|
||||||
// Normalize definition and meta
|
// Normalize definition and meta
|
||||||
const module: ModuleDefinition<OptionsT> & Required<Pick<ModuleDefinition<OptionsT>, 'meta'>> = defu(definition, { meta: {} })
|
const module: ModuleDefinition<TOptions, TOptionsDefaults, TWith> & Required<Pick<ModuleDefinition<TOptions, TOptionsDefaults, TWith>, 'meta'>> = defu(definition, { meta: {} })
|
||||||
if (module.meta.configKey === undefined) {
|
|
||||||
module.meta.configKey = module.meta.name
|
module.meta.configKey ||= module.meta.name
|
||||||
}
|
|
||||||
|
|
||||||
// Resolves module options from inline options, [configKey] in nuxt.config, defaults and schema
|
// Resolves module options from inline options, [configKey] in nuxt.config, defaults and schema
|
||||||
async function getOptions (inlineOptions?: OptionsT, nuxt: Nuxt = useNuxt()) {
|
async function getOptions (
|
||||||
const configKey = module.meta.configKey || module.meta.name!
|
inlineOptions?: Partial<TOptions>,
|
||||||
const _defaults = module.defaults instanceof Function ? module.defaults(nuxt) : module.defaults
|
nuxt: Nuxt = useNuxt(),
|
||||||
let _options = defu(inlineOptions, nuxt.options[configKey as keyof NuxtOptions], _defaults) as OptionsT
|
): Promise<
|
||||||
|
TWith extends true
|
||||||
|
? ResolvedModuleOptions<TOptions, TOptionsDefaults>
|
||||||
|
: TOptions
|
||||||
|
> {
|
||||||
|
const nuxtConfigOptionsKey = module.meta.configKey || module.meta.name
|
||||||
|
|
||||||
|
const nuxtConfigOptions: Partial<TOptions> = nuxtConfigOptionsKey && nuxtConfigOptionsKey in nuxt.options ? nuxt.options[<keyof NuxtOptions> nuxtConfigOptionsKey] : {}
|
||||||
|
|
||||||
|
const optionsDefaults: TOptionsDefaults =
|
||||||
|
module.defaults instanceof Function
|
||||||
|
? module.defaults(nuxt)
|
||||||
|
: module.defaults ?? <TOptionsDefaults> {}
|
||||||
|
|
||||||
|
let options = defu(inlineOptions, nuxtConfigOptions, optionsDefaults)
|
||||||
|
|
||||||
if (module.schema) {
|
if (module.schema) {
|
||||||
_options = await applyDefaults(module.schema, _options) as OptionsT
|
options = await applyDefaults(module.schema, options) as any
|
||||||
}
|
}
|
||||||
return Promise.resolve(_options)
|
|
||||||
|
// @ts-expect-error ignore type mismatch when calling `defineNuxtModule` without `.with()`
|
||||||
|
return Promise.resolve(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Module format is always a simple function
|
// Module format is always a simple function
|
||||||
async function normalizedModule (this: any, inlineOptions: OptionsT, nuxt: Nuxt) {
|
async function normalizedModule (this: any, inlineOptions: Partial<TOptions>, nuxt: Nuxt): Promise<ModuleSetupReturn> {
|
||||||
if (!nuxt) {
|
if (!nuxt) {
|
||||||
nuxt = tryUseNuxt() || this.nuxt /* invoked by nuxt 2 */
|
nuxt = tryUseNuxt() || this.nuxt /* invoked by nuxt 2 */
|
||||||
}
|
}
|
||||||
@ -81,7 +129,7 @@ export function defineNuxtModule<OptionsT extends ModuleOptions> (definition: Mo
|
|||||||
if (res === false) { return false }
|
if (res === false) { return false }
|
||||||
|
|
||||||
// Return module install result
|
// Return module install result
|
||||||
return defu(res, <ModuleSetupReturn> {
|
return defu(res, <ModuleSetupInstallResult> {
|
||||||
timings: {
|
timings: {
|
||||||
setup: setupTime,
|
setup: setupTime,
|
||||||
},
|
},
|
||||||
@ -92,5 +140,5 @@ export function defineNuxtModule<OptionsT extends ModuleOptions> (definition: Mo
|
|||||||
normalizedModule.getMeta = () => Promise.resolve(module.meta)
|
normalizedModule.getMeta = () => Promise.resolve(module.meta)
|
||||||
normalizedModule.getOptions = getOptions
|
normalizedModule.getOptions = getOptions
|
||||||
|
|
||||||
return normalizedModule as NuxtModule<OptionsT>
|
return <NuxtModule<TOptions, TOptionsDefaults, TWith>> normalizedModule
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ export async function installModule<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call module
|
// Call module
|
||||||
const res = await nuxtModule(inlineOptions, nuxt) ?? {}
|
const res = await nuxtModule(inlineOptions || {}, nuxt) ?? {}
|
||||||
if (res === false /* setup aborted */) {
|
if (res === false /* setup aborted */) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { Defu } from 'defu'
|
||||||
import type { NuxtHooks } from './hooks'
|
import type { NuxtHooks } from './hooks'
|
||||||
import type { Nuxt } from './nuxt'
|
import type { Nuxt } from './nuxt'
|
||||||
import type { NuxtCompatibility } from './compatibility'
|
import type { NuxtCompatibility } from './compatibility'
|
||||||
@ -26,8 +27,7 @@ export interface ModuleMeta {
|
|||||||
/** The options received. */
|
/** The options received. */
|
||||||
export type ModuleOptions = Record<string, any>
|
export type ModuleOptions = Record<string, any>
|
||||||
|
|
||||||
/** Optional result for nuxt modules */
|
export type ModuleSetupInstallResult = {
|
||||||
export interface ModuleSetupReturn {
|
|
||||||
/**
|
/**
|
||||||
* Timing information for the initial setup
|
* Timing information for the initial setup
|
||||||
*/
|
*/
|
||||||
@ -39,19 +39,62 @@ export interface ModuleSetupReturn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Awaitable<T> = T | Promise<T>
|
type Awaitable<T> = T | Promise<T>
|
||||||
type _ModuleSetupReturn = Awaitable<void | false | ModuleSetupReturn>
|
|
||||||
|
|
||||||
/** Input module passed to defineNuxtModule. */
|
type Prettify<T> = {
|
||||||
export interface ModuleDefinition<T extends ModuleOptions = ModuleOptions> {
|
[K in keyof T]: T[K];
|
||||||
|
} & {}
|
||||||
|
|
||||||
|
export type ModuleSetupReturn = Awaitable<false | void | ModuleSetupInstallResult>
|
||||||
|
|
||||||
|
export type ResolvedModuleOptions<
|
||||||
|
TOptions extends ModuleOptions,
|
||||||
|
TOptionsDefaults extends Partial<TOptions>,
|
||||||
|
> =
|
||||||
|
Prettify<
|
||||||
|
Defu<
|
||||||
|
Partial<TOptions>,
|
||||||
|
[Partial<TOptions>, TOptionsDefaults]
|
||||||
|
>
|
||||||
|
>
|
||||||
|
|
||||||
|
/** Module definition passed to 'defineNuxtModule(...)' or 'defineNuxtModule().with(...)'. */
|
||||||
|
export interface ModuleDefinition<
|
||||||
|
TOptions extends ModuleOptions,
|
||||||
|
TOptionsDefaults extends Partial<TOptions>,
|
||||||
|
TWith extends boolean,
|
||||||
|
> {
|
||||||
meta?: ModuleMeta
|
meta?: ModuleMeta
|
||||||
defaults?: T | ((nuxt: Nuxt) => T)
|
defaults?: TOptionsDefaults | ((nuxt: Nuxt) => TOptionsDefaults)
|
||||||
schema?: T
|
schema?: TOptions
|
||||||
hooks?: Partial<NuxtHooks>
|
hooks?: Partial<NuxtHooks>
|
||||||
setup?: (this: void, resolvedOptions: T, nuxt: Nuxt) => _ModuleSetupReturn
|
setup?: (
|
||||||
|
this: void,
|
||||||
|
resolvedOptions: TWith extends true
|
||||||
|
? ResolvedModuleOptions<TOptions, TOptionsDefaults>
|
||||||
|
: TOptions,
|
||||||
|
nuxt: Nuxt
|
||||||
|
) => ModuleSetupReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NuxtModule<T extends ModuleOptions = ModuleOptions> {
|
export interface NuxtModule<
|
||||||
(this: void, inlineOptions: T, nuxt: Nuxt): _ModuleSetupReturn
|
TOptions extends ModuleOptions = ModuleOptions,
|
||||||
getOptions?: (inlineOptions?: T, nuxt?: Nuxt) => Promise<T>
|
TOptionsDefaults extends Partial<TOptions> = Partial<TOptions>,
|
||||||
|
TWith extends boolean = false,
|
||||||
|
> {
|
||||||
|
(
|
||||||
|
this: void,
|
||||||
|
resolvedOptions: TWith extends true
|
||||||
|
? ResolvedModuleOptions<TOptions, TOptionsDefaults>
|
||||||
|
: TOptions,
|
||||||
|
nuxt: Nuxt
|
||||||
|
): ModuleSetupReturn
|
||||||
|
getOptions?: (
|
||||||
|
inlineOptions?: Partial<TOptions>,
|
||||||
|
nuxt?: Nuxt
|
||||||
|
) => Promise<
|
||||||
|
TWith extends true
|
||||||
|
? ResolvedModuleOptions<TOptions, TOptionsDefaults>
|
||||||
|
: TOptions
|
||||||
|
>
|
||||||
getMeta?: () => Promise<ModuleMeta>
|
getMeta?: () => Promise<ModuleMeta>
|
||||||
}
|
}
|
||||||
|
17
test/fixtures/basic-types/types.ts
vendored
17
test/fixtures/basic-types/types.ts
vendored
@ -254,6 +254,23 @@ describe('modules', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('correctly typed resolved options in defineNuxtModule setup using `.with()`', () => {
|
||||||
|
defineNuxtModule<{
|
||||||
|
foo?: string
|
||||||
|
baz: number
|
||||||
|
}>().with({
|
||||||
|
defaults: {
|
||||||
|
foo: 'bar',
|
||||||
|
},
|
||||||
|
setup: (resolvedOptions) => {
|
||||||
|
expectTypeOf(resolvedOptions).toEqualTypeOf<{
|
||||||
|
foo: string
|
||||||
|
baz?: number | undefined
|
||||||
|
}>()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('nuxtApp', () => {
|
describe('nuxtApp', () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user