ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Chrome Extension 개발 이제 여기에 react를 끼얹은...
    javascript 2021. 2. 12. 01:48

    검색검색을 하다가 비교적 최신의, 귀여운 react로 만든 chrome extension 프로젝트를 찾았다.

     

    이 문서는 아래 아티클에 대한 번역 + 따라가는 과정이 담겨있습니다.

    https://react.christmas/2020/12

    Chrome Extension의 기초

    extension에는 세가지 메인 컴포넌트가 있습니다. popup, content script, background script입니다.

    - Popup은 extension icon이 눌리면 뜨는 콘텐츠입니다.

    - Content script는 Javascript나 CSS로 된 코드이고, 현재 웹 페이지의 context에 주입(inject)됩니다.

    - Background script는 브라우저에서 분리된 인스턴스로 실행되는 javascript 코드입니다. 그리고 거의 이벤트를 listening하고, 브라우저를 다루기 위해서 사용됩니다.

     

    (저) 아티클에서는 react와 typescript를 이용하여 chrome extension을 만들어 보도록 하겠습니다.

     

    프로젝트 셋팅

    create-react-app으로 리액트 프로젝트를 시작하면 쉽지만, chrome extension으로 개발하기에는 좀 까다로운 편이어서 저 아티클에서는 Webpack을 기반으로 리액트 프로젝트를 구상합니다. 

    미리 만든 보일러플레이트를 기반으로 우선 디렉토리를 구상합니다.

    $ npx degit https://github.com/sivertschou/react-typescript-chrome-extension-boilerplate.git#christmas snow-extension

    npx degit 명령어는 clone을 하고, 그 repository의 git 파일들을 삭제합니다. 따라서 위 git repo의 파일을 순수하게 (기존 git dependency 없이 사용할 수 있습니다)

     

    이 프로젝트는, react의  build 결과 -> dist 폴더를 extension으로 업로드 하도록 구성되어 있습니다.

    그래서 일단 manifest를 조작할 수 있는 public부터 보시죠

     

    public

    public 폴더에는 extension의 meta data가 되는 애들이 들어갑니다.

    icon 이미지 파일들, popup으로 띄울 - react의 mounting point인 popup.html, extension의 config가 되는 manifest.json

    manifest.json이 extension개발의 가장 중요한 파일이므로 이 파일을 먼저 봅시다.

     

    우선, metadata가 되는 name, description, manifest_version, version을 설정합니다.

    (여기서는 manifest_version을 2로 설정했네요)

    그리고 아이콘은 적어도 16, 48, 128에 대해서는 제공해야만 합니다. 

    // public/manifest.json
    {
      "name": "Snow Extension",
      "description": "Chrome Extension for making it snow in your browser!",
      "manifest_version": 2,
      "version": "1.0.0",
      "icons": {
        "16": "icon16.png",
        "48": "icon48.png",
        "128": "icon128.png"
      },
      ...

     

    extension을 클릭 할 때 띄울 popup에 대한 설정은 browser_action이나 page_action으로 가능합니다.

    page_action은 특정 페이지에서만 뜨도록 설정되는데, 그에 반해 browser_action으로 설정하면 어떤 페이지든 브라우저에서 popup을 띄울 수 있으므로, 여기서는 browser_action으로 popup에 대한 설정을 하겠습니다.

     

    browser_action의 default_icon은 브라우저에서 클릭 가능한 아이콘의 이미지를 의미합니다. 우리가 클릭하는 바로 그 아이콘 말이죠.

    그리고 defuault_popup 은 그 아이콘이 클릭될 때 뜰 popup에 대한 설정 정보입니다. 여기서는, React 코드를 실행할 그 popup.html이죠.

    // public/manifest.json
    
      ...
      "browser_action": {
        "default_icon": {
          "16": "icon16.png",
          "48": "icon48.png"
        },
        "default_popup": "popup.html"
      }
      ...

     

    background script는 아래와 같이 설정합니다.

    .ts가 아닌 .js로 쓴 것은 이 script는 그대로 browser에서 실행되는 것이어서, ts로는 실행이 불가합니다. 우리는 .ts로 파일을 만들더라도 이게 js로 빌드된 결과물이 들어가야하죠.

    persistent: false 설정은, 이 background script를 항상 불러올지, idle일땐 업로드하지 않을지에 대한 설정입니다. 여기서는 false로 하면서 extension이 idle 상태일 때 background script가 로딩되지 않게 설정하였습니다.

    // public/manifest.json
    
      ...
      "background": {
        "scripts": [
          "background.js"
        ],
        "persistent": false
      },
      ...

     

    content_scripts 에서는 "특정" 웹 페이지에서 실행될 script를 설정합니다. matches를 통해 해당 script가 주입되고, 실행 될 페이지 주소를 지정할 수 있고, 여기서는 모든 페이지로 설정했습니다. background script 설정과 같이 content_scripts로 지정하는 js 역시 .js 포맷이어야 합니다.

    // public/manifest.json
    
      ...
      "content_scripts": [
        {
          "matches": [
            "*://*/*"
          ],
          "js": [
            "content.js"
          ]
        }
      ]
    }

     

    src 둘러보기

    우리가 필요할 파일은 popup.html에 들어갈 popup.js(보통의 react 프로젝트에서의 index.js), background.js, content.js 입니다.

    물론 우리는 이 프로젝트를 ts로 개발 할 것이기 때문에, 빌드 결과물이 그렇게 나와야겠죠. - 이 부분은 webpack의 도움을 받읍시다.

     

    popup.tsx로 index.js와 같은 구성을 하고 - document의 특정 id에 <App/>을 root로 두는 React component를 뿌립니다. - 그 이하 App의 구성 component들이 있겠죠. 여기서는 App.tsx로 충분했습니다. 따라서 다음과 같은 파일 구성이 되었습니다.

    - App.css
    - App.tsx
    - background.ts
    - content.ts
    - custom.d.ts
    - logo.svg
    - popup.css
    - popup.tsx

     

    Content script 작성하기

    현재 content script는 console.log를 찍는 한 줄 뿐입니다. 지금대로면 모든 페이지 진입시마다 content.ts가 실행되고, console log가 찍히는 것을 볼 수 있습니다.

    console.log("Hello from content script!")

    여기다가 눈이 내리게 하는 코드를 끼얹으면 끝! 이 이하는 코드 복붙으로 완성되어서 넘어갑니다. - 리액트 관련된건 넘어갔으니!

     

    Webpack 설정 보기

    여기서는 3개의 entry가 필요합니다. 각각 popup.tsx, content.ts, background.ts 이죠. 그리고 각각 그 이름으로 된 js 파일로 output 되어야 합니다.

    const config = {
      entry: {
        popup: path.join(__dirname, "src/popup.tsx"),
        content: path.join(__dirname, "src/content.ts"),
        background: path.join(__dirname, "src/background.ts"),
      },
      output: { path: path.join(__dirname, "dist"), filename: "[name].js" },

    module로는 js babel-loader, css loader, ts loader, svg loader, png loader가 들어갔습니다.

      ...,
      module: {
        rules: [
          {
            test: /\.(js|jsx)$/,
            use: "babel-loader",
            exclude: /node_modules/,
          },
          {
            test: /\.css$/,
            use: ["style-loader", "css-loader"],
            exclude: /\.module\.css$/,
          },
          {
            test: /\.ts(x)?$/,
            loader: "ts-loader",
            exclude: /node_modules/,
          },
          {
            test: /\.css$/,
            use: [
              "style-loader",
              {
                loader: "css-loader",
                options: {
                  importLoaders: 1,
                  modules: true,
                },
              },
            ],
            include: /\.module\.css$/,
          },
          {
            test: /\.svg$/,
            use: "file-loader",
          },
          {
            test: /\.png$/,
            use: [
              {
                loader: "url-loader",
                options: {
                  mimetype: "image/png",
                },
              },
            ],
          },
        ],
      },

    아래 설정은 참고 할만 한 것 같습니다

      resolve: {
        extensions: [".js", ".jsx", ".tsx", ".ts"],
        alias: {
          "react-dom": "@hot-loader/react-dom",
        },
      },
      devServer: {
        contentBase: "./dist",
      },
      plugins: [
        new CopyPlugin({
          patterns: [{ from: "public", to: "." }],
        }),
      ],

     

     

     

    여기까지 코드를 바탕으로 react, typescript로 chrome extension 개발하는 틀을 잘 잡을 수 있을 것 같군요 :b

    댓글

Designed by Tistory.