mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-30 15:22:39 +00:00
feat(kit, schema)!: finalize nuxt 3 module spec and utils (#2275)
This commit is contained in:
parent
6e32bde7ae
commit
045b9edb5d
@ -8,6 +8,7 @@ export default defineBuildConfig({
|
|||||||
],
|
],
|
||||||
externals: [
|
externals: [
|
||||||
'webpack',
|
'webpack',
|
||||||
'vite'
|
'vite',
|
||||||
|
'vue-meta'
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@ -3,20 +3,22 @@ module.exports = function (...args) {
|
|||||||
return import('./dist/module.mjs').then(m => m.default.call(this, ...args))
|
return import('./dist/module.mjs').then(m => m.default.call(this, ...args))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pkg = require('./package.json')
|
||||||
|
|
||||||
module.exports.defineNuxtConfig = (config = {}) => {
|
module.exports.defineNuxtConfig = (config = {}) => {
|
||||||
if (config.bridge !== false) {
|
if (config.bridge !== false) {
|
||||||
|
config.bridge = config.bridge || {}
|
||||||
|
config.bridge._version = pkg.version
|
||||||
if (!config.buildModules) {
|
if (!config.buildModules) {
|
||||||
config.buildModules = []
|
config.buildModules = []
|
||||||
}
|
}
|
||||||
if (!config.buildModules.find(m => m === '@nuxt/bridge' || m === '@nuxt/bridge-edge')) {
|
if (!config.buildModules.find(m => m === '@nuxt/bridge' || m === '@nuxt/bridge-edge')) {
|
||||||
config.buildModules.push('@nuxt/bridge')
|
config.buildModules.unshift('@nuxt/bridge')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
const pkg = require('./package.json')
|
|
||||||
|
|
||||||
module.exports.meta = {
|
module.exports.meta = {
|
||||||
pkg,
|
pkg,
|
||||||
name: pkg.name,
|
name: pkg.name,
|
||||||
|
@ -35,5 +35,5 @@ export async function setupAutoImports () {
|
|||||||
autoImports.push({ name: 'useNuxt2Meta', as: 'useNuxt2Meta', from: '#app' })
|
autoImports.push({ name: 'useNuxt2Meta', as: 'useNuxt2Meta', from: '#app' })
|
||||||
})
|
})
|
||||||
|
|
||||||
await installModule(nuxt, autoImports)
|
await installModule(autoImports)
|
||||||
}
|
}
|
||||||
|
@ -29,5 +29,5 @@ export const setupMeta = async (opts: SetupMetaOptions) => {
|
|||||||
const runtimeDir = resolve(distDir, 'runtime/meta')
|
const runtimeDir = resolve(distDir, 'runtime/meta')
|
||||||
nuxt.options.alias['#meta'] = runtimeDir
|
nuxt.options.alias['#meta'] = runtimeDir
|
||||||
|
|
||||||
await installModule(nuxt, metaModule)
|
await installModule(metaModule)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { createRequire } from 'module'
|
import { createRequire } from 'module'
|
||||||
import { defineNuxtModule, installModule, checkNuxtCompatibilityIssues } from '@nuxt/kit'
|
import { defineNuxtModule, installModule, checkNuxtCompatibility, nuxtCtx } from '@nuxt/kit'
|
||||||
|
import type { NuxtModule } from '@nuxt/schema'
|
||||||
|
import { NuxtCompatibility } from '@nuxt/schema/src/types/compatibility'
|
||||||
import type { BridgeConfig, ScriptSetupOptions } from '../types'
|
import type { BridgeConfig, ScriptSetupOptions } from '../types'
|
||||||
import { setupNitroBridge } from './nitro'
|
import { setupNitroBridge } from './nitro'
|
||||||
import { setupAppBridge } from './app'
|
import { setupAppBridge } from './app'
|
||||||
@ -12,8 +14,10 @@ import { setupTranspile } from './transpile'
|
|||||||
import { setupScriptSetup } from './setup'
|
import { setupScriptSetup } from './setup'
|
||||||
|
|
||||||
export default defineNuxtModule({
|
export default defineNuxtModule({
|
||||||
|
meta: {
|
||||||
name: 'nuxt-bridge',
|
name: 'nuxt-bridge',
|
||||||
configKey: 'bridge',
|
configKey: 'bridge'
|
||||||
|
},
|
||||||
defaults: {
|
defaults: {
|
||||||
nitro: true,
|
nitro: true,
|
||||||
vite: false,
|
vite: false,
|
||||||
@ -22,7 +26,7 @@ export default defineNuxtModule({
|
|||||||
transpile: true,
|
transpile: true,
|
||||||
scriptSetup: true,
|
scriptSetup: true,
|
||||||
autoImports: true,
|
autoImports: true,
|
||||||
constraints: true,
|
compatibility: true,
|
||||||
meta: null,
|
meta: null,
|
||||||
// TODO: Remove from 2.16
|
// TODO: Remove from 2.16
|
||||||
postcss8: true,
|
postcss8: true,
|
||||||
@ -32,6 +36,11 @@ export default defineNuxtModule({
|
|||||||
async setup (opts, nuxt) {
|
async setup (opts, nuxt) {
|
||||||
const _require = createRequire(import.meta.url)
|
const _require = createRequire(import.meta.url)
|
||||||
|
|
||||||
|
// Allow using kit compasables in all modules
|
||||||
|
if (!nuxtCtx.use()) {
|
||||||
|
nuxtCtx.set(nuxt)
|
||||||
|
}
|
||||||
|
|
||||||
if (opts.nitro) {
|
if (opts.nitro) {
|
||||||
await setupNitroBridge()
|
await setupNitroBridge()
|
||||||
}
|
}
|
||||||
@ -51,11 +60,11 @@ export default defineNuxtModule({
|
|||||||
await setupAutoImports()
|
await setupAutoImports()
|
||||||
}
|
}
|
||||||
if (opts.vite) {
|
if (opts.vite) {
|
||||||
const viteModule = await import('./vite/module').then(r => r.default || r)
|
const viteModule = await import('./vite/module').then(r => r.default || r) as NuxtModule
|
||||||
await installModule(nuxt, viteModule)
|
await installModule(viteModule)
|
||||||
}
|
}
|
||||||
if (opts.postcss8) {
|
if (opts.postcss8) {
|
||||||
await installModule(nuxt, _require.resolve('@nuxt/postcss8'))
|
await installModule(_require.resolve('@nuxt/postcss8'))
|
||||||
}
|
}
|
||||||
if (opts.typescript) {
|
if (opts.typescript) {
|
||||||
await setupTypescript()
|
await setupTypescript()
|
||||||
@ -66,12 +75,12 @@ export default defineNuxtModule({
|
|||||||
if (opts.transpile) {
|
if (opts.transpile) {
|
||||||
setupTranspile()
|
setupTranspile()
|
||||||
}
|
}
|
||||||
if (opts.constraints) {
|
if (opts.compatibility) {
|
||||||
nuxt.hook('modules:done', (moduleContainer: any) => {
|
nuxt.hook('modules:done', async (moduleContainer: any) => {
|
||||||
for (const [name, m] of Object.entries(moduleContainer.requiredModules || {})) {
|
for (const [name, m] of Object.entries(moduleContainer.requiredModules || {})) {
|
||||||
const requires = (m as any)?.handler?.meta?.requires
|
const compat = ((m as any)?.handler?.meta?.compatibility || {}) as NuxtCompatibility
|
||||||
if (requires) {
|
if (compat) {
|
||||||
const issues = checkNuxtCompatibilityIssues(requires, nuxt)
|
const issues = await checkNuxtCompatibility(compat, nuxt)
|
||||||
if (issues.length) {
|
if (issues.length) {
|
||||||
console.warn(`[bridge] Detected module incompatibility issues for \`${name}\`:\n` + issues.toString())
|
console.warn(`[bridge] Detected module incompatibility issues for \`${name}\`:\n` + issues.toString())
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,12 @@ import { middlewareTemplate, storeTemplate } from './templates'
|
|||||||
import type { ViteOptions } from './types'
|
import type { ViteOptions } from './types'
|
||||||
|
|
||||||
export default defineNuxtModule<ViteOptions>({
|
export default defineNuxtModule<ViteOptions>({
|
||||||
|
meta: {
|
||||||
name: 'nuxt-bridge:vite',
|
name: 'nuxt-bridge:vite',
|
||||||
defaults: {},
|
|
||||||
version,
|
version,
|
||||||
configKey: 'vite',
|
configKey: 'vite'
|
||||||
|
},
|
||||||
|
defaults: {},
|
||||||
setup (viteOptions, nuxt) {
|
setup (viteOptions, nuxt) {
|
||||||
nuxt.options.cli.badgeMessages.push(`⚡ Vite Mode Enabled (v${version})`)
|
nuxt.options.cli.badgeMessages.push(`⚡ Vite Mode Enabled (v${version})`)
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
2
packages/bridge/types.d.ts
vendored
2
packages/bridge/types.d.ts
vendored
@ -15,7 +15,7 @@ export interface BridgeConfig {
|
|||||||
scriptSetup: boolean | ScriptSetupOptions
|
scriptSetup: boolean | ScriptSetupOptions
|
||||||
autoImports: boolean
|
autoImports: boolean
|
||||||
transpile: boolean
|
transpile: boolean
|
||||||
constraints: boolean
|
compatibility: boolean
|
||||||
postcss8: boolean
|
postcss8: boolean
|
||||||
resolve: boolean
|
resolve: boolean
|
||||||
typescript: boolean
|
typescript: boolean
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import satisfies from 'semver/functions/satisfies.js' // npm/node-semver#381
|
import satisfies from 'semver/functions/satisfies.js' // npm/node-semver#381
|
||||||
import type { Nuxt, NuxtCompatibilityConstraints, NuxtCompatibilityIssues } from '@nuxt/schema'
|
import type { Nuxt, NuxtCompatibility, NuxtCompatibilityIssues } from '@nuxt/schema'
|
||||||
import { useNuxt } from './context'
|
import { useNuxt } from './context'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check version constraints and return incompatibility issues as an array
|
* Check version constraints and return incompatibility issues as an array
|
||||||
*/
|
*/
|
||||||
export function checkNuxtCompatibilityIssues (constraints: NuxtCompatibilityConstraints, nuxt: Nuxt = useNuxt()): NuxtCompatibilityIssues {
|
export async function checkNuxtCompatibility (constraints: NuxtCompatibility, nuxt: Nuxt = useNuxt()): Promise<NuxtCompatibilityIssues> {
|
||||||
const issues: NuxtCompatibilityIssues = []
|
const issues: NuxtCompatibilityIssues = []
|
||||||
|
|
||||||
|
// Nuxt version check
|
||||||
if (constraints.nuxt) {
|
if (constraints.nuxt) {
|
||||||
const nuxtVersion = getNuxtVersion(nuxt)
|
const nuxtVersion = getNuxtVersion(nuxt)
|
||||||
const nuxtSemanticVersion = nuxtVersion.split('-').shift()
|
const nuxtSemanticVersion = nuxtVersion.split('-').shift()
|
||||||
@ -17,15 +19,39 @@ export function checkNuxtCompatibilityIssues (constraints: NuxtCompatibilityCons
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
issues.toString = () => issues.map(issue => ` - [${issue.name}] ${issue.message}`).join('\n')
|
|
||||||
|
// Bridge compatibility check
|
||||||
|
if (isNuxt2(nuxt)) {
|
||||||
|
const bridgeRequirement = constraints?.bridge
|
||||||
|
const hasBridge = !!(nuxt.options as any).bridge
|
||||||
|
if (bridgeRequirement === true && !hasBridge) {
|
||||||
|
issues.push({
|
||||||
|
name: 'bridge',
|
||||||
|
message: 'Nuxt bridge is required'
|
||||||
|
})
|
||||||
|
} else if (bridgeRequirement === false && hasBridge) {
|
||||||
|
issues.push({
|
||||||
|
name: 'bridge',
|
||||||
|
message: 'Nuxt bridge is not supported'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow extending compatibility checks
|
||||||
|
await nuxt.callHook('kit:compatibility', constraints, issues)
|
||||||
|
|
||||||
|
// Issues formatter
|
||||||
|
issues.toString = () =>
|
||||||
|
issues.map(issue => ` - [${issue.name}] ${issue.message}`).join('\n')
|
||||||
|
|
||||||
return issues
|
return issues
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check version constraints and throw a detailed error if has any, otherwise returns true
|
* Check version constraints and throw a detailed error if has any, otherwise returns true
|
||||||
*/
|
*/
|
||||||
export function ensureNuxtCompatibility (constraints: NuxtCompatibilityConstraints, nuxt: Nuxt = useNuxt()): true {
|
export async function assertNuxtCompatibility (constraints: NuxtCompatibility, nuxt: Nuxt = useNuxt()): Promise<true> {
|
||||||
const issues = checkNuxtCompatibilityIssues(constraints, nuxt)
|
const issues = await checkNuxtCompatibility(constraints, nuxt)
|
||||||
if (issues.length) {
|
if (issues.length) {
|
||||||
throw new Error('Nuxt compatibility issues found:\n' + issues.toString())
|
throw new Error('Nuxt compatibility issues found:\n' + issues.toString())
|
||||||
}
|
}
|
||||||
@ -35,8 +61,9 @@ export function ensureNuxtCompatibility (constraints: NuxtCompatibilityConstrain
|
|||||||
/**
|
/**
|
||||||
* Check version constraints and return true if passed, otherwise returns false
|
* Check version constraints and return true if passed, otherwise returns false
|
||||||
*/
|
*/
|
||||||
export function hasNuxtCompatibility (constraints: NuxtCompatibilityConstraints, nuxt: Nuxt = useNuxt()) {
|
export async function hasNuxtCompatibility (constraints: NuxtCompatibility, nuxt: Nuxt = useNuxt()): Promise<boolean> {
|
||||||
return !checkNuxtCompatibilityIssues(constraints, nuxt).length
|
const issues = await checkNuxtCompatibility(constraints, nuxt)
|
||||||
|
return !issues.length
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import { pascalCase, kebabCase } from 'scule'
|
import { pascalCase, kebabCase } from 'scule'
|
||||||
import type { ComponentsDir, Component } from '@nuxt/schema'
|
import type { ComponentsDir, Component } from '@nuxt/schema'
|
||||||
import { useNuxt } from './context'
|
import { useNuxt } from './context'
|
||||||
import { ensureNuxtCompatibility } from './compatibility'
|
import { assertNuxtCompatibility } from './compatibility'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a directory to be scanned for components and imported only when used.
|
* Register a directory to be scanned for components and imported only when used.
|
||||||
*
|
*
|
||||||
* Requires Nuxt 2.13+
|
* Requires Nuxt 2.13+
|
||||||
*/
|
*/
|
||||||
export function addComponentsDir (dir: ComponentsDir) {
|
export async function addComponentsDir (dir: ComponentsDir) {
|
||||||
const nuxt = useNuxt()
|
const nuxt = useNuxt()
|
||||||
ensureNuxtCompatibility({ nuxt: '>=2.13' }, nuxt)
|
await assertNuxtCompatibility({ nuxt: '>=2.13' }, nuxt)
|
||||||
nuxt.options.components = nuxt.options.components || []
|
nuxt.options.components = nuxt.options.components || []
|
||||||
nuxt.hook('components:dirs', (dirs) => { dirs.push(dir) })
|
nuxt.hook('components:dirs', (dirs) => { dirs.push(dir) })
|
||||||
}
|
}
|
||||||
@ -24,9 +24,9 @@ export type AddComponentOptions = { name: string, filePath: string } & Partial<E
|
|||||||
*
|
*
|
||||||
* Requires Nuxt 2.13+
|
* Requires Nuxt 2.13+
|
||||||
*/
|
*/
|
||||||
export function addComponent (opts: AddComponentOptions) {
|
export async function addComponent (opts: AddComponentOptions) {
|
||||||
const nuxt = useNuxt()
|
const nuxt = useNuxt()
|
||||||
ensureNuxtCompatibility({ nuxt: '>=2.13' }, nuxt)
|
await assertNuxtCompatibility({ nuxt: '>=2.13' }, nuxt)
|
||||||
nuxt.options.components = nuxt.options.components || []
|
nuxt.options.components = nuxt.options.components || []
|
||||||
|
|
||||||
// Apply defaults
|
// Apply defaults
|
||||||
|
@ -6,19 +6,44 @@ import { addTemplate } from '../template'
|
|||||||
import { addServerMiddleware } from '../server'
|
import { addServerMiddleware } from '../server'
|
||||||
import { isNuxt2 } from '../compatibility'
|
import { isNuxt2 } from '../compatibility'
|
||||||
import { addPluginTemplate } from '../plugin'
|
import { addPluginTemplate } from '../plugin'
|
||||||
|
import { useNuxt } from '../context'
|
||||||
import { installModule } from './install'
|
import { installModule } from './install'
|
||||||
|
|
||||||
export function createModuleContainer (nuxt: Nuxt): ModuleContainer {
|
const MODULE_CONTAINER_KEY = '__module_container__'
|
||||||
return <ModuleContainer>{
|
|
||||||
|
export function useModuleContainer (nuxt: Nuxt = useNuxt()): ModuleContainer {
|
||||||
|
if (nuxt[MODULE_CONTAINER_KEY]) {
|
||||||
|
return nuxt[MODULE_CONTAINER_KEY]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requireModule (moduleOpts) {
|
||||||
|
let src, inlineOptions
|
||||||
|
if (typeof moduleOpts === 'string') {
|
||||||
|
src = moduleOpts
|
||||||
|
} else if (Array.isArray(moduleOpts)) {
|
||||||
|
[src, inlineOptions] = moduleOpts
|
||||||
|
} else if (typeof moduleOpts === 'object') {
|
||||||
|
if (moduleOpts.src || moduleOpts.handler) {
|
||||||
|
src = moduleOpts.src || moduleOpts.handler
|
||||||
|
inlineOptions = moduleOpts.options
|
||||||
|
} else {
|
||||||
|
src = moduleOpts
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
src = moduleOpts
|
||||||
|
}
|
||||||
|
await installModule(src, inlineOptions, nuxt)
|
||||||
|
}
|
||||||
|
|
||||||
|
nuxt[MODULE_CONTAINER_KEY] = <ModuleContainer>{
|
||||||
nuxt,
|
nuxt,
|
||||||
options: nuxt.options,
|
options: nuxt.options,
|
||||||
|
|
||||||
ready () { return Promise.resolve() },
|
ready () { return Promise.resolve() },
|
||||||
addVendor () {},
|
addVendor () {},
|
||||||
|
|
||||||
requireModule: installModule,
|
requireModule,
|
||||||
addModule: installModule,
|
addModule: requireModule,
|
||||||
|
|
||||||
addServerMiddleware,
|
addServerMiddleware,
|
||||||
|
|
||||||
@ -73,4 +98,6 @@ export function createModuleContainer (nuxt: Nuxt): ModuleContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nuxt[MODULE_CONTAINER_KEY]
|
||||||
}
|
}
|
||||||
|
@ -3,69 +3,101 @@ import defu from 'defu'
|
|||||||
import { applyDefaults } from 'untyped'
|
import { applyDefaults } from 'untyped'
|
||||||
import consola from 'consola'
|
import consola from 'consola'
|
||||||
import { dirname } from 'pathe'
|
import { dirname } from 'pathe'
|
||||||
import type { Nuxt, NuxtTemplate, NuxtModule, LegacyNuxtModule, ModuleOptions } from '@nuxt/schema'
|
import type { Nuxt, NuxtTemplate, NuxtModule, ModuleOptions, ModuleDefinition } from '@nuxt/schema'
|
||||||
import { useNuxt, nuxtCtx } from '../context'
|
import { useNuxt, nuxtCtx } from '../context'
|
||||||
import { isNuxt2, checkNuxtCompatibilityIssues } from '../compatibility'
|
import { isNuxt2, checkNuxtCompatibility } from '../compatibility'
|
||||||
import { templateUtils, compileTemplate } from '../internal/template'
|
import { templateUtils, compileTemplate } from '../internal/template'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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> (input: NuxtModule<OptionsT> | ((nuxt: Nuxt) => NuxtModule<OptionsT>)): LegacyNuxtModule {
|
export function defineNuxtModule<OptionsT extends ModuleOptions> (definition: ModuleDefinition<OptionsT>): NuxtModule<OptionsT> {
|
||||||
let mod: NuxtModule<OptionsT>
|
// Normalize definition and meta
|
||||||
|
if (!definition.meta) { definition.meta = {} }
|
||||||
function wrappedModule (inlineOptions: OptionsT) {
|
if (!definition.meta.configKey) {
|
||||||
// Get nuxt context
|
// @ts-ignore TODO: Remove non-meta fallbacks in RC
|
||||||
const nuxt: Nuxt = this.nuxt || useNuxt()
|
definition.meta.name = definition.meta.name || definition.name
|
||||||
|
// @ts-ignore
|
||||||
// Resolve function
|
definition.meta.configKey = definition.meta.configKey || definition.configKey || definition.meta.name
|
||||||
if (typeof input === 'function') {
|
|
||||||
mod = input(nuxt)
|
|
||||||
} else {
|
|
||||||
mod = input
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install hooks
|
// Resolves module options from inline options, [configKey] in nuxt.config, defaults and schema
|
||||||
if (mod.hooks) {
|
function getOptions (inlineOptions?: OptionsT, nuxt: Nuxt = useNuxt()) {
|
||||||
if (isNuxt2(nuxt)) {
|
const configKey = definition.meta.configKey || definition.meta.name
|
||||||
nuxt.addHooks(mod.hooks)
|
const _defaults = typeof definition.defaults === 'function' ? definition.defaults(nuxt) : definition.defaults
|
||||||
} else {
|
let _options = defu(inlineOptions, nuxt.options[configKey], _defaults) as OptionsT
|
||||||
nuxt.hooks.addHooks(mod.hooks)
|
if (definition.schema) {
|
||||||
|
_options = applyDefaults(definition.schema, _options) as OptionsT
|
||||||
}
|
}
|
||||||
|
return Promise.resolve(_options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop if no install provided
|
// Module format is always a simple function
|
||||||
if (typeof mod.setup !== 'function') {
|
async function normalizedModule (inlineOptions: OptionsT, nuxt: Nuxt) {
|
||||||
|
if (!nuxt) {
|
||||||
|
nuxt = useNuxt() || this.nuxt /* invoked by nuxt 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
return
|
||||||
}
|
}
|
||||||
|
nuxt.options._requiredModules[uniqueKey] = true
|
||||||
|
}
|
||||||
|
|
||||||
// check nuxt version range
|
// Check compatibility contraints
|
||||||
if (mod.requires) {
|
if (definition.meta.compatibility) {
|
||||||
const issues = checkNuxtCompatibilityIssues(mod.requires, nuxt)
|
const issues = await checkNuxtCompatibility(definition.meta.compatibility, nuxt)
|
||||||
if (issues.length) {
|
if (issues.length) {
|
||||||
consola.warn(`Module \`${mod.name}\` is disabled due to incompatibility issues:\n${issues.toString()}`)
|
consola.warn(`Module \`${definition.meta.name}\` is disabled due to incompatibility issues:\n${issues.toString()}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve options
|
// Prepare
|
||||||
const configKey = mod.configKey || mod.name
|
nuxt2Shims(nuxt)
|
||||||
const userOptions = defu(inlineOptions, nuxt.options[configKey]) as OptionsT
|
|
||||||
const resolvedOptions = applyDefaults(mod.defaults as any, userOptions) as OptionsT
|
|
||||||
|
|
||||||
// Ensure nuxt instance exists (nuxt2 compatibility)
|
// Resolve module and options
|
||||||
|
const _options = await getOptions(inlineOptions, nuxt)
|
||||||
|
|
||||||
|
// Register hooks
|
||||||
|
if (definition.hooks) {
|
||||||
|
nuxt.hooks.addHooks(definition.hooks)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call setup
|
||||||
|
await definition.setup?.call(null, _options, nuxt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()) {
|
if (!nuxtCtx.use()) {
|
||||||
nuxtCtx.set(nuxt)
|
nuxtCtx.set(nuxt)
|
||||||
// @ts-ignore
|
|
||||||
if (!nuxt.__nuxtkit_close__) {
|
|
||||||
nuxt.hook('close', () => nuxtCtx.unset())
|
nuxt.hook('close', () => nuxtCtx.unset())
|
||||||
// @ts-ignore
|
|
||||||
nuxt.__nuxtkit_close__ = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNuxt2()) {
|
|
||||||
// Support virtual templates with getContents() by writing them to .nuxt directory
|
// Support virtual templates with getContents() by writing them to .nuxt directory
|
||||||
let virtualTemplates: NuxtTemplate[]
|
let virtualTemplates: NuxtTemplate[]
|
||||||
nuxt.hook('builder:prepared', (_builder, buildOptions) => {
|
nuxt.hook('builder:prepared', (_builder, buildOptions) => {
|
||||||
@ -95,13 +127,4 @@ export function defineNuxtModule<OptionsT extends ModuleOptions> (input: NuxtMod
|
|||||||
await fsp.writeFile(template.dst, contents)
|
await fsp.writeFile(template.dst, contents)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
// Call setup
|
|
||||||
return mod.setup.call(null, resolvedOptions, nuxt)
|
|
||||||
}
|
|
||||||
|
|
||||||
wrappedModule.meta = mod
|
|
||||||
|
|
||||||
return wrappedModule
|
|
||||||
}
|
}
|
||||||
|
@ -1,63 +1,34 @@
|
|||||||
import type { LegacyNuxtModule, NuxtModule, ModuleMeta, ModuleInstallOptions, ModuleOptions, ModuleSrc, Nuxt } from '@nuxt/schema'
|
import type { NuxtModule, Nuxt } from '@nuxt/schema'
|
||||||
|
import { useNuxt } from '../context'
|
||||||
import { resolveModule, requireModule, importModule } from '../internal/cjs'
|
import { resolveModule, requireModule, importModule } from '../internal/cjs'
|
||||||
import { resolveAlias } from '../resolve'
|
import { resolveAlias } from '../resolve'
|
||||||
import { defineNuxtModule } from './define'
|
import { useModuleContainer } from './container'
|
||||||
import { createModuleContainer } from './container'
|
|
||||||
|
|
||||||
/** Installs a module on a Nuxt instance. */
|
/** Installs a module on a Nuxt instance. */
|
||||||
export async function installModule (nuxt: Nuxt, installOpts: ModuleInstallOptions) {
|
export async function installModule (nuxtModule: string | NuxtModule, inlineOptions?: any, nuxt: Nuxt = useNuxt()) {
|
||||||
let src: ModuleSrc
|
// Detect if `installModule` used with older signuture (nuxt, nuxtModule)
|
||||||
let options: ModuleOptions = {}
|
// TODO: Remove in RC
|
||||||
const meta: ModuleMeta = {}
|
// @ts-ignore
|
||||||
|
if (nuxtModule?._version || nuxtModule?.version || nuxtModule?.constructor?.version || '') {
|
||||||
// Extract src, meta and options
|
// @ts-ignore
|
||||||
if (typeof installOpts === 'string') {
|
[nuxt, nuxtModule] = [nuxtModule, inlineOptions]
|
||||||
src = installOpts
|
inlineOptions = {}
|
||||||
} else if (Array.isArray(installOpts)) {
|
console.warn(new Error('`installModule` is being called with old signature!'))
|
||||||
[src, options] = installOpts
|
|
||||||
} else if (typeof installOpts === 'object') {
|
|
||||||
if (installOpts.src || installOpts.handler) {
|
|
||||||
src = installOpts.src || installOpts.handler
|
|
||||||
options = installOpts.options
|
|
||||||
Object.assign(meta, installOpts.meta)
|
|
||||||
} else {
|
|
||||||
src = installOpts as NuxtModule
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
src = installOpts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve as legacy handler
|
// Import if input is string
|
||||||
let handler: LegacyNuxtModule
|
if (typeof nuxtModule === 'string') {
|
||||||
if (typeof src === 'string') {
|
const _src = resolveModule(resolveAlias(nuxtModule, nuxt.options.alias), { paths: nuxt.options.modulesDir })
|
||||||
const _src = resolveModule(resolveAlias(src, nuxt.options.alias), { paths: nuxt.options.modulesDir })
|
|
||||||
// TODO: also check with type: 'module' in closest `package.json`
|
// TODO: also check with type: 'module' in closest `package.json`
|
||||||
const isESM = _src.endsWith('.mjs') || meta.isESM
|
const isESM = _src.endsWith('.mjs')
|
||||||
handler = isESM ? await importModule(_src) : requireModule(_src)
|
nuxtModule = isESM ? await importModule(_src) : requireModule(_src)
|
||||||
if (!meta.name) {
|
|
||||||
meta.name = src
|
|
||||||
}
|
|
||||||
} else if (typeof src === 'function') {
|
|
||||||
handler = src
|
|
||||||
} else {
|
|
||||||
handler = defineNuxtModule(src)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge meta
|
// Throw error if input is not a function
|
||||||
if (handler.meta) {
|
if (typeof nuxtModule !== 'function') {
|
||||||
Object.assign(meta, handler.meta)
|
throw new TypeError('Nuxt module should be a function: ' + nuxtModule)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure module is required once
|
// Call module
|
||||||
if (typeof meta.name === 'string') {
|
await nuxtModule.call(useModuleContainer(), inlineOptions, nuxt)
|
||||||
nuxt.options._requiredModules = nuxt.options._requiredModules || {}
|
|
||||||
if (nuxt.options._requiredModules[meta.name]) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
nuxt.options._requiredModules[meta.name] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute in legacy container
|
|
||||||
const container = createModuleContainer(nuxt)
|
|
||||||
await handler.call(container, options)
|
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ export default defineBuildConfig({
|
|||||||
externals: [
|
externals: [
|
||||||
'@vue/reactivity',
|
'@vue/reactivity',
|
||||||
'@vue/shared',
|
'@vue/shared',
|
||||||
'@vueuse/head'
|
'@vueuse/head',
|
||||||
|
'vue-meta'
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@ -67,7 +67,7 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
for (const m of modulesToInstall) {
|
for (const m of modulesToInstall) {
|
||||||
await installModule(nuxt, m)
|
await installModule(m, nuxt)
|
||||||
}
|
}
|
||||||
|
|
||||||
await nuxt.callHook('modules:done', { nuxt } as ModuleContainer)
|
await nuxt.callHook('modules:done', { nuxt } as ModuleContainer)
|
||||||
|
@ -26,6 +26,6 @@ export default defineBuildConfig({
|
|||||||
'vite',
|
'vite',
|
||||||
// Implicit
|
// Implicit
|
||||||
'@vue/compiler-core',
|
'@vue/compiler-core',
|
||||||
'@vue/shared ',
|
'@vue/shared'
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@ -225,7 +225,7 @@ export default {
|
|||||||
* function () {}
|
* function () {}
|
||||||
* ]
|
* ]
|
||||||
* ```
|
* ```
|
||||||
* @type {typeof import('../src/types/module').ModuleInstallOptions[]}
|
* @type {(typeof import('../src/types/module').NuxtModule | string)[]}
|
||||||
* @version 2
|
* @version 2
|
||||||
* @version 3
|
* @version 3
|
||||||
*/
|
*/
|
||||||
@ -262,7 +262,7 @@ export default {
|
|||||||
* decreases the size of `node_modules` in production deployments. Please refer to each
|
* decreases the size of `node_modules` in production deployments. Please refer to each
|
||||||
* module's documentation to see if it is recommended to use `modules` or `buildModules`.
|
* module's documentation to see if it is recommended to use `modules` or `buildModules`.
|
||||||
*
|
*
|
||||||
* @type {typeof import('../src/types/module').ModuleInstallOptions[]}
|
* @type {(typeof import('../src/types/module').NuxtModule | string)[]}
|
||||||
* @version 2
|
* @version 2
|
||||||
* @version 3
|
* @version 3
|
||||||
*/
|
*/
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
|
|
||||||
// Types
|
// Types
|
||||||
import './types/global'
|
import './types/global'
|
||||||
|
|
||||||
|
export * from './types/compatibility'
|
||||||
|
export * from './types/components'
|
||||||
export * from './types/config'
|
export * from './types/config'
|
||||||
export * from './types/hooks'
|
export * from './types/hooks'
|
||||||
export * from './types/module'
|
|
||||||
export * from './types/meta'
|
|
||||||
export * from './types/nuxt'
|
|
||||||
export * from './types/components'
|
|
||||||
export * from './types/imports'
|
export * from './types/imports'
|
||||||
|
export * from './types/meta'
|
||||||
|
export * from './types/module'
|
||||||
|
export * from './types/nuxt'
|
||||||
export * from './types/pages'
|
export * from './types/pages'
|
||||||
|
|
||||||
// Schema
|
// Schema
|
||||||
|
29
packages/schema/src/types/compatibility.ts
Normal file
29
packages/schema/src/types/compatibility.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
export interface NuxtCompatibility {
|
||||||
|
/**
|
||||||
|
* Required nuxt version in semver format.
|
||||||
|
*
|
||||||
|
* @example `^2.14.0` or `>=3.0.0-27219851.6e49637`.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
nuxt?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bridge constraint for Nuxt 2 support.
|
||||||
|
*
|
||||||
|
* - `true`: When using Nuxt 2, using bridge module is required
|
||||||
|
* - `false`: When using Nuxt 2, using bridge module is not supported
|
||||||
|
*/
|
||||||
|
bridge?: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NuxtCompatibilityIssue {
|
||||||
|
name: string
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NuxtCompatibilityIssues extends Array<NuxtCompatibilityIssue> {
|
||||||
|
/**
|
||||||
|
* Return formatted error message
|
||||||
|
*/
|
||||||
|
toString(): string
|
||||||
|
}
|
@ -6,6 +6,7 @@ import type { NuxtTemplate, Nuxt, NuxtApp } from './nuxt'
|
|||||||
import type { AutoImport, AutoImportSource } from './imports'
|
import type { AutoImport, AutoImportSource } from './imports'
|
||||||
import type { NuxtConfig, NuxtOptions } from './config'
|
import type { NuxtConfig, NuxtOptions } from './config'
|
||||||
import type { Component, ComponentsDir, ScanDir, ComponentsOptions } from './components'
|
import type { Component, ComponentsDir, ScanDir, ComponentsOptions } from './components'
|
||||||
|
import { NuxtCompatibility, NuxtCompatibilityIssues } from '..'
|
||||||
|
|
||||||
type HookResult = Promise<void> | void
|
type HookResult = Promise<void> | void
|
||||||
|
|
||||||
@ -39,6 +40,9 @@ export type NuxtPage = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface NuxtHooks {
|
export interface NuxtHooks {
|
||||||
|
// Kit
|
||||||
|
'kit:compatibility': (compatibility: NuxtCompatibility, issues: NuxtCompatibilityIssues) => HookResult
|
||||||
|
|
||||||
// nuxt3
|
// nuxt3
|
||||||
'app:resolve': (app: NuxtApp) => HookResult
|
'app:resolve': (app: NuxtApp) => HookResult
|
||||||
'app:templates': (app: NuxtApp) => HookResult
|
'app:templates': (app: NuxtApp) => HookResult
|
||||||
|
@ -1,25 +1,6 @@
|
|||||||
import { NuxtHooks } from './hooks'
|
import { NuxtHooks } from './hooks'
|
||||||
import type { Nuxt, NuxtTemplate } from "./nuxt";
|
import type { Nuxt, NuxtTemplate } from "./nuxt"
|
||||||
|
import type { NuxtCompatibility } from './compatibility'
|
||||||
|
|
||||||
export interface NuxtCompatibilityConstraints {
|
|
||||||
/**
|
|
||||||
* Required nuxt version. for example, `^2.14.0` or `>=3.0.0-27219851.6e49637`.
|
|
||||||
*/
|
|
||||||
nuxt?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NuxtCompatibilityIssue {
|
|
||||||
name: string
|
|
||||||
message: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NuxtCompatibilityIssues extends Array<NuxtCompatibilityIssue> {
|
|
||||||
/**
|
|
||||||
* Return formatted error message
|
|
||||||
*/
|
|
||||||
toString(): string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ModuleMeta {
|
export interface ModuleMeta {
|
||||||
/** Module name */
|
/** Module name */
|
||||||
@ -35,9 +16,9 @@ export interface ModuleMeta {
|
|||||||
configKey?: string
|
configKey?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Semver constraints for the versions of Nuxt or features this module are supported.
|
* Constraints for the versions of Nuxt or features this module requires
|
||||||
*/
|
*/
|
||||||
requires?: NuxtCompatibilityConstraints
|
compatibility?: NuxtCompatibility
|
||||||
|
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
@ -45,33 +26,22 @@ export interface ModuleMeta {
|
|||||||
/** The options received */
|
/** The options received */
|
||||||
export type ModuleOptions = Record<string, any>
|
export type ModuleOptions = Record<string, any>
|
||||||
|
|
||||||
/** A pre-kit Nuxt module */
|
/** Input module passed to defineNuxtModule */
|
||||||
export interface LegacyNuxtModule {
|
export interface ModuleDefinition<T extends ModuleOptions = ModuleOptions> {
|
||||||
(this: ModuleContainer, inlineOptions?: ModuleOptions): void | Promise<void>
|
|
||||||
meta?: ModuleMeta
|
meta?: ModuleMeta
|
||||||
}
|
defaults?: T | ((nuxt: Nuxt) => T)
|
||||||
|
schema?: T
|
||||||
/** A Nuxt module definition */
|
|
||||||
export interface NuxtModule<T extends ModuleOptions = any> extends ModuleMeta {
|
|
||||||
defaults?: T
|
|
||||||
setup?: (this: null, resolvedOptions: T, nuxt: Nuxt) => void | Promise<void>
|
|
||||||
hooks?: Partial<NuxtHooks>
|
hooks?: Partial<NuxtHooks>
|
||||||
|
setup?: (this: void, resolvedOptions: T, nuxt: Nuxt) => void | Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ModuleSrc = string | NuxtModule | LegacyNuxtModule
|
/** Nuxt modules are always a simple function */
|
||||||
|
export interface NuxtModule<T extends ModuleOptions = ModuleOptions> {
|
||||||
export interface ModuleInstallOptionsObj {
|
(this: void, inlineOptions: T, nuxt: Nuxt): void | Promise<void>
|
||||||
src: ModuleSrc,
|
getOptions?: (inlineOptions?: T, nuxt?: Nuxt) => Promise<T>
|
||||||
meta: ModuleMeta
|
getMeta?: () => Promise<ModuleMeta>
|
||||||
options: ModuleOptions
|
|
||||||
handler: LegacyNuxtModule
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ModuleInstallOptions =
|
|
||||||
ModuleSrc |
|
|
||||||
[ModuleSrc, ModuleOptions?] |
|
|
||||||
Partial<ModuleInstallOptionsObj>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Legacy ModuleContainer for backwards compatibility with Nuxt 2 module format.
|
* Legacy ModuleContainer for backwards compatibility with Nuxt 2 module format.
|
||||||
*/
|
*/
|
||||||
@ -104,8 +74,8 @@ export interface ModuleContainer {
|
|||||||
extendRoutes(fn): void
|
extendRoutes(fn): void
|
||||||
|
|
||||||
/** Registers a module */
|
/** Registers a module */
|
||||||
requireModule(nuxt: Nuxt, opts: any): Promise<void>
|
requireModule(installOptions: any, opts: any): Promise<void>
|
||||||
|
|
||||||
/** Registers a module */
|
/** Registers a module */
|
||||||
addModule(nuxt: Nuxt, opts: any): Promise<void>
|
addModule(installOptions: any, opts: any): Promise<void>
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user