블로그 목록으로
튜토리얼

크롬 확장 프로그램 개발 실전 가이드

chrome-extensionjavascriptmanifest-v3

크롬 확장 프로그램 개발 실전 가이드

왜 크롬 확장 프로그램인가

크롬 확장 프로그램은 독립 개발자에게 매력적인 제품 형태입니다. 웹 기술(HTML, CSS, JavaScript)만으로 개발할 수 있고, Chrome Web Store를 통해 전 세계 사용자에게 배포할 수 있으며, 브라우저 위에서 동작하므로 별도의 서버 인프라가 필수가 아닙니다. 무엇보다 사용자가 매일 사용하는 브라우저에 자연스럽게 통합된다는 점이 가장 큰 장점입니다.

저는 WCAG 접근성 검사 도구를 크롬 확장 프로그램으로 만들면서 Manifest V3의 다양한 기능을 활용하게 되었습니다. 이 글에서는 그 경험을 바탕으로 실전에서 필요한 핵심 개념들을 정리합니다.

Manifest V3 기본 구조

2024년부터 Chrome Web Store는 Manifest V3만 허용합니다. 기존 Manifest V2와의 가장 큰 차이는 백그라운드 페이지가 Service Worker로 변경된 것과 원격 코드 실행이 금지된 것입니다.

확장 프로그램의 핵심 파일인 manifest.json의 기본 구조는 다음과 같습니다.

{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "description": "확장 프로그램 설명",
  "permissions": ["activeTab", "storage"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icon-48.png"
  },
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"],
      "css": ["content.css"]
    }
  ]
}

각 항목을 살펴보겠습니다. manifest_version은 반드시 3으로 설정합니다. permissions는 확장 프로그램이 사용할 Chrome API 권한을 선언합니다. action은 툴바의 확장 프로그램 아이콘과 팝업 UI를 정의합니다. background는 Service Worker 파일을 지정합니다. content_scripts는 웹 페이지에 삽입될 스크립트와 스타일을 설정합니다.

Content Scripts: 웹 페이지와 상호작용

Content Script는 사용자가 방문하는 웹 페이지의 DOM에 직접 접근할 수 있는 스크립트입니다. 페이지의 내용을 읽거나 수정할 수 있지만, 페이지의 JavaScript 컨텍스트와는 격리된 환경에서 실행됩니다.

// content.js
const headings = document.querySelectorAll('h1, h2, h3');
const headingData = Array.from(headings).map(h => ({
  level: h.tagName,
  text: h.textContent.trim(),
  hasId: !!h.id
}));

// 수집한 데이터를 백그라운드로 전송
chrome.runtime.sendMessage({
  type: 'HEADINGS_DATA',
  data: headingData
});

Content Script에서 주의할 점은 페이지의 성능에 영향을 줄 수 있다는 것입니다. DOM 조작을 최소화하고, 무거운 연산은 백그라운드 Service Worker에서 처리하세요. 또한 matches 패턴을 <all_urls> 대신 필요한 사이트로 제한하면 불필요한 스크립트 실행을 방지할 수 있습니다.

Background Service Worker

Manifest V3의 Service Worker는 이벤트 기반으로 동작합니다. 이벤트가 발생하면 깨어나고, 처리가 끝나면 비활성화됩니다. 이는 Manifest V2의 백그라운드 페이지와 가장 큰 차이점입니다.

// background.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'HEADINGS_DATA') {
    // 데이터 처리
    processHeadings(message.data);
    sendResponse({ success: true });
  }
  return true; // 비동기 응답을 위해 true 반환
});

chrome.action.onClicked.addListener((tab) => {
  // 확장 프로그램 아이콘 클릭 시 동작
  chrome.tabs.sendMessage(tab.id, { type: 'ANALYZE_PAGE' });
});

Service Worker의 비활성화 특성 때문에 전역 변수에 상태를 저장하면 안 됩니다. 상태 유지가 필요하면 반드시 chrome.storage API를 사용하세요.

Popup UI 구성

팝업은 확장 프로그램 아이콘을 클릭했을 때 나타나는 작은 UI 패널입니다. 일반적인 HTML 파일로 작성하며, React나 Vue 같은 프레임워크도 사용할 수 있습니다.

