.pnpmfile.mjs

pnpm을 사용하면 특수 기능 (후크)을 통해 설치 프로세스에 직접 연결할 수 있습니다. 훅은 .pnpmfile.mjs(ESM) 또는 .pnpmfile.cjs(CommonJS)라는 파일에 선언할 수 있습니다.

기본적으로 .pnpmfile.mjs 은 lockfile과 같은 디렉터리에 있어야 합니다. 예를 들어, 공유 lockfile이 있는 워크스페이스에서 .pnpmfile.mjs는 모노레포의 루트에 있어야 합니다.

후크#

요약#

후크 함수처리사용
hooks.readPackage(pkg, context): pkgpnpm이 의존성 패키지 매니페스트를 파싱한 후에 호출됩니다.종속성의 package.json을 변경할 수 있습니다.
hooks.afterAllResolved(lockfile, context): lockfile의존성이 해결된 후에 호출됩니다.lockfile을 변경할 수 있습니다.
hooks.beforePacking(pkg): pkg패키지를 pack/publish하는 중 tarball을 생성하기 전에 호출됩니다게시되는 package.json을 사용자 정의할 수 있습니다
resolvers패키지 해석 중에 호출됩니다.사용자 정의 패키지 리졸버를 등록할 수 있습니다.
fetchers패키지를 가져오는 중에 호출됩니다.사용자 정의 패키지 페처를 등록할 수 있습니다.

hooks.readPackage(pkg, context): pkg | Promise<pkg>#

파싱 후 또는 해결 전에 의존성의 package.json을 변경할 수 있습니다. 이러한 변형은 파일시스템에 저장되지 않지만, lockfile에서 해결되는 내용과 이에 따라 설치되는 내용에 영향을 줍니다.

수정하려는 의존성을 이미 해결했다면, pnpm-lock.yaml 을 삭제해야 합니다.

팁

package.json의 변경 사항을 파일 시스템에 저장해야 하는 경우, pnpm patch 명령을 사용하여 package.json 파일을 패치해야 합니다. 예를 들어 종속성의 bin 필드를 제거하려는 경우에 유용할 수 있습니다.

인수#

  • pkg - 패키지의 매니페스트. 레지스트리 응답 또는 package.json 컨텐츠입니다.
  • context - 단계에 대한 컨텍스트 객체입니다. #log(msg) 메서드는 단계에 대한 디버그 로그를 사용할 수 있도록 합니다.

사용#

.pnpmfile.mjs의 예제 (의존성의 의존성 변경):

function readPackage(pkg, context) {
  // Override the manifest of foo@1.x after downloading it from the registry
  if (pkg.name === 'foo' && pkg.version.startsWith('1.')) {
    // Replace bar@x.x.x with bar@2.0.0
    pkg.dependencies = {
      ...pkg.dependencies,
      bar: '^2.0.0'
    }
    context.log('bar@1 => bar@2 in dependencies of foo')
  }

  // This will change any packages using baz@x.x.x to use baz@1.2.3
  if (pkg.dependencies.baz) {
    pkg.dependencies.baz = '1.2.3';
  }

  return pkg
}

export const hooks = {
  readPackage
}

알려진 제약 사항#

readPackage을 통해 의존성의 매니페스트에서 scripts 필드를 제거해도 pnpm이 의존성을 구축하는 것을 방지하지 못합니다. 의존성을 빌드할 때, pnpm은 패키지의 아카이브에서 패키지의 package.json를 읽으며, 이는 후크의 영향을 받지 않습니다. 패키지의 빌드를 무시하려면 allowBuilds 필드를 사용하세요.

hooks.updateConfig(config): config | Promise<config>#

버전 10.8.0에서 추가됨

이 훅으로 pnpm에서 사용하는 설정을 수정할 수 있습니다. 이 훅은 configDependencies와 함께 사용할 때 가장 유용하며, 여러 Git 저장소에서 설정을 공유하고 재사용할 수 있게 해 줍니다.

예를 들어, @pnpm/plugin-better-defaults는 updateConfig 훅을 사용하여 엄선된 권장 설정을 적용합니다.

사용 예#

