next.js/packages/next/src/server/load-manifest.external.ts
load-manifest.external.ts180 lines4.2 KB
import type { DeepReadonly } from '../shared/lib/deep-readonly'

import { join } from 'path'
import { readFileSync } from 'fs'
import { runInNewContext } from 'vm'
import { deepFreeze } from '../shared/lib/deep-freeze'

const sharedCache = new Map<string, unknown>()

/**
 * Load a manifest file from the file system. Optionally cache the manifest in
 * memory to avoid reading the file multiple times using the provided cache or
 * defaulting to a shared module cache. The manifest is frozen to prevent
 * modifications if it is cached.
 *
 * @param path the path to the manifest file
 * @param shouldCache whether to cache the manifest in memory
 * @param cache the cache to use for storing the manifest
 * @returns the manifest object
 */
export function loadManifest<T extends object>(
  path: string,
  shouldCache: false
): T
export function loadManifest<T extends object>(
  path: string,
  shouldCache?: boolean,
  cache?: Map<string, unknown>,
  skipParse?: boolean
): DeepReadonly<T>
export function loadManifest<T extends object>(
  path: string,
  shouldCache?: boolean,
  cache?: Map<string, unknown>,
  skipParse?: boolean,
  handleMissing?: boolean
): DeepReadonly<T>
export function loadManifest<T extends object>(
  path: string,
  shouldCache: boolean = true,
  cache = sharedCache,
  skipParse = false,
  handleMissing?: boolean
): T {
  const cached = shouldCache && cache.get(path)
  if (cached) {
    return cached as T
  }

  let manifest: any

  if (handleMissing) {
    try {
      manifest = readFileSync(/* turbopackIgnore: true */ path, 'utf8')
    } catch (err) {
      let result = {} as any
      cache.set(path, result)
      return result
    }
  } else {
    manifest = readFileSync(/* turbopackIgnore: true */ path, 'utf8')
  }

  if (!skipParse) {
    manifest = JSON.parse(manifest)

    // Freeze the manifest so it cannot be modified if we're caching it.
    if (shouldCache) {
      manifest = deepFreeze(manifest)
    }
  }

  if (shouldCache) {
    cache.set(path, manifest)
  }

  return manifest
}

export function evalManifest<T extends object>(
  path: string,
  shouldCache: false
): T
export function evalManifest<T extends object>(
  path: string,
  shouldCache?: boolean,
  cache?: Map<string, unknown>,
  handleMissing?: boolean
): DeepReadonly<T>
export function evalManifest<T extends object>(
  path: string,
  shouldCache?: true,
  cache?: Map<string, unknown>
): DeepReadonly<T>
export function evalManifest<T extends object>(
  path: string,
  shouldCache: boolean = true,
  cache = sharedCache,
  handleMissing?: boolean
): T {
  const cached = shouldCache && cache.get(path)
  if (cached) {
    return cached as T
  }

  let content: any
  if (handleMissing) {
    try {
      content = readFileSync(/* turbopackIgnore: true */ path, 'utf8')
    } catch (err) {
      let result = {} as any
      cache.set(path, result)
      return result
    }
  } else {
    content = readFileSync(/* turbopackIgnore: true */ path, 'utf8')
  }

  if (content.length === 0) {
    throw new Error('Manifest file is empty')
  }

  let contextObject = {
    process: { env: { NEXT_DEPLOYMENT_ID: process.env.NEXT_DEPLOYMENT_ID } },
  }
  runInNewContext(content, contextObject)

  // Freeze the context object so it cannot be modified if we're caching it.
  if (shouldCache) {
    contextObject = deepFreeze(contextObject)
  }

  if (shouldCache) {
    cache.set(path, contextObject)
  }

  return contextObject as T
}

export function loadManifestFromRelativePath<T extends object>({
  projectDir,
  distDir,
  manifest,
  shouldCache,
  cache,
  skipParse,
  handleMissing,
  useEval,
}: {
  projectDir: string
  distDir: string
  manifest: string
  shouldCache: boolean
  cache?: Map<string, unknown>
  skipParse?: boolean
  handleMissing?: boolean
  useEval?: boolean
}): DeepReadonly<T> {
  const manifestPath = join(
    /* turbopackIgnore: true */ projectDir,
    distDir,
    manifest
  )

  if (useEval) {
    return evalManifest<T>(manifestPath, shouldCache, cache, handleMissing)
  }
  return loadManifest<T>(
    manifestPath,
    shouldCache,
    cache,
    skipParse,
    handleMissing
  )
}

export function clearManifestCache(path: string, cache = sharedCache): boolean {
  return cache.delete(path)
}
Quest for Codev2.0.0
/
SIGN IN