<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
  <style>
    body { width: 350px; padding: 16px; font-family: sans-serif; }
    .result { padding: 8px; margin: 4px 0; border-radius: 4px; }
    .error { background: #fee; border-left: 3px solid #e53; }
    .pass { background: #efe; border-left: 3px solid #3a3; }
  </style>
</head>
<body>
  <h2>접근성 검사 결과</h2>
  <div id="results"></div>
  <script src="popup.js"></script>
</body>
</html>

팝업은 닫히면 완전히 소멸됩니다. 팝업이 닫혔다가 다시 열려도 이전 상태를 유지해야 한다면 chrome.storage에 저장하고 팝업이 열릴 때 불러와야 합니다.

권한 관리

Manifest V3에서는 권한을 최소한으로 요청하는 것이 중요합니다. 불필요한 권한은 Chrome Web Store 심사에서 거부 사유가 될 수 있고, 사용자의 신뢰도 떨어뜨립니다.

자주 사용하는 권한들은 다음과 같습니다.

  • activeTab: 사용자가 확장 프로그램을 클릭했을 때만 현재 탭에 접근
  • storage: 로컬 데이터 저장 (설정, 캐시 등)
  • tabs: 탭 정보 조회 (URL, 제목 등)
  • scripting: 프로그래밍 방식으로 스크립트 삽입

선택적으로 필요한 권한은 optional_permissions로 선언하고, 필요한 시점에 chrome.permissions.request()로 요청하는 것이 좋습니다.

메시징: 구성 요소 간 통신

확장 프로그램의 세 구성 요소(Content Script, Service Worker, Popup)는 서로 직접 접근할 수 없습니다. 메시징 API를 통해 통신해야 합니다.

// Content Script → Background
chrome.runtime.sendMessage({ type: 'DATA', payload: data });

// Background → Content Script
chrome.tabs.sendMessage(tabId, { type: 'COMMAND', action: 'scan' });

// 양방향 통신 (응답 포함)
chrome.runtime.sendMessage({ type: 'GET_SETTINGS' }, (response) => {
  console.log('설정:', response.settings);
});

복잡한 통신이 필요하면 chrome.runtime.connect()를 사용하여 지속적인 연결(port)을 생성할 수도 있습니다.

Storage API 활용

chrome.storage는 확장 프로그램의 모든 구성 요소에서 접근 가능한 비동기 저장소입니다. localStorage와 달리 Content Script에서도 사용할 수 있고, 데이터 변경을 감지할 수 있습니다.

// 데이터 저장
await chrome.storage.local.set({
  settings: { theme: 'dark', language: 'ko' },
  lastScan: Date.now()
});

// 데이터 읽기
const { settings } = await chrome.storage.local.get('settings');

// 변경 감지
chrome.storage.onChanged.addListener((changes, area) => {
  if (area === 'local' && changes.settings) {
    applySettings(changes.settings.newValue);
  }
});

chrome.storage.sync를 사용하면 사용자의 Chrome 계정을 통해 여러 기기 간 데이터를 동기화할 수 있습니다. 단, 용량 제한(100KB)이 있으므로 설정 같은 소량의 데이터에만 사용하세요.

디버깅 팁

확장 프로그램 디버깅은 일반 웹 개발과 조금 다릅니다. 각 구성 요소별로 다른 방법으로 디버깅해야 합니다.

  • Popup: 팝업을 열어둔 상태에서 우클릭 → "검사"를 선택하면 DevTools가 열립니다.
  • Service Worker: chrome://extensions에서 확장 프로그램의 "서비스 워커" 링크를 클릭하면 전용 DevTools가 열립니다.
  • Content Script: 일반 페이지의 DevTools 콘솔에서 확인 가능합니다. Sources 패널에서 Content Scripts 탭을 확인하세요.

개발 중에는 chrome://extensions에서 "개발자 모드"를 켜고 "압축해제된 확장 프로그램을 로드합니다"로 로컬 폴더를 불러오세요. 코드를 수정한 후에는 확장 프로그램 카드의 새로고침 버튼을 클릭하면 됩니다.

Chrome Web Store 배포

배포를 위해서는 Chrome Web Store 개발자 계정(등록비 $5, 1회)이 필요합니다. 준비물은 다음과 같습니다.

  • 확장 프로그램을 ZIP 파일로 압축
  • 스크린샷 최소 1장 (1280x800 또는 640x400)
  • 자세한 설명 텍스트
  • 개인정보 처리방침 URL (권한에 따라 필수)

심사는 보통 1~3일 소요되며, 권한이 많을수록 심사가 까다로워집니다. 첫 제출 전에 가이드라인을 충분히 숙지하고, 권한 사용 이유를 명확히 설명하는 것이 심사 통과의 핵심입니다.

크롬 확장 프로그램은 진입 장벽이 낮으면서도 실용적인 도구를 빠르게 만들어 배포할 수 있는 좋은 선택지입니다. 이 가이드가 여러분의 첫 확장 프로그램 개발에 도움이 되기를 바랍니다.

크롬 확장 프로그램 개발 실전 가이드