.pnpmfile.mjs
export const hooks = {
  updateConfig (config) {
    return Object.assign(config, {
      enablePrePostScripts: false,
      optimisticRepeatInstall: true,
      resolutionMode: 'lowest-direct',
      verifyDepsBeforeRun: 'install',
    })
  }
}

hooks.afterAllResolved(lockfile, context): lockfile | Promise<lockfile>#

직렬화되기 전에 lockfile 결과를 변경할 수 있습니다.

인수#

  • lockfile - pnpm-lock.yaml로 직렬화되는 lockfile 해결 객체입니다.
  • context - 단계에 대한 컨텍스트 객체입니다. #log(msg) 메서드는 단계에 대한 디버그 로그를 사용할 수 있도록 합니다.

사용 예#

.pnpmfile.mjs
function afterAllResolved(lockfile, context) {
  // ...
  return lockfile
}

export const hooks = {
  afterAllResolved
}

알려진 제약사항#

lockfile로 할 수 있는 모든 것은 이 함수를 통해 수 있으며, lockfile의 기능을 확장할 수도 있습니다.

hooks.beforePacking(pkg): pkg | Promise<pkg>#

추가된 버전: v10.28.0

이 훅으로 package.json 매니페스트가 pnpm pack 또는 pnpm publish 중에 tarball로 패킹되기 전에 수정할 수 있습니다. 이는 로컬 개발용 package.json에 영향을 주지 않고 게시되는 패키지를 사용자 정의할 때 유용합니다.

설치 중 종속성이 해석되는 방식을 수정하는 hooks.readPackage와 달리, beforePacking은 게시되는 tarball의 내용에만 영향을 줍니다.

인수#

  • pkg - 게시되는 tarball에 포함될 패키지 매니페스트 객체입니다.

사용 예#

.pnpmfile.mjs
function beforePacking(pkg) {
  // Remove development-only fields from published package
  delete pkg.devDependencies
  delete pkg.scripts.test

  // Add publication metadata
  pkg.publishedAt = new Date().toISOString()

  // Modify package exports for production
  if (pkg.name === 'my-package') {
    pkg.main = './dist/index.js'
  }

  return pkg
}

export const hooks = {
  beforePacking
}

참고

이 훅으로 수정한 내용은 tarball 내부의 package.json에만 영향을 줍니다. 로컬 package.json 파일은 변경되지 않습니다.

hooks.preResolution(options): Promise<void>#

이 훅은 프로젝트의 lockfile을 읽고 파싱한 후, 종속성을 해석하기 전에 실행됩니다. 이 훅으로 lockfile 객체를 수정할 수 있습니다.

인수#

  • options.existsCurrentLockfile - node_modules/.pnpm/lock.yaml에 lockfile이 있으면 true인 불리언입니다.
  • options.currentLockfile - node_modules/.pnpm/lock.yaml의 lockfile 객체입니다.
  • options.existsNonEmptyWantedLockfile - pnpm-lock.yaml에 lockfile이 있으면 true인 불리언입니다.
  • options.wantedLockfile - pnpm-lock.yaml의 lockfile 객체입니다.
  • options.lockfileDir - wanted lockfile이 있는 디렉터리입니다.
  • options.storeDir - 스토어 디렉터리의 위치입니다.
  • options.registries - 스코프와 레지스트리 URL의 맵입니다.

hooks.importPackage(destinationDir, options): Promise<string | undefined>#

이 훅을 사용하면 패키지가 node_modules에 기록되는 방식을 변경할 수 있습니다. 반환 값은 선택 사항이며 종속성을 가져오는 데 사용된 방식을 나타냅니다. 예: clone, hardlink.

인수#

  • destinationDir - 패키지가 기록될 대상 디렉터리입니다.
  • options.disableRelinkLocalDirDeps
  • options.filesMap
  • options.force
  • options.resolvedFrom
  • options.keepModulesDir

hooks.fetchers#

v11.0.0에서 제거됨

hooks.fetchers는 제거되었습니다. 대신 최상위 fetchers를 사용하세요. 새 API는 사용자 정의 페처 섹션을 참고하세요.

파인더#

버전 10.16.0에서 추가됨

