성장 과정/개인 프로젝트

개인 프로젝트지만 협업처럼 행동하자 (git flow, inject issue?)

tact 2025. 4. 18. 04:18

들어가며

항상 들려오는 말이 있다.

"아무리 능력이 뛰어난 사람이라도 같이 일하기 어려운 상대는 기피하게 된다."

 

개발에서 말하는 협업은 단순히 사람 대 사람으로서 말이 잘 통하느냐를 의미하지 않는다.

개인 프로젝트지만, 마치 협업인 것처럼 행동하자.

  • 깃 브랜치란 무엇이며, 왜 있는 걸까?
  • 커밋은 어떻게 하는 게 좋을까?

브랜치 관례와 커밋 관례를 찾아보며, 이번 프로젝트에서 최대한 적용해보려 한다.


리포지토리

아래 깃허브 링크를 남겨두겠다.

기획부터 개발, 배포, 마케팅까지 고독하지만 재미있는 기록이 될 것이다.

 

GitHub - funczun/where-was-i

Contribute to funczun/where-was-i development by creating an account on GitHub.

github.com


협업을 위한 전략

기능은 브랜치로, 작업은 커밋으로

내가 이해한 바로는 '기능별 브랜치와 작업별 커밋' 정도로 요약 가능하다.

main
 ├── chore   # 초기 세팅
 └── develop # 개발 브랜치
      ├── feature/scroll-sync
      │     ├── 스크롤 저장 커밋
      │     ├── 스크롤 복원 커밋
      │     └── 토스트 표시 커밋
      └── feature/...

