본문 바로가기

프로그래밍 일반/프론트엔드

[react] cra, vite 없이 직접 리엑트 프로젝트 세팅하기1(webpack) - 바벨, 타입스크립트, css 전처리, 환경변수, 정적 파일

목차
프로젝트 기본 구조
웹팩
바벨
타입스크립트
CSS전처리
환경변수

정적 파일1(image, svg)
정적 파일2(font)
라우팅
완성파일

chat gpt가 제공한 웹팩 그림1

 

일반적으로 리엑트 프로젝트는 cra나 vite 등의 cli로 자동 세팅이 가능하다.

이 방식은 학습 단계에서 간편하지만, 실무에서 scss나 타입스크립트, 바벨 등 다양한 환경 세팅을 해야할 때 배경 지식이 전무하여 문제가 생길 수 있다. 따라서 프로젝트 세팅을 직접 해보기로 했다. 도구로 webpack을 사용한 이유는 웹팩의 설정방식이 복잡하지만, 빌드 방식의 커스터마이징이 상세하여 실무에서 사용 비중이 높기 때문이다.

 

프로젝트 기본 구조


1. 새 프로젝트를 만들고 npm과 git 세팅을 초기화한다.

 

npm 초기화

npm init -y

 

git 초기화

git init

 

만약 .gitignore이 추가되지 않았다면 해당 파일을 만들고 아래처럼 설정한다.

node_modules
.env
.env.local
.env.development
.env.production

 

2. 디렉토리 구조를 세팅한다.

/public/index.html을 생성한다.

public 폴더는 웹 어플리케이션의 정적 자원 중 webpack의 빌드 프로세스와 크게 관련 없이 직접 클라이언트에 제공되는 파일들이 위치한다.(ex: 파비콘, 정적 컨텐츠, robots.txt, manifest.json, JSON, txt 등)

index.html은 webpack으로 빌드 시 사용될 html플릿으로, 정적으로 추가 가능한 폰트, css, cdn 등이 있다면 추가할 수 있다.

 

index.html 코드

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>weppack + react</title>
  </head>
  <body>
    <!-- 리엑트 앱의 root가 될 html-->
    <div id="react-root"></div>
  </body>
</html>

 

/src/index.js를 생성한다.

src 폴더는 webpack의 빌드 프로세스를 거쳐야 하는 파일들이 위치한다. js파일, css 파일, 프로세싱이 필요한 컨텐츠 자원 등이 포함된다.

index.js는 리엑트 프로젝트의 진입점이 될 파일이다.

 

index.js 코드

import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";

const container = document.getElementById("react-root");
const root = createRoot(container);

root.render(<App />);

 

3. 의존성 설치(리엑트)

아래 커맨드로 리엑트를 설치한다.

npm install react react-dom

 

웹팩


웹팩의 기본 역할은 소스 코드를 하나의 파일로 묶어주는 번들링이다. 현대 프론트엔드 개발에서 번들링은 브라우저가 다운로드받아야할 js 파일 갯수를 줄여서 파일간 의존성 문제와 http 오버헤드에 따른 속도 저하 문제를 개선하기 위해 필수적이다. 이 역할은 웹팩말고도 rollup이나 vite도 있으나, 다양한 커스터마이징이 용이하고 한국 내 레퍼런스가 풍부한 웹팩을 사용하기로 하였다.

주의
해당 설정은 웹팩 버전5를 사용했다. 따라서 다른 버전에서는 동작방식, 특히 package.json에서의 명령어 설정에 차이가 있을 수 있으니 주의바란다.

 

1. webpack 의존성을 설치한다.

npm install -D webpack webpack-cli webpack-dev-server html-webpack-plugin

 

각 의존성의 역할은 다음과 같다.

  • webpack
    • 리엑트 프로젝트 환경 설정의 핵심 역할로 번들링과 트리 셰이킹, asset 파일의 빌드 방식, 바벨이나 scss 등 전처리 플러그인의 연결을 담당한다
  • webpack-cli
    • 웹팩 명령어를 사용할 수 있게 한다.
  • webpack-dev-server
    • 개발 모드로 로컬 서버를 띄워 번들링 이전의 js 파일로 빠르게 테스트할 수 있다.
  • html-webpack-plugin
    • 번들링 시, 템플릿으로 삼을 html을 설정 가능하다.
      • 템플릿 html 파일에 <script src="./bundle.js" defer/>가 추가되어 번들링된 js 파일과 자동으로 연결된다.

 

2. ./webpack.config.js 를 생성한다.

webpack.config.js는 웹팩의 번들링 방식을 설정하는 중요한 파일이다.

기본적인 파일 설정은 다음과 같다.

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = () => {
  const isDevelopment = process.env.NODE_ENV !== "production";

  return {
    entry: "./src/index.jsx", // 애플리케이션의 진입점. 번들링 시 가장 먼저 시작하는 파일

    // 번들링 결과물
    output: {
      filename: "bundle.js", // 파일 이름
      path: path.resolve(__dirname, "dist"), // 파일 저장 경로
      publicPath: "/", // 브라우저에서 빌드 파일을 요청할 때 서버의 경로. 정적 파일을 찾을 때 중요
      clean: true, // 빌드 시, 이전 빌드 파일을 삭제함. 불필요한 파일이 누적되는 걸 방지
    },

    // 모듈의 경로나 파일 타입을 해석하는 방식을 결정
    // import나 require로 모듈 삽입 시, 경로를 정의하는 방식
    resolve: {
      extensions: [".jsx", ".js"], // 확장자가 생략된 경우 가져올 모듈의 파일 확장자 지정
      modules: ["src", "node_modules"], // 모듈을 찾을 때 참조할 디렉토리의 목록을 정의. 자동으로 절대경로로 src와 node_modules부터 사용가능함

      // 특정 경로에 대한 절대경로를 별칭으로 설정
      // ex: import publicImage1 from @public/image/publicImage1.png;
      alias: {
        "@src": path.resolve(__dirname, "src"),
        "@asset": path.resolve(__dirname, "src", "asset"),
        "@public": path.resolve(__dirname, "public"),
      },
    },
    // 개발용 로컬 서버
    devServer: {
      port: 3000, // 포트번호
      hot: true, // hmr 기능 활성화
      static: path.resolve(__dirname, "public"), // 개발 서버에서 정적 파일을 참조하는 경로
      allowedHosts: "all", // 외부 도메인에서의 접속 허용
      compress: true, // gzip 압축
      host: "0.0.0.0", // ip를 이용해 외부 네트워크 인터페이스에서 접속 허용
    },

    // eval-source-map: 번들 파일 내에 소스맵이 포함. 디버깅 시 원본 소스에 가까운 코드를 제공하나 무거우므로 성능이 느림 => 개발에 적합
    // source-map: 디버깅 시 번들 파일과 별도의 소스 맵을 제공함. eval-source-map보다 원본이 변형되었으나 성능이 빠름 => 배포에 적합
    devtool: isDevelopment ? "eval-source-map" : "source-map",

    plugins: [
      // 템플릿으로 사용할 html 파일 설정. 해당 html에 정적 리소스 추가 가능
      new HtmlWebpackPlugin({
        template: "public/index.html",
      }),
    ],
  };
};

 

추가 설명

entry

애플리케이션의 진입점으로 번들링 시 가장 먼저 시작하는 파일을 설정한다.

 

output

번들링 시 산출되는 결과물과 관련된 설정을 한다.

 

resolve

웹팩은 기본적으로 모듈의 확장자 명시가 없을 시 모든 import 모듈을 json 파일 혹은 js파일로 간주하고 찾는다.

