포스트

[Chrome Extension 개발] 프레임워크 완벽 비교: WXT vs Plasmo vs CRXJS 그리고 더 많은 선택지

[Chrome Extension 개발] 프레임워크 완벽 비교: WXT vs Plasmo vs CRXJS 그리고 더 많은 선택지

들어가며

Chrome 확장 프로그램을 vanilla JavaScript로 개발하다가 프레임워크의 필요성을 느끼셨나요? 저도 같은 경험을 했습니다. 수동 빌드, 반복적인 새로고침, 복잡한 설정 파일… 이 모든 것을 해결해주는 다양한 프레임워크가 있다는 걸 알게 되었죠.

저는 Plasmo로 시작해서 WXT로 마이그레이션하는 여정을 거쳤는데, 그 과정에서 알게 된 모든 프레임워크들을 비교 분석해보겠습니다.

1. Chrome Extension 프레임워크 전체 지형도

2025년 현재 사용 가능한 프레임워크들

mindmap
  root((Chrome Extension<br/>빌드 도구))
    프레임워크
      WXT
      Extension.js
      Plasmo
      Bedframe
    Vite 플러그인
      CRXJS
    직접 구성
      Vite
      Rollup
      Parcel

프레임워크 현황 한눈에 보기

도구 시작할 때 비교할 점
WXT 엔트리포인트 규칙으로 manifest를 생성하며 React·Vue 등과 함께 사용
Plasmo popup.tsx 같은 파일에서 UI를 시작
CRXJS Vite 프로젝트에 플러그인을 추가하고 manifest를 직접 작성
Extension.js CLI로 시작하고 manifest에 UI 진입점을 연결
Bedframe 도입 전 필요한 기능과 유지보수 상태를 저장소에서 확인

CRXJS는 종료 예정이라는 이전 설명과 달리 2.x 릴리스를 공개했습니다. Vite와의 호환 여부는 CRXJS 릴리스에서 사용할 버전으로 확인하세요.

2. 각 프레임워크 심층 분석

WXT - The Next Generation Framework

1
2
3
4
5
6
7
8
9
10
// wxt.config.ts
import { defineConfig } from 'wxt';

export default defineConfig({
  srcDir: 'src',
  modules: ['@wxt-dev/module-react'],
  manifest: {
    permissions: ['storage'],
  },
});

장점:

  • ✅ React, Vue, Svelte, Solid 등과 통합 가능
  • ✅ 뛰어난 TypeScript 지원
  • ✅ 활발한 개발과 커뮤니티
  • ✅ Vite의 모든 장점 활용
  • ✅ 멀티 브라우저 지원 (Chrome, Firefox, Safari, Edge)

단점:

  • ❌ Plasmo보다 약간 복잡한 초기 설정
  • ❌ 상대적으로 적은 예제 (빠르게 개선 중)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// popup.tsx
import { useState } from "react"

function IndexPopup() {
  const [data, setData] = useState("")

  return (
    <div>
      <h1>
        Welcome to your Plasmo Extension!
      </h1>
    </div>
  )
}

export default IndexPopup

장점:

  • ✅ 가장 큰 커뮤니티와 많은 예제
  • ✅ React 개발자에게 친숙한 구조
  • ✅ 빠른 시작 가능
  • ✅ Plasmo Platform 통합

단점:

  • ❌ 본문의 팝업 예제는 React 기준
  • ❌ 유지보수 속도 저하 우려
  • ❌ 커스터마이징 제한적
  • ❌ 번들 크기가 큼

CRXJS - Vite 플러그인

Vite 설정에 플러그인을 추가하고 기존 manifest.json을 넘길 수 있습니다. 아래는 @crxjs/vite-plugin을 설치한 뒤 적용하는 최소 설정입니다. CRXJS 저장소

1
2
3
4
5
6
7
8
// vite.config.ts
import { defineConfig } from 'vite'
import { crx } from '@crxjs/vite-plugin'
import manifest from './manifest.json'

export default defineConfig({
  plugins: [crx({ manifest })],
})

Extension.js - The Zero-Config Solution

1
2
3
npx extension@latest create my-extension
cd my-extension
npm run dev

