크롬 확장 프로젝트 구조 정리하기: popup, options 폴더 분리

크롬 확장 루트 디렉토리에 뒤섞인 파일들을 기능별 폴더로 정리했습니다. manifest.json 경로 업데이트, git mv로 히스토리 보존, 트러블슈팅 팁까지 리팩토링 전 과정을 공유합니다.

크롬 확장 프로젝트 구조 정리하기: popup, options 폴더 분리

1. 문제 상황

증상: 뒤섞인 파일들

크롬 확장 개발 초기에는 모든 파일을 루트 디렉토리에 두었습니다.

extension/
  background.js
  manifest.json
  popup.html        # 팝업 관련
  popup.css         # 팝업 관련
  popup.js          # 팝업 관련
  options.html      # 옵션 관련
  options.css       # 옵션 관련
  options.js        # 옵션 관련
  images/

문제점:

  1. 파일 수가 늘어날수록 어떤 파일이 어떤 기능인지 파악 어려움
  2. 관련 파일을 찾으려면 목록에서 일일이 검색
  3. 새로운 기능(onboarding, manager 등) 추가 시 더 혼란
  4. 협업 시 파일 위치 설명에 시간 소요

목표: 기능별 폴더 분리

extension/
  background.js
  manifest.json
  popup/            # 팝업 관련 파일 모음
    popup.html
    popup.css
    popup.js
  options/          # 옵션 관련 파일 모음
    options.html
    options.css
    options.js
  images/

2. Manifest V3 경로 규칙

상대 경로 기준

manifest.json의 모든 경로는 manifest.json 위치 기준 상대 경로입니다.

extension/
  manifest.json     <- 기준점
  popup/
    popup.html      <- "popup/popup.html"
  options/
    options.html    <- "options/options.html"

지원되는 경로 형식

{
  "action": {
    "default_popup": "popup/popup.html"
  },
  "background": {
    "service_worker": "background.js"
  },
  "options_page": "options/options.html"
}

주의: 절대 경로(/popup/popup.html)는 지원되지 않습니다.


3. 리팩토링 단계

Step 1: 폴더 생성

cd extension
mkdir popup options

Step 2: 파일 이동

# popup 관련 파일 이동
mv popup.html popup/
mv popup.css popup/
mv popup.js popup/

# options 관련 파일 이동
mv options.html options/
mv options.css options/
mv options.js options/

또는 Git 명령어로 히스토리 보존:

# Git에서 이동 추적
git mv popup.html popup/
git mv popup.css popup/
git mv popup.js popup/

git mv options.html options/
git mv options.css options/
git mv options.js options/

Step 3: manifest.json 업데이트

// Before
{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "action": {
    "default_popup": "popup.html"
  },
  "options_page": "options.html",
  "background": {
    "service_worker": "background.js"
  }
}

// After
{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "action": {
    "default_popup": "popup/popup.html"
  },
  "options_page": "options/options.html",
  "background": {
    "service_worker": "background.js"
  }
}

Step 4: HTML 내부 참조 확인

파일 위치가 바뀌었으므로 HTML 내의 상대 경로 참조도 확인합니다.

<!-- popup/popup.html -->

<!-- 같은 폴더 내 파일 참조 - 변경 없음 -->
<link rel="stylesheet" href="popup.css">
<script src="popup.js"></script>

<!-- 상위 폴더의 파일 참조 - 변경 필요 -->
<!-- Before (루트에 있을 때) -->
<link rel="stylesheet" href="shared/common.css">

<!-- After (popup/ 안에 있을 때) -->
<link rel="stylesheet" href="../shared/common.css">

Step 5: 확장 리로드 및 테스트

  1. chrome://extensions 열기
  2. 개발자 모드 활성화
  3. "리로드" 버튼 클릭
  4. 팝업 열어서 정상 동작 확인
  5. 옵션 페이지 열어서 확인

4. 권장 폴더 구조

기본 구조

extension/
  manifest.json           # 확장 설정
  background.js           # Service Worker

  popup/                  # 브라우저 액션 팝업
    popup.html
    popup.css
    popup.js

  options/                # 설정 페이지
    options.html
    options.css
    options.js

  images/                 # 아이콘, 이미지
    icon-16.png
    icon-48.png
    icon-128.png

  shared/                 # 공유 리소스
    common.css
    utils.js

확장된 구조 (대규모 프로젝트)