그래서, import 시 두 형식은 굳이 확장자를 명시하지 않아도 되지만, jsx류의 파일은 그렇지 않으므로 extensions속성으로 jsx 확장자를 자동으로 인식하도록 한다. 이 때 해당 속성 값인 배열 내 순서에 따라 확장자를 검색하므로, 자주 사용하는 형식을 우선으로 적는 것이 좋다.

또한, 경로에서도 상대 경로 대신 절대 경로를 사용할 수 있도록 module과 alias 속성을 제공한다.

  • module | 기본적으로 모듈을 찾는 디렉토리다. 절대경로로 위치를 명시할 경우, module에 명시된 디렉토리를 순서대로 탐색한다. 자주 사용하는 디렉토리("src", "node_modules")를 명시하자.
  • alias | 특정한 절대경로를 별칭으로 설정한다. 예컨대, public처럼 자주 사용하진 않지만, 사용할 경우 상대 경로로 명시하려면 "../../../" 같은 식으로 경로가 복잡해지는 것들이 있는데, 이럴 때 사용하면 유용하다. 혹은 src절대경로를 명시하여 가독성을 높일 때 좋다.

devServer

로컬 서버로 개발 테스트를 할 시 관련 설정을 할 수 있다. 포트번호, 빌드 이전 정적 파일의 경로, hmr 설정 등을 한다.

 

devtool

디버그용 소스맵의 생성 방식을 결정한다.

소스 맵이란?
소스 맵은 원본 코드와 컴파일 코드 간에 매핑 정보를 제공하는 JSON 파일이다.
개발자가 작성한 소스 코드는 실제 환경(서버, 브라우저)에서 배포되어 작동하는 코드와 크게 차이난다. 왜냐하면 배포 과정에서 트랜스 파일링, 번들링, 최적화 과정을 거치기 때문이다. 따라서 개발자가 디버깅을 하기 위해 특정 코드 부분을 확인해야할 필요가 있는데, 이 때 사용하는 것이 바로 소스 맵이다.
 webpack의 경우, 빌드 시 output폴더 내에 bundle.js.map(번들 파일 이름에 따라 다름)으로 저장된다.

 

module과 plugin에 대해

웹팩의 기본 역할은 단순히 소스 코드의 번들링이다. 하지만, 현대의 개발 실무에서는 번들링 툴이 트랜스 파일링, css 전처리, 파일 최적화 등까지 담당하는데, module과 plugin에 해당 패키지를 추가하여 빌드 과정을 커스터마이징한다.

 

module

빌드 과정에서 특정 파일을 대상으로 추가적인 처리 과정을 설정한다.

바벨, 리엑트 코드 트랜스파일링, css 전처리, 파일 로더 등이 해당한다.

(이 부분에서는 다루지 않았지만, 나중에 다룰 예정이다)

하위 속성은 다음과 같다.

  • test: 처리할 파일의 확장자를 정규 표현식으로 정의한다
  • use: 해당 파일을 처리할 로더를 지정한다.
  • exclude: 처리 대상에서 특정 디렉토리를 제외한다(예: node_modules).

 

plugins

빌드 과정에서 모듈이 처리된 파일에 기능을 추가하거나, 전체 빌드 과정에 기능을 추가한다.

환경변수 주입, html 파일 생성, hmr 설정, micro frontend 모듈 구성, 코드 압축, 빌드 최적화 등을 다룰 수 있다.

 

정리하면 module은 번들링 과정에서 특정 유형의 파일만을 처리하는 기능을 추가하고, plugin은 번들링 전체에 영향을 끼치는 기능을 추가한다.

 

3. package.json에 script 명령어를 설정한다.

개발 서버를 실행하는 커맨드로 배포용 설정을 사용하는 prod와 개발용 설정을 사용하는 dev를 만든다. 이 둘은 일반적으로 환경 변수나 브라우저 등 구동 환경을 고려하여 분기처리한다.

빌드 파일 생성용 커맨드인 build를 생성한다. 서버를 다루는 백엔드가 별도로 있는 경우, build만 하면 충분하다. 반면, 해당 프로젝트에서 백엔드까지 다루는 경우, start 커멘드를 추가해야 한다.(여기서는 전자로 가정하고 start를 추가하지 않았다)

  "scripts": {
    "prod": "NODE_ENV=production webpack serve",
    "dev": "NODE_ENV=development webpack serve",
    "build": "NODE_ENV=production webpack"
  },
  • NODE_ENV 부분은 process.env.NODE_ENV를 설정한다. 이 값은 webpack.config.js에서 개발용 빌드 설정과 배포용 빌드 설정을 분기처리할 때 사용한다. 비슷하게 --mode 플래그를 사용해서 분기처리 가능하다.
  • webpack serve는 웹팩5에서 추가된 기능으로 webpack dev sercver를 실행한다. 이 커맨드의 설정 사항은 설정 파일 내 devServer를 자동 반영한다.
필요 시 추가하는 설정
--config
웹팩 작동에 사용될 config 파일 위치를 명시한다. config파일이 여러 개인 경우 유용하다.
예시 | "webpack --config ./webpack.config.dev.js"

--mode 
빌드나 개발 서버 가동 시, 번들링 코드에 추가적인 설정을 한다. 값은 "none", "development ", "production"이고, 기본 값은 "production"이다. 
코드 압축(minification), 트리 쉐이킹(tree shaking), Dead Code Elimination, 소스 맵 생성, 디버깅 정보, hmr 여부 등을 설정하는데, config.js의 설정으로 오버라이드된다.

 

바벨


바벨은 최신 자바스크립트 소스 코드를 구형 버전의 자바스크립트 코드로 다운그레이드하여 브라우저 호환성을 챙겨준다. 추가로, 여러 모듈을 추가함으로써 트랜스 파일링을 커스터마이징 가능한데, 여기서는 react 코드 변환을 추가할 예정이다.

(만약 타입스크립트를 추가한다면, 타입스크립트 컴파일링 역시 추가 가능하다. 이 부분은 아래에서 다시 다룰 예정이다.)

 

1. 의존성 설치

npm install @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/preset-react babel-loader

 

2. ./.babelrc를 생성한다.

해당 파일은 바벨의 트랜스 파일링을 관리하는 주요 설정파일이다.

{
  "presets": [
    "@babel/preset-env", // 최신 js 코드를 구버전 js 코드로 변환
    // 변환 타겟을 최신형 브라우저에 맞출 때에는 이 설정 사용
    // [
    //   "@babel/preset-env",
    //   { "targets": { "browsers": ["last 2 versions", ">= 5% in KR"] } }
    // ],
    ["@babel/preset-react", { "runtime": "automatic" }] // reactJSX코드를 변환함. runtime:automatic은 React17이상에서 적용되는 기능으로 React를 import하지 않고도 JSX를 사용할 수 있습니다.
  ],
  "plugins": ["@babel/transform-runtime"] // 번들링된 코드에서 자주 사용되는 헬퍼 함수를 직접 작성하는 대신 babel-runtime 패키지에서 가져온다.
}

각 코드의 기능은 다음과 같다.

  • @babel/preset-env | 최신 js 코드를 구버전 js 코드로 변환한다. 만약 target을 설정하면 해당 유저 브라우저에 최적화된 코드를 생성할 수 있다.
  • @babel/preset-react | reactJSX코드를 변환한다. "runtime": "automatic"이 있으면 jsx파일마다 react의 import를 명시하지 않아도 자동으로 추가된다.
  • @babel/transform-runtime | 번들링된 코드에 필요한 일부 헬퍼 함수를 직접 작성하는 대신, 외부 플러그인에서 추가한다. 번들링 시간을 줄이고 번들 코드 자체의 크기를 감소시킨다.

 

3. webpack.config.js에 바벨 모듈(babel-loader)을 추가한다.

아래 module을 추가하면 된다.

