feat(kit, schema)!: finalize nuxt 3 module spec and utils (#2275)

This commit is contained in:
pooya parsa 2021-12-21 14:57:26 +01:00 committed by GitHub
parent 6e32bde7ae
commit 045b9edb5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 287 additions and 219 deletions

View File

@ -8,6 +8,7 @@ export default defineBuildConfig({
], ],
externals: [ externals: [
'webpack', 'webpack',
'vite' 'vite',
'vue-meta'
] ]
}) })

View File

@ -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,

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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())
} }

View File

@ -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

View File

@ -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

View File

@ -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
} }
/** /**

View File

@ -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

View File

@ -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]
} }

View File

@ -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
} }

View File

@ -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)
} }

View File

@ -21,6 +21,7 @@ export default defineBuildConfig({
externals: [ externals: [
'@vue/reactivity', '@vue/reactivity',
'@vue/shared', '@vue/shared',
'@vueuse/head' '@vueuse/head',
'vue-meta'
] ]
}) })

View File

@ -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)

View File

@ -26,6 +26,6 @@ export default defineBuildConfig({
'vite', 'vite',
// Implicit // Implicit
'@vue/compiler-core', '@vue/compiler-core',
'@vue/shared ', '@vue/shared'
] ]
}) })

View File

@ -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
*/ */

View File

@ -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

View 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
}

View File

@ -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

View File

@ -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>
} }