fe.resolver.ts
fe.resolver.ts

Powered by Notion & Next.js

Navigate

  • 개인정보처리방침

Connect

  • GitHub

© 2026 Hanul Lee. All rights reserved.

Powered by Notion & Next.js

목록으로
Development2025년 5월 31일

Git hook 톺아보기 (feat. lefthook)

pnpm install을 하고싶지 않은자는 나에게

#Tools

개요

디펜다봇을 물리쳤더니 갑자기 프로젝트가 켜지지 않았다.

Loading image...
Notion Image

메이저버전 업데이트는 없었는데 왜터지나 보니 rspack에서 마이너 업데이트 주제에 1.3.0 버전으로 올라가면서

ReactRefreshPlugin 이라는 class를 ReactRefreshRspackPlugin 으로 바꿔놨었다.

typescript
// v1.2.1
import { type Compiler } from '@rspack/core';
import type { NormalizedPluginOptions, PluginOptions } from './options';
export type { PluginOptions };
declare class ReactRefreshRspackPlugin {
    options: NormalizedPluginOptions;
    static deprecated_runtimePaths: string[];
    static loader: string;
    constructor(options?: PluginOptions);
    apply(compiler: Compiler): void;
}
export = ReactRefreshRspackPlugin;



// v1.3.0
import type { Compiler } from '@rspack/core';
import type { NormalizedPluginOptions, PluginOptions } from './options';
export type { PluginOptions };
declare class ReactRefreshRspackPlugin {
    options: NormalizedPluginOptions;
    static deprecated_runtimePaths: string[];
    static loader: string;
    constructor(options?: PluginOptions);
    apply(compiler: Compiler): void;
}
export default ReactRefreshRspackPlugin;
export { ReactRefreshRspackPlugin };

typescript
const ReactRefreshPlugin = require('@rspack/plugin-react-refresh'); // ❌


const { ReactRefreshRspackPlugin } = require('@rspack/plugin-react-refresh'); // ✅

module export 도 만들어놨길래 export 문으로 config를 수정해 두었는데, 나중에 dev가 안켜진다는 팀원들의 아우성이 많았다.

원인은 두가지였는데,

  • 디펜다봇을 물리치고 config를 수정하지 않은 텀이 주말이 껴있었는데, 그 사이에 브랜치를 딴 사람
  • 브랜치는 최신으로 따왔지만 pnpm install 을 안한사람

흠.. 이거 어떻게 못하나?

사실 이 문제의 본질은 간단하다.

“팀원의 로컬 환경과 리모트의 의존성 상태가 어긋나는 것”

그걸 사람의 기억력에 의존하고 있었던 것이 원인이었다.

자동화할 수 있으면 자동화해야 한다. 그리고 이런 종류의 자동화에 가장 적합한 도구가 바로 Git Hook이다.

처음 떠오른 건 당연히 Husky였다. npm 다운로드 수 기준으로 사실상 Git Hook 도구의 표준이나 다름없고, 실제로 서버 레포에서도 이미 husky + lint-staged 조합을 쓰고 있었다.

그런데 막상 웹 프로젝트에 husky를 붙이려고 조금 걸리는 점이 있었다.

Husky v5의 변화로 인한 설정의 직관성이었다.

Husky v4까지는 package.json 에 설정을 넣으면 끝이었다. 그런데 v5부터 완전히 방향이 바뀌었다. .husky/ 디렉토리를 만들고, 훅마다 쉘 스크립트 파일을 하나씩 생성하고, chmod +x로 실행 권한까지 챙겨줘야 한다.

post-checkout, post-merge, post-rebase… 이 세 훅에 똑같은 스크립트를 연결하는 건 사실 아주 단순한 요구사항인데, husky로는 파일이 3개(.husky/post-checkout, .husky/post-merge, .husky/post-rebase) 필요했다. 이게 뭔가 DRY하지 않달까.

그러다 lefthook을 발견했다. Go 기반의 Git Hook 매니저인데, 가볍고 빠르며 무엇보다 설정이 간편하다.

husky 와 lefthook의 비교를 위한 포스팅은 아니므로 더이상 자세한 내용은 생략한다.

Git hook

Git - Git Hooks

faviconhttps://git-scm.com/book/ko/v2/Git%EB%A7%9E%EC%B6%A4-Git-Hooks