위치는 entry이나 output같은 주요 속성과 동일한 계층에 추가하면 순서는 상관없다.

    module: {
      rules: [
        {
          test: /\.(js|jsx)$/, //.js, .jsx 확장자에 대해
          exclude: /node_modules/,
          use: [
          
            {
              loader: "babel-loader", // JavaScript 변환을 위한 Babel 로더
            },
          ],
        },
      ],
    },

 

3. webpack.config.js에 resolve로 tsx, ts를 추가한다.

모듈 경로 해석 시, .tsx, ts 파일을 해석할 수 있도록 추가한다.

    // 모듈의 경로나 파일 타입을 해석하는 방식을 결정
    // import나 require로 모듈 삽입 시, 경로를 정의하는 방식
    resolve: {
      extensions: [".tsx", ".ts", ".jsx", ".js"], // 확장자가 생략된 경우 가져올 모듈의 파일 확장자 지정
      modules: ["src", "node_modules"], // 모듈을 찾을 때 참조할 디렉토리의 목록을 정의. 자동으로 절대경로로 src와 node_modules부터 사용가능함

      // 특정 경로에 대한 절대경로를 별칭으로 설정
      // ex: import publicImage1 from @public/image/publicImage1.png;
      alias: {
        "@src": path.resolve(__dirname, "src"),
        "@asset": path.resolve(__dirname, "src", "asset"),
        "@public": path.resolve(__dirname, "public"),
      },
    },

 

 

타입스크립트


타입스크립트는 프론트엔드 프로젝트에서 필수는 아니지만 거의 필수나 다름없다. 타입 정의를 통해 에러를 방지하므로 유지보수 및 변경에서 큰 도움이 된다. 만약 타입스크립트를 설정할 생각이라면 이 부분도 추가해주자.

 

1. 의존성 설치

npm install --save-dev @babel/preset-typescript @types/react @types/react-dom ts-loader typescript

각 의존성의 역할은 다음과 같다.

  • @babel/preset-typescript
    •  Babel에서 TypeScript 파일을 처리할 수 있도록 해주는 프리셋이다. TypeScript 코드를 JavaScript로 변환하는 데 사용된다.
  • @types/react
    •  React 라이브러리에 대한 TypeScript 타입 정의를 제공한다.
  • @types/react-dom
    • React DOM에 대한 TypeScript 타입 정의를 제공한다.
  • ts-loader
    • TypeScript 파일을 Webpack에서 처리할 수 있도록 해주는 로더이다. TypeScript 컴파일러를 사용하여 TypeScript 코드를 JavaScript로 변환한다.
  • typescript
    • TypeScript 언어 자체를 위한 패키지다.

 

2. ./tsconfig.json를 생성한다.

해당 파일은 ts-loader의 컴파일링 과정에서 엄격한 타입 검사와 다양한 기능, 트랜스 파일링을 설정한다.

 

직접 만들어도 되지만, 아래 커맨드를 사용하면 자동으로 생성된다.

npx tsc --init

 

파일이 생성되면 "jsx": "react-jsx"    이 부분만 추가한다.


// 변경해야 함

완성된 결과

{
  "compilerOptions": {
    "outDir": "./dist",
    "target": "es5", //코드가 컴파일되는 버전 = es5
    "module": "esnext", // 모듈 시스템 = 최신 모듈 시스템
    "jsx": "react-jsx", // react JSX구문을 변환.  react17버전 이상의 기능 지원
    "strict": true, // 엄격한 타입 검사
    "moduleResolution": "node", // 모듈 해석 방식
    "esModuleInterop": true, // esmodule과 commonjs 모듈을 함께 사용할 수 있도록 허용. 특히 commonJS 모듈을 esModule로 가져올 때 유용
    "skipLibCheck": true, //라이브러리 파일의 타입 검사를 건너뜀. 컴파일 시간을 단축함
    "forceConsistentCasingInFileNames": true, // 파일 이름의 대소문자를 일관되게 유지. 서로 다른 운영 체제 간의 호환성을 개선
    "baseUrl": "./",
    "allowJs": true, // js파일을 모듈로 추가할 수 있도록 타입 체킹을 허가한다. 자바스크립트와 타입스크립트를 혼용 시 유용
    "resolveJsonModule": true, // JSON 파일을 모듈처럼 가져올 수 있음.
    // Disallow features that require cross-file information for emit.
    "isolatedModules": true,
    "declaration": true,

    "paths": {
      "*": ["src/*"],
      "@src/*": ["src/*"], // '@src' 별칭을 'src' 폴더와 연결
      "@public/*": ["public/*"], // '@public' 별칭을 'public' 폴더와 연결
      "@asset/*": ["src/asset/*"]
    }
  },

  "include": ["src"] // 검사를 하는 대상
}

 

설정에 대한 부가 설명

  • resolveJsonModule
    • JSON 파일을 js모듈처럼 import할 수 있게 한다. 만약 이 속성이 없으면, json파일을 require로 가져오거나 node 환경이라면 fs로 가져와야하므로, 이 부분이 번거로우면 true로 하는 것을 추천.
  • allowJS
    • 이 속성을 활성화하여 추가되는 .js와 .jsx 파일은 타입 추론이 되지 않는다. 따라서, 이 부분에 타입 검사를 원한다면 .d.ts 파일을 작성해야 한다.

 

절대경로 설정에서 webpack과 tsconfig.json의 차이
지금까지 프로젝트 내 특정경로(./src, ./public)를 절대경로로 설정하기 위해 webpack.config.json의 resolve.alias와 tsconfig.json의 compileOptions.path를 수정했다. 둘의 차이는 뭘까.

webpack.config.json의 절대경로 설정은 빌드 및 런타임에서 실제로 이용하는 경로다.
tsconfig.json의 절대경로 설정은 컴파일 때에만 사용하는 경로다.
따라서, 두 경로설정은 반드시 동일하게 설정해야 한다.

 

 

3. webpack.config.js에 타입스크립트 모듈(ts-loader)을 추가한다.

tsx, ts 같은 타입스크립트 소스코드를 자바스크립트로 컴파일하기 위해 ts-loader를 추가한다.

 

설정 파일 중 module의 변경 부분

  module: {
      rules: [
        {
          test: /\.(ts|tsx|js|jsx)$/, // .ts, .tsx, .js, .jsx 확장자에 대해
          exclude: /node_modules/,
          use: [
            {
              loader: "ts-loader", // TypeScript 로더
              options: {
                transpileOnly: true, // 트랜스파일 이전에 타입 체킹을 수행 // 상황에 따라 유동적 설정
              },
            },
            {
              loader: "babel-loader", // JavaScript 변환을 위한 Babel 로더
            },
          ],
        },
      ],
    },

위 부분에서 options.transpileOnly는 타입스크립트의 컴파일링 이전 타입 검사를 생략할지 결정한다. false로 하면 컴파일 과정에서 타입 체킹에 에러가 날 시, 컴파일링을 취소한다. 반면 true로 하면 타입 체킹 과정을 생략한다.

빠른 프로토타입 개발을 원한다면 true로 하고 최종적인 배포를 준비하는 시점에는 false로 해서 런타임 에러의 가능성을 최대한 줄이자.

