Chrome Extension 프로젝트 세팅 (React, Typescript, Webpack)

2023. 1. 15. 21:50프로그래밍/Chrome Extension

우선 CRA 없이 제로 베이스에서 해보기로 했다.


프로젝트 생성

weekly-pang
ㄴ node_modules
ㄴ public
   ㄴ index.html
ㄴ src
   ㄴ index.js
ㄴ package-lock.json
ㄴ package.json
ㄴ webpack.config.js

프로젝트 하이어라키는 다음과 같다.

mkdir weekly-pang
cd weekly-pang
npm init

// 기타 파일 생성은 알아서...

웹팩 설정

우선 src/index.js 파일을 생성한다.

// index.js

const a = ["a"];

console.log(a);

webpack.config.js를 생성해준다.

// webpack.config.js

const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "main.js",
    path: path.resolve(__dirname, "dist"),
  },
};

그 다음, webpack을 설치해준다.

npm i webpack webpack-cli -D

webpack을 실행한다.

npx webpack

결과물

// dist.main.js

console.log(["a"]);

webpack 추가 설정 (css, ts, react, svg)

우선 테스트를 위해 src/index.jssrc/index.tsx로 바꾼다.
(본인이 타입 스크립트를 안 쓴다면 바꿀 필요는 없다)

// src/index.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import logo from "./logo.svg";

const rootElement = document.getElementById("root");

if (!rootElement) throw new Error("Failed to find the root element");

const root = ReactDOM.createRoot(rootElement);

root.render(
  <React.StrictMode>
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
      </header>
    </div>
  </React.StrictMode>
);

추가적으로 svg를 해석할 수 있는 타입을 선언해준다. (custom.d.ts)

declare module "*.svg" {
  const content: string;
  export default content;
}

svg파일을 string 타입으로 해석할수 있도록 해줬다.

테스트를 위해 index.csslogo.svgsrc폴더에 만들어준다.
index.htmlpublic폴더에 만들어준다.
(아무 svg를 써도 좋다)

// src/index.css

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
    "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
    monospace;
}

.App {
  text-align: center;
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
// public/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <title>Weekly pang</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <script src="main.js"></script>
  </body>
</html>

그 다음으로 할 일은 webpack 로더를 설치하는 것이다.
로더란, 특정한 파일 형식을 해석하기 위한 일종의 파일 해석 외주(또는 아웃소싱)와 같다고 보면 된다.
예를 들어 js밖에 모르는 웹팩이 svg를 해석하기 위해 file-loader에 외주를 주는 것이다.

npm i css-loader file-loader style-loader @swc/core swc-loader -D

다음은, 리액트를 설치해준다.

npm i react react-dom
npm i @types/react @types/react-dom -D

다음은 로더가 준비되었으니 webpack을 설정해주면 된다.

// webpack.config.js

const path = require("path");

module.exports = {
  entry: "./src/index.tsx",
  output: {
    filename: "main.js",
    path: path.resolve(__dirname, "dist"),
  },
  mode: "production",
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /(node_modules)/,
        use: {
          loader: "swc-loader",
          options: {
            jsc: {
              transform: {
                react: {
                  runtime: "automatic",
                },
              },
            },
          },
        },
      },
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.(png|svg)$/,
        use: [
          {
            loader: "file-loader",
            options: {
              name: "images/[name].[ext]?[hash]",
            },
          },
        ],
      },
    ],
  },
  resolve: {
    extensions: ["ts", "tsx", "js", "jsx", "json"],
  },
};

typescript니까 tsconfig.json도 추가해준다.

// tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

그 다음 webpack으로 번들링해준다.

npx webpack

요런 결과물이 나온다.


Script 만들기

자, 결국 웹페이지는 index.html이 있어야한다.
하지만 현재는 dist폴더에 index.html이 없기 때문에,
빌드할 때마다 public/index.htmldist/index.html로 복사해야한다.

package.json을 수정하자.

{
  ...
 
  "scripts": {
    "build": "npx webpack && cp public/index.html dist/index.html",
    "test": "echo \"Error: no test specified\" && exit 1"
  }, 
  
  ...
}

build 스크립트를 실행해보자.

npm run build


결과물

dist/index.html을 열어보자.
크롬에 드래그해도 좋고, 더블클릭해도 된다.

본인이 넣은 logo.svg가 돌고있으면 굿.


chrome extension 설정

열심히 빌드를 하다보니 내가 chrome extension설정을 하는지 까먹었다.

manifest.json을 만들어주자.

// public/manifest.json

{
  "name": "Weekly Stamp",
  "description": "매일매일 도장을 찍어보세요!",
  "version": "1.0",
  "manifest_version": 3,
  "chrome_url_overrides": {
    "newtab": "index.html"
  },
  "action": {
    "default_title": "주간 도장 팡팡",
    "default_popup": "index.html"
  }
}

새 탭이 열리면 우리가 만든 index.html이 열리도록 했다.

이 친구도 마찬가지로 public에서 dist로 옮겨주도록 한다.
package.json을 수정해주자.

// package.json

{
  ...

  "scripts": {
    "build": "npx webpack && cp -a public/. dist",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

  ...
}

다시 빌드 프로세스를 거쳐준다.

npm run build

이제 이 폴더를 크롬 익스텐션에 올려주자.

chrome://extensions/

주소창에 위 주소를 넣고 들어간다.
개발자 모드를 켜고, 압축해제된 확장 프로그램을 설치합니다를 누른다.
이후에는 dist 폴더를 선택해준다.

새 탭을 열면 아래와 같이 우리가 만든 페이지가 보인다.