diff --git a/packages/kit/src/loader/config.ts b/packages/kit/src/loader/config.ts index 3c42525c4d..fab118bf6c 100644 --- a/packages/kit/src/loader/config.ts +++ b/packages/kit/src/loader/config.ts @@ -4,6 +4,8 @@ import type { ConfigLayer, ConfigLayerMeta, LoadConfigOptions } from 'c12' import { loadConfig } from 'c12' import type { NuxtConfig, NuxtOptions } from '@nuxt/schema' import { NuxtConfigSchema } from '@nuxt/schema' +import { globby } from 'globby' +import defu from 'defu' export interface LoadNuxtConfigOptions extends LoadConfigOptions {} @@ -11,12 +13,19 @@ const layerSchemaKeys = ['future', 'srcDir', 'rootDir', 'dir'] const layerSchema = Object.fromEntries(Object.entries(NuxtConfigSchema).filter(([key]) => layerSchemaKeys.includes(key))) export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise { + // Automatically detect and import layers from `~~/layers/` directory + opts.overrides = defu(opts.overrides, { + _extends: await globby('layers/*', { + onlyDirectories: true, + cwd: opts.cwd || process.cwd(), + }), + }); (globalThis as any).defineNuxtConfig = (c: any) => c const result = await loadConfig({ name: 'nuxt', configFile: 'nuxt.config', rcFile: '.nuxtrc', - extend: { extendKey: ['theme', 'extends'] }, + extend: { extendKey: ['theme', 'extends', '_extends'] }, dotenv: true, globalRc: true, ...opts, diff --git a/packages/nuxt/src/core/nuxt.ts b/packages/nuxt/src/core/nuxt.ts index 2d77d45b53..390683579d 100644 --- a/packages/nuxt/src/core/nuxt.ts +++ b/packages/nuxt/src/core/nuxt.ts @@ -10,7 +10,7 @@ import { readPackageJSON, resolvePackageJSON } from 'pkg-types' import escapeRE from 'escape-string-regexp' import fse from 'fs-extra' -import { withoutLeadingSlash } from 'ufo' +import { withTrailingSlash, withoutLeadingSlash } from 'ufo' import defu from 'defu' import pagesModule from '../pages/module' @@ -71,6 +71,17 @@ async function initNuxt (nuxt: Nuxt) { } } + // Restart Nuxt when layer directories are added or removed + const layersDir = withTrailingSlash(resolve(nuxt.options.rootDir, 'layers')) + nuxt.hook('builder:watch', (event, relativePath) => { + const path = resolve(nuxt.options.srcDir, relativePath) + if (event === 'addDir' || event === 'unlinkDir') { + if (path.startsWith(layersDir)) { + return nuxt.callHook('restart', { hard: true }) + } + } + }) + // Set nuxt instance for useNuxt nuxtCtx.set(nuxt) nuxt.hook('close', () => nuxtCtx.unset()) diff --git a/test/fixtures/basic/layers/bar/nuxt.config.ts b/test/fixtures/basic/layers/bar/nuxt.config.ts new file mode 100644 index 0000000000..cf9614f668 --- /dev/null +++ b/test/fixtures/basic/layers/bar/nuxt.config.ts @@ -0,0 +1,8 @@ +export default defineNuxtConfig({ + modules: [ + function (_options, nuxt) { + // @ts-expect-error not valid nuxt option + nuxt.options.__installed_layer = true + }, + ], +}) diff --git a/test/fixtures/basic/nuxt.config.ts b/test/fixtures/basic/nuxt.config.ts index 5d675426e5..387d0ce997 100644 --- a/test/fixtures/basic/nuxt.config.ts +++ b/test/fixtures/basic/nuxt.config.ts @@ -92,6 +92,14 @@ export default defineNuxtConfig({ }, }, modules: [ + function (_options, nuxt) { + nuxt.hook('modules:done', () => { + // @ts-expect-error not valid nuxt option + if (!nuxt.options.__installed_layer) { + throw new Error('layer in layers/ directory was not auto-registered') + } + }) + }, '~/modules/subpath', './modules/test', '~/modules/example',