diff --git a/packages/kit/src/components.ts b/packages/kit/src/components.ts index 8bd96e4101..aae9e45072 100644 --- a/packages/kit/src/components.ts +++ b/packages/kit/src/components.ts @@ -40,14 +40,24 @@ export async function addComponent (opts: AddComponentOptions) { preload: false, mode: 'all', shortPath: opts.filePath, + priority: 0, ...opts } nuxt.hook('components:extend', (components: Component[]) => { const existingComponent = components.find(c => (c.pascalName === component.pascalName || c.kebabName === component.kebabName) && c.mode === component.mode) if (existingComponent) { - const name = existingComponent.pascalName || existingComponent.kebabName - console.warn(`Overriding ${name} component.`) + const existingPriority = existingComponent.priority ?? 0 + const newPriority = component.priority ?? 0 + + if (newPriority < existingPriority) { return } + + // We override where new component priority is equal or higher + // but we warn if they are equal. + if (newPriority === existingPriority) { + const name = existingComponent.pascalName || existingComponent.kebabName + console.warn(`Overriding ${name} component. You can specify a \`priority\` option when calling \`addComponent\` to avoid this warning.`) + } Object.assign(existingComponent, component) } else { components.push(component) diff --git a/packages/nuxt/src/components/scan.ts b/packages/nuxt/src/components/scan.ts index 58d040c203..851d94d111 100644 --- a/packages/nuxt/src/components/scan.ts +++ b/packages/nuxt/src/components/scan.ts @@ -118,7 +118,9 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr kebabName, chunkName, shortPath, - export: 'default' + export: 'default', + // by default, give priority to scanned components + priority: 1 } if (typeof dir.extendComponent === 'function') { diff --git a/packages/nuxt/src/core/nuxt.ts b/packages/nuxt/src/core/nuxt.ts index 1b86ea03bb..968d142338 100644 --- a/packages/nuxt/src/core/nuxt.ts +++ b/packages/nuxt/src/core/nuxt.ts @@ -158,47 +158,55 @@ async function initNuxt (nuxt: Nuxt) { // Add addComponent({ name: 'NuxtWelcome', + priority: 10, // built-in that we do not expect the user to override filePath: tryResolveModule('@nuxt/ui-templates/templates/welcome.vue')! }) addComponent({ name: 'NuxtLayout', + priority: 10, // built-in that we do not expect the user to override filePath: resolve(nuxt.options.appDir, 'components/layout') }) // Add addComponent({ name: 'NuxtErrorBoundary', + priority: 10, // built-in that we do not expect the user to override filePath: resolve(nuxt.options.appDir, 'components/nuxt-error-boundary') }) // Add addComponent({ name: 'ClientOnly', + priority: 10, // built-in that we do not expect the user to override filePath: resolve(nuxt.options.appDir, 'components/client-only') }) // Add addComponent({ name: 'DevOnly', + priority: 10, // built-in that we do not expect the user to override filePath: resolve(nuxt.options.appDir, 'components/dev-only') }) // Add addComponent({ name: 'ServerPlaceholder', + priority: 10, // built-in that we do not expect the user to override filePath: resolve(nuxt.options.appDir, 'components/server-placeholder') }) // Add addComponent({ name: 'NuxtLink', + priority: 10, // built-in that we do not expect the user to override filePath: resolve(nuxt.options.appDir, 'components/nuxt-link') }) // Add addComponent({ name: 'NuxtLoadingIndicator', + priority: 10, // built-in that we do not expect the user to override filePath: resolve(nuxt.options.appDir, 'components/nuxt-loading-indicator') }) @@ -206,6 +214,7 @@ async function initNuxt (nuxt: Nuxt) { if (nuxt.options.experimental.componentIslands) { addComponent({ name: 'NuxtIsland', + priority: 10, // built-in that we do not expect the user to override filePath: resolve(nuxt.options.appDir, 'components/nuxt-island') }) } diff --git a/packages/nuxt/src/head/module.ts b/packages/nuxt/src/head/module.ts index e83ab330c7..cb275d9b27 100644 --- a/packages/nuxt/src/head/module.ts +++ b/packages/nuxt/src/head/module.ts @@ -24,6 +24,8 @@ export default defineNuxtModule({ name: componentName, filePath: componentsPath, export: componentName, + // built-in that we do not expect the user to override + priority: 10, // kebab case version of these tags is not valid kebabName: componentName }) diff --git a/packages/nuxt/src/pages/module.ts b/packages/nuxt/src/pages/module.ts index 46fa4cc62d..69745092d0 100644 --- a/packages/nuxt/src/pages/module.ts +++ b/packages/nuxt/src/pages/module.ts @@ -43,6 +43,7 @@ export default defineNuxtModule({ }) addComponent({ name: 'NuxtPage', + priority: 10, // built-in that we do not expect the user to override filePath: resolve(distDir, 'pages/runtime/page-placeholder') }) return @@ -256,6 +257,7 @@ export default defineNuxtModule({ // Add addComponent({ name: 'NuxtPage', + priority: 10, // built-in that we do not expect the user to override filePath: resolve(distDir, 'pages/runtime/page') }) diff --git a/packages/nuxt/test/scan-components.test.ts b/packages/nuxt/test/scan-components.test.ts index 3b9bc3933a..e7d779ddd7 100644 --- a/packages/nuxt/test/scan-components.test.ts +++ b/packages/nuxt/test/scan-components.test.ts @@ -97,6 +97,7 @@ const expectedComponents = [ pascalName: 'Isle', prefetch: false, preload: false, + priority: 1, shortPath: 'components/islands/Isle.vue' }, { @@ -109,6 +110,7 @@ const expectedComponents = [ pascalName: 'Glob', prefetch: false, preload: false, + priority: 1, shortPath: 'components/global/Glob.vue' }, { @@ -121,7 +123,8 @@ const expectedComponents = [ global: undefined, island: undefined, prefetch: false, - preload: false + preload: false, + priority: 1 }, { mode: 'client', @@ -133,7 +136,8 @@ const expectedComponents = [ global: undefined, island: undefined, prefetch: false, - preload: false + preload: false, + priority: 1 }, { mode: 'server', @@ -145,7 +149,8 @@ const expectedComponents = [ global: undefined, island: undefined, prefetch: false, - preload: false + preload: false, + priority: 1 }, { chunkName: 'components/client-component-with-props', @@ -157,6 +162,7 @@ const expectedComponents = [ pascalName: 'ClientComponentWithProps', prefetch: false, preload: false, + priority: 1, shortPath: 'components/client/ComponentWithProps.vue' }, { @@ -169,6 +175,7 @@ const expectedComponents = [ pascalName: 'ClientWithClientOnlySetup', prefetch: false, preload: false, + priority: 1, shortPath: 'components/client/WithClientOnlySetup.vue' }, { @@ -181,7 +188,8 @@ const expectedComponents = [ global: undefined, island: undefined, prefetch: false, - preload: false + preload: false, + priority: 1 }, { chunkName: 'components/some-glob', @@ -193,6 +201,7 @@ const expectedComponents = [ pascalName: 'SomeGlob', prefetch: false, preload: false, + priority: 1, shortPath: 'components/some-glob.global.vue' }, { @@ -205,6 +214,7 @@ const expectedComponents = [ pascalName: 'Some', prefetch: false, preload: false, + priority: 1, shortPath: 'components/some.island.vue' } ] diff --git a/packages/schema/src/types/components.ts b/packages/schema/src/types/components.ts index 3772357b29..64eef5be17 100644 --- a/packages/schema/src/types/components.ts +++ b/packages/schema/src/types/components.ts @@ -10,6 +10,12 @@ export interface Component { global?: boolean island?: boolean mode?: 'client' | 'server' | 'all' + /** + * This number allows configuring the behavior of overriding Nuxt components. + * If multiple components are provided with the same name, then higher priority + * components will be used instead of lower priority components. + */ + priority?: number } export interface ScanDir {