빌드와 패키징
manifest.config.ts가 어떻게 manifest.json을 생성하는지, Vite와 @crxjs/vite-plugin이 dev에서 변경 감지를 어떻게 하는지, pnpm run build와 pnpm run package가 각각 무엇을 만들어 내는지를 봅니다. architecture에서 잡은 FSD 트리가 여기서 manifest와 만나 chrome이 로드 가능한 산출물이 됩니다.
파이프라인 한 장
섹션 제목: “파이프라인 한 장”graph LR src["src/: FSD 슬라이스"] manifest[manifest.config.ts] vite[vite + @crxjs/vite-plugin] dist["dist/: 로드 가능한 확장"] zip[packages/extension.zip]
src --> vite manifest --> vite vite --> dist dist --> zipsrc/의 React/TS 코드와 manifest.config.ts가 함께 vite로 들어가고, pnpm run build가 dist/를 만들어 냅니다. pnpm run package는 그 dist를 zip으로 묶을 뿐입니다.
manifest.config.ts
섹션 제목: “manifest.config.ts”저장소 루트의 manifest.config.ts는 TypeScript 객체를 export 합니다. crxjs가 제공하는 defineManifest로 감싸면 빌드 시 manifest.json으로 직렬화됩니다.
import { defineManifest } from '@crxjs/vite-plugin'
export default defineManifest({ manifest_version: 3, name: '__MSG_extName__', version: '0.1.0', permissions: ['storage', 'activeTab'], host_permissions: [], action: { default_popup: 'src/app/popup/index.html' }, options_page: 'src/app/options/index.html', background: { service_worker: 'src/app/background/index.ts' }, content_scripts: [ { matches: ['https://*.example.com/*'], js: ['src/app/content/index.ts'] }, ],})손볼 곳은 permissions, host_permissions, content_scripts.matches, action.default_popup, options_page, side_panel.default_path 정도입니다. 권한을 손댈 때는 design-manifest 스킬을 통해 ADR 한 장과 같이 다듬는 편이 안전합니다. 직접 손대도 동작은 합니다. 왜 그 권한을 가졌는지가 한 달 뒤에 사라질 뿐입니다.
Vite + @crxjs/vite-plugin
섹션 제목: “Vite + @crxjs/vite-plugin”vite.config.ts에는 crx({ manifest })가 들어 있습니다. plugin이 manifest를 읽어 entry point를 자동으로 결정합니다. content_scripts, background.service_worker, action.default_popup, options_page가 모두 vite의 빌드 입력으로 잡힙니다.
그래서 vite는 src의 React/TS 코드를 일반 웹 빌드처럼 컴파일하면서, 동시에 manifest가 가리키는 모든 진입점을 처리합니다. 진입점 추가는 manifest 한 곳만 손대면 끝납니다.
dev에서 변경 감지
섹션 제목: “dev에서 변경 감지”pnpm run devvite dev server가 뜨고, crxjs HMR이 진입점별로 다른 전략을 씁니다.
- popup, options: 일반 vite HMR을 씁니다. 저장하면 즉시 반영됩니다
- content_script: 새 빌드가 만들어지면 chrome이 자동으로 reinject 합니다. 페이지 새로고침은 필요할 수 있습니다
- background service worker: 코드가 바뀌면 worker 자체가 종료되고 다시 시작됩니다
chrome://extensions의 “Reload” 버튼은 거의 누를 일이 없습니다. 권한을 새로 추가했거나 manifest 구조가 크게 바뀐 경우에만 필요합니다.
build와 package
섹션 제목: “build와 package”pnpm run build # dist/ 에 production 빌드pnpm run package # packages/extension.zip 으로 묶기pnpm run build는 dist/에 운영 환경용 빌드를 만들어 냅니다. manifest.json도 같이 생성됩니다. 이 상태로 chrome의 “Load unpacked”로 곧장 띄울 수 있습니다.
pnpm run package는 그 dist를 zip으로 묶어 packages/extension.zip 같은 경로에 만들어 냅니다. zip 안의 구조는 dist와 동일합니다.
둘이 갈린 이유는 용도가 다르기 때문입니다. build는 개발자가 자기 chrome에 띄울 때 쓰고, package는 동료에게 zip을 건네거나 Web Store에 업로드할 때 씁니다.
자동화가 멈출 때
섹션 제목: “자동화가 멈출 때”pnpm run package로 zip이 만들어진 시점이 base 자동화의 끝입니다. 그 다음에 누구에게 보낼지, 어디에 호스팅할지, .pem 키를 어디에 둘지, Chrome Web Store에 등록할지는 도구가 관여하지 않는 영역입니다. build-and-package 스킬이 zip과 함께 만드는 DISTRIBUTION.md도 결정용 메모일 뿐, 자동으로 누군가에게 보내거나 어딘가로 올리지 않습니다.
이 경계는 의도한 것입니다. 키 파일과 스토어 콘솔과 사내 정책 서버는 사용자 자격으로 접근해야 하는 자산이고, 자동화가 그 선을 넘으면 키 유출과 검수 사고가 뒤따릅니다. 다음 절에서 세 가지 배포 경로의 장단점과 키 관리 절차를 봅니다.
다음 섹션은 distribution입니다. zip 직접 배포, Web Store 업로드, self-host .crx와 update_url 세 가지의 장단점, .pem 키 관리, 그리고 chrome이 외부 .crx를 차단하는 것을 어떻게 우회하는지를 봅니다.