2023-03-10 11:30:22 +00:00
|
|
|
import { performance } from 'node:perf_hooks'
|
2023-01-30 11:50:24 +00:00
|
|
|
import { defu } from 'defu'
|
2021-04-02 11:47:01 +00:00
|
|
|
import { applyDefaults } from 'untyped'
|
2024-06-21 08:33:42 +00:00
|
|
|
import type { ModuleDefinition, ModuleOptions, ModuleSetupInstallResult, ModuleSetupReturn, Nuxt, NuxtModule, NuxtOptions, ResolvedModuleOptions } from '@nuxt/schema'
|
2022-02-16 21:34:32 +00:00
|
|
|
import { logger } from '../logger'
|
2024-06-19 12:56:54 +00:00
|
|
|
import { tryUseNuxt, useNuxt } from '../context'
|
|
|
|
import { checkNuxtCompatibility } from '../compatibility'
|
2021-04-02 11:47:01 +00:00
|
|
|
|
2021-04-15 18:49:29 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2024-06-21 08:33:42 +00:00
|
|
|
export function defineNuxtModule<TOptions extends ModuleOptions> (
|
|
|
|
definition: ModuleDefinition<TOptions, Partial<TOptions>, false> | NuxtModule<TOptions, Partial<TOptions>, false>
|
|
|
|
): NuxtModule<TOptions, TOptions, false>
|
2024-06-10 10:16:13 +00:00
|
|
|
|
2024-06-21 08:33:42 +00:00
|
|
|
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 })
|
2024-06-10 20:25:42 +00:00
|
|
|
}
|
2021-04-02 11:47:01 +00:00
|
|
|
|
2024-06-21 08:33:42 +00:00
|
|
|
// Normalize definition and meta
|
|
|
|
const module: ModuleDefinition<TOptions, TOptionsDefaults, TWith> & Required<Pick<ModuleDefinition<TOptions, TOptionsDefaults, TWith>, 'meta'>> = defu(definition, { meta: {} })
|
|
|
|
|
|
|
|
module.meta.configKey ||= module.meta.name
|
|
|
|
|
2021-12-21 13:57:26 +00:00
|
|
|
// Resolves module options from inline options, [configKey] in nuxt.config, defaults and schema
|
2024-06-21 08:33:42 +00:00
|
|
|
async function getOptions (
|
|
|
|
inlineOptions?: Partial<TOptions>,
|
|
|
|
nuxt: Nuxt = useNuxt(),
|
|
|
|
): 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)
|
|
|
|
|
2023-06-16 14:47:38 +00:00
|
|
|
if (module.schema) {
|
2024-06-21 08:33:42 +00:00
|
|
|
options = await applyDefaults(module.schema, options) as any
|
2021-04-02 11:47:01 +00:00
|
|
|
}
|
2024-06-21 08:33:42 +00:00
|
|
|
|
|
|
|
// @ts-expect-error ignore type mismatch when calling `defineNuxtModule` without `.with()`
|
|
|
|
return Promise.resolve(options)
|
2021-12-21 13:57:26 +00:00
|
|
|
}
|
2021-04-02 11:47:01 +00:00
|
|
|
|
2021-12-21 13:57:26 +00:00
|
|
|
// Module format is always a simple function
|
2024-06-21 08:33:42 +00:00
|
|
|
async function normalizedModule (this: any, inlineOptions: Partial<TOptions>, nuxt: Nuxt): Promise<ModuleSetupReturn> {
|
2021-12-21 13:57:26 +00:00
|
|
|
if (!nuxt) {
|
2022-04-04 09:41:48 +00:00
|
|
|
nuxt = tryUseNuxt() || this.nuxt /* invoked by nuxt 2 */
|
2021-04-02 11:47:01 +00:00
|
|
|
}
|
|
|
|
|
2021-12-21 13:57:26 +00:00
|
|
|
// Avoid duplicate installs
|
2023-06-16 14:47:38 +00:00
|
|
|
const uniqueKey = module.meta.name || module.meta.configKey
|
2021-12-21 13:57:26 +00:00
|
|
|
if (uniqueKey) {
|
|
|
|
nuxt.options._requiredModules = nuxt.options._requiredModules || {}
|
|
|
|
if (nuxt.options._requiredModules[uniqueKey]) {
|
2023-01-31 16:44:19 +00:00
|
|
|
return false
|
2021-12-21 13:57:26 +00:00
|
|
|
}
|
|
|
|
nuxt.options._requiredModules[uniqueKey] = true
|
2021-04-02 11:47:01 +00:00
|
|
|
}
|
|
|
|
|
2022-02-16 21:34:32 +00:00
|
|
|
// Check compatibility constraints
|
2023-06-16 14:47:38 +00:00
|
|
|
if (module.meta.compatibility) {
|
|
|
|
const issues = await checkNuxtCompatibility(module.meta.compatibility, nuxt)
|
2021-10-02 18:31:00 +00:00
|
|
|
if (issues.length) {
|
2023-06-16 14:47:38 +00:00
|
|
|
logger.warn(`Module \`${module.meta.name}\` is disabled due to incompatibility issues:\n${issues.toString()}`)
|
2021-10-02 18:31:00 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-21 13:57:26 +00:00
|
|
|
// Resolve module and options
|
|
|
|
const _options = await getOptions(inlineOptions, nuxt)
|
2021-06-24 14:06:16 +00:00
|
|
|
|
2021-12-21 13:57:26 +00:00
|
|
|
// Register hooks
|
2023-06-16 14:47:38 +00:00
|
|
|
if (module.hooks) {
|
|
|
|
nuxt.hooks.addHooks(module.hooks)
|
2021-09-29 10:10:46 +00:00
|
|
|
}
|
|
|
|
|
2021-04-02 11:47:01 +00:00
|
|
|
// Call setup
|
2023-03-15 11:26:01 +00:00
|
|
|
const key = `nuxt:module:${uniqueKey || (Math.round(Math.random() * 10000))}`
|
|
|
|
const mark = performance.mark(key)
|
2023-06-16 14:47:38 +00:00
|
|
|
const res = await module.setup?.call(null as any, _options, nuxt) ?? {}
|
2024-05-29 16:05:21 +00:00
|
|
|
const perf = performance.measure(key, mark.name)
|
|
|
|
const setupTime = Math.round((perf.duration * 100)) / 100
|
2023-03-10 11:30:22 +00:00
|
|
|
|
|
|
|
// Measure setup time
|
2023-07-05 14:04:37 +00:00
|
|
|
if (setupTime > 5000 && uniqueKey !== '@nuxt/telemetry') {
|
2023-03-10 11:30:22 +00:00
|
|
|
logger.warn(`Slow module \`${uniqueKey || '<no name>'}\` took \`${setupTime}ms\` to setup.`)
|
|
|
|
} else if (nuxt.options.debug) {
|
|
|
|
logger.info(`Module \`${uniqueKey || '<no name>'}\` took \`${setupTime}ms\` to setup.`)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if module is ignored
|
|
|
|
if (res === false) { return false }
|
|
|
|
|
|
|
|
// Return module install result
|
2024-06-21 08:33:42 +00:00
|
|
|
return defu(res, <ModuleSetupInstallResult> {
|
2023-03-10 11:30:22 +00:00
|
|
|
timings: {
|
2024-04-05 18:08:32 +00:00
|
|
|
setup: setupTime,
|
|
|
|
},
|
2023-03-10 11:30:22 +00:00
|
|
|
})
|
2021-04-02 11:47:01 +00:00
|
|
|
}
|
|
|
|
|
2021-12-21 13:57:26 +00:00
|
|
|
// Define getters for options and meta
|
2023-06-16 14:47:38 +00:00
|
|
|
normalizedModule.getMeta = () => Promise.resolve(module.meta)
|
2021-12-21 13:57:26 +00:00
|
|
|
normalizedModule.getOptions = getOptions
|
|
|
|
|
2024-06-21 08:33:42 +00:00
|
|
|
return <NuxtModule<TOptions, TOptionsDefaults, TWith>> normalizedModule
|
2021-12-21 13:57:26 +00:00
|
|
|
}
|