diff --git a/packages/nuxt/src/components/module.ts b/packages/nuxt/src/components/module.ts index c42ab16aae..9ed4127c7a 100644 --- a/packages/nuxt/src/components/module.ts +++ b/packages/nuxt/src/components/module.ts @@ -1,6 +1,6 @@ -import fs, { statSync } from 'node:fs' +import { existsSync, statSync, writeFileSync } from 'node:fs' import { join, normalize, relative, resolve } from 'pathe' -import { addPluginTemplate, addTemplate, addTypeTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, logger, resolveAlias, updateTemplates } from '@nuxt/kit' +import { addPluginTemplate, addTemplate, addTypeTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, logger, resolveAlias, resolvePath, updateTemplates } from '@nuxt/kit' import type { Component, ComponentsDir, ComponentsOptions } from 'nuxt/schema' import { distDir } from '../dirs' @@ -169,6 +169,10 @@ export default defineNuxtModule({ await nuxt.callHook('components:extend', newComponents) // add server placeholder for .client components server side. issue: #7085 for (const component of newComponents) { + if (!(component as any /* untyped internal property */)._scanned && !(component.filePath in nuxt.vfs) && !existsSync(component.filePath)) { + // attempt to resolve component path + component.filePath = await resolvePath(component.filePath) + } if (component.mode === 'client' && !newComponents.some(c => c.pascalName === component.pascalName && c.mode === 'server')) { newComponents.push({ ...component, @@ -236,17 +240,17 @@ export default defineNuxtModule({ const selectiveClient = typeof nuxt.options.experimental.componentIslands === 'object' && nuxt.options.experimental.componentIslands.selectiveClient if (isClient && selectiveClient) { - fs.writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), 'export const paths = {}') + writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), 'export const paths = {}') if (!nuxt.options.dev) { config.plugins.push(componentsChunkPlugin.vite({ getComponents, buildDir: nuxt.options.buildDir, })) } else { - fs.writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), `export const paths = ${JSON.stringify( + writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), `export const paths = ${JSON.stringify( getComponents().filter(c => c.mode === 'client' || c.mode === 'all').reduce((acc, c) => { if (c.filePath.endsWith('.vue') || c.filePath.endsWith('.js') || c.filePath.endsWith('.ts')) { return Object.assign(acc, { [c.pascalName]: `/@fs/${c.filePath}` }) } - const filePath = fs.existsSync(`${c.filePath}.vue`) ? `${c.filePath}.vue` : fs.existsSync(`${c.filePath}.js`) ? `${c.filePath}.js` : `${c.filePath}.ts` + const filePath = existsSync(`${c.filePath}.vue`) ? `${c.filePath}.vue` : existsSync(`${c.filePath}.js`) ? `${c.filePath}.js` : `${c.filePath}.ts` return Object.assign(acc, { [c.pascalName]: `/@fs/${filePath}` }) }, {} as Record), )}`) @@ -307,7 +311,7 @@ export default defineNuxtModule({ getComponents, })) } else { - fs.writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), 'export const paths = {}') + writeFileSync(join(nuxt.options.buildDir, 'components-chunk.mjs'), 'export const paths = {}') } } }) diff --git a/packages/nuxt/src/components/scan.ts b/packages/nuxt/src/components/scan.ts index f37bea63bb..01d8331cb3 100644 --- a/packages/nuxt/src/components/scan.ts +++ b/packages/nuxt/src/components/scan.ts @@ -126,6 +126,8 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr export: 'default', // by default, give priority to scanned components priority: dir.priority ?? 1, + // @ts-expect-error untyped property + _scanned: true, } if (typeof dir.extendComponent === 'function') { diff --git a/packages/nuxt/test/scan-components.test.ts b/packages/nuxt/test/scan-components.test.ts index 17d26f03dd..65cc86aff0 100644 --- a/packages/nuxt/test/scan-components.test.ts +++ b/packages/nuxt/test/scan-components.test.ts @@ -241,6 +241,8 @@ it('components:scanComponents', async () => { for (const c of scannedComponents) { // @ts-expect-error filePath is not optional but we don't want it to be in the snapshot delete c.filePath + // @ts-expect-error _scanned is added internally but we don't want it to be in the snapshot + delete c._scanned } expect(scannedComponents).deep.eq(expectedComponents) })