feat(kit)!: expose `resolvePath` (#3110)

This commit is contained in:
pooya parsa 2022-02-07 22:00:20 +01:00 committed by GitHub
parent a1b2d09438
commit 03d5fdde2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 89 additions and 91 deletions

View File

@ -112,7 +112,8 @@ console.log(getNuxtVersion())
[source code](https://github.com/nuxt/framework/blob/main/packages/kit/src/resolve.ts) [source code](https://github.com/nuxt/framework/blob/main/packages/kit/src/resolve.ts)
- `resolvePath (path, resolveOptions?)` - `resolvePath (path, resolveOptions?)`
- `tryResolvePath (path, resolveOptions?)` - `resolveAlias (path, aliases?)`
- `findPath (paths, resolveOptions?)`
### Builder ### Builder

View File

@ -133,7 +133,7 @@ export function setupNitroBridge () {
await nuxt.callHook('nitro:context', nitroDevContext) await nuxt.callHook('nitro:context', nitroDevContext)
// Resolve middleware // Resolve middleware
const { middleware, legacyMiddleware } = resolveMiddleware(nuxt) const { middleware, legacyMiddleware } = await resolveMiddleware(nuxt)
if (nuxt.server) { if (nuxt.server) {
nuxt.server.setLegacyMiddleware(legacyMiddleware) nuxt.server.setLegacyMiddleware(legacyMiddleware)
} }

View File

@ -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. */ /** 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 { try {
return resolveModule(path, opts) return resolveModule(path, opts)
} catch (error) { } catch (error) {
@ -105,6 +105,7 @@ export function tryResolveModule (path: string, opts: ResolveModuleOptions = {})
throw error throw error
} }
} }
return null
} }
/** Require a module and return it. */ /** Require a module and return it. */

View File

@ -18,7 +18,7 @@ export async function installModule (nuxtModule: string | NuxtModule, inlineOpti
// Import if input is string // Import if input is string
if (typeof nuxtModule === '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` // TODO: also check with type: 'module' in closest `package.json`
const isESM = _src.endsWith('.mjs') const isESM = _src.endsWith('.mjs')
nuxtModule = isESM ? await importModule(_src) : requireModule(_src) nuxtModule = isESM ? await importModule(_src) : requireModule(_src)

View File

@ -1,89 +1,101 @@
import { existsSync, lstatSync, readdirSync } from 'fs' import { promises as fsp, existsSync } from 'fs'
import { basename, dirname, resolve, join } from 'pathe' import { basename, dirname, resolve, join, normalize, isAbsolute } from 'pathe'
import { globby } from 'globby' import { globby } from 'globby'
import { useNuxt } from './context'
import { tryResolveModule } from '.'
export interface ResolveOptions { export interface ResolvePathOptions {
/** /** Base for resolving paths from. Default is Nuxt rootDir. */
* The base path against which to resolve the path cwd?: string
*
* @default . /** An object of aliases. Default is Nuxt configured aliases. */
*/
base?: string
/**
* An object of aliases (alias, path) to take into account, for example
* `{ 'example/alias': '/full/path/to/alias' }`
*/
alias?: Record<string, string> alias?: Record<string, string>
/** The file extensions to try (for example, ['js', 'ts']) */
/** The file extensions to try. Default is Nuxt configured extensions. */
extensions?: string[] 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<string> {
// Always normalize input
path = normalize(path)
// Fast return if the path exists // Fast return if the path exists
if (existsSyncSensitive(path)) { if (existsSync(path)) {
return 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 // Resolve aliases
if (opts.alias) { path = resolveAlias(path)
resolvedPath = resolveAlias(path, opts.alias)
// 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 // Check if resolvedPath is a file
let isDirectory = false let isDirectory = false
if (existsSyncSensitive(resolvedPath, resolvedPathFiles)) { if (existsSync(path)) {
isDirectory = lstatSync(resolvedPath).isDirectory() isDirectory = (await fsp.lstat(path)).isDirectory()
if (!isDirectory) { if (!isDirectory) {
return resolvedPath return path
} }
} }
// Check possible extensions // Check possible extensions
for (const ext of opts.extensions) { for (const ext of extensions) {
// resolvedPath.[ext] // path.[ext]
const resolvedPathwithExt = resolvedPath + ext const pathWithExt = path + ext
if (!isDirectory && existsSyncSensitive(resolvedPathwithExt, resolvedPathFiles)) { if (!isDirectory && existsSync(pathWithExt)) {
return resolvedPathwithExt return pathWithExt
} }
// resolvedPath/index.[ext] // path/index.[ext]
const resolvedPathwithIndex = join(resolvedPath, 'index' + ext) const pathWithIndex = join(path, 'index' + ext)
if (isDirectory && existsSyncSensitive(resolvedPathwithIndex)) { if (isDirectory && existsSync(pathWithIndex)) {
return resolvedPathwithIndex return pathWithIndex
} }
} }
// If extension check fails and resolvedPath is a valid directory, return it // Try to resolve as module id
if (isDirectory) { const resolveModulePath = tryResolveModule(path, { paths: modulesDir })
return resolvedPath if (resolveModulePath) {
return resolveModulePath
} }
// Give up if it is neither a directory // Return normalized input
throw new Error(`Cannot resolve "${path}" from "${resolvedPath}"`) return path
}
function existsSyncSensitive (path: string, files?: string[]) {
if (!existsSync(path)) { return false }
const _files = files || readdirSync(dirname(path))
return _files.includes(basename(path))
} }
/** /**
* Return a path with any relevant aliases resolved. * Try to resolve first existing file in paths
*
* @example
* ```js
* const aliases = { 'test': '/here/there' }
* resolveAlias('test/everywhere', aliases)
* // '/here/there/everywhere'
*/ */
export function resolveAlias (path: string, alias: ResolveOptions['alias']) { export async function findPath (paths: string[], opts?: ResolvePathOptions): Promise<string|null> {
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, string>): string {
if (!alias) {
alias = useNuxt().options.alias
}
for (const key in alias) { for (const key in alias) {
if (key === '@' && !path.startsWith('@/')) { continue } // Don't resolve @foo/bar if (key === '@' && !path.startsWith('@/')) { continue } // Don't resolve @foo/bar
if (path.startsWith(key)) { if (path.startsWith(key)) {
@ -93,21 +105,15 @@ export function resolveAlias (path: string, alias: ResolveOptions['alias']) {
return path return path
} }
/** // --- Internal ---
* Resolve the path of a file but don't emit an error,
* even if the module can't be resolved. async function existsSensitive (path: string) {
*/ if (!existsSync(path)) { return false }
export function tryResolvePath (path: string, opts: ResolveOptions = {}) { const dirFiles = await fsp.readdir(dirname(path))
try { return dirFiles.includes(basename(path))
return resolvePath(path, opts)
} catch (e) {
}
} }
export async function resolveFiles (path: string, pattern: string | string[]) { export async function resolveFiles (path: string, pattern: string | string[]) {
const files = await globby(pattern, { const files = await globby(pattern, { cwd: path, followSymbolicLinks: true })
cwd: path,
followSymbolicLinks: true
})
return files.map(p => resolve(path, p)) return files.map(p => resolve(path, p))
} }

View File

@ -2,7 +2,7 @@ import { resolve, join, extname } from 'pathe'
import { joinURL } from 'ufo' import { joinURL } from 'ufo'
import { globby } from 'globby' import { globby } from 'globby'
import { watch } from 'chokidar' import { watch } from 'chokidar'
import { tryResolveModule, tryResolvePath } from '@nuxt/kit' import { resolvePath } from '@nuxt/kit'
import type { Nuxt } from '@nuxt/schema' import type { Nuxt } from '@nuxt/schema'
import type { Middleware } from 'h3' import type { Middleware } from 'h3'
@ -68,7 +68,7 @@ export function scanMiddleware (serverDir: string, onChange?: (results: ServerMi
return scan() return scan()
} }
export function resolveMiddleware (nuxt: Nuxt) { export async function resolveMiddleware (nuxt: Nuxt) {
const middleware: ServerMiddleware[] = [] const middleware: ServerMiddleware[] = []
const legacyMiddleware: ServerMiddleware[] = [] const legacyMiddleware: ServerMiddleware[] = []
@ -83,11 +83,7 @@ export function resolveMiddleware (nuxt: Nuxt) {
delete m.path delete m.path
middleware.push({ middleware.push({
...m, ...m,
handle: tryResolvePath(handle, { handle: await resolvePath(handle),
extensions: ['.ts', '.mjs', '.js', '.cjs'],
alias: nuxt.options.alias,
base: nuxt.options.srcDir
}) || tryResolveModule(handle, { paths: nuxt.options.modulesDir }),
route route
}) })
} }

View File

@ -112,7 +112,7 @@ function generateDts (ctx: AutoImportContext) {
const resolved = {} const resolved = {}
const r = (id: string) => { const r = (id: string) => {
if (resolved[id]) { return resolved[id] } if (resolved[id]) { return resolved[id] }
let path = resolveAlias(id, nuxt.options.alias) let path = resolveAlias(id)
if (isAbsolute(path)) { if (isAbsolute(path)) {
path = relative(join(nuxt.options.buildDir, 'types'), path) path = relative(join(nuxt.options.buildDir, 'types'), path)
} }

View File

@ -55,7 +55,7 @@ export default defineNuxtModule<ComponentsOptions>({
componentDirs = allDirs.filter(isPureObjectOrString).map((dir) => { componentDirs = allDirs.filter(isPureObjectOrString).map((dir) => {
const dirOptions: ComponentsDir = typeof dir === 'object' ? dir : { path: 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 transpile = typeof dirOptions.transpile === 'boolean' ? dirOptions.transpile : 'auto'
const extensions = (dirOptions.extensions || nuxt.options.extensions).map(e => e.replace(/^\./g, '')) const extensions = (dirOptions.extensions || nuxt.options.extensions).map(e => e.replace(/^\./g, ''))

View File

@ -2,7 +2,7 @@ import { promises as fsp } from 'fs'
import { dirname, resolve } from 'pathe' import { dirname, resolve } from 'pathe'
import defu from 'defu' import defu from 'defu'
import type { Nuxt, NuxtApp } from '@nuxt/schema' 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' 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) { 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) // Resolve main (app.vue)
if (!app.mainComponent) { if (!app.mainComponent) {
app.mainComponent = tryResolvePath('~/App', resolveOptions) || tryResolvePath('~/app', resolveOptions) app.mainComponent = await findPath(['~/App', '~/app'])
} }
if (!app.mainComponent) { if (!app.mainComponent) {
app.mainComponent = resolve(nuxt.options.appDir, 'components/nuxt-welcome.vue') app.mainComponent = resolve(nuxt.options.appDir, 'components/nuxt-welcome.vue')

View File

@ -59,7 +59,7 @@ export function initNitro (nuxt: Nuxt) {
await nuxt.callHook('nitro:context', nitroDevContext) await nuxt.callHook('nitro:context', nitroDevContext)
// Resolve middleware // Resolve middleware
const { middleware, legacyMiddleware } = resolveMiddleware(nuxt) const { middleware, legacyMiddleware } = await resolveMiddleware(nuxt)
nuxt.server.setLegacyMiddleware(legacyMiddleware) nuxt.server.setLegacyMiddleware(legacyMiddleware)
nitroContext.middleware.push(...middleware) nitroContext.middleware.push(...middleware)
nitroDevContext.middleware.push(...middleware) nitroDevContext.middleware.push(...middleware)