Search
Duplicate
👨🏻‍💻

[Unsolved.wa 개발기 - 2] chrome extension 시작부터 끝까지

간단소개
unsolved.wa 개발 두번째 이야기
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
Javascript
태그
Scrap
8 more properties
본격적으로 chrome extension 개발을 진행하면서 extension구성요소를 공부했었는데 관련 래퍼런스가 적어서 개발을 진행하면서 어려움이 있었다. 그래서 extension 개발 과정에서 필요했던 내용들을 가볍게 정리해보려고 한다!

chrome extension 만들기를 위한 기본 세팅

chrome extension 을 만들기위한 기본 세팅은 아래 구글 공식 문서를 따라하면 된다. 구글의 공식 가이드만 따라해도 대부분의 내용을 구현할 수 있다. 그 중에서 핵심적으로 extension을 구성하는 script요소들에 대해 다뤄볼 예정이다.
2023년 부터는 Manifest V2 extension은 더이상 chrome 에서 동작하지 않는다! 따라서 공식 문서, 블로그 등을 참고할 때 Manifest V3 를 기준으로 진행해야 한다.

1) Manifest 만들기

extension은 manifest파일로부터 시작된다. extension 개발을 시작하기 위해 아래처럼 manifest.json 파일을 생성한다.
// manifest.json { "name": "Getting Started Example", "description": "Build an Extension!", "version": "1.0", "manifest_version": 3 }
JSON

2) extension 을 chrome 브라우저에 올려보기

1.
chrome 브라우저에서 chrome://extensions 로 이동
2.
Developer mode를 활성화
3.
압축해제된 확장프로그램을 로드합니다 혹은 Load unpacked를 클릭(이때, manifest.json 파일이 들어있는 디렉토리를 선택)

3) extension 역할 및 script의 동작 위치 확인하기

extension에서 사용할 수 있는 script는 크게 3가지가 있다.
1.
chrome의 extension 바에서 볼 수 있는 popup script
2.
extension이 활성화 될 때 함께 활성화 되는 service worker script
3.
현재 탭에 띄워진 문서에 주입할 수 있는 content scripts
각 script들에 대해 자세히 알아보자

1) service worker

예제에서는 가장 먼저 background 블록을 추가하고 있다. service worker script는 아래와 같이 추가할 수 있다.
{ "name": "Getting Started Example", "description": "Build an Extension!", "version": "1.0", "manifest_version": 3, "background": { "service_worker": "background.js" } }
JSON
실제로 추가한 코드를 보면 큰 역할을 하지 않는다. chrome storage sync를 활용해서 미리 코드에 작성해둔 정적 자원을 브라우저에 저장한다.
// background.js let color = '#3aa757'; chrome.runtime.onInstalled.addListener(() => { chrome.storage.sync.set({ color }); console.log('Default background color set to %cgreen', `color: ${color}`); });
JavaScript
chrome storage sync 와 chrome storage local 의 차이점을 알아보면 좋다.
service worker는 브라우저 백그라운드에서 특정 이벤트를 감지할 때에만 동작하기 때문에 효과적으로 프로그램을 설계할 수 있다. service worker를 활용해 개발을 진행할 때 가장 주의해야할 점은, service worker의 script는 계속 실행되고 있는게 아니란걸 인지하는 것이다. service worker는 필요할 때만 동작하고 작동하고 있지 않을 때에는 비활성화 상태가 된다!
그렇기 때문에 service worker의 script에는 대부분 eventListener가 들어간다. extension에서 사용되는 다양한 eventListener는 다음 포스팅에서 알아보자.

2) popup(action script)

popup script는 말그대로 popup 형태로 동작하는 하나의 창에서 동작하는 script이다. 사진처럼 Chrome 브라우저 우측 상단에 extension list가 보이고 해당 extension을 클릭했을 때 보이는 것이 바로 popup이다. 공식 문서에서는 action script라고 소개하고 있는데 popup이 좀 더 직관적이라 아래부터는 popup이라고 하겠다!
manifest.jsonaction에서 popup과 관련된 파일들을 추가할 수 있다.
// manifest.json { ... "action": { "default_title": "...", "default_popup": "popup.html" }, }
JSON
popup script를 사용하는 방법은 아주 간단하다! 기존 HTML에 script를 삽입하는 것처럼 작성한 js파일을 script 태그로 넣어주면 된다.
<!DOCTYPE html> <html> <head> <script src="popup.js" type="module"></script> </head> <body> <button id="hide">hide</button> <button id="update">update</button> </body> </html>
HTML
이 popup을 활용해서 사용자로부터 입력을 받거나 사용자에게 UserInterface를 제공할 수 있다. extension에서 사용자와 상호작용이 필요하다면 popup을 활용하면 된다.

