diff --git a/examples/config-extends/app.vue b/examples/config-extends/app.vue
index 18f3c2b3f4..bea4876876 100644
--- a/examples/config-extends/app.vue
+++ b/examples/config-extends/app.vue
@@ -10,6 +10,7 @@ const bar = getBar()
{{ JSON.stringify(themeConfig, null, 2) }}
Base Button
Fancy Button
+ UI Button
{{ foo }} {{ bar }}
diff --git a/examples/config-extends/nuxt.config.ts b/examples/config-extends/nuxt.config.ts
index a08add69b9..27351ed2a6 100644
--- a/examples/config-extends/nuxt.config.ts
+++ b/examples/config-extends/nuxt.config.ts
@@ -1,7 +1,10 @@
import { defineNuxtConfig } from 'nuxt3'
export default defineNuxtConfig({
- extends: './base',
+ extends: [
+ './ui',
+ './base'
+ ],
publicRuntimeConfig: {
theme: {
primaryColor: 'user_primary'
diff --git a/examples/config-extends/ui/components/Button.vue b/examples/config-extends/ui/components/Button.vue
new file mode 100644
index 0000000000..8508b7cd00
--- /dev/null
+++ b/examples/config-extends/ui/components/Button.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/examples/config-extends/ui/nuxt.config.ts b/examples/config-extends/ui/nuxt.config.ts
new file mode 100644
index 0000000000..06d8bba06b
--- /dev/null
+++ b/examples/config-extends/ui/nuxt.config.ts
@@ -0,0 +1,7 @@
+import { defineNuxtConfig } from 'nuxt3'
+
+export default defineNuxtConfig({
+ components: [
+ { path: './components', prefix: 'UI' }
+ ]
+})
diff --git a/packages/bridge/src/module.ts b/packages/bridge/src/module.ts
index a3e74a7052..01470a74ff 100644
--- a/packages/bridge/src/module.ts
+++ b/packages/bridge/src/module.ts
@@ -41,8 +41,12 @@ export default defineNuxtModule({
nuxtCtx.set(nuxt)
}
- // Mock _extends
- nuxt.options._extends = nuxt.options._extends || []
+ // Mock _layers
+ nuxt.options._layers = nuxt.options._layers || [{
+ config: nuxt.options,
+ cwd: nuxt.options.rootDir,
+ configFile: nuxt.options._nuxtConfigFile
+ }]
if (opts.nitro) {
await setupNitroBridge()
diff --git a/packages/kit/package.json b/packages/kit/package.json
index 7e46ec8850..17750c39de 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -14,7 +14,7 @@
},
"dependencies": {
"@nuxt/schema": "^3.0.0",
- "c12": "^0.1.4",
+ "c12": "^0.2.0",
"consola": "^2.15.3",
"defu": "^5.0.1",
"globby": "^13.1.1",
diff --git a/packages/kit/src/loader/config.ts b/packages/kit/src/loader/config.ts
index 909e30d4e3..a783b7465a 100644
--- a/packages/kit/src/loader/config.ts
+++ b/packages/kit/src/loader/config.ts
@@ -44,7 +44,7 @@ export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise layer.configFile && !layer.configFile.endsWith('.nuxtrc'))
// Resolve and apply defaults
return applyDefaults(NuxtConfigSchema, nuxtConfig) as NuxtOptions
diff --git a/packages/nitro/src/build.ts b/packages/nitro/src/build.ts
index 343a6c658f..4db4b99080 100644
--- a/packages/nitro/src/build.ts
+++ b/packages/nitro/src/build.ts
@@ -93,10 +93,7 @@ export async function writeTypes (nitroContext: NitroContext) {
}
async function _build (nitroContext: NitroContext) {
- const serverDirs = [
- ...nitroContext._extends.map(layer => layer.serverDir),
- nitroContext._nuxt.serverDir
- ]
+ const serverDirs = nitroContext._layers.map(layer => layer.serverDir)
nitroContext.scannedMiddleware = (
await Promise.all(serverDirs.map(async dir => await scanMiddleware(dir)))
@@ -181,7 +178,7 @@ async function _watch (nitroContext: NitroContext) {
let watcher = startRollupWatcher(nitroContext)
const serverDirs = [
- ...nitroContext._extends.map(layer => layer.serverDir),
+ ...nitroContext._layers.map(layer => layer.serverDir),
nitroContext._nuxt.serverDir
]
diff --git a/packages/nitro/src/context.ts b/packages/nitro/src/context.ts
index ea8b7da227..ddb5bc9e46 100644
--- a/packages/nitro/src/context.ts
+++ b/packages/nitro/src/context.ts
@@ -84,7 +84,7 @@ export interface NitroContext {
runtimeDir: string
hooks: Hookable
},
- _extends: Array<{
+ _layers: Array<{
serverDir: string
}>
}
@@ -158,7 +158,7 @@ export function getNitroContext (nuxtOptions: NuxtOptions, input: NitroInput): N
runtimeDir,
hooks: createHooks()
},
- _extends: nuxtOptions._extends.map(layer => ({
+ _layers: nuxtOptions._layers.map(layer => ({
serverDir: resolve(layer.config.srcDir, (layer.config.dir as any)?.server || 'server')
}))
}
diff --git a/packages/nuxt3/src/auto-imports/module.ts b/packages/nuxt3/src/auto-imports/module.ts
index b580bba570..ab2e9d2c83 100644
--- a/packages/nuxt3/src/auto-imports/module.ts
+++ b/packages/nuxt3/src/auto-imports/module.ts
@@ -40,14 +40,9 @@ export default defineNuxtModule>({
imports: options.imports
})
- // composables/ dirs
- let composablesDirs = [
- join(nuxt.options.srcDir, 'composables'),
- ...options.dirs
- ]
-
- // Extend with layers
- for (const layer of nuxt.options._extends) {
+ // composables/ dirs from all layers
+ let composablesDirs = []
+ for (const layer of nuxt.options._layers) {
composablesDirs.push(resolve(layer.config.srcDir, 'composables'))
for (const dir of (layer.config.autoImports?.dirs ?? [])) {
composablesDirs.push(resolve(layer.config.srcDir, dir))
diff --git a/packages/nuxt3/src/components/module.ts b/packages/nuxt3/src/components/module.ts
index 7a01452cee..99d3d358ec 100644
--- a/packages/nuxt3/src/components/module.ts
+++ b/packages/nuxt3/src/components/module.ts
@@ -39,26 +39,25 @@ export default defineNuxtModule({
}))
}
}
- if (dir && typeof dir === 'object') {
- return {
- ...dir,
- path: resolve(cwd, resolveAlias(dir.path, {
- ...nuxt.options.alias,
- '~': cwd
- }))
- }
+ if (!dir) {
+ return []
}
- return []
+ const dirs = (dir.dirs || [dir]).filter(_dir => _dir.path)
+ return dirs.map(_dir => ({
+ ..._dir,
+ path: resolve(cwd, resolveAlias(_dir.path, {
+ ...nuxt.options.alias,
+ '~': cwd
+ }))
+ }))
}
// Resolve dirs
nuxt.hook('app:resolve', async () => {
- const allDirs = [
- ...normalizeDirs(componentOptions.dirs, nuxt.options.srcDir),
- ...nuxt.options._extends
- .map(layer => normalizeDirs(layer.config.components, layer.cwd))
- .flat()
- ]
+ // components/ dirs from all layers
+ const allDirs = nuxt.options._layers
+ .map(layer => normalizeDirs(layer.config.components, layer.cwd))
+ .flat()
await nuxt.callHook('components:dirs', allDirs)
diff --git a/packages/nuxt3/src/core/app.ts b/packages/nuxt3/src/core/app.ts
index 7726229f59..3d878a8df9 100644
--- a/packages/nuxt3/src/core/app.ts
+++ b/packages/nuxt3/src/core/app.ts
@@ -71,9 +71,9 @@ export async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
app.errorComponent = (await findPath(['~/error'])) || resolve(nuxt.options.appDir, 'components/nuxt-error-page.vue')
}
- // Resolve layouts
+ // Resolve layouts/ from all config layers
app.layouts = {}
- for (const config of [nuxt.options, ...nuxt.options._extends.map(layer => layer.config)]) {
+ for (const config of nuxt.options._layers.map(layer => layer.config)) {
const layoutFiles = await resolveFiles(config.srcDir, `${config.dir?.layouts || 'layouts'}/*{${nuxt.options.extensions.join(',')}}`)
for (const file of layoutFiles) {
const name = getNameFromPath(file)
@@ -82,16 +82,19 @@ export async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
}
// Resolve plugins
- app.plugins = []
- for (const config of [...nuxt.options._extends.map(layer => layer.config), nuxt.options]) {
+ app.plugins = [
+ ...nuxt.options.plugins.map(normalizePlugin)
+ ]
+ for (const config of nuxt.options._layers.map(layer => layer.config)) {
app.plugins.push(...[
- ...config.plugins ?? [],
+ ...(config.plugins || []),
...await resolveFiles(config.srcDir, [
'plugins/*.{ts,js,mjs,cjs,mts,cts}',
'plugins/*/index.*{ts,js,mjs,cjs,mts,cts}'
])
].map(plugin => normalizePlugin(plugin as NuxtPlugin)))
}
+ app.plugins = uniqueBy(app.plugins, 'src')
// Extend app
await nuxt.callHook('app:resolve', app)
@@ -100,3 +103,15 @@ export async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
function getNameFromPath (path: string) {
return kebabCase(basename(path).replace(extname(path), '')).replace(/["']/g, '')
}
+
+function uniqueBy (arr: any[], uniqueKey: string) {
+ const seen = new Set()
+ const res = []
+ for (const i of arr) {
+ const key = i[uniqueKey]
+ if (seen.has(key)) { continue }
+ res.push(i)
+ seen.add(key)
+ }
+ return res
+}
diff --git a/packages/nuxt3/src/core/nitro-legacy.ts b/packages/nuxt3/src/core/nitro-legacy.ts
index ca00a10424..3f5233cc0b 100644
--- a/packages/nuxt3/src/core/nitro-legacy.ts
+++ b/packages/nuxt3/src/core/nitro-legacy.ts
@@ -77,10 +77,7 @@ export function initNitro (nuxt: Nuxt) {
})
nuxt.hook('build:before', async () => {
- const serverDirs = [
- ...nitroDevContext._extends.map(layer => layer.serverDir),
- nitroDevContext._nuxt.serverDir
- ]
+ const serverDirs = nitroDevContext._layers.map(layer => layer.serverDir)
nitroDevContext.scannedMiddleware = (
await Promise.all(serverDirs.map(async dir => await scanMiddleware(dir)))
diff --git a/packages/schema/package.json b/packages/schema/package.json
index cd2cd5139a..500ac8b498 100644
--- a/packages/schema/package.json
+++ b/packages/schema/package.json
@@ -19,7 +19,7 @@
"unbuild": "latest"
},
"dependencies": {
- "c12": "^0.1.4",
+ "c12": "^0.2.0",
"create-require": "^1.1.1",
"defu": "^5.0.1",
"jiti": "^1.13.0",
diff --git a/packages/schema/src/types/config.ts b/packages/schema/src/types/config.ts
index ac8765132c..85f0528729 100644
--- a/packages/schema/src/types/config.ts
+++ b/packages/schema/src/types/config.ts
@@ -10,7 +10,7 @@ export interface NuxtConfig extends DeepPartial {
/** Normalized Nuxt options available as `nuxt.options.*` */
export interface NuxtOptions extends ConfigSchema {
- _extends: ResolvedConfig[]
+ _layers: ResolvedConfig[]
}
export interface PublicRuntimeConfig extends Record { }
diff --git a/yarn.lock b/yarn.lock
index f60fa4eea9..a72645de19 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2914,7 +2914,7 @@ __metadata:
"@nuxt/schema": ^3.0.0
"@types/lodash.template": ^4
"@types/semver": ^7
- c12: ^0.1.4
+ c12: ^0.2.0
consola: ^2.15.3
defu: ^5.0.1
globby: ^13.1.1
@@ -3084,7 +3084,7 @@ __metadata:
dependencies:
"@types/lodash.template": ^4
"@types/semver": ^7
- c12: ^0.1.4
+ c12: ^0.2.0
create-require: ^1.1.1
defu: ^5.0.1
jiti: ^1.13.0
@@ -7061,6 +7061,21 @@ __metadata:
languageName: node
linkType: hard
+"c12@npm:^0.2.0":
+ version: 0.2.0
+ resolution: "c12@npm:0.2.0"
+ dependencies:
+ defu: ^5.0.1
+ dotenv: ^14.3.2
+ gittar: ^0.1.1
+ jiti: ^1.12.14
+ mlly: ^0.4.1
+ pathe: ^0.2.0
+ rc9: ^1.2.0
+ checksum: 1f69ed861368c8dc057f235a4925c4863b0423683a98578d3fa3c208dbcb43aadb907a5c855bf95969157210f632bfeb2bdf539f15a6c2d731d68f20273c00df
+ languageName: node
+ linkType: hard
+
"cac@npm:^6.7.12":
version: 6.7.12
resolution: "cac@npm:6.7.12"