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

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

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. 참고 자료


13. 다음 단계

이 글에서는 크롬 확장의 파일 구조 정리를 다뤘습니다. 이 시리즈의 다른 글들도 확인해보세요.

시리즈 목차:

  1. JavaScript URL 비교와 정규화
  2. Web Crypto API로 안전한 해싱 구현하기
  3. CSS 변수와 다크 모드 구현하기
  4. 크롬 확장 프로젝트 구조 정리하기 ← 현재 글