파인더 함수는 --find-by 플래그를 통해 pnpm list 및 pnpm why와 함께 사용됩니다.

예시:

.pnpmfile.mjs
export const finders = {
  react17: (ctx) => {
    return ctx.readManifest().peerDependencies?.react === "^17.0.0"
  }
}

사용법:

pnpm why --find-by=react17

자세한 내용은 파인더를 참고하세요.

사용자 정의 리졸버와 페처#

추가된 버전: v11.0.0

사용자 정의 리졸버와 페처를 사용하면 새로운 패키지 식별자 스킴(my-protocol:package-name 등)에 대한 사용자 정의 패키지 해석 및 가져오기 로직을 구현할 수 있습니다. 이들은 .pnpmfile.cjs에서 최상위 export로 등록됩니다:

module.exports = {
  resolvers: [customResolver1, customResolver2],
  fetchers: [customFetcher1, customFetcher2],
}

TypeScript 인터페이스#

interface CustomResolver {
  canResolve?: (wantedDependency: WantedDependency) => boolean | Promise<boolean>
  resolve?: (wantedDependency: WantedDependency, opts: ResolveOptions) => ResolveResult | Promise<ResolveResult>
  shouldRefreshResolution?: (depPath: string, pkgSnapshot: PackageSnapshot) => boolean | Promise<boolean>
}

interface CustomFetcher {
  canFetch?: (pkgId: string, resolution: Resolution) => boolean | Promise<boolean>
  fetch?: (cafs: Cafs, resolution: Resolution, opts: FetchOptions, fetchers: Fetchers) => FetchResult | Promise<FetchResult>
}

사용자 정의 리졸버#

사용자 정의 리졸버는 패키지 디스크립터(예: foo@^1.0.0)를 lockfile에 저장되는 해석 결과로 변환합니다.

리졸버 인터페이스#

사용자 정의 리졸버는 다음 메서드를 원하는 조합으로 구현할 수 있는 객체입니다:

canResolve(wantedDependency): boolean | Promise<boolean>#

이 리졸버가 주어진 wanted 종속성을 해석할 수 있는지 판단합니다.

인수:

  • wantedDependency - 다음 속성을 가진 객체:
    • alias - package.json에 표시된 패키지 이름 또는 별칭
    • bareSpecifier - 버전 범위, git URL, 파일 경로 또는 기타 지정자

반환 값: 이 리졸버가 패키지를 처리할 수 있으면 true, 그렇지 않으면 false입니다. 이 값에 따라 resolve의 호출 여부가 결정됩니다.

resolve(wantedDependency, opts): ResolveResult | Promise<ResolveResult>#

주어진 wanted 종속성을 구체적인 패키지 메타데이터와 해석 정보로 해석합니다.

인수:

  • wantedDependency - wanted 종속성(canResolve와 동일)
  • opts - 다음 속성을 가진 객체:
    • lockfileDir - lockfile이 있는 디렉터리
    • projectDir - 프로젝트 루트 디렉터리
    • preferredVersions - 패키지 이름과 선호 버전의 맵

반환 값: 다음 속성을 가진 객체:

  • id - 고유한 패키지 식별자(예: 'custom-pkg@1.0.0')
  • resolution - 해석 메타데이터입니다. 다음 중 하나일 수 있습니다:
    • 표준 해석, 예: { tarball: 'https://...', integrity: '...' }
    • 사용자 정의 해석: { type: 'custom:cdn', url: '...' }

사용자 정의 해석은 해당하는 사용자 정의 페처가 처리해야 합니다.

사용자 정의 해석 유형

사용자 정의 해석은 pnpm의 내장 해석 유형과 구별하기 위해 type 필드에 custom: 접두사를 사용해야 합니다(예: custom:cdn, custom:artifactory).

shouldRefreshResolution(depPath, pkgSnapshot): boolean | Promise<boolean>#

"Lockfile is up to date" 최적화를 건너뛰고 모든 패키지의 전체 해석을 실행하려면 true를 반환하세요. 이는 시간 기반 캐시 무효화나 기타 사용자 정의 재해석 로직을 구현할 때 유용합니다.

