๐ง ํฌ๋กฌ ์ต์คํ ์ ๊ตฌ์กฐ ๊ฐ๋ ์ ๋ฆฌ
ํฌ๋กฌ ์ต์คํ ์ ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์น ๊ธฐ์ ๋ก ๋ง๋ค ์ ์๋ ์์ ์ฑ์ด๋ค.
๊ธฐ๋ณธ์ ์ธ ๊ตฌ์ฑ ์์๋ ๋ค์๊ณผ ๊ฐ๋ค.
- mainfest.json : ์ต์คํ ์ ์ค์ ํ์ผ (ํ์) (์ด ํ์ผ์ด ์์ด์ผ ํฌ๋กฌ ์ต์คํ ์ ์ผ๋ก ์ธ์ํ ์ ์๋ค.)
- popup : ํฌ๋กฌ ํด๋ฐ์์ ์์ด์ฝ์ ํด๋ฆญํ๋ฉด ๋์ค๋ UI
- background script : ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๊ณ์ ๋์ํ๋ JS
- content script : ์น ํ์ด์ง์ ์ฝ์ ๋๋ JS
- options page : ์ฌ์ฉ์ ์ค์ ํ์ด์ง
- permissions : ์ด๋ค ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ง ์ค์ (์: ํญ ์ ๊ทผ, ์คํ ๋ฆฌ์ง)
์์)
{
"manifest_version": 3, // ๋ฒ์ 3 ์ฌ์ฉ (๋ฒ์ 2๋ ์ง์ ์ข
๋ฃ)
"name": "My First Extension", // ์ต์คํ
์
์ด๋ฆ
"version": "0.1.0", // ํ์ฌ ๋ฒ์ (์ฑ ์ฒ๋ผ ๊ด๋ฆฌ๋จ)
"description": "This is a beginner-friendly Chrome extension.", // ์ต์คํ
์
์ค๋ช
"icons": { // ํ์ฅ ํ๋ก๊ทธ๋จ ์์ด์ฝ ํ์ผ ๊ฒฝ๋ก
"16": "assets/icon-16.png",
"48": "assets/icon-48.png",
"128": "assets/icon-128.png"
},
"action": { // ํฌ๋กฌ ํด๋ฐ์์ ํด๋ฆญํ์ ๋ ๋์ค๋ ํ์
์ค์
"default_popup": "src/popup.html", // ์์ด์ฝ ํด๋ฆญ ์ ํ์๋ HTML ํ์ผ ๊ฒฝ๋ก
"default_icon": "assets/icon-48.png" // ์์ด์ฝ ํ์ผ ๊ฒฝ๋ก
},
"background": {
"service_worker": "src/background.js" // ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๊ณ์ ์คํ๋๋ ์คํฌ๋ฆฝํธ
},
"permissions": ["storage", "tabs"], // ์ต์คํ
์
์ด ์ฌ์ฉํ๋ ค๋ ๊ธฐ๋ฅ๋ค
// storage: ๋ฐ์ดํฐ ์ ์ฅ , tabs: ํญ ์กฐ์
"host_permissions": ["https://example.com/*"], // ํน์ ๋๋ฉ์ธ ์ ๊ทผ ๊ถํ ์ค์
"content_scripts": [ // ์น ํ์ด์ง์ ์ฝ์
๋๋ JS ์ค์
{
"matches": ["https://*/*"], // ์ด๋ค URL์์ ์ฝํ
์ธ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ ์ง ๋ช
์
"js": ["src/content.js"] // ์คํํ ์๋ฐ์คํฌ๋ฆฝํธ ํ์ผ ๊ฒฝ๋ก
}
]
}
๐งฉ ์ popup.html์ ์จ์ผ ํ ๊น?
๋๋ React ๋ก ํ๋ก์ ํธ๋ฅผ ์์ฑํ๊ธฐ ๋๋ฌธ์, ๋ด ๊ฒฝ์ฐ src/Popup.tsx ์์ ํ์ ์ปดํฌ๋ํธ๋ฅผ ์์ฑํด๋์๋ค.
๊ทธ๋ฐ๋ฐ, ํฌ๋กฌ ์ต์คํ ์ ์ default_popup์ HTML ํ์ผ ๊ฒฝ๋ก๋ง ๋ฐ์ ์ ์๋ค๊ณ ํ๋ค.
React ์ปดํฌ๋ํธ(popup.tsx)๋ ๋ธ๋ผ์ฐ์ ๊ฐ ์ง์ ์ดํดํ ์ ์๊ธฐ ๋๋ฌธ์, ๋ฐ๋์ HTML๋ก ๊ฐ์ธ์ค์ผ ํ๋ค.
์ฆ, popup.tsx๋ ์ง์ ๋ฃ์ ์ ์๊ณ ,
"action": {
"default_popup": "popup.html"
}
์ด๋ฐ ์์ผ๋ก ์จ์ผ ํ๊ณ , popup.html ์์์ React ์ฑ(popup.tsx)์ ๋ง์ดํธํ๋ ๋ฐฉ์์ผ๋ก ํด๊ฒฐํ ์ ์๋ค.
๐ ๊ตฌ์กฐ ์์
src/
โโโ popup.tsx
โโโ popup.html
popup.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Popup</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/popup.tsx"></script>
</body>
</html>
popup.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
function Popup() {
return <div className="text-xl text-blue-500">Hello from Popup!</div>
}
const root = document.getElementById('root')!
ReactDOM.createRoot(root).render(<Popup />)
๐ Vite ์ค์ ํ
Vite๋ ๊ธฐ๋ณธ์ ์ผ๋ก index.html์ ๊ธฐ์ค์ผ๋ก ์๋ํ์ง๋ง, ์ฌ๋ฌ ๊ฐ์ entry HTML์ ๋ง๋ค ์ ์๋ค.
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
input: {
popup: resolve(__dirname, 'src/popup.html'),
// background ์คํฌ๋ฆฝํธ๋ content script๋ entry๋ง ๋ฑ๋กํด์ค๋ ๋จ
},
output: {
// ํด๋ ๊ตฌ์กฐ ๊น๋ํ๊ฒ ์ ๋ฆฌํ๊ณ ์ถ๋ค๋ฉด ์ฌ๊ธฐ์ ์กฐ์
}
},
outDir: 'dist',
},
})
๐ ๋๋ฒ๊น ํ๋ ๋ฐฉ๋ฒ
๐ ๊ฐ์ด๋
- ๋ฆฌ์กํธ๋ก ๊ฐ๋ฐํ๊ณ ์๋ค๋ฉด, build ํ, dist/ ํด๋์๋ manifest.json์ ๋ณต์ฌํ๋ค.
- ํฌ๋กฌ ์ฃผ์์ฐฝ์ chrome://extensions๋ฅผ ์ ๋ ฅํ๋ค.
- "๊ฐ๋ฐ์ ๋ชจ๋"๋ฅผ ํ์ฑํํ๋ค.
- "์์ถํด์ ๋ ํ์ฅ ํ๋ก๊ทธ๋จ์ ๋ก๋ํฉ๋๋ค" ๋ฒํผ์ ํด๋ฆญํ๋ค. (Load unpacked ๋ฒํผ ํด๋ฆญ)
- ํ๋ก์ ํธ ํด๋๋ฅผ ์ ํํ๋ค. (๋ก์ปฌ ๋๋ฐ์ด์ค์ dist ํด๋ ๊ฒฝ๋ก๋ฅผ ์ฐพ์์ ์
๋ก๋ํด ์ค๋ค. )
- ์ ์์ ์ผ๋ก ๋ก๋๋์๋ค๋ฉด ํด๋ฐ์ ์์ด์ฝ์ด ๋ํ๋ ๊ฒ์ด๋ค.
๐ ์ฐธ๊ณ ์ฌํญ
background์ content_scripts๋ ๊ฐ๊ฐ ๋ณ๋์ ๋๋ฒ๊น ์ฝ์์ ๊ฐ์ง๋ค.