장점:

  • ✅ Zero-config 철학
  • ✅ 빠른 시작
  • ✅ 모던한 개발 경험
  • ✅ HMR 지원

단점:

  • ❌ 아직 초기 단계
  • ❌ 커뮤니티가 작음
  • ❌ 고급 기능 부족

Bedframe - The Experimental One

1
2
3
4
5
6
// Early access 단계
import { defineConfig } from 'bedframe';

export default defineConfig({
  // 설정 진행 중
});

현재 상황:

  • Vite 기반의 실험적 프레임워크
  • Early Access 프로그램 운영 중
  • 프로덕션 사용 비추천

3. 실전 비교: 같은 기능, 다른 구현

graph LR
    A[Popup 개발] --> B{프레임워크}
    B --> C[WXT]
    B --> D[Plasmo]
    B --> E[Extension.js]
    
    C --> C1[src/entrypoints/popup/App.tsx]
    D --> D1[popup.tsx]
    E --> E1[pages/popup.tsx]
    
    style C fill:#9f9,stroke:#333,stroke-width:2px
    style D fill:#99f,stroke:#333,stroke-width:2px
    style E fill:#f99,stroke:#333,stroke-width:2px

WXT 방식

srcDir: 'src'와 React 모듈을 설정한 프로젝트 기준입니다. 아래 두 TSX 파일과 같은 폴더의 index.html에 다음 요소를 넣습니다. WXT 팝업 진입점

1
2
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
1
2
3
4
5
6
7
8
9
10
// src/entrypoints/popup/App.tsx
export default function App() {
  return <div>WXT Popup</div>
}

// src/entrypoints/popup/main.tsx
import { createRoot } from 'react-dom/client'
import App from './App'

createRoot(document.getElementById('root')!).render(<App />)

Plasmo 방식

팝업은 브라우저 툴바에서 열립니다. 사이트에 콘텐츠 스크립트를 삽입하는 matches는 팝업에 필요하지 않습니다. Plasmo 팝업 문서

1
2
3
4
// popup.tsx
export default function Popup() {
  return <div>Plasmo Popup</div>
}

Extension.js 방식

React 템플릿에서 아래 컴포넌트를 렌더링하는 HTML 파일을 만든 뒤, manifest의 action.default_popup을 그 HTML 경로에 연결합니다. Extension.js의 HTML 진입점 문서

1
2
3
4
// pages/popup.tsx
export default function Popup() {
  return <div>Extension.js Popup</div>
}

Content Script UI 비교

기능 WXT Plasmo Extension.js CRXJS
Shadow DOM ✅ 네이티브 지원 ✅ getInlineAnchor ⚠️ 수동 ⚠️ 수동
CSS 격리 ✅ 자동 ✅ 자동 ❌ 수동 ❌ 수동
React Portal ✅ 지원 ✅ 내장 ⚠️ 직접 구현 ⚠️ 직접 구현
HMR ✅ 완벽 ⚠️ 부분적 ✅ 지원 ✅ 지원

4. 성능 비교

빌드 시간 벤치마크

같은 Node.js 버전, 브라우저, 기능을 사용한 샘플에서 첫 프로덕션 빌드와 개발 중 파일 한 개를 바꿨을 때 반영되는 시간을 따로 재세요. 팝업 변경과 콘텐츠 스크립트 변경은 각각 새로고침이 필요한지 확인합니다.

번들 크기 비교 (동일한 React 앱)

각 도구의 프로덕션 산출물을 같은 압축 방식으로 비교합니다. 소스맵과 아이콘 같은 공통 파일의 포함 여부를 맞춰야 브라우저에 실제로 배포할 ZIP 크기를 비교할 수 있습니다.

5. 마이그레이션 가이드

Plasmo → WXT 마이그레이션

저도 이 경로로 마이그레이션했습니다. 주요 변경사항:

flowchart LR
    A[Plasmo 프로젝트] --> B[구조 변경]
    B --> C[설정 파일 생성]
    C --> D[엔트리포인트 이동]
    D --> E[API 호출 수정]
    E --> F[WXT 프로젝트]
    
    style A fill:#f96,stroke:#333,stroke-width:2px
    style F fill:#9f9,stroke:#333,stroke-width:2px

