[이슈] 크롬 익스텐션 보일러 플레이트 - content script react

2022. 7. 3. 16:54취미로 하는 개발

 

GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript

Chrome Extension Boilerplate with React + Vite + Typescript - GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript

github.com

 

한 번 만들어 보고 싶어서 만든 보일러 플레이트의 stars가 70을 넘었다. fork는 10개가 되었다.

다른 오픈소스 프로젝트에 비하면 초라한 개수지만, 뭔가 일면식도 없는 전 세계의 개발자들이 내 코드를 사용한다는 생각이 드니 책임감과 부담감이 좀 생기는 것 같다.

 


 

오늘은 얼마 올라온 이슈를 해결한 내용을 정리해보려고 한다.

 

Content Script cannot use React · Issue #7 · Jonghakseo/chrome-extension-boilerplate-react-vite

Hi, I have the content script that will create a div element and use React render to that div then append it into the document.body, but when content script loaded it show this error Cannot use imp...

github.com

콘텐츠 스크립트에서 react를 사용할 수 없다고 한다.

 

콘텐츠 스크립트는 크롬 익스텐션에 추가할 수 있는 파일로, 사용자가 보고 있는 페이지에 뭔가 동작을 하기 원할 경우 사용하는 파일이다.

adBlock처럼 사용자의 화면에 실질적인 변화를 일으킬 때 주로 사용된다.

 

보일러 플레이트의 contentScript.ts 에서 react를 호출하면 'Cannot use import statement outside a module'라는 에러가 발생하게 되는데, 이는 크롬 내에서 콘텐츠 스크립트의 esm(EcmaScript Module)의 로드를 허용하지 않기 때문이다.

 

일반적으로 html에서는 esm을 불러오기 위해서 script tag에 type="module"을 넣는데, 콘텐츠 스크립트는 Popup, Newtab과 같이 뷰(html)가 제공되지 않고 background처럼 js 모듈을 실행시키는 방식으로 동작한다.

 

혹시 크롬 익스텐션의 구성에 대해 기본적인 내용이 궁금하다면 공식 문서를 참고하면 좋다.

 

 

Getting started - Chrome Developers

Step-by-step instructions on how to create a Chrome Extension.

developer.chrome.com

 

아무튼, 해당 이슈를 찾아보니 해결 방법이 있었다.

 

1. 콘텐츠 스크립트에서 현재 탭의 dom 내부에 script를 삽입하여 실행시키고 싶은 esm을 로드

2. esm 말고 common js로 컴파일해서 로드

 

일반적으로는 1번 방법을 많이 사용하는 것 같고, common js로 컴파일해서 사용하면 vite의 이점도 없어지는 셈이라 1번 방법을 통해 해결했다.

 

 

먼저 컨텐츠 스크립트에서 로드할 모듈을 contentView라는 이름으로 분리했다.

contentView는 일반적인 react 프로젝트처럼 구성하고, 콘텐츠 스크립트에서 스크립트에 끼워 넣는 방식을 사용했다.

 

try {
  console.log("content loaded");
  injectContentViewScript();
} catch (e) {
  console.error(e);
}

/**
 * @description
 * Chrome extensions don't support modules in content scripts.
 * Bundling solves this problem, but not in the case of React, which is imported as JSX automatic conversion.
 * So, I created a tag that brings the 'content view' from the 'content script', and implemented it by bypassing the tag in the form of inserting it into the html.
 * If there is a better implementation (bundling option or other method, etc.), please suggest it.
 */
function injectContentViewScript() {
  const script = createContentViewScript();
  const target = getTargetElement();

  target.insertBefore(script, target.lastChild);
}

function createContentViewScript(): HTMLScriptElement {
  const script = document.createElement("script");
  script.setAttribute("type", "module");
  script.setAttribute(
    "src",
    chrome.runtime.getURL("src/pages/contentView/index.js")
  );
  return script;
}

function getTargetElement(): HTMLElement {
  return (
    document.head ||
    document.getElementsByTagName("head")[0] ||
    document.documentElement
  );
}

하면서 뭔가 해키해키한 방법이라 찜찜하다는 생각이 들었다;;

주석으로 남겨놓기도 했는데, 보다 좋은(정석에 가까운) 방법이 나오면 좋을 것 같다.

 

어쨌건 이슈는 잘 해결된 것 같아서 귀찮았지만 살짝 좋았다.