인수:

  • depPath - 패키지 식별자 문자열(예: lodash@4.17.21)
  • pkgSnapshot - 이 패키지의 lockfile 항목으로, 해석 정보, 종속성 등에 직접 접근할 수 있습니다.

반환 값: 재해석을 강제하려면 true, 그렇지 않으면 false입니다.

참고

shouldRefreshResolution은 frozen lockfile 설치 중에는 해석이 허용되지 않으므로 건너뜁니다.

사용자 정의 페처#

사용자 정의 페처는 사용자 정의 패키지 유형의 가져오기를 전적으로 처리하며, 사용자 정의 소스에서 패키지 내용을 다운로드하여 pnpm의 콘텐츠 주소 지정 파일 시스템에 저장합니다.

페처 인터페이스#

사용자 정의 페처는 다음 메서드를 구현할 수 있는 객체입니다:

canFetch(pkgId, resolution): boolean | Promise<boolean>#

이 페처가 주어진 해석 정보로 패키지를 가져올 수 있는지 판단합니다.

인수:

  • pkgId - 해석 단계에서 얻은 고유한 패키지 식별자
  • resolution - 리졸버의 resolve 메서드가 반환한 해석 객체

반환 값: 이 페처가 이 패키지를 가져올 수 있으면 true, 그렇지 않으면 false입니다.

fetch(cafs, resolution, opts, fetchers): FetchResult | CustomFetcherDelegation | Promise<FetchResult | CustomFetcherDelegation>#

패키지 파일을 가져오고 가져온 패키지에 대한 메타데이터를 반환합니다.

인수:

  • cafs - 파일 저장용 콘텐츠 주소 지정 파일 시스템 인터페이스
  • resolution - 해석 객체(canFetch에 전달된 것과 동일)
  • opts - 다음을 포함한 가져오기 옵션:
    • lockfileDir - lockfile이 있는 디렉터리
    • filesIndexFile - 파일 인덱스 경로
    • onStart - 가져오기가 시작될 때 호출되는 선택적 콜백
    • onProgress - 선택적 진행 상황 콜백
  • fetchers - 위임용 pnpm 표준 페처를 담은 객체:
    • remoteTarball - 원격 tarball용 페처
    • localTarball - 로컬 tarball용 페처
    • gitHostedTarball - GitHub/GitLab/Bitbucket tarball용 페처
    • directory - 로컬 디렉터리용 페처
    • git - git 저장소용 페처

반환 값: 가져오기 결과 또는 위임 envelope입니다.

가져오기 결과는 다음 속성을 가진 객체입니다:

  • filesIndex - 상대 파일 경로와 실제 위치의 맵입니다. 원격 패키지의 경우, 이는 pnpm의 콘텐츠 주소 지정 스토어(CAFS) 내 경로입니다. 로컬 패키지의 경우(local: true일 때), 이는 디스크에 있는 파일의 절대 경로입니다.
  • manifest - 선택 사항입니다. 가져온 패키지의 package.json입니다. 제공되지 않으면 pnpm이 필요할 때 디스크에서 읽습니다. 이를 제공하면 추가 파일 I/O 작업을 피할 수 있으므로, 매니페스트 데이터를 바로 사용할 수 있는 경우(예: 가져오는 중 이미 파싱한 경우) 권장됩니다.
  • requiresBuild - 패키지에 실행해야 하는 빌드 스크립트가 있는지 나타내는 불리언입니다. 패키지에 preinstall, install 또는 postinstall 스크립트가 있거나 binding.gyp 또는 .hooks/ 파일이 포함된 경우 true로 설정하세요. 표준 페처는 매니페스트와 파일 목록을 사용하여 이를 자동으로 판단합니다.
  • local - 선택 사항입니다. 디스크에서 직접 패키지를 불러오고 pnpm의 스토어로 복사하지 않으려면 true로 설정하세요. 값이 true이면 filesIndex에 디스크에 있는 파일의 절대 경로가 포함되어야 하며, pnpm은 이를 복사하는 대신 node_modules에 하드 링크합니다. 디렉터리 페처는 이 방식으로 로컬 종속성(예: file:../my-package)을 처리합니다.

내장 페처에 위임하기#

추가된 버전: v11.12.0

