diff --git a/docs/content/3.docs/4.advanced/3.kit.md b/docs/content/3.docs/4.advanced/3.kit.md index 8401841a3a..1b926f519c 100644 --- a/docs/content/3.docs/4.advanced/3.kit.md +++ b/docs/content/3.docs/4.advanced/3.kit.md @@ -112,7 +112,8 @@ console.log(getNuxtVersion()) [source code](https://github.com/nuxt/framework/blob/main/packages/kit/src/resolve.ts) - `resolvePath (path, resolveOptions?)` -- `tryResolvePath (path, resolveOptions?)` +- `resolveAlias (path, aliases?)` +- `findPath (paths, resolveOptions?)` ### Builder diff --git a/packages/bridge/src/nitro.ts b/packages/bridge/src/nitro.ts index f8d63fa7b7..355ebbf04e 100644 --- a/packages/bridge/src/nitro.ts +++ b/packages/bridge/src/nitro.ts @@ -133,7 +133,7 @@ export function setupNitroBridge () { await nuxt.callHook('nitro:context', nitroDevContext) // Resolve middleware - const { middleware, legacyMiddleware } = resolveMiddleware(nuxt) + const { middleware, legacyMiddleware } = await resolveMiddleware(nuxt) if (nuxt.server) { nuxt.server.setLegacyMiddleware(legacyMiddleware) } diff --git a/packages/kit/src/internal/cjs.ts b/packages/kit/src/internal/cjs.ts index 6a911c6efa..f8781c6e64 100644 --- a/packages/kit/src/internal/cjs.ts +++ b/packages/kit/src/internal/cjs.ts @@ -97,7 +97,7 @@ export function resolveModule (id: string, opts: ResolveModuleOptions = {}) { } /** Try to resolve the path of a module, but don't emit an error if it can't be found. */ -export function tryResolveModule (path: string, opts: ResolveModuleOptions = {}) { +export function tryResolveModule (path: string, opts: ResolveModuleOptions = {}): string | null { try { return resolveModule(path, opts) } catch (error) { @@ -105,6 +105,7 @@ export function tryResolveModule (path: string, opts: ResolveModuleOptions = {}) throw error } } + return null } /** Require a module and return it. */ diff --git a/packages/kit/src/module/install.ts b/packages/kit/src/module/install.ts index c6415c407c..f0e088e525 100644 --- a/packages/kit/src/module/install.ts +++ b/packages/kit/src/module/install.ts @@ -18,7 +18,7 @@ export async function installModule (nuxtModule: string | NuxtModule, inlineOpti // Import if input is string if (typeof nuxtModule === 'string') { - const _src = resolveModule(resolveAlias(nuxtModule, nuxt.options.alias), { paths: nuxt.options.modulesDir }) + const _src = resolveModule(resolveAlias(nuxtModule), { paths: nuxt.options.modulesDir }) // TODO: also check with type: 'module' in closest `package.json` const isESM = _src.endsWith('.mjs') nuxtModule = isESM ? await importModule(_src) : requireModule(_src) diff --git a/packages/kit/src/resolve.ts b/packages/kit/src/resolve.ts index fa228918d6..faeb6db76f 100644 --- a/packages/kit/src/resolve.ts +++ b/packages/kit/src/resolve.ts @@ -1,89 +1,101 @@ -import { existsSync, lstatSync, readdirSync } from 'fs' -import { basename, dirname, resolve, join } from 'pathe' +import { promises as fsp, existsSync } from 'fs' +import { basename, dirname, resolve, join, normalize, isAbsolute } from 'pathe' import { globby } from 'globby' +import { useNuxt } from './context' +import { tryResolveModule } from '.' -export interface ResolveOptions { - /** - * The base path against which to resolve the path - * - * @default . - */ - base?: string - /** - * An object of aliases (alias, path) to take into account, for example - * `{ 'example/alias': '/full/path/to/alias' }` - */ +export interface ResolvePathOptions { + /** Base for resolving paths from. Default is Nuxt rootDir. */ + cwd?: string + + /** An object of aliases. Default is Nuxt configured aliases. */ alias?: Record - /** The file extensions to try (for example, ['js', 'ts']) */ + + /** The file extensions to try. Default is Nuxt configured extensions. */ extensions?: string[] } -function resolvePath (path: string, opts: ResolveOptions = {}) { +/** + * Resolve full path to a file or directory respecting Nuxt alias and extensions options + * + * If path could not be resolved, normalized input path will be returned + */ +export async function resolvePath (path: string, opts: ResolvePathOptions = {}): Promise { + // Always normalize input + path = normalize(path) + // Fast return if the path exists - if (existsSyncSensitive(path)) { + if (existsSync(path)) { return path } - let resolvedPath: string + // Use current nuxt options + const nuxt = useNuxt() + const cwd = opts.cwd || nuxt.options.rootDir + const extensions = opts.extensions || nuxt.options.extensions + const modulesDir = nuxt.options.modulesDir - // Resolve alias - if (opts.alias) { - resolvedPath = resolveAlias(path, opts.alias) + // Resolve aliases + path = resolveAlias(path) + + // Resolve relative to cwd + if (!isAbsolute(path)) { + path = resolve(cwd, path) } - // Resolve relative to base or cwd - resolvedPath = resolve(opts.base || '.', resolvedPath) - - const resolvedPathFiles = readdirSync(dirname(resolvedPath)) - // Check if resolvedPath is a file let isDirectory = false - if (existsSyncSensitive(resolvedPath, resolvedPathFiles)) { - isDirectory = lstatSync(resolvedPath).isDirectory() + if (existsSync(path)) { + isDirectory = (await fsp.lstat(path)).isDirectory() if (!isDirectory) { - return resolvedPath + return path } } // Check possible extensions - for (const ext of opts.extensions) { - // resolvedPath.[ext] - const resolvedPathwithExt = resolvedPath + ext - if (!isDirectory && existsSyncSensitive(resolvedPathwithExt, resolvedPathFiles)) { - return resolvedPathwithExt + for (const ext of extensions) { + // path.[ext] + const pathWithExt = path + ext + if (!isDirectory && existsSync(pathWithExt)) { + return pathWithExt } - // resolvedPath/index.[ext] - const resolvedPathwithIndex = join(resolvedPath, 'index' + ext) - if (isDirectory && existsSyncSensitive(resolvedPathwithIndex)) { - return resolvedPathwithIndex + // path/index.[ext] + const pathWithIndex = join(path, 'index' + ext) + if (isDirectory && existsSync(pathWithIndex)) { + return pathWithIndex } } - // If extension check fails and resolvedPath is a valid directory, return it - if (isDirectory) { - return resolvedPath + // Try to resolve as module id + const resolveModulePath = tryResolveModule(path, { paths: modulesDir }) + if (resolveModulePath) { + return resolveModulePath } - // Give up if it is neither a directory - throw new Error(`Cannot resolve "${path}" from "${resolvedPath}"`) -} - -function existsSyncSensitive (path: string, files?: string[]) { - if (!existsSync(path)) { return false } - const _files = files || readdirSync(dirname(path)) - return _files.includes(basename(path)) + // Return normalized input + return path } /** - * Return a path with any relevant aliases resolved. - * - * @example - * ```js - * const aliases = { 'test': '/here/there' } - * resolveAlias('test/everywhere', aliases) - * // '/here/there/everywhere' + * Try to resolve first existing file in paths */ -export function resolveAlias (path: string, alias: ResolveOptions['alias']) { +export async function findPath (paths: string[], opts?: ResolvePathOptions): Promise { + for (const path of paths) { + const rPath = await resolvePath(path, opts) + if (await existsSensitive(rPath)) { + return rPath + } + } + return null +} + +/** + * Resolve path aliases respecting Nuxt alias options + */ +export function resolveAlias (path: string, alias?: Record): string { + if (!alias) { + alias = useNuxt().options.alias + } for (const key in alias) { if (key === '@' && !path.startsWith('@/')) { continue } // Don't resolve @foo/bar if (path.startsWith(key)) { @@ -93,21 +105,15 @@ export function resolveAlias (path: string, alias: ResolveOptions['alias']) { return path } -/** - * Resolve the path of a file but don't emit an error, - * even if the module can't be resolved. - */ -export function tryResolvePath (path: string, opts: ResolveOptions = {}) { - try { - return resolvePath(path, opts) - } catch (e) { - } +// --- Internal --- + +async function existsSensitive (path: string) { + if (!existsSync(path)) { return false } + const dirFiles = await fsp.readdir(dirname(path)) + return dirFiles.includes(basename(path)) } export async function resolveFiles (path: string, pattern: string | string[]) { - const files = await globby(pattern, { - cwd: path, - followSymbolicLinks: true - }) + const files = await globby(pattern, { cwd: path, followSymbolicLinks: true }) return files.map(p => resolve(path, p)) } diff --git a/packages/nitro/src/server/middleware.ts b/packages/nitro/src/server/middleware.ts index e90ff78083..b0f7f5c7e7 100644 --- a/packages/nitro/src/server/middleware.ts +++ b/packages/nitro/src/server/middleware.ts @@ -2,7 +2,7 @@ import { resolve, join, extname } from 'pathe' import { joinURL } from 'ufo' import { globby } from 'globby' import { watch } from 'chokidar' -import { tryResolveModule, tryResolvePath } from '@nuxt/kit' +import { resolvePath } from '@nuxt/kit' import type { Nuxt } from '@nuxt/schema' import type { Middleware } from 'h3' @@ -68,7 +68,7 @@ export function scanMiddleware (serverDir: string, onChange?: (results: ServerMi return scan() } -export function resolveMiddleware (nuxt: Nuxt) { +export async function resolveMiddleware (nuxt: Nuxt) { const middleware: ServerMiddleware[] = [] const legacyMiddleware: ServerMiddleware[] = [] @@ -83,11 +83,7 @@ export function resolveMiddleware (nuxt: Nuxt) { delete m.path middleware.push({ ...m, - handle: tryResolvePath(handle, { - extensions: ['.ts', '.mjs', '.js', '.cjs'], - alias: nuxt.options.alias, - base: nuxt.options.srcDir - }) || tryResolveModule(handle, { paths: nuxt.options.modulesDir }), + handle: await resolvePath(handle), route }) } diff --git a/packages/nuxt3/src/auto-imports/module.ts b/packages/nuxt3/src/auto-imports/module.ts index 4ebc768bcb..8d73d8d4e0 100644 --- a/packages/nuxt3/src/auto-imports/module.ts +++ b/packages/nuxt3/src/auto-imports/module.ts @@ -112,7 +112,7 @@ function generateDts (ctx: AutoImportContext) { const resolved = {} const r = (id: string) => { if (resolved[id]) { return resolved[id] } - let path = resolveAlias(id, nuxt.options.alias) + let path = resolveAlias(id) if (isAbsolute(path)) { path = relative(join(nuxt.options.buildDir, 'types'), path) } diff --git a/packages/nuxt3/src/components/module.ts b/packages/nuxt3/src/components/module.ts index 56a658d84b..22f504c6cc 100644 --- a/packages/nuxt3/src/components/module.ts +++ b/packages/nuxt3/src/components/module.ts @@ -55,7 +55,7 @@ export default defineNuxtModule({ componentDirs = allDirs.filter(isPureObjectOrString).map((dir) => { const dirOptions: ComponentsDir = typeof dir === 'object' ? dir : { path: dir } - const dirPath = resolveAlias(dirOptions.path, nuxt.options.alias) + const dirPath = resolveAlias(dirOptions.path) const transpile = typeof dirOptions.transpile === 'boolean' ? dirOptions.transpile : 'auto' const extensions = (dirOptions.extensions || nuxt.options.extensions).map(e => e.replace(/^\./g, '')) diff --git a/packages/nuxt3/src/core/app.ts b/packages/nuxt3/src/core/app.ts index 19940b5b87..3e63dfdf85 100644 --- a/packages/nuxt3/src/core/app.ts +++ b/packages/nuxt3/src/core/app.ts @@ -2,7 +2,7 @@ import { promises as fsp } from 'fs' import { dirname, resolve } from 'pathe' import defu from 'defu' import type { Nuxt, NuxtApp } from '@nuxt/schema' -import { tryResolvePath, resolveFiles, normalizePlugin, normalizeTemplate, compileTemplate, templateUtils } from '@nuxt/kit' +import { findPath, resolveFiles, normalizePlugin, normalizeTemplate, compileTemplate, templateUtils } from '@nuxt/kit' import * as defaultTemplates from './templates' @@ -54,15 +54,9 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp) { } export async function resolveApp (nuxt: Nuxt, app: NuxtApp) { - const resolveOptions = { - base: nuxt.options.srcDir, - alias: nuxt.options.alias, - extensions: nuxt.options.extensions - } - // Resolve main (app.vue) if (!app.mainComponent) { - app.mainComponent = tryResolvePath('~/App', resolveOptions) || tryResolvePath('~/app', resolveOptions) + app.mainComponent = await findPath(['~/App', '~/app']) } if (!app.mainComponent) { app.mainComponent = resolve(nuxt.options.appDir, 'components/nuxt-welcome.vue') diff --git a/packages/nuxt3/src/core/nitro.ts b/packages/nuxt3/src/core/nitro.ts index c387d580dd..7250dd5b57 100644 --- a/packages/nuxt3/src/core/nitro.ts +++ b/packages/nuxt3/src/core/nitro.ts @@ -59,7 +59,7 @@ export function initNitro (nuxt: Nuxt) { await nuxt.callHook('nitro:context', nitroDevContext) // Resolve middleware - const { middleware, legacyMiddleware } = resolveMiddleware(nuxt) + const { middleware, legacyMiddleware } = await resolveMiddleware(nuxt) nuxt.server.setLegacyMiddleware(legacyMiddleware) nitroContext.middleware.push(...middleware) nitroDevContext.middleware.push(...middleware)