next.js/packages/next/src/server/app-render/segment-explorer-path.ts
segment-explorer-path.ts129 lines4.0 KB
import type { LoaderTree } from '../lib/app-dir-module'

export const BUILTIN_PREFIX = '__next_builtin__'

const nextInternalPrefixRegex =
  /^(.*[\\/])?next[\\/]dist[\\/]client[\\/]components[\\/]builtin[\\/]/

/**
 * Normalize a file path to be relative to the project directory.
 * Handles Turbopack [project] prefix and monorepo setups.
 */
export function normalizeFilePath(
  projectDir: string,
  filePath: string | undefined
): string {
  // Turbopack project path is formed as: "<project root>/<cwd>".
  // When project root is not the working directory, we can extract the relative project root path.
  // This is mostly used for running Next.js inside a monorepo.
  const cwd = process.env.NEXT_RUNTIME === 'edge' ? '' : process.cwd()
  const relativeProjectRoot = projectDir.replace(cwd, '').replace(/^[\\/]/, '')

  let relativePath = (filePath || '')
    // remove turbopack [project] prefix
    .replace(/^\[project\][\\/]?/, '')
    // remove the project root from the path (absolute)
    .replace(projectDir, '')
    // remove cwd prefix (absolute)
    .replace(cwd, '')
    // normalize path separators and remove leading slash
    .replace(/\\/g, '/')
    .replace(/^\//, '')

  // remove relative project path prefix (e.g., "test/e2e/app-dir/actions/")
  if (relativeProjectRoot && relativePath.startsWith(relativeProjectRoot)) {
    relativePath = relativePath
      .slice(relativeProjectRoot.length)
      .replace(/^\//, '')
  }

  // Handle case where filename is relative to a parent of projectDir
  // (e.g., in tests where filename is "test/tmp/next-test-XXX/app/page.js"
  // but projectDir is the test temp directory)
  if (relativePath.includes('/')) {
    const projectDirName = projectDir.split(/[\\/]/).pop() || ''
    if (projectDirName) {
      const projectDirWithSlash = projectDirName + '/'
      const idx = relativePath.indexOf(projectDirWithSlash)
      if (idx >= 0) {
        relativePath = relativePath.slice(idx + projectDirWithSlash.length)
      }
    }
  }

  return relativePath
}

export function normalizeConventionFilePath(
  projectDir: string,
  conventionPath: string | undefined
) {
  let relativePath = normalizeFilePath(projectDir, conventionPath)
    // remove /(src/)?app/ dir prefix
    .replace(/^(src\/)?app\//, '')

  // If it's internal file only keep the filename, strip nextjs internal prefix
  if (nextInternalPrefixRegex.test(relativePath)) {
    relativePath = relativePath.replace(nextInternalPrefixRegex, '')
    // Add a special prefix to let segment explorer know it's a built-in component
    relativePath = `${BUILTIN_PREFIX}${relativePath}`
  }

  return relativePath
}

// if a filepath is a builtin file. e.g.
// .../project/node_modules/next/dist/client/components/builtin/global-error.js -> true
// .../project/app/global-error.js -> false
export const isNextjsBuiltinFilePath = (filePath: string) => {
  return nextInternalPrefixRegex.test(filePath)
}

export const BOUNDARY_SUFFIX = '@boundary'
export function normalizeBoundaryFilename(filename: string) {
  return filename
    .replace(new RegExp(`^${BUILTIN_PREFIX}`), '')
    .replace(new RegExp(`${BOUNDARY_SUFFIX}$`), '')
}

export const BOUNDARY_PREFIX = 'boundary:'
export function isBoundaryFile(fileType: string) {
  return fileType.startsWith(BOUNDARY_PREFIX)
}

// if a filename is a builtin file.
// __next_builtin__global-error.js -> true
// page.js -> false
export function isBuiltinBoundaryFile(fileType: string) {
  return fileType.startsWith(BUILTIN_PREFIX)
}

export function getBoundaryOriginFileType(fileType: string) {
  return fileType.replace(BOUNDARY_PREFIX, '')
}

export function getConventionPathByType(
  tree: LoaderTree,
  dir: string,
  conventionType:
    | 'layout'
    | 'template'
    | 'page'
    | 'not-found'
    | 'error'
    | 'loading'
    | 'forbidden'
    | 'unauthorized'
    | 'defaultPage'
    | 'global-error'
) {
  const modules = tree[2]
  const conventionPath = modules[conventionType]
    ? modules[conventionType][1]
    : undefined
  if (conventionPath) {
    return normalizeConventionFilePath(dir, conventionPath)
  }
  return undefined
}
Quest for Codev2.0.0
/
SIGN IN