타입 체킹 시 주의사항.
tsconfig.json에서 "strict": true를 설정하면 몇 가지 타입 회피 기능이 작동하지 않는다.
예컨대, 특정 변수가 null일 때 발생하는 컴파일 오류를 회피하기 위해 !연산자나 type assertion(as로 타입을 단언하는 방식)을 자주 사용하는데, 이것이 먹히지 않는다.
주의할 점으로, 위 코드는 ts-loader와 babel-loader를 함께 사용하고 있다.
리엑트 프로젝트에서 웹팩의 js 처리는 타입스크립트 트랜스파일링, 리엑트 코드 트랜스파일링, 브라우저 호환성인데, 두 로더 모두 타입스크립트 트랜스파일링과 리엑트 코드 트랜스파일링, 브라우저 호환성을 담당할 수 있다.
그럼 하나만 쓰면 될까? 단기적으로는 상관이 없으나, 각 로더는 특정 부분에 특화되어 있다. ts-loader는 타입스크립트와 리엑트 코드 변환에 많은 옵션을 제공할 수 있고, babel-loader는 브라우저 호환성의 측면에서 다양한 플러그인과 설정을 할 수 있다. 그러니 장기적으로는 두 로더를 모두 사용하는 걸 추천한다. 단, 이럴 경우 컴파일 시간이 증가하고 설정이 복잡해지니 유념하자.

 

4. .babelrc

바벨 설정 파일에 타입스크립트 변환 기능을 추가하자.

{
  "presets": [
    "@babel/preset-env", // 최신 js 코드를 구버전 js 코드로 변환
    // 변환 타겟을 최신형 브라우저에 맞출 때에는 이 설정 사용
    // [
    //   "@babel/preset-env",
    //   { "targets": { "browsers": ["last 2 versions", ">= 5% in KR"] } }
    // ],
     ["@babel/preset-react", { "runtime": "automatic" }], //jsx 코드의 변환을 담당
    "@babel/preset-typescript" // TypeScript 코드를 JavaScript로 변환하여 타입 정보를 제거
  ],
  "plugins": ["@babel/transform-runtime"]
}

 

부가 설명

  • ts-loader에서 이미 react 코드를 자바스크립트로 변환하지만, 그건 tsx 확장자로만 한정된다. 따라서 jsx 코드의 변환을 위해 @babel/preset-react 프리셋을 유지해야 한다.

 

CSS전처리

 


import css나 css모듈, scss 등을 전처리하기 위한 설정이다.

css를 html에 삽입하는 경우에는 필요하지 않지만 현대 프론트엔드 개발에서 엄청나게 큰 css를 유지보수하기 위해서는 전처리 설정을 하고 이들을 사용하는 것을 추천한다.

 

1. 의존성 설치

npm install --save-dev style-loader css-loader sass-loader sass

각 의존성의 역할은 다음과 같다.

  • style-loader
    • DOM에 스타일을 추가한다.
  • css-loader
    • CSS를 모듈로 변환한다.
  • sass-loader
    • webpack에 로더를 추가한다.
  • sass
    • sass 혹은 scss 파일을 css로 컴파일하는 컴파일러.
  • @types/react
    •  React 라이브러리에 대한 TypeScript 타입 정의를 제공한다.

 

2. webpack.config.js에 css관련 모듈(style-loader, css-loader, sass-loader)을 추가한다.

설정 파일 중 module.rules 내에 다음 코드를 추가한다.

주의할 점은 아래 로더의 순서가 바뀌면 안된다.

 {
          test: /\.(css|s[ac]ss)$/i, // .scss 및 .sass 파일을 처리
          use: [
            "style-loader", // DOM에 스타일 추가
            "css-loader", // CSS를 모듈로 변환
            "sass-loader", // Sass 파일을 CSS로 변환
          ],
        },

 

완료되면 아래처럼 css 파일을 import해서 잘 작동하는지 테스트해보자.

// 추가한 css 코드 - 해당 경로에 파일을 먼저 추가하자
import "@src/asset/styles/app.css";
import "@src/asset/styles/app2.scss";
//

function App() {
  return (
    <div>
      hello
    </div>
  );
}

export default App;

 

환경 변수


 

이번에는 개발용 환경변수 파일과 배포용 환경변수 파일을 웹팩에 추가하고 프로젝트의 실행 방식에 따라 분기처리하자.

 

1. 의존성 설치

환경 변수 파일을 추가하기 위해서는 크게 두 가지 방법이 있다. 하나는 dotenv 라이브러리를 사용하는 것이고, 다른 하나는 dotenv-webpack 라이브러리를 사용하는 것이다. 둘이 큰 차이가 나는 건 아니고, dotenv에 webpack.DefinePlugin()이 포함되었느냐 마냐의 차이일 뿐이니, 여기서는 dotenv-webpack을 사용하였다.

npm install dotenv-webpack --save-dev

 

만약 타입스크립트를 사용 중이라면 아래 의존성도 설치하자.

npm install --save-dev @types/webpack-env

 

2. 환경 변수 파일을 추가한다.

프로젝트 폴더의 루트에 개발용 환경변수 파일인 .env.development와 배포용인 .env.production를 추가한다.

 

3. webpack.config.js에 dotenv 관련 모듈과 코드를 추가한다.

개발환경이냐 아니냐에 따라 환경변수 파일을 선택해야 하므로 package.json에서 설정했던 process.env.NODE_ENV를 이용한다.

 

  1. 먼저 dotenv-webpack 라이브러리를 삽입한다.
  2. 그리고 process.env.NODE_ENV에 따라 파일 경로를 설정한다.
  3. 이후, new Dotenv({ path: $파일 경로 })으로  환경변수 파일을 추가한다.

코드 예시

const path = require("path");

//라이브러리 삽입
const Dotenv = require("dotenv-webpack");

module.exports = () => {
// 개발환경 여부에 따른 파일 경로 분기처리
  const isDevelopment = process.env.NODE_ENV !== "production";
  const envPath = isDevelopment
    ? path.resolve(__dirname, ".env.development")
    : path.resolve(__dirname, ".env.production");

  return {
/// 다른 설정들
    plugins: [
      new Dotenv({ path: envPath }),
    
    ],
  };
};

 

이제 확인을 위해서 다른 컴포넌트에서 환경 변수를 아무거나 출력해보자.

 

.env.development

API_KEY = 1234

 

./src/App.tsx

  console.log("a", process.env.API_KEY);

 

정적 파일


정적 파일의 사전적 정의는 서버단에서 가공을 거치지 않는 파일로, 일반적으로 서버사이드 렌더링을 하지 않는 파일 일체를 지칭하나, 여기서는 svg, font, image와 같은 컨텐츠를 중점으로 설명하겠다. 이유는 리엑트+웹팩 프로젝트에서 css, js는 src의 빌드 파일로 위 글에서 모든 처리가 가능하기 때문이다.

 

1. 컨텐츠 파일들은 <빌드를 거치지 않는 파일>과 <빌드를 거치는 파일>로 구분된다.

이 때, 전자는 /public 폴더에, 후자는 /src/asset에 보관하는 게 일반적이다. 두 유형의 차이는 빌드를 통한 최적화 및 경로검사 과정이 필요하냐이다. (여기서 최적화란 압축, 트리 쉐이킹, 스프라이트 등)을 의미하고, 경로 검사는 컨텐츠 파일이 일부 컴포넌트에서 사용되는 특수한 경우 해당 경로에 파일이 있는지 확인하는 기능이다.

 

또한, 미 빌드 파일은 정적 경로 파일과 동적 경로 파일로 구분된다. 전자는 로고처럼 공용으로 사용하는 몇 안되는 파일이라 프론트 소스 코드에 포함해도 괜찮지만, 후자는 파일 서버에 별도로 관리하는 것이 좋다.

(자세한 차이는 아래 글을 참조하자)

https://chartist1206.tistory.com/221

 

[프론트엔드] public 폴더와 src 폴더의 차이

프론트엔드 프로젝트를 하면서 asset 파일을 어디에 두어야 할지 고민하던 중 두 폴더의 용도 차이에 대한 고민이 생겼다. 일반적인 번들링 툴에서 public은 가공과정이 필요없는 정적 자산을, src

chartist1206.tistory.com

 