extension/
  manifest.json
  background.js

  popup/
    popup.html
    popup.css
    popup.js
    components/           # 팝업 전용 컴포넌트
      list-item.js

  options/
    options.html
    options.css
    options.js

  onboarding/             # 온보딩 페이지
    onboarding.html
    onboarding.css
    onboarding.js

  manager/                # 관리 페이지 (확장 페이지)
    manager.html
    manager.css
    manager.js

  content/                # Content Script
    content.js
    content.css

  images/
    icons/
      icon-16.png
      icon-48.png
      icon-128.png
    screenshots/

  shared/
    common.css
    utils.js
    constants.js

  _locales/               # 다국어 지원
    en/
      messages.json
    ko/
      messages.json

5. 주요 고려사항

Service Worker (background.js) 위치

{
  "background": {
    "service_worker": "background.js"
  }
}

Service Worker는 import 경로 해석 문제로 루트에 두는 것이 일반적입니다.

하위 폴더에 둘 경우:

{
  "background": {
    "service_worker": "background/service-worker.js"
  }
}

그러나 ES Modules 사용 시 경로 복잡성이 증가하므로 루트 권장.

Content Script 경로

{
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content/content.js"],
      "css": ["content/content.css"]
    }
  ]
}

모든 경로는 manifest.json 기준.

Web Accessible Resources

{
  "web_accessible_resources": [
    {
      "resources": [
        "images/icon-48.png",
        "shared/injected.js"
      ],
      "matches": ["<all_urls>"]
    }
  ]
}

Content Script에서 확장 리소스에 접근할 때 사용.


6. 실제 변경 예시

Before: 변경 전 manifest.json

{
  "manifest_version": 3,
  "name": "Secret Guard",
  "version": "0.1.0",
  "description": "Incognito session restore and secret bookmarks.",
  "incognito": "split",
  "permissions": ["tabs", "bookmarks", "sessions", "storage", "contextMenus"],
  "action": {
    "default_title": "Secret Guard",
    "default_popup": "popup.html"
  },
  "background": {
    "service_worker": "background.js"
  },
  "options_page": "options.html"
}

After: 변경 후 manifest.json

{
  "manifest_version": 3,
  "name": "Secret Guard",
  "version": "0.1.0",
  "description": "Incognito session restore and secret bookmarks.",
  "incognito": "split",
  "permissions": ["tabs", "bookmarks", "sessions", "storage", "contextMenus"],
  "action": {
    "default_title": "Secret Guard",
    "default_popup": "popup/popup.html"
  },
  "background": {
    "service_worker": "background.js"
  },
  "options_page": "options/options.html"
}

변경 사항:

  • popup.html -> popup/popup.html
  • options.html -> options/options.html
  • background.js는 루트에 유지

7. Git으로 히스토리 보존하기

git mv 사용

# 파일 이동 + Git 추적
git mv popup.html popup/popup.html
git mv popup.css popup/popup.css
git mv popup.js popup/popup.js

# 커밋
git commit -m "refactor(extension): reorganize popup files into subdirectory"

git mv의 장점

  • 파일 히스토리가 보존됨 (git log --follow popup/popup.html)
  • 삭제 + 추가가 아닌 이동으로 인식됨
  • 코드 리뷰 시 변경 내용 명확

한 번에 여러 파일 이동

# 폴더 먼저 생성
mkdir -p popup options

# 파일 이동
git mv popup.html popup/
git mv popup.css popup/
git mv popup.js popup/
git mv options.html options/
git mv options.css options/
git mv options.js options/

# 커밋
git commit -m "refactor(extension): reorganize popup and options into subdirectories

- popup.html/css/js를 popup/ 하위 폴더로 이동
- options.html/css/js를 options/ 하위 폴더로 이동
- manifest.json 경로 참조 업데이트
- 유지보수성 향상을 위한 파일 구조 개선"

8. 트러블슈팅

문제 1: 팝업이 열리지 않음

증상: 확장 아이콘 클릭 시 빈 화면 또는 에러

원인: manifest.json 경로 오타

해결:

// 확인 사항
{
  "action": {
    "default_popup": "popup/popup.html"  // 슬래시 방향, 오타 확인
  }
}

개발자 도구 > 오류 확인:

Error: Could not load manifest. 'action.default_popup' not found at path: popap/popup.html

문제 2: CSS/JS 로드 실패

증상: HTML은 열리지만 스타일이 적용 안 됨

원인: HTML 내 상대 경로 잘못됨

해결:

<!-- popup/popup.html -->

<!-- 같은 폴더 내 참조: 경로 그대로 -->
<link rel="stylesheet" href="popup.css">

<!-- 상위 폴더 참조: ../ 추가 -->
<link rel="stylesheet" href="../shared/common.css">