1. 디렉토리 구조 변경

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Plasmo
├── popup.tsx
├── contents/
├── background.ts

# WXT
├── src/
│   └── entrypoints/
│       ├── popup/
│       │   ├── index.html
│       │   ├── main.tsx
│       │   └── App.tsx
│       ├── app.content.ts
│       └── background.ts

2. 설정 파일 추가

1
2
3
4
5
6
7
8
9
10
// wxt.config.ts
import { defineConfig } from 'wxt';

export default defineConfig({
  srcDir: 'src',
  modules: ['@wxt-dev/module-react'],
  manifest: {
    // Plasmo의 package.json manifest를 여기로
  }
});

3. Storage API 마이그레이션

예제의 value는 저장할 값입니다. 기존 사용자의 설정을 유지하려면 Plasmo에서 사용하던 storage 영역과 실제 저장 키를 먼저 확인하세요. 새 키에 쓰는 것만으로 기존 데이터가 옮겨지지는 않습니다. WXT 저장소 문서

1
2
3
4
5
6
7
8
// Plasmo
import { Storage } from "@plasmohq/storage"
const storage = new Storage()
await storage.set("key", value)

// WXT
import { storage } from '#imports';
await storage.setItem('local:key', value)

실제 마이그레이션 사례: ChatGPT Writer

ChatGPT Writer 개발자도 Plasmo에서 WXT로 전환한 경험을 공유했습니다. 해당 확장 프로그램의 사례이므로 내 프로젝트에서는 위와 같은 조건으로 직접 비교하세요. 개발자의 마이그레이션 기록

6. 2025년 프레임워크 선택 가이드

상황별 추천

graph TD
    A[프로젝트 시작] --> B{팀 상황은?}
    
    B --> C[React 전문팀]
    B --> D[다양한 프레임워크]
    B --> E[빠른 프로토타입]
    B --> F[기업 프로젝트]
    
    C --> G[Plasmo 또는 WXT]
    D --> H[WXT 강력 추천]
    E --> I[Extension.js]
    F --> J[WXT]
    
    style H fill:#9f9,stroke:#333,stroke-width:4px
    style J fill:#9f9,stroke:#333,stroke-width:4px

최종 추천 매트릭스

상황 1순위 2순위 피해야 할 것
새 프로젝트 WXT Extension.js -
React 전용 WXT Plasmo -
Vue/Svelte WXT Vite 수동 설정 Plasmo
빠른 프로토타입 Extension.js Plasmo -
대규모 앱 WXT - Extension.js
기존 Vite 프로젝트 CRXJS WXT -

7. 왜 최종적으로 WXT를 선택했나?

제가 Plasmo에서 WXT로 마이그레이션한 이유:

  1. 프레임워크 자유도: Vue 컴포넌트를 일부 사용하고 싶었는데 Plasmo는 React만 지원
  2. 번들 크기: 제가 옮긴 프로젝트에서 배포 파일 크기가 줄어듦
  3. TypeScript 지원: WXT의 타입 지원이 훨씬 우수
  4. 미래 지향적: Vite 생태계와 완벽한 통합
  5. 활발한 개발: 이슈 대응이 빠르고 업데이트가 잦음

마무리

Chrome Extension 개발 프레임워크 선택은 프로젝트의 성공을 좌우할 수 있는 중요한 결정입니다.

제가 WXT로 옮길 때의 선택:

  • 🏆 종합 우승: WXT - 유연성, 성능, 미래 지향성
  • 🥈 차선책: Plasmo - React 전용이라면 여전히 좋은 선택
  • 🆕 주목할 만한: Extension.js - 간단한 프로젝트에 적합
  • 기존 Vite 구성을 유지할 때: CRXJS도 비교 대상

여러분의 프로젝트에 가장 적합한 프레임워크를 선택하시길 바랍니다. 저처럼 마이그레이션을 고려 중이시라면, WXT를 강력히 추천드립니다!

참고 자료


Chrome Extension 개발 경험이나 프레임워크 선택에 대한 고민이 있으시다면 댓글로 공유해주세요!

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.