브랜치 구조 흐름을 나타내면 대강 위와 같은 모습이다.

  • main: 최종 배포 브랜치다. 따라서, main에 merge 하는 것은 상당히 조심스러워야 한다.
  • chore: 예시 속 chore은 아이콘 이미지 준비 같은 초기 세팅 브랜치다. 편의상 main에서 바로 분기하였다.
  • develop: 개발 통합 브랜치다. 각 기능들은 이 develop을 분기한 브랜치에서 진행된다.
  • feature/*: feature는 하나의 예시일 뿐이며, 이는 실제 기능별 작업 브랜치다.

모든 기능 개발이 끝나면 develop에 merge 하고, 통합 테스트를 진행한다.

통합 테스트가 정상적으로 종료되면, main에 merge 한다.

 

일반적인 흐름은 위와 같지만, 실제 기업에서는 다양한 브랜치 전략이 존재한다.

예를 들어, main과 develop 사이 특수한 브랜치가 추가될 수 있다.

  • release/*: 베타 테스트나 QA를 위한 릴리즈 브랜치다.
  • test: 사내 테스트 서버에 올릴 전용 테스트 브랜치다.
feature → develop → release → main
feature → develop → test → main

feature는 설명하기 위해 대표적인 기능 작업 브랜치로써 썼을 뿐, 이외에도 다양한 브랜치가 존재한다.

아무튼 이런 흐름도 있다는 걸 기억하며, 브랜치 전략이란 것에 대해 넓게 생각하자.

*사실, 정확히는 hotfix도 있고, 이러한 git flow를 위한 extension도 존재한다.

 

커밋도 마찬가지다.

다양하지만, 이상적으로는 작업별 커밋을 추천한다.

상단 이미지는 내가 where-was-i를 개발하며 남긴 초기 커밋들이다.

feature/scroll-sync 안에서 스크롤 포지션 저장 기능, 스크롤 포지션 복원 기능 등 작업별로 나누어 커밋했다.

한 가지 불편한 사실이 있다면, 사실 fix: update manifest file path for iconschore에서 진행했으면 더 좋았을 듯하다.

 

그렇다고 갈아엎진 않기로 했다.

너무 빡빡하게 관례라는 틀에 맞춰 작업하기보단, 어느 정도 유연하게 가자고 생각했다.


프로젝트 구조

보기 좋게 트리 구조로 나타내면 현재 진행 상황은 이렇다.

where-was-i/
├── icons/
│   ├── icon16.png
│   ├── icon48.png
│   └── icon128.png
├── src/
│   ├── content/
│   │   └── content.js
│   └── services/
│       ├── key-util.js
│       ├── restore-service.js
│       └── storage-service.js
├── background.js
├── manifest.json
└── popup.html
  • background.jspopup.html은 아직 비어있는 상태다. 어차피 필요할 것 같아서 형식상 만들어두기만 했다.
  • asset 패키지를 만들까 했지만, 현재 icon 파일만 사용하고 있는지라 icons로 빼는 게 나을 것 같았다.
  • manifest.json은 진입점으로 보아 루트 디렉토리에 위치시켰고, src 안에는 모듈화 된 코드들을 넣어 관리하기로 했다.

치명적 이슈 발생

익명의 함수

사실 content.js에 스파게티처럼 코드를 짜 테스트해 본 뒤, 다시 깔끔하게 작업하는 상황이다.

당시 크롬 확장 프로그램에 대한 지식이 전무했기 때문에, "이게 되나?" 하는 마음에 테스트했던 것이다.

 

아무튼, 여기서 말하는 깔끔하게 작업한다는 것은, 내가 자프링으로 병원 예약 시스템을 개발할 때 신경 쓴 소프트웨어 아키텍처적 결정을 뜻한다.

 

디렉토리를 나누고 싶었다. 또한, 내부는 OOP로 설계하고 싶었다.

그러다 치명적인 이슈가 발생하였고, 나는 이 문제에 대해 20시간 이상 고민했다.

 

앞서 테스트했던 기능들이었기에 분리했다고 하여 기능적인 결함이 있을 것이라 생각하지 못했다.

그러나 일정 작업을 마친 뒤 직접 테스트해 보니 오류가 발생했다.

그나마 다행인 사실은, 오류를 직접 볼 수 있었기 때문에 직접 로그를 찍어볼 필요는 없었다.

"type": "module"

서칭하니 content.js가 모듈이 아닌 일반 js 파일로 인식해서 발생하는 문제라고 한다.

manifest.json에서 content_scripts 타입을 모듈로 설정하면 해결된다는 정보가 많았다.

그래서 "type": "module"을 넣어줬다.

 

유감스럽게도 해결되지 않았다.

이후, 정말 다양하게 고민하고 시도했다.

경로에 오타가 있었나?
정적 import 말고 동적 import로 해볼까?
상대경로가 문제인가? 절대경로로 해볼까?
혹시 크롬 문제? 크롬 업데이트를 해볼까?
캐시 문제인가? 캐시 초기화해 볼까?

영향도 없는 background.js도 지워보고 정말 별짓 다한 것 같다.

 

인젝션 미스?

다시 돌아와서 고민했다.

밖에 나가서 바람 좀 쐬고, 쉽게 생각해 보기로 했다.

아까 보였던 '익명의 함수'라는 게, 결국 해당 경로로 갔을 때 해당 함수가 존재하지 않았다는 의미 아니었을까?

 

경로를 입력해 직접 진입해 보았다.

근데 이게 무슨 일인가?

보다시피 해당 함수를 담은 모듈이 존재한다.

 

...

 

그러던 중, 정말 긴 시간 끝에 정답에 가까워졌다.

해당 모듈 파일이 브라우저에 inject 되지 않아서 생긴 문제라는 관점이다.

크롬 확장 프로그램의 content script는 웹페이지에 삽입되는 JS 파일이다. 상대경로로 다른 파일을 import 하려고 할 때, 일반적인 경우처럼 경로 참조가 되지 않는다.

 

크롬 확장 프로그램에서의 import 구문이 실행되면, 브라우저는 그 경로를 확장 프로그램의 리소스 경로에서 찾으려고 한다.

 

여기서 문제는 src/*, 이하 storage-service.js 같은 파일들을 manifest.json에 명시하지 않으면, 해당 파일은 배포 대상이 아니란 소리다. 그러니, 직접 web_accessible_resources를 사용해 모듈 파일을 노출시켜 보자.

직접 inject 하니 import 구문에서는 더 이상 오류가 나지 않는다. (대신 함수가 아니라는 새로운 에러 발생)

나름대로 트릭을 써서 브라우저가 해당 파일을 로드할 수는 있게 되었지만, 아무래도 이상하다.

 

번들러 도구

만약 위 방법이 성공적이었다고 하더라도, 유지보수 측면에서 최악이다.

왜냐하면 manifest.json에서 계속해서 갱신해야 하기 때문이다.

 

결국 번들러까지 왔다.

번들러를 써서 content.js에 필요한 의존성을 다 묶어버리는 게 일반적이라고 한다.

 

번들러가 무엇이냐면, 제일 처음 내가 테스트를 위해 content.js에 모든 코드를 다 때려 박았던, 뭐 그런 거랑 비슷하다.

"content_scripts": [
    {
        "matches": ["<all_urls>"],
        "js": ["dist/content.bundle.js"],
        "type": "module"
    }
]

manifest.json에는 위와 같이 작성할 수 있다.

 

부품을 조립해 한 파일로 만든다고 생각하자. 모든 코드가 하나로 합쳐져서 content_scripts로 들어간 것이다.

 

마치 content.js에 다 때려 박는 방식이랑 다를 바가 없어 보인다.

번들러의 장점은 무엇일까?

  1. ES 모듈을 자유롭게 쓸 수 있다. (import/export)
  2. 코드를 조직화하여 개발자 경험이 좋아진다. (분리/재사용)
  3. 코드 압축, 트리 쉐이킹뿐만 아니라 타입스크립트나 Sass 도입도 가능하다.
쉽게 말해서, 개발은 모듈스럽게 하되, 배포는 간단하게 돕는다. 자바 백엔드 개발에서 Maven/Gradle로 빌드하고 build/libs/에 .jar 생성해 서버로 배포하는 걸 떠올리자. 비슷한 비유가 될지는 모르겠다.

 

번들링 도구도 다양한데, Vite가 가장 핫하다는 듯하다. (쉽고 빨라서)

Vite로 크롬 확장 프로그램을 개발하자.