개발 동기
회사에서 Electron으로 응용프로그램을 개발하여 크롬 탭 제어와 화면 제어 기능을 구현했지만, 여러 문제점에 직면했습니다.
- 유지보수의 어려움
- 업데이트 과정의 복잡성: 사용자 환경마다 다른 업데이트 이슈
- 테스트의 한계: 다양한 환경에서의 테스트 어려움
- 설치 문제: 노트북 기종, OS 버전별 호환성 이슈
이러한 문제점들을 해결하기 위해 다른 방법을 찾아보다 Chrome Extension으로도 크롬을 제어할 수 있다는 것을 알게되었어요. 가벼운 설치와 배포, 간편한 업데이트가 가능하기 때문에 react와 typescript를 이용하여 chrome extension을 만들어 보기로 했습니다.
프로젝트 구조
src/
├── components/ # 공통 컴포넌트
├── hooks/ # 커스텀 훅
└── pages/ # Extension 페이지들
├── background/ # Background Script
│ └── index.ts
├── content/ # Content Script
│ ├── App.tsx
│ └── main.tsx
├── newtab/ # New Tab Override
│ ├── NewTab.tsx
│ ├── index.html
│ └── index.tsx
└── popup/ # Extension Popup
├── Popup.tsx
├── index.html
└── index.tsx페이지 아래 pages/ 폴더를 보면 background, content, newtab, popup으로 나뉘어 있는데요.
Chrome Extension의 주요 구성 요소는 다음과 같습니다.
- Popup: 확장 프로그램 아이콘을 클릭하면 나타나는 UI
- Content Scripts: 웹 페이지에 삽입되어 DOM을 제어하는 스크립트
- Background: 브라우저 이벤트를 감지하고 컴포넌트 간 통신을 담당
- New Tab (선택): Chrome의 기본 새 탭 페이지를 대체하는 커스텀 페이지
개발 환경 설치
1. Vite 프로젝트 생성
npm create vite@latest exe_app -- --template react-ts
cd exe-app
npm install2. CRXJS 설치 및 특징
CRXJS는 Chrome Extension 개발을 위한 Vite 플러그인이에요.
npm i @crxjs/vite-plugin@beta -D주요 특징
- manifest.json 기반 자동 파일 인식 : CRXJS는 manifest.json을 파싱하여 확장 프로그램에 포함할 파일들을 자동으로 찾습니다.
- Content Script에서도 작동하는 HMR : Content Script에서도 확장 프로그램의 상태를 유지하면서 즉시 UI가 업데이트됩니다.
- Zero Configuration : Vite config에 플러그인만 추가하면 바로 개발을 시작할 수 있습니다.
3. Vite 설정
CRXJS 플러그인을 활성화하고, manifest.json 파일을 전달합니다.
// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { crx } from "@crxjs/vite-plugin";
import manifest from "./manifest.json";
export default defineConfig({
plugins: [
react(),
crx({ manifest }),
(server: {
port: 3000,
}),
],
});4. mainfest 설정
chrome extension의 핵심이라고 할 수 있는 파일인 manifest.json은 브라우저에 이 앱에 대한 정보를 알려주는 파일이에요. 이 파일을 통해 브라우저에 extension의 정보를 전달하고, extension으로 인식되도록 합니다.
아래 필드는 필수적으로 넣어줘야 하는 값들로, 하나라도 없으면 extension 앱으로 만들 수 없어요.
필수 설정
- manifest_version: manifest 버전
- version: 앱의 버전
- name: 사용자에게 표시되는 chrome extension의 이름
- action.default_popup: 확장 프로그램 아이콘 클릭 시 표시될 HTML 파일 경로
아이콘 설정 (옵션)
- action.default_icon: 툴바에 표시될 확장 프로그램 아이콘
- action.default_title: 아이콘에 마우스를 올렸을 때 표시되는 툴팁
{
"manifest_version": 3,
"version": "1.0.0",
"short_name": "React App",
"name": "Create React App",
"action": {
"default_icon": {
"16": "images/icon16.png",
"24": "images/icon24.png",
"32": "images/icon32.png"
},
"default_title": "Click Me",
"default_popup": "index.html"
}
}manifest.json을 직접 만들어도 되지만, 타입 안정성과 동적 버전 관리를 위해 TypeScript로 manifest.config.ts를 작성할 수 있습니다.
작동 방식
- 1.개발 시: manifest.config.ts 작성
- 2.vite.config.ts에서 import하여 CRXJS에 전달
- 3.빌드 시: CRXJS가 JavaScript 객체를 처리하여 최종 manifest.json 생성
- 4.배포 시: dist/manifest.json을 Chrome이 읽음
//manifest.config.ts
import { defineManifest } from "@crxjs/vite-plugin";
import packageJson from "./package.json";
const { version } = packageJson;
// 버전 문자열을 파싱하여 major.minor.patch.label로 분리
const [major, minor, patch, label = "0"] = version
.replace(/[^\d.-]+/g, "")
.split(/[.-]/);
// defineManifest: CRXJS가 제공하는 헬퍼 함수
// - 타입 자동완성 지원
// - 동적/비동기 manifest 정의 가능
// - env 파라미터로 빌드 환경 정보 접근 가능
export default defineManifest(async (env) => ({
manifest_version: 3,
name: env.mode === "staging" ? "[INTERNAL] Tools - EXE" : "Tools - EXE",
description: "응용프로그램 EXE App",
version:
label === "0"
? `${major}.${minor}.${patch}`
: `${major}.${minor}.${patch}.${label}`,
version_name: version,
action: {
default_title: "popup",
default_popup: "src/pages/popup/index.html", // 아이콘 클릭 시 열릴 팝업 HTML 파일
},
chrome_url_overrides: {
newtab: "src/pages/newtab/index.html", // 새 탭을 열 때 표시될 커스텀 페이지 지정
},
icons: {
"16": "e_image.png",
"48": "e_image.png",
"128": "e_image.png",
},
background: {
service_worker: "src/pages/background/index.ts",
type: "module",
},
content_scripts: [
{
matches: ["*://*/*"],
js: ["src/pages/content/main.tsx"],
},
],
web_accessible_resources: [
{
resources: ["assets/js/*.js", "assets/css/*.css"],
matches: ["*://*/*"],
},
],
permissions: ["storage", "scripting", "activeTab", "system.display", "tabs"],
}));
chrome extension 등록
manifest 설정이 완료되었으면, react 프로젝트를 빌드를 해야해요. 빌드된 산출물을 로컬 chrome 브라우저에 extension을 추가해 줍니다.
- 1.chrome://extensions/ 으로 들어가 우측 상단에 "개발자 모드"를 눌러줍니다.
- 2."압축해제된 확장 프로그램을 로드합니다." 를 클릭하여 빌드 dist 폴더를 선택합니다.
- 3.추가된 크롬 extension을 확인하고, 핀 고정을 클릭하면 default_pop에 설정한 index.html 이 보입니다.
