개발을 하다 보면 외부 라이브러리에서 버그를 발견하거나, 우리 프로젝트에 맞게 살짝 동작을 바꾸고 싶을 때가 있다. 이럴 때 가장 흔한 해결책은 node_modules 안의 파일을 직접 수정하는 것인데....
문제는, node_modules는 git에 올리지 않는다. 누군가 npm install을 다시 실행하면 수정한 내용이 날아가버리는 문제가... 팀 전체가 같은 수정을 반복해야 하거나, CI/CD 파이프라인이 깨지는 상황이 발생하게 된다.
이 문제를 깔끔하게 해결해주는 도구가 바로 patch-package 이다.
patch-package는 node_modules에 가한 변경 사항을 diff 파일 형태로 저장해두고, npm install 이후 자동으로 해당 패치를 다시 적용해주는 도구다.
핵심 동작 원리는 아래와 같다.
node_modules/some-package/index.js를 직접 수정npx patch-package some-package 명령을 실행patches/some-package+1.0.0.patch 파일이 생성됨.patch 파일을 git에 커밋npm install을 하더라도 postinstall 훅을 통해 패치가 자동 적용.patch 파일은 일반적인 unix diff 형식이라 코드 리뷰도 가능하고, 변경 이력도 git에 남기때문에 대상 라이브러리에서 어떤부분을 바꿨는지 확인 및 관리가 용이하다.
오픈소스 라이브러리에 버그를 발견하고 PR을 올렸는데, 메인테이너가 몇 달째 응답이 없는 경우가 있다. 아니면 이미 지원이 종료되어 더이상의 업데이트가 없는 라이브러리 이거나... 이때 patch-package를 쓰면 릴리즈를 기다리지 않고 지금 당장 문제를 해결할 수 있다.
.patch 파일을 git에 올려두면, 팀원 누구든 npm install 한 번으로 동일한 환경을 갖출 수 있다. "내 로컬에서는 됐는데?"라는 곤란한 상황을 피해보도록 하자...
직접 node_modules를 수정하면 왜 고쳤는지 아무도 모른다. patch-package를 쓰면 .patch 파일에 변경 내용이 diff 형식으로 남고, git commit 메시지로 이유까지 기록할 수 있다.
패치가 적용된 라이브러리를 버전 업그레이드하면 패치가 실패하게 된다. 이는 "이 라이브러리 업그레이드할 때 패치를 다시 확인하세요" 라는 명확한 신호 역할을 하게된다.
npm install patch-package --save-dev
package.json에 postinstall 스크립트를 추가.
(이 스크립트는 npm install 이후 자동으로 실행됨)
{
"scripts": {
"postinstall": "patch-package"
}
}
패치를 적용하고 싶은 라이브러리의 파일을 직접 수정한다.
예를 들어 some-library 패키지의 index.js에서 버그를 고쳤다면:
# 파일 수정 후 터미널에서 패치 파일 생성명령어 실행
npx patch-package some-library
patches/ 디렉토리에 다음과 같은 파일이 생성된다.
patches/some-library+2.3.1.patch
이제 변경사항을 git 에 커밋 후 올리고,
팀원이 npm install을 실행하면 postinstall 단계에서 패치가 자동 적용된다.
pnpm은 v6.35.0 이후로 pnpm patch 라는 빌트인 기능을 제공하기 때문에 npm 을 사용하는 프로젝트와 설정방법이 약간 다르다. patch-package를 별도로 설치하지 않아도 되며, pnpm의 방식이 더 공식적이고 안정적이다.
참고: pnpm의 빌트인 patch 기능을 사용하는 것을 권장하지만, 기존
patch-package워크플로우에 익숙하다면 pnpm에서도patch-package를 그대로 사용할 수 있다.
pnpm patch some-library@2.3.1
pnpm이 임시 디렉토리를 만들고 해당 경로를 터미널에 출력해준다.
You can now edit the following folder: /tmp/abcd1234/node_modules/some-library
Once you're done with your changes, run "pnpm patch-commit /tmp/abcd1234"
출력된 임시 경로로 이동해 파일을 수정한다.
# 예시
vim /tmp/abcd1234/node_modules/some-library/index.js
pnpm patch-commit /tmp/abcd1234
patches/some-library@2.3.1.patch 파일이 생성되고, package.json에 자동으로 다음 내용이 추가된다.
{
"pnpm": {
"patchedDependencies": {
"some-library@2.3.1": "patches/some-library@2.3.1.patch"
}
}
}
변경사항을 git에 올린 이후 다른 팀원이 pnpm install을 실행하면 패치가 자동 적용된다.
기존 patch-package 방식을 선호하거나, npm/pnpm 혼용 환경이라면 pnpm에서도 patch-package를 사용할 수 있다.
pnpm add -D patch-package
pnpm은 기본적으로 node_modules를 symlink 구조로 관리하기 때문에 직접 수정이 까다롭다. .npmrc에 다음 설정을 추가해 평탄한(flat) 구조로 만들어준다.
# .npmrc
shamefully-hoist=true
또는 패치 대상 라이브러리만 hoist 설정을 추가할 수도 있다.
public-hoist-pattern[]=some-library
{
"scripts": {
"postinstall": "patch-package"
}
}
# node_modules/some-library/index.js 수정 후
npx patch-package some-library
변경된 사항을 git 에 올려준다.
실제로 생성된 .patch 파일의 내용은 아래와 같다.
diff --git a/node_modules/some-library/index.js b/node_modules/some-library/index.js
index a1b2c3d..e4f5g6h 100644
--- a/node_modules/some-library/index.js
+++ b/node_modules/some-library/index.js
@@ -42,7 +42,7 @@ function doSomething(input) {
if (!input) {
- return null;
+ return undefined; // null 대신 undefined 반환하도록 수정
}
일반적인 git diff 형식이라 코드 리뷰 시 변경 내용이 명확하게 확인이 가능하다.
patch-package는 패치 적용 시 성공/실패 여부를 터미널에 출력한다. CI 환경에서 패치 실패 시 빌드가 중단되도록 설정하면 안전하다.
라이브러리를 업그레이드하면 기존 패치가 맞지 않아 실패할 수 있다. 이 경우 다음 단계로 처리하도록 하자.
npm update some-library).patch 파일을 확인하고 여전히 필요한지 검토.patch 파일 삭제변경 범위가 크고 장기적인 유지보수가 필요하다면, 해당 라이브러리를 fork해서 package.json에서 fork 주소를 직접 참조하는 방법도 있다. 반면 작은 버그 수정이나 단기 임시 패치라면 patch-package가 훨씬 가볍고 관리가 편합니다.
간혹 로컬머신에서는 정상적으로 실행이 되었는데 배포시 빌드가 실패하는 경우가 있는데 내가 겪었던 케이스는 pnpm 의 버전이 로컬머신과 배포시 실행되는 버전이 달라서 발생된 문제였다.
이 경우는 pnpm 의 버전을 동일하게 맞춰주면 되는데 package.json 에 packageManager 를 명시해주면 해결된다.
{
...
"packageManager": "pnpm@9.12.3",
...
}
참고 링크