2021-09-29 10:10:46 +00:00
|
|
|
import { promises as fsp } from 'fs'
|
2021-04-02 11:47:01 +00:00
|
|
|
import defu from 'defu'
|
|
|
|
import { applyDefaults } from 'untyped'
|
2021-10-02 18:31:00 +00:00
|
|
|
import consola from 'consola'
|
2021-10-26 14:42:10 +00:00
|
|
|
import { dirname } from 'pathe'
|
2021-12-21 13:57:26 +00:00
|
|
|
import type { Nuxt, NuxtTemplate, NuxtModule, ModuleOptions, ModuleDefinition } from '@nuxt/schema'
|
2021-11-21 16:14:46 +00:00
|
|
|
import { useNuxt, nuxtCtx } from '../context'
|
2021-12-21 13:57:26 +00:00
|
|
|
import { isNuxt2, checkNuxtCompatibility } from '../compatibility'
|
2021-11-21 16:14:46 +00:00
|
|
|
import { templateUtils, compileTemplate } from '../internal/template'
|
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.
|
|
|
|
*/
|
2021-12-21 13:57:26 +00:00
|
|
|
export function defineNuxtModule<OptionsT extends ModuleOptions> (definition: ModuleDefinition<OptionsT>): NuxtModule<OptionsT> {
|
|
|
|
// Normalize definition and meta
|
|
|
|
if (!definition.meta) { definition.meta = {} }
|
|
|
|
if (!definition.meta.configKey) {
|
|
|
|
// @ts-ignore TODO: Remove non-meta fallbacks in RC
|
|
|
|
definition.meta.name = definition.meta.name || definition.name
|
|
|
|
// @ts-ignore
|
|
|
|
definition.meta.configKey = definition.meta.configKey || definition.configKey || definition.meta.name
|
|
|
|
}
|
2021-04-02 11:47:01 +00:00
|
|
|
|
2021-12-21 13:57:26 +00:00
|
|
|
// Resolves module options from inline options, [configKey] in nuxt.config, defaults and schema
|
|
|
|
function getOptions (inlineOptions?: OptionsT, nuxt: Nuxt = useNuxt()) {
|
|
|
|
const configKey = definition.meta.configKey || definition.meta.name
|
|
|
|
const _defaults = typeof definition.defaults === 'function' ? definition.defaults(nuxt) : definition.defaults
|
|
|
|
let _options = defu(inlineOptions, nuxt.options[configKey], _defaults) as OptionsT
|
|
|
|
if (definition.schema) {
|
|
|
|
_options = applyDefaults(definition.schema, _options) as OptionsT
|
2021-04-02 11:47:01 +00:00
|
|
|
}
|
2021-12-21 13:57:26 +00:00
|
|
|
return Promise.resolve(_options)
|
|
|
|
}
|
2021-04-02 11:47:01 +00:00
|
|
|
|
2021-12-21 13:57:26 +00:00
|
|
|
// Module format is always a simple function
|
|
|
|
async function normalizedModule (inlineOptions: OptionsT, nuxt: Nuxt) {
|
|
|
|
if (!nuxt) {
|
|
|
|
nuxt = useNuxt() || 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
|
|
|
|
const uniqueKey = definition.meta.name || definition.meta.configKey
|
|
|
|
if (uniqueKey) {
|
|
|
|
nuxt.options._requiredModules = nuxt.options._requiredModules || {}
|
|
|
|
if (nuxt.options._requiredModules[uniqueKey]) {
|
|
|
|
// TODO: Notify user if inline options is provided since will be ignored!
|
|
|
|
return
|
|
|
|
}
|
|
|
|
nuxt.options._requiredModules[uniqueKey] = true
|
2021-04-02 11:47:01 +00:00
|
|
|
}
|
|
|
|
|
2021-12-21 13:57:26 +00:00
|
|
|
// Check compatibility contraints
|
|
|
|
if (definition.meta.compatibility) {
|
|
|
|
const issues = await checkNuxtCompatibility(definition.meta.compatibility, nuxt)
|
2021-10-02 18:31:00 +00:00
|
|
|
if (issues.length) {
|
2021-12-21 13:57:26 +00:00
|
|
|
consola.warn(`Module \`${definition.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
|
|
|
// Prepare
|
|
|
|
nuxt2Shims(nuxt)
|
2021-04-02 11:47:01 +00:00
|
|
|
|
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
|
|
|
|
if (definition.hooks) {
|
|
|
|
nuxt.hooks.addHooks(definition.hooks)
|
2021-09-29 10:10:46 +00:00
|
|
|
}
|
|
|
|
|
2021-04-02 11:47:01 +00:00
|
|
|
// Call setup
|
2021-12-21 13:57:26 +00:00
|
|
|
await definition.setup?.call(null, _options, nuxt)
|
2021-04-02 11:47:01 +00:00
|
|
|
}
|
|
|
|
|
2021-12-21 13:57:26 +00:00
|
|
|
// Define getters for options and meta
|
|
|
|
normalizedModule.getMeta = () => Promise.resolve(definition.meta)
|
|
|
|
normalizedModule.getOptions = getOptions
|
|
|
|
|
|
|
|
return normalizedModule as NuxtModule<OptionsT>
|
|
|
|
}
|
|
|
|
|
|
|
|
// -- Nuxt 2 compatibility shims --
|
|
|
|
const NUXT2_SHIMS_KEY = '__nuxt2_shims_key__'
|
|
|
|
function nuxt2Shims (nuxt: Nuxt) {
|
|
|
|
// Avoid duplicate install and only apply to Nuxt2
|
|
|
|
if (!isNuxt2(nuxt) || nuxt[NUXT2_SHIMS_KEY]) { return }
|
|
|
|
nuxt[NUXT2_SHIMS_KEY] = true
|
|
|
|
|
|
|
|
// Allow using nuxt.hooks
|
|
|
|
// @ts-ignore Nuxt 2 extends hookable
|
|
|
|
nuxt.hooks = nuxt
|
|
|
|
|
|
|
|
// Allow using useNuxt()
|
|
|
|
if (!nuxtCtx.use()) {
|
|
|
|
nuxtCtx.set(nuxt)
|
|
|
|
nuxt.hook('close', () => nuxtCtx.unset())
|
|
|
|
}
|
2021-04-02 11:47:01 +00:00
|
|
|
|
2021-12-21 13:57:26 +00:00
|
|
|
// Support virtual templates with getContents() by writing them to .nuxt directory
|
|
|
|
let virtualTemplates: NuxtTemplate[]
|
|
|
|
nuxt.hook('builder:prepared', (_builder, buildOptions) => {
|
|
|
|
virtualTemplates = buildOptions.templates.filter(t => t.getContents)
|
|
|
|
for (const template of virtualTemplates) {
|
|
|
|
buildOptions.templates.splice(buildOptions.templates.indexOf(template), 1)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
nuxt.hook('build:templates', async (templates) => {
|
|
|
|
const context = {
|
|
|
|
nuxt,
|
|
|
|
utils: templateUtils,
|
|
|
|
app: {
|
|
|
|
dir: nuxt.options.srcDir,
|
|
|
|
extensions: nuxt.options.extensions,
|
|
|
|
plugins: nuxt.options.plugins,
|
|
|
|
templates: [
|
|
|
|
...templates.templatesFiles,
|
|
|
|
...virtualTemplates
|
|
|
|
],
|
|
|
|
templateVars: templates.templateVars
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for await (const template of virtualTemplates) {
|
|
|
|
const contents = await compileTemplate({ ...template, src: '' }, context)
|
|
|
|
await fsp.mkdir(dirname(template.dst), { recursive: true })
|
|
|
|
await fsp.writeFile(template.dst, contents)
|
|
|
|
}
|
|
|
|
})
|
2021-04-02 11:47:01 +00:00
|
|
|
}
|