3) content scripts

content scripts는 아래와 같이 정의하고 있다. content scripts에 대해 자세히 공부하고 싶다면 아래 공식 document를 참고하면 된다.
Content scripts are files that run in the context of web pages. By using the standard Document Object Model(DOM), they are able to read details of the web pages the browser visits, make changes to them, and pass information to their parent extension.
또 아래와 같은 설명이 추가로 이어진다.
Content scripts live in an isolated world, allowing a content script to make changes to its JavaScript environment without conflicting with the page or other extensions' content scripts.
An isolated world is a private execution environment that isn't accessible to the page or other extensions. A practical consequence of this isolation is that JavaScript variables in an extension's content scripts are not visible to the host page or other extensions' content scripts. The concept was originally introduced with the initial launch of Chrome, providing isolation for browser tabs.
Content Scripts란, 완전하게 독립적으로 존재하며 web page의 context내에서 동작하는 script이다.
Content Scripts는 위의 script들에 비해 설명이 길었다. 이유는, 다른 scripts들과 달리 webpage의 context에 의존하기 때문에 개발시 많은 부분을 고려해야 한다. 거의 모든 API에 접근할 수 있는 popup(action) scripts와 달리 context scripts는 접근할 수 있는 API도 제한적이다. Content Scripts가 직접적으로 접근할 수 있는 API는 아래와 같다.
또한 브라우저만 동작하면 실행되는 다른 script들과 달리 webpage context에 따라 적용되지 않을 수 있다. 예시로 chrome:// 로 시작하는 모든 위치에 대해 content scripts는 적용되지 않는다. 하지만 popup에는 접근할 수 있다.
content scripts를 추가하기 위해서는 아래와 같이 manifest.json에 추가해주어야 한다.
{ "name": "My extension", ... "content_scripts": [ { "matches": ["https://*.nytimes.com/*"], "css": ["my-styles.css"], "js": ["content-script.js"] } ], ... }
JSON
예시에서 보여주는 코드는 아래와 같습니다. 만약 현재 탭에서 접근하고 있는 웹페이지의 코드가 아래와 같다면,
<html> <button id="mybutton">click me</button> <script> var greeting = "hello, "; var button = document.getElementById("mybutton"); button.person_name = "Bob"; button.addEventListener("click", () => alert(greeting + button.person_name + ".") , false); </script> </html>
HTML
아래와 같은 inject scripts를 추가할 수 있다.
// content-script.js var greeting = "hola, "; var button = document.getElementById("mybutton"); button.person_name = "Roberto"; button.addEventListener("click", () => alert(greeting + button.person_name + ".") , false);
JavaScript
만약 webpage의 특정 요소들에 접근하고 싶거나 webpage에 원하는 형태의 element를 주입하고 싶다면 content scripts를 활용해서 개발을 진행하면 된다.

Manifest V3

크롬 extension에는 manifest.json파일이 항상 필요하다. 이 파일에는 extension에 필요한 자원들에 대한 정보가 모두 들어가 있다. 기본적인 형태는 아래 링크에서 볼 수 있다.
2020년 11월 Chrome에서는 Manifest V3를 도입했다. 추후 2023년 부터는 Manifest V2로 개발된 extension은 Chrome브라우저에서 실행할 수 없다.
하지만 웹상에 떠도는 대다수의 정보들은 Manifest V2인 경우가 많다. 그래서 V2와 V3에서 가장 신경써야할 차이점에 대해서 적어보았다.

MV3 핵심 기능

Chrome에서는 아래 사항들을 Manifest V3의 핵심 기능이라고 소개하고 있다.
Service workers replace background pages.
Network request modification is now handled with the new declarativeNetRequest API.
Remotely hosted code is no longer allowed; an extension can only execute JavaScript that is included within its package.
Promise support has been added to many methods, though callbacks are still supported as an alternative. (We will eventually support promises on all appropriate methods.)
A number of other, relatively minor feature changes are also introduced in Manifest V3.

MV2 → MV3 키워드 변경점 및 추가된 키워드