1. 빌드를 거치지 않는 파일의 처리 방법

이 글에서는 미 빌드 파일의 두 종류인 정적 경로 파일과 동적 경로 파일중 전자에 대해서만 다룬다. 목표는 /public 파일을 html에서 참조가능하도록 정적 파일 경로를 세팅하고 webpack 빌드 파일에 public 파일을 복제하는 것이다.

 

가. 의존성 설치

npm install copy-webpack-plugin --save-dev

copy-webpack-plugin은 정적 파일을 빌드 디렉토리 내에 복제하여 프론트 소스 코드와 함께 배포할 수 있게 해주는 플러그인이다.

 

나. 웹팩 설정

아까 설치한 플러그인을 아래처럼 webpack.config.js 파일 내 모듈 객체의 plugin 내에 추가한다.

아래의 pattern은 public 폴더 전체를 "빌드 디렉토리/public"로 복제한다.

만약, 특정 확장자를 명시하는 경우에는 정규표현식으로 변경해주자.

또한, 개발 서버 실행 시, 빌드되지 않은 public 파일을 참조할 수 있도록 devServer.static을 설정해주자. 

* 아래의 설정에서는 정적 파일의 루트 경로를 "/public"으로 설정했다.

const CopyWebpackPlugin = require("copy-webpack-plugin");

// 웹팩 설정 객체
module.exports = {
//기타 설정


 // 개발용 로컬 서버
    devServer: {
      port: 3000, // 포트번호
      hot: true, // hmr 기능 활성화
      
      // 개발 서버에서 정적 파일을 가져오는 루트 설정
      // "/public" 폴더를 /public 경로로 가져오도록 설정
      static: [
        {
          directory: path.join(__dirname, "public"),
          publicPath: "/public",
        },
      ],
    },


// 플러그인 - 추가될 부분
    plugins: [
     // 환경 변수 플러그인 추가 시,  new Dotenv({ path: envPath }), 
     
     // public 내에 특정 파일만 복제 시, patterns[0].from을 정규표현식으로 변경
      new CopyWebpackPlugin({ patterns: [{ from: "public", to: "public" }] }),
      
    // html 템플릿 플러그인 추가 시,
    // new HtmlWebpackPlugin({
    //    template: "public/index.html",
    //  }),
    ],
}

 

다. 테스트

이제 개발서버와 배포서버에서 프론트 소스 내 정적파일을 잘 가져올 수 있는지 테스트해보자.

 

테스트를 위해 템플릿 html인 public/index.html에 icon을 추가한다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    
    <!-- 정적 파일 디렉토리 내 아이콘 -->  
    <link rel="icon" href="/public/icon/tree1.png" />
    
    <title>weppack + react</title>
  </head>
  <body>
    <div id="react-root"></div>
  </body>
</html>

 

npm script로 개발서버를 실행하여 아이콘이 정상적으로 로드되는지 확인한다.

// 명령어
  "scripts": {
    "dev": "NODE_ENV=development webpack serve",
  },
npm run dev

 

빌드 파일을 테스트해보기 위해서 실제 배포환경 대신, 가상의 테스트 서버를 실행한다.

두 가지 방법이 있는데, 하나는 express를 이용해서 동적 요청을 처리할 수 있는 WAS를 구축하는 것이고, 다른 하나는 serve를 이용해서 정적 요청을 처리할 수 있는 WS를 구축하는 것이다. 지금은 정적 요청의 처리를 확인하는 것이고 상대적으로 간단한 두 번째 방법을 써보겠다.

아래 글을 참조하여 serve라이브러리 기반의 http 서버를 실행하고 빌드된 /dist 파일을 제공하자.

https://chartist1206.tistory.com/223

 

빌드한 파일을 실제 배포 환경처럼 실행해보기(serve)

웹팩 같은  걸로 빌드를 하면 devServer와 다르게 실제 배포를 가정하고 테스트해봐야 할 때가 있다.serve 라이브러리를 이용해서 간단한 http 서버를 만들고 실행해보자.(serve는 일반적인 was나 express

chartist1206.tistory.com

 

이후 실행하여 아래처럼 icon이 뜨면 성공이다.

 

2. 빌드를 거치는 파일의 처리 방법(이미지, svg)

웹팩 버전에 따라 두 가지 방법이 있다. 하나는 웹팩5보다 낮은 버전에서 사용하는 방법으로 file-loader와 @svgr/webpack을 추가로 설치하여 정적 파일의 빌드를 위한 로더로 사용한다.다른 하나는 웹팩 5버전부터 가능한 방식으로 webpack의 Asset Modules을 이용해서 별도의 로더 없이 정적 파일을 처리하는 방식이다. 

따라서 아래의 두 방식 중 하나를 사용하자.

 

if 1) 웹팩 5이전 버전 방식(추가 로더 사용)

목표는 이미지와 svg를 react 소스 코드 내에 모듈로 import하고 빌드 타임에 이 모듈을 경로나 리엑트 컴포넌트로 변경하는 것이다. 이를 위해서 file-loader와 @svgr/webpack을 사용할 것이다.

  • file-loader | 모듈을 소스코드와 별도의 정적 파일로 export하고 경로를 추출하여 모듈을 대체한다.
  • @svgr/webpack | 모듈을 svg 태그로 대체하여 인라인으로 삽입한다.
svg 삽입을 위한 두 가지 방식 비교: svg 태그 vs img.src
svg는 소스코드에 두 가지 방식으로 삽입 가능하다. 먼저 svg 태그 형식은 스타일 관련 속성에 직접 접근하여 동적으로 변경가능하다. 반면, img.src 방식은 src의 스타일 관련 속성에 접근할 수 없지만, 브라우저의 이미지 캐싱 방식을 사용하므로 최적화에 유리하다.

이 글에서는 두 가지 방식을 모두 사용할 수 있게 처리한다.

 

의존성 설치

npm install -save-dev @svgr/webpack file-loader

 

webpack.config.js에 로더를 추가한다.

 module: {
      rules: [
        {
          test: /\.svg$/, // .svg 확장자를 가진 파일에 대해 설정을 적용

          //  확장자 패턴에 대해 복수의 로더 중 하나만 적용하는 분기처리 옵션
          oneOf: [
            {
              use: ["@svgr/webpack"], // @svgr/webpack 로더를 사용하여 SVG를 React 컴포넌트로 변환 - svg 태그로 삽입
              issuer: /\.[jt]sx?$/, // JavaScript 또는 TypeScript 파일(.js, .jsx, .ts, .tsx) 안에서만 이 설정을 적용. svg의 동적 스타일링을 위함
              resourceQuery: { not: [/url/] }, // 모듈 경로에서 url이 포함되어 있을 시, 모듈 적용 x
            },
            {
              // img.src 태그로 삽입하기 위해 svg 파일 모듈을 url로 변환
              loader: "file-loader",
              options: {
                name: "[path][name].[ext]", // 파일 경로와 이름을 그대로 유지
              },
              resourceQuery: /url/, // *.svg?url.
            },
          ],
        },
        // 만약 svg 확장자를 분기처리 없이 리엑트 컴포넌트로만 다룰 경우, 아래 설정 사용
        // {
        //   test: /\.svg$/i,
        //   issuer: /\.[jt]sx?$/,
        //   use: ["@svgr/webpack"],
        // },
        {
          test: /\.(png|jpe?g|gif|ico)$/i, // 이미지 파일 확장자 처리
          use: [
            {
              loader: "file-loader",
              options: {
                name: "[path][name].[ext]", // 파일 경로와 이름을 그대로 유지
              },
            },
          ],
        },
      ],
    },

 

만약 타입스크립트라면 declation.d.ts에서 정적 파일 모듈을 정의한다.