사용자 정의 페처는 패키지를 직접 가져오는 대신 작업을 pnpm에 다시 넘길 수 있습니다. 이를 위한 방법은 두 가지입니다.

{ delegate } envelope를 반환합니다. 가져오기 결과 대신, pnpm이 대신 가져와야 할 해석 정보를 담은 단일 delegate 키를 가진 객체를 반환하세요. pnpm은 패키지의 해석 정보를 해당 형태로 다시 작성하고 내장 가져오기 경로를 실행합니다:

.pnpmfile.cjs
const customFetcher = {
  canFetch: (pkgId, resolution) => resolution.type === 'custom:url',
  fetch: (cafs, resolution) => ({
    delegate: {
      tarball: resolution.customUrl,
      integrity: resolution.integrity,
    },
  }),
}

module.exports = { fetchers: [customFetcher] }

위임된 해석 정보는 완전하고 가져올 수 있는 형태여야 합니다(예: { tarball, integrity }). 위임은 한 단계만 가능합니다: 그 자체가 사용자 정의 유형인 delegate는 거부됩니다.

직접 fetchers.*를 호출합니다. fetchers 인수로 pnpm의 표준 페처가 제공되므로, 해석 정보를 변환한 뒤 직접 호출할 수 있습니다.

이식성을 위해 envelope 방식을 권장합니다

{ delegate } envelope는 pnpm과 pacquet(pnpm의 Rust 포트) 모두에서 작동하는 유일한 위임 방식입니다. pacquet은 IPC를 통해 pnpmfile 페처를 호출하는데, 이때 cafs와 fetchers는 존재할 수 없으므로 둘 다 null로 전달됩니다. 두 스택 모두에서 실행되어야 하는 페처는 fetchers.*를 호출하지 말고 envelope를 반환해야 합니다.

사용 예시#

기본 사용자 정의 리졸버#

이 예시는 사용자 정의 레지스트리에서 패키지를 해석하는 사용자 정의 리졸버를 보여줍니다:

.pnpmfile.cjs
const customResolver = {
  // Only handle packages with @company scope
  canResolve: (wantedDependency) => {
    return wantedDependency.alias.startsWith('@company/')
  },

  resolve: async (wantedDependency, opts) => {
    // Fetch metadata from custom registry
    const response = await fetch(
      `https://custom-registry.company.com/${wantedDependency.alias}/${wantedDependency.bareSpecifier}`
    )
    const metadata = await response.json()

    return {
      id: `${metadata.name}@${metadata.version}`,
      resolution: {
        tarball: metadata.tarballUrl,
        integrity: metadata.integrity
      }
    }
  }
}

module.exports = {
  resolvers: [customResolver]
}
사용자 정의 리졸버와 페처: shouldRefreshResolution 사용#

이 예시는 사용자 정의 해석 유형과 시간 기반 캐시 무효화를 사용하여 함께 작동하는 리졸버와 페처를 보여줍니다:

.pnpmfile.cjs
const customResolver = {
  canResolve: (wantedDependency) => {
    return wantedDependency.alias.startsWith('company-cdn:')
  },

  resolve: async (wantedDependency, opts) => {
    const actualName = wantedDependency.alias.replace('company-cdn:', '')
    const version = await fetchVersionFromCompanyCDN(actualName, wantedDependency.bareSpecifier)

    return {
      id: `company-cdn:${actualName}@${version}`,
      resolution: {
        type: 'custom:cdn',
        cdnUrl: `https://cdn.company.com/packages/${actualName}/${version}.tgz`,
        cachedAt: Date.now(), // Custom metadata for shouldRefreshResolution
      },
    }
  },

  shouldRefreshResolution: (depPath, pkgSnapshot) => {
    // Check custom metadata stored in the resolution
    const cachedAt = pkgSnapshot.resolution?.cachedAt
    if (cachedAt && Date.now() - cachedAt > 24 * 60 * 60 * 1000) {
      return true // Re-resolve if cached more than 24 hours ago
    }
    return false
  },
}

