리액트로 크롬 익스텐션 만들어 보기 시작 부터 출시까지
최근들어 많은 웹사이트들이 blur 효과와 회원가입 창을 화면 전체에 뛰어서 원하지 않는 가입을 강요 합니다. 여러 서비스가 필요하다면 유저가 가입 하겠지만 어떤 한 정보만 확인 하고 가고 싶은 유저에게는 사요을 많이 불편하게 합니다.
그래서 이번에 공부도 할 겸 크롬 익스텐션을 한번 리액트로 만들어보고 런칭을 해보려고 합니다. 만들고자 하는 기능은 간단 합니다.
유저가 익스탠션을 클릭하면 로딩된 것중에서 filter blur css 효과를 없에주고 화면 전체를 가리는 회원가입 권유 창등을 제거 해 줍니다.
일단 그러기 위해서는 리액트 프로젝트 설정을 해야 합니다. 먼저 create-react-app을 통해서 시작을 해보겠습니다.
타입스크립트 공부도 할 겸 일단 타입스크립트를 베이스로 만들어 보겠습니다.
yarn create react-app extension-example --template typescript
cd extension-example && code .
먼저 그냥 cra를 처음 시작하면 볼 수 있는 이 것을 extension에 한번 뛰워 보겠습니다.
크롬 익스탠션에 대한 메타 정보는 manifest.json에 입력하게 됩니다. 맨 처음에 있는 manifest.json에서 몇 가지를 지워야 합니다.
아래에 정보는 웹사이트를 만들때 사용하는 정보라 extension을 업로드 하려고 하면 에러가 뜹니다.
start_url
display
theme_color
background_color
short_name
icons는 사용하지만 array에서 object로 변경 시켜 줘야 합니다.
"icons": {
"16": "wand-16.png",
"48": "wand-48.png",
"128": "wand-128.png"
}
결과적으로 아래와 같이 적어 주시면 됩니다.
manifest_version은 구글이 manifest를 읽을 때 필요 한 정보 입니다. 현재 가장 최신 버전은 3입니다. 예를들어 버전 2에서는 action 대신에 browser_action이 였던 것으로 알고 있습니다.
{
"name": "background-changer",
"description": "change background color",
"version": "1.0",
"manifest_version": 3,
"action": {
"default_popup": "index.html",
"default_title": "change background color"
},
"icons": {
"16": "logo192.png",
"48": "logo192.png",
"128": "logo192.png"
}
}
이제 크롬 익스탠션에 올리기 위해서 yarn build를 쳐서 코드를 빌드해 줍니다.
이제 서치바에 아래와 같이 입력해 줍니다.
chrome://extensions/
그러면 설치된 익스텐션에 리스트가 보이고 화면 오른쪽 상단에 developer mode를 클릭해서 활성화 시켜 줍니다.
그리고 아래 처럼 맨 왼쪽에 있는 Load unpacked를 클릭해서 build 폴더를 선택해 주면 됩니다.
저는 기본언어가 영어라서 영어로 나오는데 한국어는 어떤지 모르겠내요. 에러가 뜨지 않으면 background-changer라는 익스텐션을 선택해주고 한번 실행 시켜 봅니다. 그러면 아래와 같이 나올 것입니다.
아무런 기능 없이 UI만 보여지는 플러그인이 설치가 완료 되었습니다. 이제 배경색을 바꾸는 plugin을 본격적으로 한번 만들어 보겠습니다.
크롬 익스텐션에는 크게 4가지 파일이 필요하게 되는데 아래와 같습니다.
html, content.js, background.js, manifest.json
이제 부터는 3rd 파티 코드를 실행 할 수 없게 되었기 때문에 뭔 로직을 실행 시킬려면 content.js에 모두 넣어야 하고 파일 이름 그대로 background.js는 service worker를 통하여 할 수 있는 일을 하게 됩니다. UI 상에서 무슨 코드를 실행 시키려면 sendMessage를 통해서 정보를 보내게 됩니다. 모든 경우에 이렇다고 할 수는 없지만 아주 간단한 크롬 익스텐션에 경우는 그렇습니다.
이제 앱에서 content.js로 데이터를 보내는 방법에 대해서 알아 보겠습니다. 아래와 같이 chrome.tabs.query를 통해서 현재 윈도우, 액티브한 탭에 정보를 불러 올 수 있습니다.
chrome.tabs && chrome.tabs.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
tabs[0].id || 0,
message as Message,
(response: ContentResponse) => onResponse(response));
});
좀더 자세한 정보는 아래 링크에 나와 있습니다.
https://developer.chrome.com/docs/extensions/reference/
https://developer.chrome.com/docs/extensions/reference/tabs/
먼저 content.js도 타입스크립트를 사용하기 위해서 조금 설정을 바꿔 줘야 합니다. 먼저 CRA에서 webpack 세팅을 바꾸기 위해서는 craco가 필요한데 설치 해주도록 합니다.
yarn add -D @craco/craco @types/chrome
아래와 같이 파일들을 생성해 줍니다.
craco.config.js
src/chrome/content.ts
craco.config.js은 아래와 같이 만들어 줍니다.
// craco.config.js
module.exports = {
webpack: {
configure: (webpackConfig, {env, paths}) => {
return {
...webpackConfig,
entry: {
main: [env === 'development' &&
require.resolve('react-dev-utils/webpackHotDevClient'),paths.appIndexJs].filter(Boolean),
// 파일이름: 현재 path
content: './src/chrome/content.ts',
},
output: {
...webpackConfig.output,
// build 폴더 안에 생성될 path
filename: 'static/js/[name].js',
},
optimization: {
...webpackConfig.optimization,
runtimeChunk: false,
}
}
},
}
}
그리고 package.json에 가셔서 scripts에 build가 craco config를 사용 할 수 있도록 아래와 같이 바꿔 줍시다,
INLINE_RUNTIME_CHUNK를 .env에 넣으시던지 아래와 같이 바로 사용해도 됩니다. runtime chunk가 inline으로 들어가면 content security policy 에러가 나기 때문에 사용합니다.
INLINE_RUNTIME_CHUNK=false craco build
ref-https://medium.com/@dathanbennett/chrome-extension-development-in-react-9b31ff0eb9d1
이제 manifest.json도 조금 바꿔줘야 합니다. 스크립트를 사용하려면 아래와 같이 정보를 입력해야 합니다.
"content_scripts": [
{
"matches": [
"http://*/*",
"https://*/*"
],
"js": [
"./static/js/content.js"
]
}
],
"permissions": [
"activeTab"
],
우리가 만들고 있는 extension 처럼 모든 사이트에 다 사용가능 하다면 위와 같이 적어 주고 특정 사이트만을 타겟팅 한 익스텐션이라면
https://mysite/* 이런 식으로 matches에 적어 주셔도 됩니다.
마지막으로 우리는 activeTab을 알아야 되기 때문에 permissions에 activeTab을 추가 해 줍니다.
아까 craco를 추가해주고 webpack을 조금 변경 했기 때문에 build를 하고 나면 이제 content.js가 생겨난 것을 알 수 있습니다. 이제 위와 같이 필요한 js를 추가 해주면 됩니다. 아래 처럼 content.js가 생성된걸 볼 수 있습니다.
이제 content.js에 간단하게 console.log를 해서 되는지 확인을 해보겠습니다.
//content.ts
const reactAppListener = (
color: string,
) => {
console.log('content got the messsage ', color)
document.body.style.background = color
}
// attach listener
chrome.runtime.onMessage.addListener(reactAppListener);
UI에서 보낸 메세지를 받기 위해서는 onMessage.addListener를 해주셔야 합니다.
그럼 이제 build를 해보겠습니다. 빌드를 마쳤으면 아래와 같이 refresh 버튼을 눌러 익스텐션을 업데이트 해보겠습니다.
너무 눌려 있는거 보니 스타일을 조금 변경 해주겠습니다.
/* app.css */
html,
body {
--app-height: 80px;
--app-width: 200px;
background: rgb(65, 61, 61);
width: var(--app-width);
height: var(--app-height);
border: 1px solid white;
}
.App {
width: var(--app-width);
height: var(--app-height);
text-align: center;
color: white;
display: flex;
align-items: center;
}
.App > div {
margin-left: 1rem;
}
.App label {
margin-left: 0.5rem;;
}
이제 아마 아래 처럼 보일 거에요
// App.tsx
<div className="App" style={{ backgroundColor: selectedBackgroundColor }}>
<div>
<input type="color" onChange={onChange} />
<label>{selectedBackgroundColor}</label>
</div>
</div>
배경화면 색깔이 바뀌면 익스텐션 배경도 바뀌게 아래 처럼 state으로 저장 합니다.
const [selectedBackgroundColor, setBackgroundColor] = useState<string>('rgb(65, 61, 61)')
그리고 바탕색이 바뀔 때 state에 저장을 하고 content.ts로 메세지를 보내 줍니다.
const onBackgroundChange = (color: string) => {
setBackgroundColor(color)
chrome.tabs && chrome.tabs.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
tabs[0].id || 0,
color as string,
);
});
}
input color를 사용 해보면 마우스 커서가 움직일 때 마다 엄청나게 배경이 바뀌기 때문에 debounce를 해 줍시다. lodash debounce를 사용 했습니다.
const debounceSetBackgroundColor = useCallback(debounce(onBackgroundChange, 250), [])
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
debounceSetBackgroundColor(e.target.value)
}
이제 build를 하셔서 익스텐션을 업데이트 해준 다음 사용해보면 배경색깔이 바뀌는 것을 볼 수 있습니다.
아래와 같은 에러가 보인다면 리프레시를 하고 다시 사용해 보세요.