문제 3: Chrome Extension Reload 안 됨

증상: 파일 변경 후에도 이전 상태 유지

해결:

  1. chrome://extensions에서 "리로드" 버튼 클릭
  2. 또는 확장 ID 클릭 > "업데이트" 버튼
  3. 그래도 안 되면: 확장 제거 > 다시 로드

문제 4: 옵션 페이지 접근 안 됨

증상: 확장 > 옵션 클릭 시 404 또는 빈 화면

원인: options_page 경로 오류

해결:

{
  "options_page": "options/options.html"
}

또는 chrome_url_overrides 사용 시:

{
  "options_ui": {
    "page": "options/options.html",
    "open_in_tab": true
  }
}

9. 핵심 개념 정리

경로 참조 정리

위치 참조 대상 경로
manifest.json popup.html popup/popup.html
manifest.json options.html options/options.html
popup/popup.html popup.css popup.css
popup/popup.html shared/common.css ../shared/common.css
background.js - 루트에 유지 권장

폴더 분리 체크리스트

  • [ ] 폴더 생성 (mkdir popup options)
  • [ ] 파일 이동 (git mv 권장)
  • [ ] manifest.json 경로 업데이트
  • [ ] HTML 내부 참조 확인
  • [ ] 확장 리로드 및 테스트
  • [ ] 팝업 동작 확인
  • [ ] 옵션 페이지 동작 확인

10. 베스트 프랙티스

DO (해야 할 것)

  • [x] 기능별 폴더 분리 (popup/, options/, content/)
  • [x] 공통 리소스는 shared/ 폴더에
  • [x] git mv로 이동하여 히스토리 보존
  • [x] 이동 후 즉시 테스트
  • [x] 명확한 커밋 메시지

DON'T (하지 말아야 할 것)

  • [ ] 모든 파일을 루트에 방치
  • [ ] 깊은 중첩 폴더 (2단계 이상 지양)
  • [ ] 파일 삭제 후 재생성 (히스토리 손실)
  • [ ] manifest.json 수정 없이 파일만 이동

명명 규칙 권장

popup/
  popup.html          # 진입점: 폴더명 반복 OK
  popup.css
  popup.js

# 또는

popup/
  index.html          # 진입점: index 사용
  styles.css
  script.js

폴더명을 반복하는 방식이 명확하고 검색하기 쉬움.


11. FAQ

Q: Service Worker도 폴더로 옮길 수 있나요?

A: 가능하지만 권장하지 않습니다. ES Modules 사용 시 import 경로 해석이 복잡해질 수 있어 루트에 두는 것이 일반적입니다.

// 가능하지만 권장하지 않음
{
  "background": {
    "service_worker": "background/service-worker.js"
  }
}

Q: 아이콘 경로는 어떻게 되나요?

A: manifest.json 기준 상대 경로:

{
  "action": {
    "default_icon": {
      "16": "images/icon-16.png",
      "48": "images/icon-48.png",
      "128": "images/icon-128.png"
    }
  }
}

Q: TypeScript/번들러 사용 시에도 동일한가요?

A: 번들러 출력 디렉토리 기준으로 동일하게 적용됩니다. 보통 dist/ 폴더가 extension 역할을 하며, manifest.json도 그 안에 위치합니다.

src/                    # 소스 코드
  popup/
    popup.ts
dist/                   # 번들 출력 (= extension)
  manifest.json
  popup/
    popup.html
    popup.js

Q: Content Script도 폴더로 분리하나요?

A: 네, 권장합니다:

content/
  content.js
  content.css
  injected/
    injected.js
{
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["content/content.js"],
    "css": ["content/content.css"]
  }]
}

Q: 다국어 지원 시 구조는?

A: Chrome 확장의 i18n은 _locales 폴더를 사용합니다:

_locales/
  en/
    messages.json
  ko/
    messages.json

manifest.json에서:

{
  "default_locale": "en"
}

12. 참고 자료


📚 크롬 확장 개발 시리즈 (9부작)

  1. JavaScript URL 비교와 정규화
  2. Web Crypto API로 안전한 해싱 구현하기
  3. CSS 변수와 다크 모드 구현하기
  4. 크롬 확장 프로젝트 구조 정리하기 (현재 글)
  5. 크롬 확장 공유 모듈 설계
  6. Chrome Storage로 실시간 상태 동기화
  7. 크롬 확장 다국어(i18n) 구현하기
  8. Chrome Alarms API로 자동 잠금 타이머
  9. 크롬 확장 보안 강화: CSP와 최소 권한