컴파일 타임에 정적 파일을 모듈로 import할때 타입에러가 나지 않도록 하기 위함이다.

declare module "*.jpg" {
  const value: string;
  export default value;
}
declare module "*.jpeg" {
  const value: string;
  export default value;
}

declare module "*.png" {
  const value: string;
  export default value;
}

declare module "*.gif" {
  const value: string;
  export default value;
}
declare module "*.svg" {
  import React = require("react");

  export const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>;
  const src: string;
  export default src;
}

declare module "*.svg?url" {
  const content: any;
  export default content;
}

 

실제로 작동하는지 테스트한다.

src/asset에다가 정적 파일인 asset/images/snow1.jpg와 asset/svg/group를 세팅하고 아래처럼 tsx에 추가하자.

("@asset"은 웹팩 설정파일에서 추가한 경로의 별칭이다)

 

App.tsx에서 이미지인 snow1.jpg와 svg인 group.svg를 모듈로 추가해보자. 이 때, svg는 리엑트 컴포넌트(svg 태그)로 추가하는 방식과 img.src에 경로를 추가하는 방식을 모두 사용해보겠다.

아래 코드를 실행해서 정상적으로 img 태그와 svg태그가 출력되면 성공이다.

import imgSnowSrc from "@asset/images/snow1.jpg";
import svgGroup from "@asset/svg/group.svg";

// 경로에 url을 넣어서 웹팩 설정에 따라 경로로 인식
import svgGroupUrl from "@asset/svg/group.svg?url";


function App() {

  return (
    <div>
      hello
       {/* 이미지 모듈을 uri로 삽입 */}
  		<img src={imgSnowSrc} />
        
      {/* svg 모듈을 리엑트 컴포넌트로 삽입 */}
        <Group1 />
        
      {/* svg 모듈을 uri로 삽입 */}
		<img src={Group1url} alt="" />
        
    </div>
  );


}

 

if 2) 웹팩 5버전 방식(내장 로더 사용)

웹팩의 asset module은 이전의 file-loader, url-loader, raw-loader를 처리하는 새로운 방식으로, webpack에 내장되어 있어 별도의 설치가 필요하지 않다. 단, svg를 리엑트 컴포넌트로 내보내는 @svgr/webpack는 대체할 수 없으므로 이 방식에서도 필요하다.

((if1)의 방식과 이 방식중 하나를 선택하면 된다. 호환성을 걱정한다면 if1이 나을 것이고, 간결한 코드와 최신기능을 원한다면 if2방식을 추천한다,)

 

https://webpack.kr/guides/asset-modules/

 

Asset Modules | 웹팩

웹팩은 모듈 번들러입니다. 주요 목적은 브라우저에서 사용할 수 있도록 JavaScript 파일을 번들로 묶는 것이지만, 리소스나 애셋을 변환하고 번들링 또는 패키징할 수도 있습니다.

webpack.kr

 

1. webpack.config.js에 asset module을 설정한다.

module: {
	rules: [
        // 에셋 처리 방식: 웹팩 5
        {
          test: /\.svg$/, // .svg 확장자를 가진 파일에 대해 설정을 적용

          //  확장자 패턴에 대해 복수의 로더 중 하나만 적용하는 분기처리 옵션
          oneOf: [
            {
              use: ["@svgr/webpack"], // @svgr/webpack 로더를 사용하여 SVG를 React 컴포넌트로 변환 - svg 태그로 삽입
              issuer: /\.[jt]sx?$/, // JavaScript 또는 TypeScript 파일(.js, .jsx, .ts, .tsx) 안에서만 이 설정을 적용. svg의 동적 스타일링을 위함
              resourceQuery: { not: [/url/] }, // 모듈 경로에서 url이 포함되어 있을 시, 모듈 적용 x
            },
            {
              type: "asset",
              resourceQuery: /url/, // *.svg?url.
            },
          ],
        },

        {
          test: /\.(png|jpe?g|gif|ico)$/i, // 이미지 파일 확장자 처리
          type: "asset",
        },

    ]

}

 

3. 폰트

폰트는 일반적인 방식으로 css 파일에 import하는 방식과, 모듈로 js 파일에 import하는 방식으로 구분된다.

전자를 사용하는 것이 일반적인데, 후자는 특정 컴포넌트에 css-in-js로 삽입할 때 유용하다.

 

가. css 파일에 추가하는 방식

먼저 폰트파일을 추가한다. 내 경우에는 src/asset/fonts 에 추가하였다. 파일 형식은 otf, ttf, woff 모두 상관없다.

그리고 전역에서 사용될 폰트를 모아놓은 css 파일을 하나 만든다. 내 경우에는 src/asset/styles/font.css를 만들었다. 

그리고 해당 파일에 @font-face로 폰트의 경로와 이름을 선언한다.

(만약 해당 폰트가 다른 폰트를 함께 사용해야 하는 경우, 같이 정의해줘야 한다.)

/* 예시 NanumPenScript */
@font-face {
  font-family: "NanumPenScript";
  font-style: normal;
  font-weight: 100;
  src: url("../fonts/NanumPenScript-Regular.woff");
}

@font-face {
  font-family: "NanumPenScript";
  font-style: normal;
  font-weight: 200;
  src: url("../fonts/NanumPenScript-Regular.woff");
}

@font-face {
  font-family: "NanumPenScript";
  font-style: normal;
  font-weight: 300;
  src: url("../fonts/NanumPenScript-Regular.woff");
}

@font-face {
  font-family: "NanumPenScript";
  font-style: normal;
  font-weight: 400;
  src: url("../fonts/NanumPenScript-Regular.woff");
}

@font-face {
  font-family: "NanumPenScript";
  font-style: normal;
  font-weight: 500;
  src: url("../fonts/NanumPenScript-Regular.woff");
}

@font-face {
  font-family: "NanumPenScript";
  font-style: normal;
  font-weight: 600;
  src: url("../fonts/NanumPenScript-Regular.woff");
}

 

font.css 파일은 가장 계층이 높은 css 파일에 import 하면 된다.(일반적으로 app.css나 index.css에 삽입하면 된다)

/* 폰트 파일 추가 */
@import url("./font.css");



body {
/* 폰트 사용 */
  font-family: NanumPenScript;
  background-color: rgb(255, 205, 205);
}
body * {
  /* 폰트 사용 */
  font-family: MonsieurLaDoulaise, NanumBrushScript, NanumPenScript;
}

 

나. 모듈로서 js파일에 추가하는 방식

 

webpack.config.js에서 웹팩5의 asset module로 폰트의 확장자를 가진 파일을 모듈로 정의한다.

module : {
	rules:[
      // 폰트 처리
      // 웹팩5의 방식
         {
          test: /\.(woff|woff2|eot|ttf|otf)$/i,
          type: "asset/resource",
          generator: {
            filename: "assets/fonts/[name].[hash:8][ext]", // 폰트 파일이 번들된 후 저장될 위치
          },
        },
        
        // 아래는 웹팩4의 방식인데, 이 방식으로 하면 font-face로 읽어오지 못한다.
     	// 해당 문제는 아래 참조
       // {
        //   test: /\.(woff|woff2|ttf|eot|otf)$/,
        //   use: [
        //     {
        //       loader: "file-loader",
        //       options: {
        //         name: "assets/fonts/[name].[hash:8].[ext]", // 폰트 파일이 번들된 후 저장될 위치
        //       },
        //     },
        //   ],
        // },
    ]

}

 

issue(미해결)
이론대로라면, file-loader를 이용해서 위와 동일하게 폰트를 빌드하고 참조할 수 있어야 한다.
하지만, file-loader를 이용해서 폰트를 참조해본 결과, font-face로 빌드한 폰트 파일을 가져올 수 없었다.
분명 경로 상으로는 문제가 없었고, 해당 경로를 url로 입력하면 다운로드가 가능했다.