Version 명시를 3으로 변경해주어야 한다.
// Manifest V2 { "manifest_version": 2, ... }
JavaScript
// Manifest V2 { "manifest_version": 3, ... }
JavaScript
Manifest V3에서는 background를 service worker를 사용하도록 변경되다. Manifest V3는 여러개의 background script를 지원하지 않는다. 필요시 type을 지정해서 선택적으로 module로 코드를 가져올 수 있다.
// Manifest V2 { ... "background": { "scripts": [ "backgroundContext.js", "backgroundOauth.js" ], "persistent": false }, ... }
JavaScript
// Manifest V3 { ... "background": { "service_worker": "background.js", "type": "module" //optional } ... }
JavaScript
Manifest V3에서는 기존의 permissions와 optional_permissions에 작성하던 host permissions를 분리해서 작성하여야 한다.
// Manifest V2 { ... "permissions": [ "tabs", "bookmarks", "http://www.blogger.com/", ], "optional_permissions": [ "unlimitedStorage", "*://*/*", ] ... }
JavaScript
// Manifest V3 { ... "permissions": [ "tabs", "bookmarks" ], "optional_permissions": [ "unlimitedStorage" ], "host_permissions": [ "http://www.blogger.com/", ], "optional_host_permissions": [ "*://*/*", ] ... }
JavaScript
패턴을 "host_permissions"로 이동해도 Content scripts에는 영향을 미치지 않는다. Content scripts에서는 "content_scripts.matches" 아래에 작성하여야 한다.
문자열로 작성되던 기존 V2와는 달리 Manifest V3에서는 객체 형태로 작성된다.
// Manifest V2 { ... "content_security_policy": "..." ... }
JavaScript
// Manifest V3 { ... "content_security_policy": { "extension_pages": "...", "sandbox": "..." } ... }
JavaScript
기존에 browser_action과 page_action이 분리되어있 것들이 action으로 통합되었다. API도 action으로 통합되었다.
// Manifest V2 // manifest.json { ... "browser_action": { ... }, "page_action": { ... } ... } // background.js chrome.browserAction.onClicked.addListener(tab => { ... }); chrome.pageAction.onClicked.addListener(tab => { ... });
JavaScript
// Manifest V3 // manifest.json { ... "action": { ... } ... } // background.js chrome.action.onClicked.addListener(tab => { ... });
JavaScript
기존 path file들에 대한 배열에서 object list로 변경되었다. 따라서 조건에 따라 적절한 자원들을 사용하도록 설정할 수 있다.
// Manifest V2 { ... "web_accessible_resources": [ RESOURCE_PATHS ] ... }
JSON
// Manifest V3 { ... "web_accessible_resources": [{ "resources": [RESOURCE_PATHS], "matches": [MATCH_PATTERNS], "extension_ids": [EXTENSION_IDS], "use_dynamic_url": boolean //optional }] ... }
JSON
Manifest V3에서 Service worker를 사용하도록 변경되면서 바뀐 내용들을 아래와 같이 정리하고 있다.

Chrome extension에서 React + Typescript로 개발하기

vanilla js로 extension을 열심히 개발하던 중, typescript가 너무 필요해졌다. 정적 타입 지정을 통해 개발 과정에서 디버깅시간을 조금 더 줄이고 싶었다. 구글에 검색을 하던 중, React와 Typescript를 활용해서 chrome extension을 개발하는 boilerplate 레포지토리를 발견했다!! 야호!
react-chrome-extension-boilerplate
JasonXian
안그래도 vanilla js에서 상태관리에 어려움을 느끼던 중이라 바로 해당 boilerplate를 활용해 리팩토링을 진행했다.
구조는 생각보다 간단했다. webpack.common.js 파일에서 entry file들을 지정해주면 해당 파일들을 활용해서 build 파일을 생성한다. 나머지 package와 tsconfig설정은 일반적인 React 프로젝트와 동일하게 설정했다.
// webpack.common.js module.exports = { entry: { popup: path.resolve('src/popup/popup.tsx'), background: path.resolve('src/background/background.ts'), contentScript: path.resolve('src/contentScript/contentScript.tsx'), }, ... }
JavaScript
빌드를 하고나면 아래와 같이 루트에 dist 디렉토리가 생기게 된다. 해당 디렉토리를 사용해 위 설명처럼 extension에 추가해주면 끝이다
// after build root ㄴ dist ㄴ src
JavaScript

Reference