const customFetcher = {
  canFetch: (pkgId, resolution) => {
    return resolution.type === 'custom:cdn'
  },

  fetch: async (cafs, resolution, opts, fetchers) => {
    // Delegate to pnpm's standard tarball fetcher
    const tarballResolution = {
      tarball: resolution.cdnUrl,
      integrity: resolution.integrity,
    }

    return fetchers.remoteTarball(cafs, tarballResolution, opts)
  },
}

module.exports = {
  resolvers: [customResolver],
  fetchers: [customFetcher],
}
기본 사용자 정의 페처#

이 예시는 특정 패키지를 다른 소스에서 가져오는 사용자 정의 페처를 보여줍니다:

.pnpmfile.cjs
const customFetcher = {
  canFetch: (pkgId, resolution) => {
    return pkgId.startsWith('@company/')
  },

  fetch: async (cafs, resolution, opts, fetchers) => {
    // Delegate to pnpm's tarball fetcher with modified URL
    const tarballResolution = {
      tarball: resolution.tarball.replace(
        'https://registry.npmjs.org/',
        'https://custom-registry.company.com/'
      ),
      integrity: resolution.integrity
    }

    return fetchers.remoteTarball(cafs, tarballResolution, opts)
  }
}

module.exports = {
  fetchers: [customFetcher]
}
리졸버와 페처를 사용한 사용자 정의 해석 유형#

이 예시는 사용자 정의 해석 유형을 사용하여 함께 작동하는 사용자 정의 리졸버와 페처를 보여줍니다:

.pnpmfile.cjs
const customResolver = {
  canResolve: (wantedDependency) => {
    return wantedDependency.alias.startsWith('@internal/')
  },

  resolve: async (wantedDependency) => {
    return {
      id: `${wantedDependency.alias}@${wantedDependency.bareSpecifier}`,
      resolution: {
        type: 'custom:internal-directory',
        directory: `/packages/${wantedDependency.alias}/${wantedDependency.bareSpecifier}`
      }
    }
  }
}

const customFetcher = {
  canFetch: (pkgId, resolution) => {
    return resolution.type === 'custom:internal-directory'
  },

  fetch: async (cafs, resolution, opts, fetchers) => {
    // Delegate to pnpm's directory fetcher for local packages
    const directoryResolution = {
      type: 'directory',
      directory: resolution.directory
    }

    return fetchers.directory(cafs, directoryResolution, opts)
  }
}

module.exports = {
  resolvers: [customResolver],
  fetchers: [customFetcher]
}

우선순위와 순서#

여러 리졸버가 등록된 경우 순서대로 확인합니다. 해석에는 canResolve가 true를 반환하는 첫 번째 리졸버가 사용됩니다. 페처에도 같은 규칙이 적용됩니다: canFetch가 true를 반환하는 첫 번째 페처가 가져오기 단계에서 사용됩니다.

사용자 정의 리졸버는 pnpm의 내장 리졸버(npm, git, tarball 등)보다 먼저 시도되므로 패키지 해석을 완전히 제어할 수 있습니다.

성능 고려 사항#

canResolve(), canFetch(), shouldRefreshResolution()은 해석 중 모든 종속성에 대해 호출되므로 가벼운 검사여야 합니다(가급적 동기식).

관련 구성#

ignorePnpmfile#

  • 기본값: false
  • 유형: Boolean

해당 pnpmfile은 무시됩니다. 설치 중에 스크립트가 실행되지 않도록 하려는 경우, --ignore-scripts 과 함께 사용하면 유용합니다.

pnpmfile#

  • 기본값: ['.pnpmfile.mjs']
  • 유형: path[]
  • 예시: ['.pnpm/.pnpmfile.mjs']

로컬 pnpmfile(들)의 위치입니다.

globalPnpmfile#

  • 기본값: null
  • 유형: path
  • 예시: ~/.pnpm/global_pnpmfile.mjs

전역 pnpmfile의 위치입니다. 전역 pnpmfile은 설치하는 동안 모든 프로젝트에서 사용됩니다.

참고

로컬 pnpmfile를 사용하는 것이 좋습니다. pnpm을 기본 패키지 관리자로 사용하지 않는 프로젝트에서 pnpm을 사용하는 경우에만 전역 pnpmfile을 사용하십시오.