한 가지 단서는, file-loader와 asset module이 파일을 처리하는 방식에 차이가 있다는 점이다. file-loader로 빌드한 파일은 파일 경로를 모듈로 export하여 정적 파일을 참조한다. 반면, asset-module은 해당 경로로 바로 파일을 참조한다. 따라서, css파일의 font-face에서 경로를 참조할 때, file-loader로 참조한 경로는 export module을 경유하기 때문에 이를 처리하지 못해 에러가 난 거 아닐까.

그런데, 이 정도 에러이면 stackoverflow에도 있을 법 한데, 관련 레퍼런스를 찾지 못하였다.
일단 대안으로 asset-module만 사용해야 할 것 같다.

 

만약 타입스크립트를 사용한다면, 해당 확장자를 모듈로 정의해야 한다.

declare.d.ts 같은 타입선언 파일에서 다음같이 작성한다.

// 폰트 파일 모듈 선언
declare module "*.woff" {
  const content: any;
  export default content;
}

declare module "*.ttf" {
  const content: any;
  export default content;
}
declare module "*.otf" {
  const content: any;
  export default content;
}

////

 

이제 테스트를 위해서 자바스크립트 혹은 타입스크립트 파일에서 임의로 폰트파일을 모듈로 가져와보자.

import React from "react";
import NanumBrushScriptRegular from "@asset/fonts/NanumBrushScript-Regular.woff";
import MonsieurLaDoulaiseRegular from "@asset/fonts/MonsieurLaDoulaise-Regular.woff";

const NanumBrushScript = {
  name: "NanumBrushScript",
  url: NanumBrushScriptRegular,
};

export default function FontLoader() {
  return <div style={{ fontFamily: NanumBrushScript.name }}>FontLoader</div>;
}

 

 

4. 라우팅

SPA앱에서 라우팅은 서버단에서 처리하는 것이 아니라 클라이언트단에서 history API를 이용해 처리하며, 실제로는 단일한 html 리소스만을 사용한다. 만약 개발서버에서 앱을 실행할 시, 새로고침이나 특정 url에 직접 접근하려하면 해당 경로를 찾지 못해 에러가 발생한다. 따라서 이 부분에 대한 웹팩 처리가 필요하다.

 

가. 테스트 환경 구성: react-router-dom

먼저 리엑트 라우터를 설치한다.

npm install react-router-dom

 

그리고 아래 공식문서를 참조하여 아래처럼 경로를 하나 설정하였다.

https://reactrouter.com/en/main/start/tutorial#adding-a-router

 

Tutorial | React Router

 

reactrouter.com

 

아래 페이지에서는 "/about"이라는 페이지로 이동한다.

개발서버를 실행하고  이동 후에 새로고침을 하거나, 위 경로를 직접 검색해서 이동하려 하면 에러가 날 것이다.

import { useState } from "react";
import { Link, Outlet } from "react-router-dom";

function Root() {

  return (
    <div>
      <Link to={`about`}>About</Link>
      <Outlet />
    </div>
  );
}

export default Root;

 

나. webpack.config.js 설정

아래와 같이 devServer를 구성한다.

특히 devServer.historyApiFallback =true를 해야 한다.

    // 개발용 로컬 서버
    devServer: {
      port: 3000, // 포트번호
      hot: true, // hmr 기능 활성화
      historyApiFallback: true, // spa앱의 history api에 의한 변경으로, url 접근이나 새로고침 시 404에러가 나지 않도록 함
      static: [
        {
          directory: path.join(__dirname, "public"),
          publicPath: "/public",
        },
      ],
    },

 

완성된 주요 파일


 

@webpack.config.js

const path = require("path");
const Dotenv = require("dotenv-webpack");

const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");

module.exports = () => {
  const isDevelopment = process.env.NODE_ENV !== "production";
  const envPath = isDevelopment
    ? path.resolve(__dirname, ".env.development")
    : path.resolve(__dirname, ".env.production");

  return {
    entry: "./src/index.tsx", // 애플리케이션의 진입점. 번들링 시 가장 먼저 시작하는 파일

    // 번들링 결과물
    output: {
      filename: "bundle.js", // 파일 이름
      path: path.resolve(__dirname, "dist"), // 빌드된 파일이 저장되는 경로
      publicPath: "/", // 브라우저에서 빌드된 프론트엔드 파일에 접근하는 url경로
      clean: true, // 빌드 시, 이전 빌드 파일을 삭제함. 불필요한 파일이 누적되는 걸 방지
    },

    // 모듈의 경로나 파일 타입을 해석하는 방식을 결정
    // import나 require로 모듈 삽입 시, 경로를 정의하는 방식
    resolve: {
      extensions: [".ts", ".tsx", ".js", ".jsx"], // 확장자가 생략된 경우 가져올 모듈의 파일 확장자 지정
      // extensions: [".js", ".jsx"],
      modules: ["src", "node_modules"], // 모듈을 찾을 때 참조할 디렉토리의 목록을 정의. 자동으로 절대경로로 src와 node_modules부터 사용가능함

      // 특정 경로에 대한 절대경로를 별칭으로 설정
      // ex: import publicImage1 from @public/image/publicImage1.png;
      alias: {
        "@src": path.resolve(__dirname, "src"),
        "@public": path.resolve(__dirname, "public"),
        "@asset": path.resolve(__dirname, "src", "asset"),
      },
    },
    // 개발용 로컬 서버
    devServer: {
      port: 3000, // 포트번호
      hot: true, // hmr 기능 활성화
      historyApiFallback: true, // spa앱의 history api에 의한 변경으로, url 접근이나 새로고침 시 404에러가 나지 않도록 함
      static: [
        {
          directory: path.join(__dirname, "public"),
          publicPath: "/public",
        },
      ],
    },

    // eval-source-map: 번들 파일 내에 소스맵이 포함. 디버깅 시 원본 소스에 가까운 코드를 제공하나 무거우므로 성능이 느림 => 개발에 적합
    // source-map: 디버깅 시 번들 파일과 별도의 소스 맵을 제공함. eval-source-map보다 원본이 변형되었으나 성능이 빠름 => 배포에 적합
    devtool: isDevelopment ? "eval-source-map" : "source-map",
    // optimization: {
    //   splitChunks: {
    //     chunks: "all", // 모든 청크에서 공통 모듈 추출
    //   },
    // },
    module: {
      rules: [
        {
          test: /\.(ts|tsx|js|jsx)$/, // .ts, .tsx, .js, .jsx 확장자에 대해
          exclude: /node_modules/,
          use: [
            {
              loader: "ts-loader", // TypeScript 로더
              options: {
                transpileOnly: true, // 타입 검사만 하기. 컴파일 검사를 하지 않음
              },
            },
            {
              loader: "babel-loader", // JavaScript 변환을 위한 Babel 로더
            },
          ],
        },

        // 폰트 처리
        {
          test: /\.(woff|woff2|eot|ttf|otf)$/i,
          type: "asset/resource",
          generator: {
            filename: "assets/fonts/[name].[hash:8][ext]", // 폰트 파일이 번들된 후 저장될 위치
          },
        },
        // {
        //   test: /\.(woff|woff2|ttf|eot|otf)$/,
        //   use: [
        //     {
        //       loader: "file-loader",
        //       options: {
        //         name: "assets/fonts/[name].[hash:8].[ext]", // 폰트 파일이 번들된 후 저장될 위치
        //       },
        //     },
        //   ],
        // },

        {
          test: /\.(css|s[ac]ss)$/i, // .scss 및 .sass 파일을 처리
          use: [
            "style-loader", // DOM에 스타일 추가
            "css-loader", // CSS를 모듈로 변환
            "sass-loader", // Sass 파일을 CSS로 변환
          ],
        },

        // 에셋 처리 방식: 웹팩 5
        {
          test: /\.svg$/, // .svg 확장자를 가진 파일에 대해 설정을 적용

          //  확장자 패턴에 대해 복수의 로더 중 하나만 적용하는 분기처리 옵션
          oneOf: [
            {
              use: ["@svgr/webpack"], // @svgr/webpack 로더를 사용하여 SVG를 React 컴포넌트로 변환 - svg 태그로 삽입
              issuer: /\.[jt]sx?$/, // JavaScript 또는 TypeScript 파일(.js, .jsx, .ts, .tsx) 안에서만 이 설정을 적용. svg의 동적 스타일링을 위함
              resourceQuery: { not: [/url/] }, // 모듈 경로에서 url이 포함되어 있을 시, 모듈 적용 x
            },
            {
              type: "asset",
              resourceQuery: /url/, // *.svg?url.
            },
          ],
        },

        {
          test: /\.(png|jpe?g|gif|ico)$/i, // 이미지 파일 확장자 처리
          type: "asset",
        },

        // 에셋 처리 방식: 웹팩 4
        // {
        //   test: /\.svg$/, // .svg 확장자를 가진 파일에 대해 설정을 적용

        //   //  확장자 패턴에 대해 복수의 로더 중 하나만 적용하는 분기처리 옵션
        //   oneOf: [
        //     {
        //       use: ["@svgr/webpack"], // @svgr/webpack 로더를 사용하여 SVG를 React 컴포넌트로 변환 - svg 태그로 삽입
        //       issuer: /\.[jt]sx?$/, // JavaScript 또는 TypeScript 파일(.js, .jsx, .ts, .tsx) 안에서만 이 설정을 적용. svg의 동적 스타일링을 위함
        //       resourceQuery: { not: [/url/] }, // 모듈 경로에서 url이 포함되어 있을 시, 모듈 적용 x
        //     },
        //     {
        //       // img.src 태그로 삽입하기 위해 svg 파일 모듈을 url로 변환
        //       loader: "file-loader",
        //       options: {
        //         name: "[path][name].[ext]", // 파일 경로와 이름을 그대로 유지
        //       },
        //       resourceQuery: /url/, // *.svg?url.
        //     },
        //   ],
        // },
        // // 만약 svg 확장자를 분기처리 없이 리엑트 컴포넌트로만 다룰 경우, 아래 설정 사용
        // // {
        // //   test: /\.svg$/i,
        // //   issuer: /\.[jt]sx?$/,
        // //   use: ["@svgr/webpack"],
        // // },
        // {
        //   test: /\.(png|jpe?g|gif|ico)$/i, // 이미지 파일 확장자 처리
        //   use: [
        //     {
        //       loader: "file-loader",
        //       options: {
        //         name: "[path][name].[ext]", // 파일 경로와 이름을 그대로 유지
        //       },
        //     },
        //   ],
        // },
      ],
    },
    plugins: [
      new Dotenv({ path: envPath }),
      new CopyWebpackPlugin({ patterns: [{ from: "public", to: "public" }] }),
      new HtmlWebpackPlugin({
        template: "public/index.html",
      }),
    ],
  };
};

 