깃훅이란 쉽게 말해 깃에서 제공하는 어떤 이벤트가 생겼을때 자동으로 스크립트를 실행할 수 있는 도구이다.

기본적으로 .git/hooks 디렉토리에서 관리되며, husky는 메이저버전 v4 까지는 Node 환경에서 깃훅을 자체적으로 관리했지만 5버전부터 완전히 git에 종속시켰다.

북마크해둔 깃 공식문서에는 커밋 워크플로 훅, 서버 훅, 기타 훅으로 정리해두었는데, 현재 우리 상황에서 필요한 이벤트는 두가지 상황일 것이다.

  • 브랜치를 이동했을때
  • (주로 최신버전의) 브랜치를 merge/rebase pull 받을때

이와 관련된 깃 훅은 세개다.

  • post-checkout
  • post-merge / post-rebase

이 세가지 이벤트가 트리거 될떄, 단순히 pnpm install을 시키면 된다.

하지만 브랜치를 바꿀때마다 pnpm install을 하는건 불필요하다.

이번 상황처럼 디펜다봇을 물리쳤거나, 누군가 작업에 필요한 패키지를 설치했을때만 설치해주는걸로 충분하다.

그리고 그 확인 방법은 pnpm-lock.yaml 이 수행해 줄 수 있다.

Lefthook 스크립트를 작성해보자

우리가 원하는 건 패키지 매니저의 Lock 파일(pnpm-lock.yaml)이 변경되었을 때만 설치를 다시 하는 것이다. 무턱대고 pnpm install을 돌리면 브랜치 바꿀 때마다 몇십초씩 기다려야 하니까.

Lefthook은 .yaml 파일 하나로 Git Hook을 아주 직관적으로 관리할 수 있다.

lefthook.yml 설정

프로젝트 루트에 lefthook.yml 파일을 만들고, 아까 말한 3가지 Git Hook(post-checkout, post-merge, post-rebase)에 스크립트를 연결한다.

yaml
# lefthook.yml

post-checkout:
  scripts:
    "install-deps":
      runner: bash .lefthook/scripts/install-if-needed.sh

post-merge:
  scripts:
    "install-deps":
      runner: bash .lefthook/scripts/install-if-needed.sh

post-rebase:
  scripts:
    "install-deps":
      runner: bash .lefthook/scripts/install-if-needed.sh

아주 심플하다.

어떤 깃 동작이 발생하든 install-if-needed.sh 라는 쉘 스크립트를 실행하도록 통일했다.

install-if-needed.sh 스크립트

이제 핵심 로직을 쉘 스크립트로 짠다.

Git 명령어로 직전 상태와 현재 상태 사이에서 특정 파일이 바뀌었는지 알 수 있다.

bash
#!/bin/bash

# .lefthook/scripts/install-if-needed.sh

# 1. diff-tree 명령어로 변경된 파일 목록을 가져온다.
# HEAD@{1} = 변경 전 커밋(이동 전 브랜치)
# HEAD      = 현재 커밋(이동 후 브랜치)
changed_files="$(git diff-tree -r --name-only --no-commit-id HEAD@{1} HEAD)"

# 2. 변경된 파일 중에 'pnpm-lock.yaml'이 있는지 확인한다.
if echo "$changed_files" | grep --quiet "pnpm-lock.yaml"; then
  echo "📦 pnpm-lock.yaml changed. Running pnpm install..."
  
  # 3. 변경되었다면 설치 진행!
  pnpm install
else
  echo "✨ No dependency changes. Skipping install."
fi

끝맺음

이제 팀원들은 아무 생각 없이 git pull이나 git checkout을 해도 된다.

  • Lock 파일이 안 바뀌었으면? → Skipping install (0.1초 컷)
  • Lock 파일이 바뀌었으면? → Running pnpm install... (알아서 설치)

더 이상 “어? 왜 안 켜지지?" 하다가 "아 맞다 인스톨 안 했네" 하며 머리를 긁적일 필요가 없다.

Lefthook은 Husky보다 설정이 훨씬 직관적이고(yaml), 속도도 빠르다.

복잡한 모노레포 환경에서 깃 훅 관리가 고민된다면 강력하게 추천한다.

  • 개요
  • Git hook
  • Lefthook 스크립트를 작성해보자
  • lefthook.yml 설정
  • install-if-needed.sh 스크립트
  • 끝맺음