@tsconfig.json

{
  "compilerOptions": {
    "outDir": "./dist",
    "target": "es5", //코드가 컴파일되는 버전 = es5
    "module": "esnext", // 모듈 시스템 = 최신 모듈 시스템
    "jsx": "react-jsx", // react JSX구문을 변환.  react17버전 이상의 기능 지원
    "strict": true, // 엄격한 타입 검사
    "moduleResolution": "node", // 모듈 해석 방식
    "esModuleInterop": true, // esmodule과 commonjs 모듈을 함께 사용할 수 있도록 허용. 특히 commonJS 모듈을 esModule로 가져올 때 유용
    "skipLibCheck": true, //라이브러리 파일의 타입 검사를 건너뜀. 컴파일 시간을 단축함
    "forceConsistentCasingInFileNames": true, // 파일 이름의 대소문자를 일관되게 유지. 서로 다른 운영 체제 간의 호환성을 개선
    "baseUrl": "./",
    "allowJs": true, // js파일을 모듈로 추가할 수 있도록 타입 체킹을 허가한다. 자바스크립트와 타입스크립트를 혼용 시 유용
    "resolveJsonModule": true, // JSON 파일을 모듈처럼 가져올 수 있음.
    // Disallow features that require cross-file information for emit.
    "isolatedModules": true,
    "declaration": true,

    "paths": {
      "*": ["src/*"],
      "@src/*": ["src/*"], // '@src' 별칭을 'src' 폴더와 연결
      "@public/*": ["public/*"], // '@public' 별칭을 'public' 폴더와 연결
      "@asset/*": ["src/asset/*"]
    }
  },

  "include": ["src"] // 검사를 하는 대상
}

 

@.babelrc

{
  "presets": [
    "@babel/preset-env", // 최신 js 코드를 구버전 js 코드로 변환
    // 변환 타겟을 최신형 브라우저에 맞출 때에는 이 설정 사용
    // [
    //   "@babel/preset-env",
    //   { "targets": { "browsers": ["last 2 versions", ">= 5% in KR"] } }
    // ],
    ["@babel/preset-react", { "runtime": "automatic" }],
    "@babel/preset-typescript" // TypeScript 코드를 JavaScript로 변환하여 타입 정보를 제거
  ],
  "plugins": ["@babel/transform-runtime"]
}

 

@npm 설치 명령어

npm install @babel/runtime react react-dom react-router-dom @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/preset-react @babel/preset-typescript @svgr/webpack @types/react @types/react-dom @types/webpack-env babel-loader copy-webpack-plugin css-loader dotenv-webpack file-loader html-webpack-plugin sass sass-loader style-loader ts-loader typescript webpack webpack-cli webpack-dev-server

 

 

@프로젝트 구조

C:\Users\FE\Desktop\char-tae-young\testCode\pracmicrofrontend1
├── package-lock.json
├── package.json
├── public
|  └── index.html
├── README.md
├── src
|  ├── App.tsx
|  ├── asset
|  ├── components
|  ├── contants
|  ├── index.tsx
|  ├── pages
|  ├── Root.tsx
|  ├── routes
|  └── types
├── tsconfig.json
├── webpack.config.js
└── webpack.config.main.js

 

참고자료 


https://noah-dev.tistory.com/46

 

웹팩으로 리액트 시작하기 1

리액트를 시작하기 위해 Create-React-App(CRA) 혹은 Vite을 많이 사용한다. 이러한 도구는 webpack설정 없이 편하게 리액트를 시작할 수 있도록 도와준다. 때문에 많은 개발자들이 사용하고 있는 도구이

noah-dev.tistory.com

 

https://spectrum20.tistory.com/entry/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%9B%B9%ED%8C%A9%EC%9C%BC%EB%A1%9C-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0

 

[React] 리액트 웹팩으로 시작하기

지난 포스팅에서 CRA를 이용하여 쉽고 빠르게 리액트 개발환경을 구축해보았다. https://spectrum20.tistory.com/entry/React-CRA%EB%A1%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-%EC%84%A4%EC%B9%98%EB%B6%80%E

spectrum20.tistory.com

 

https://maxkim-j.github.io/posts/frontend-tooling-ideas/

 

React 개발 환경 구축하며 알게된 자잘한 것들

요즈음엔 외대 종강시계의 새로운 버전을 만들고 있습니다. Vue로 개발했던 프로젝트를 React+Typescript로 새롭게 만드는 프로젝트를 진행하고 있는데요. 기존의 보일러플레이트를 사용하지 않고 ya

maxkim-j.github.io