React + TS boilerplate 제작기 1 - 환경 구성

DD·2021년 6월 12일
45
post-thumbnail

2편 : React + TS boilerplate 제작기 - 설치 패키지 & npx

📣 경고!?

현재 react 17버전부터 가능해진 import React from 'react' 제거 작업을 진행중입니다. ts-loader에게 모든걸 맡긴 상태라 현재 아래 글에는 고려되지 않았습니다.
조만간 수정할 예정이니 참고만해주세요.

❓ 왜 시작했는가?

React를 처음 접할 때 대부분의 강의는 create-react-app(이하 CRA)을 사용합니다. 저 또한 React 프로젝트를 시작할 때 마다 일단 CRA를 실행하고, 추가적으로 필요한 의존성을 추가한 후 개발을 시작하곤 했습니다.

CRA는 webpack, babel, eslint 등 복잡한 환경 설정을 신경쓰지 않아도 되며, autoprefixer 를 지원하는 등 개발을 매우 편리하게, 시간을 절약해주는 아주 유용한 툴입니다.

🤦‍♂️ 다만, 그만큼 단점도 존재하죠.

  • CRA는 다양한 경우를 커버하기 위해 (경우에 따라) 쓸데없이 무겁다.
  • npm eject 명령으로 숨겨진 설정, 의존성을 드러낼 수 있지만 잘못 건드리면 엄청 꼬일 수 있다.
  • 그럼에도 불구하고 eject를 해야하는 상황이 있다. (현재 프로젝트에 필요한 의존성이 CRA 기존 의존성과 충돌하는 경우 등)

즉, CRA는 React는 라이브러리가 아닌 프레임워크라 부르게 만드는 이유 중 하나입니다. 이미 촘촘하게 짜여진 환경에 따라야하며, 사용자가 커스텀하기엔 큰 부담이 따르거든요.

👉 하지만 언젠가는 직접 환경설정을 해야하는 날이 올 것이기 때문에, CRA에만 의존해서는 안 되겠다는 생각이 들었습니다.

아래 설명 부분부터는 말투가 좀 바뀔겁니다..


⛏️ 그러니까 일단 한 번 만들어 보자.

사실 보일러플레이트가 뭐 별거 있겠나(사실 별거임). 이미 수많은 블로그에 다양한 정보가 넘쳐난다.

하지만 블로그마다 환경 설정이 조금씩 다르다. 근데 다 제대로 동작한다.

🤦‍♂️ 이 미묘한 차이가 초보자 입장에서 아주 골치다.

  • 어떤게 더 나은 방식인지
  • 얘는 왜 이렇게 했고, 쟤는 왜 이렇게 했지?
  • 이 설정은 어디까지 커버하는거지?
  • 이 설정 값의 의미는 뭐지? 안 하면 어떻게 되는거지?

의문이 들지만 명확한 설명은 없다. 보통의 환경 설정을 설명하는 블로그 글은 대부분 이 파일은 이렇게 작성하세요에서 그친다.. 다 그렇다는건 아니지만!

일단 한 번 따라해봐! 그럼, 짠! 동작해!

👉 가능하면 각종 설정 파일들(package.json, tsconfig ..)의 각 속성들이 어떤 의미인지 최대한 설명하겠다.. 이후 글에는 내가 진행하면서 궁금하고 찾아본 사소한(?) 내용들은 ❓로 표시하고 찾아낸 내용을 기술할 예정이다. 그러다보니 뭐 이런거까지 언급하냐 싶은 내용이 많을 수 있으니 참고해주시길..


📣 그럼 시작합니다

npm init -y

❓ -y 는 뭐지?

  • -y 옵션은 npm init시 물어보는 질문들을 모두 yes처리한다.
  • 구체적인 정보를 입력하려면 -y를 생략하고 물어오는 질문에 맞춰 작성하면 된다.

🚀 React

npm i react react-dom

  • 가장 기본적인 두 패키지만 설치하겠다.
  • router, styled-components 등등 부가적인 패키지는 원한다면 개인적으로 추가하기!

🚀 TypeScript

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

❓ 왜 typescript 관련 설치는 -D를 붙이나?

  • D는 해당 패키지를 devDependencies에 설치한다는 의미인데, 사실 로컬에서만 실행할 프로젝트라면 큰 상관 없기도 하다..
    • dependencies는 프로덕션 환경에서 필요한 패키지
    • devDependencies는 로컬 개발 및 테스트 단계까지만 필요한 패키지이다.
  • ts의 경우 개발을 진행할 때와 배포를 위한 빌드 파일 생성 전까지만 쓰이기 때문에 dev에 설치하는 것이고,
  • react는 프로덕션 환경에서도 필요하기에 dev가 아닌 dependencies에 설치하는 것이다!

📃 tsconfig.json

  • 이 파일이 존재하는 경로가 TypeScript 프로젝트의 root 경로가 된다.

  • compilerOptions, files, include, exclude, extends, compileOnSave 등의 속성을 작성할 수 있다. 이에 대한 설명은 간단하게만 작성하고, 자세한 내용은 검색!

    • compilerOptions : 이름 그대로 어떻게 컴파일 할 것인지 지정하는 속성
    • files : tsc 명령어 입력시 컴파일 대상 파일을 미리 지정해두는 속성
    • include : files와 비슷한 동작을 하지만 파일이 아니라 폴더 경로를 지정한다.
    • exclude : include와 반대로 컴파일하지 않을 폴더 경로를 지정한다
    • extends : 상속 받을 다른 tsconfig 설정 파일 경로를 지정한다
    • compileOnSave : 파일을 변경하면 바로 컴파일을 할 것인지 boolean값으로 지정한다. 에디터마다 동작하지 않을 수도 있다. vscode 2015, TypeScript 1.8.4 이상이어야 한다

TIP
컴파일 대상 경로를 정의하는 속성의 우선 순위
files > include = exclude // exclude에 있더라도 files에 지정된 파일은 컴파일 대상이 된다.

TIP2
exclude에 node_modules를 지정하더라도 @types 폴더는 컴파일에 포함한다!

  • 프로젝트 root 폴더에 tsconfig.json 파일을 만들어준다.

  • 타입스크립트가 글로벌로 설치되어 있다면 tsc --init 명령어로 만들어도 되지만 그러면 모든 속성값이 적힌 tsconfig.json파일이 생성된다. 궁금하면 해당 명령어로 생성해 보자.

  • 이 글에서는 직접 tsconfig.json 파일을 생성하고, 내용을 추가하는 방식을 진행한다.

{
  "compilerOptions": {
    "target": "es5", // 컴파일 된 결과물이 어느 버전의 ECMAScript를 따를 것인지
    "lib": ["DOM", "DOM.Iterable", "ESNext"], // 컴파일 시 포함시켜야하는 javascript 내장 API들의 타입 정의에 대한 정보들
    "module": "esnext", // 프로그램에서 사용할 모듈 시스템. import/export 코드가 어떤 방식의 코드로 컴파일 될지 결정한다
    "allowJs": true, // js files를 허용할 것인가
    "jsx": "react", // jsx 코드를 어떻게 컴파일 할 것인가
    "baseUrl": "./", // 비상대적 import 모듈 해석시 기준이 되는 경로
    "moduleResolution": "Node", // 모듈 해석 전략. 웬만해선 node로 고정할 것
    "sourceMap": true, // map 파일을 생성할 것인가
    "esModuleInterop": true, // es module 사용시 컴파일 단계에서 헬퍼 함수를 사용할 것인가
    "strict": true, // strict family 속성 전부를 true로 할 것인가
    "noImplicitAny": false, // any 타입으로 구현된 표현식 혹은 정의를 에러처리 할 것인가
    "isolatedModules": true, // 각 파일을 분리된 모듈로 트랜스파일링할 것인가
    "forceConsistentCasingInFileNames": true, // 사용할 파일의 이름을 대소문자까지 정확하게 작성하도록 강제할 것인가
    "declaration": false, // d.ts 파일을 생성할 것인가
    "removeComments": true, // 컴파일시 주석을 제거할 것인가
    "pretty": true, // 에러와 메세지를 색, 컨텍스트를 사용해서 스타일을 지정할 것인가
    "strictFunctionTypes": true, // 함수, 메소드의 인자 타입을 더 정확히 추론할 것인가
    "skipLibCheck": true, // 사용하는 라이브러리의 타입 검사를 skip할 것인가
    "noImplicitThis": true, // any 타입으로 암시한 this 표현식에 오류를 보고할 것인가
    "noFallthroughCasesInSwitch": true, // switch문에서 fallthrough case가 발견되면 에러를 발생시킬 것인가
    "noImplicitReturns": true, // void가 아닌 함수가 리턴을 제대로 하지 않는 경우가 있다면 에러 발생
    "noEmit": true, // 컴파일러가 js 파일 등 출력 결과물을 만들지 않을 것인가
    "noEmitOnError": true, // 에러 발생시 js 소스코드, source map, declaration 등이 생성되지 않는다
    //
    "noUnusedLocals": false,
    "downlevelIteration": true
    // 사용되지 않는 지역 변수에 대해 에러를 발생시킬 것인가
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}

❓ 이 많은 옵션이 다 필요한건가?

  • 사실 tsconfig 설정을 어디까지 지정할 것인가가 가장 큰 고민인거 같다.
  • 실제 tsc --init을 실행하면 기본으로 주어지는 compilerOptions은 4가지이다
    • target
    • module
    • strict
    • esModuleInterop
  • 이 4가지는 ts를 어디에서 쓰든 가장 중요한 옵션이라고 생각해서 자체 지정한 옵션인가? 싶다.
  • 물론 이 외에도 React와 함께 사용하기 위해서 jsx, lib 등 추가적인 필수 사항이 존재한다
  • 나머지는 필수는 아니지만,
    • 얼마나 엄격한 ts 규칙을 구성할 것인가
    • 어떤 환경에서 사용할 것인가(서버, 리액트 Next.js 등)
  • 선택하는건 개인의 몫인거 같다.
  • 좀 더 고수가 돼서 능숙하게 tsconfig를 조절할 수 있는 개발자가 되자!

참고 링크

여기 상당히 잘 정리된 4개의 문서가 있다. 각자 공부해보면서 자신만의 tsconfig.json 파일을 만들어보자


🚀 Webpack

이제 모든 프로젝트를 번들링해줄 웹팩 설정을 해보자

npm i -D webpack webpack-cli webpack-dev-server
npm i -D ts-loader css-loader style-loader file-loader html-webpack-plugin
npm i dotenv

❓ loader와 plugin은 무슨 차이일까?

  • 단순히 비교하면 번들이 생성 되기 전(빌드 전)에 쓰이냐, 에 쓰이냐로 구분할 수 있다

  • loader는 webpack으로 빌드를 진행하기 전/진행 중에 개별 파일들(빌드 과정에 포함되는 각 파일들)에게 적용하기 위해 사용된다

  • plugin은 번들이 생성 된 후 결과 파일에 적용하기 위해 사용된다. 번들된 js 난독화, 압축, 복사, 특정 텍스트 추출, 별칭 등 부가 작업 등등 후처리를 한다.

  • 이것들 모두 프로덕션 배포 이전 빌드 단계에서만 사용되기 때문에 -D 옵션으로 설치한다.


📃 webpack.config.js

require("dotenv").config(); // ❓

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

const isProd = process.env.NODE_ENV === "production"; // ❓ 
const PORT = process.env.PORT || 3000;

module.exports = {
  mode: isProd ? "production" : "development",
  devtool: isProd ? "hidden-source-map" : "source-map", // development 환경에서만 source-map을 만든다.
  entry: "./src/index.tsx",
  output: {
    filename: "[name].js", // [name]은 청크의 이름을 사용한다. ❓
    path: path.join(__dirname, "/dist"), // ❓
  },
  resolve: {
    modules: ["node_modules"],
    extensions: [".js", ".jsx", ".ts", ".tsx"], // ❓
  },
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        loader: "ts-loader",
        options: {
          transpileOnly: isProd ? false : true, // ❓
        },
      },
      {
        test: /\.css?$/,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.(webp|jpg|png|jpeg)$/,
        loader: "file-loader",
        options: {
          name: "[name].[ext]?[hash]",
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "public", "index.html"),
      hash: true,
    }), // ❓
  ],
  devServer: {
    contentBase: path.resolve(__dirname, "public"),
    host: "localhost",
    port: PORT,
    open: true,
    hot: true,
    compress: true,
    historyApiFallback: true,
    overlay: true,
    stats: "errors-only",
  },
};

❓ dotenv는 뭘까?

  • 프로젝트에서 사용하는 환경변수를 별도의 파일(.env)로 관리하기 쉽게 해주는 도구
  • .env 파일을 하나만 사용할 땐 단순히 config()
  • production, development 모드 등 상황에 따라 환경 변수를 달리하려면 관련된 객체를 전달해야한다.
  • 보일러플레이트가 지정해줄 영역은 아니기에 빈값을 디폴트로 한다.

❓ NODE_ENV는 뭘까?

  • 이후 package.json에서 webpack을 실행하는 scripts 명령문에서 mode를 지정해줄 예정이다. 그 때 지정한 mode의 값이 담기는 환경변수.

❓ Chunk(청크)는 뭘까?

  • 번들링 시 모든 코드를 하나의 거대한 파일(Bundle)로 만들지 않기 위해 여러개로 나누는데 그 단위를 Chunk(청크)라고 한다.

❓ path.resolve vs path.join

  • 둘 다 인자로 전달 받는 경로를 합쳐서 문자열 형태의 path를 반환한다.
  • 차이점은 join은 전달받은 인자의 왼쪽부터, resolve는 오른쪽부터 합치면서 진행한다.
  • resolve는 합치기를 진행하다가 "/" 를 만나면 절대 경로로 인식해서 나머지 경로 인자들을 무시한다.
  • __dirname 은 현재 실행중인 경로를 의미한다.

❓ resolve 필드는 어떤 역할을 할까?

  • resolve는 웹팩이 알아서 경로, 확장자를 처리할 수 있게 도와주는 옵션이다
  • modules에 node_modules를 지정해줘야 외부 라이브러리를 바로 가져올 수 있다

    import Calender from "@jjunyjjuny/react-calendar"

  • extensions에 넣은 확장자를 웹팩이 알아서 처리한다. 따라서 import시 파일명 뒤에 확장자를 붙일 필요가 없다.

    import a from 'src/App'

❓ transpileOnly는 어떤 속성일까

  • ts-loader는 기본적으로 TS->JS로 트랜스파일링 하는 작업type check 작업을 구분 하고 같은 스레드에서 동시에 실행한다.
  • 해당 옵션을 true로 지정하면 타입체크를 수행하지 않고 트랜스파일링만 진행한다. 또한 d.ts 파일도 생성되지 않는다. 이를 통해 속도 향상을 노릴 수 있다.
  • 즉 development 모드에서는 true로 설정해서 개발 속도를 높이고, production 모드로 빌드할 때는 타입체크와 d.ts 파일 생성을 허용함으로써 안정성을 높인다.
  • 보통 속도 향상과 안정성 모두를 취하기 위해 transpileOnly는 true로하고 fork-ts-checker-webpack-plugin라는 플러그인으로 타입 체크를 한다.

❓ HtmlWebpackPlugin는 어떤 역할을 할까?

  • 빌드과정이 끝나고 따로 분리하여 bundle한 css, js 파일 등을 지정한 html 파일의 link, script 태그에 추가해준다.

📃 package.json

  • 위에서 필요한 설정을 마치고 이제 마무리로 이 파일을 정리해보자

npm i -D cross-env

webpack 실행시 NODE_ENV 값을 지정하기 위해 보통 --mode production/development 옵션을 추가해주는데, 이는 Mac에서만 동작한다

윈도우에서 NODE_ENV값을 변경하기 위해서는 cross-env 라이브러리를 설치, 사용해야한다. 서러워서 윈도우 쓰겠나..


{
  "name": "", // 실제로 공백은 불가능하다. 
  "version": "0.1.0", 
  "description": "", 
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack serve --open --hot --progress", 
    "start": "cross-env NODE_ENV=development webpack --progress",
    "build": "cross-env NODE_ENV=production webpack --progress"
  }, 
  "keywords": [], // npm 검색 키워드 
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/react": "^17.0.9",
    "@types/react-dom": "^17.0.6",
    "cross-env": "^7.0.3",
    "css-loader": "^5.2.6",
    "file-loader": "^6.2.0",
    "html-webpack-plugin": "^5.3.1",
    "styled-loader": "*",
    "ts-loader": "^9.2.3",
    "typescript": "^4.3.2",
    "url-loader": "^4.1.1",
    "webpack": "^5.38.1",
    "webpack-cli": "^4.7.2",
    "webpack-dev-server": "^3.11.2"
  },
  "dependencies": {
    "dotenv": "^10.0.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "homepage": "", // git 저장소 주소 or 홈페이지 
  "repository": {
    "type": "git",
    "url": "" // git 저장소 주소
  }
}

❓ scripts

  • 위에서 설명했다시피 cross-env NODE_ENV=development/production 은 윈도우 용이다
  • mac 환경이라면 --mode development/production 만으로 충분하다
  • webpack-dev-server를 실행시키는 명령어가 webpack serve로 변경되었다. 오래된 블로그에는 여전히 webpack-dev-server 명령어가 남아있으니 주의하자. 공식문서에도 안 나와있다고 한다;;

🚀 나머지 필요 폴더 / 파일들

.gitignore

  node_modules
  /dist

  .DS_Store
  .env.local
  .env.development.local
  .env.test.local
  .env.production.local

  npm-debug.log*
  yarn-debug.log*
  yarn-error.log*

  package-lock.json
  yarn.lock

폴더/파일

// src/App.tsx
import React from "react";

export default function App() {
  return <div>sample</div>;
}
// src/index.tsx
import React from "react";
import ReactDOM from "react-dom";

import App from "./App";

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

ReactDOM.render(<App />, rootElement);
// public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Title</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

📁 최종 폴더 구조

├── node_modules
├── package-lock.json
├── package.json
├── public
|  └── index.html
├── src
|  ├── App.tsx
|  └── index.tsx
├── tsconfig.json
└── webpack.config.js

👍 끝!

가능한 이건 뭐지? 싶은 내용을 최대한 설명하려고 노력했으나 여전히 부족한 점이 많습니다. 사실 의구심이 들면 스스로 찾아보면서 공부해가는게 더 개발자다운 방식이라고 생각하기도 합니다. 하지만 초보자 입장에서 환경 설정은 너무나 복잡하고 이해하기 힘든 영역이라 쪼금 더 설명을 덧붙였습니다.

다음은 이렇게 제작한 보일러플레이트를 CRA처럼 npx를 사용해 설치하는 방법에 대해 포스팅하겠습니다.

위 내용에서 오류를 발견하시면 댓글 부탁드립니다!

참고한 아티클들

profile
기억보단 기록을 / TIL 전용 => https://velog.io/@jjuny546

3개의 댓글

comment-user-thumbnail
2021년 10월 19일

똑같이 따라했는데 "Typescript emitted no output for [경로] at makeSourceMapAndFinish, successLoader,Object.loader" 라는 에러를 뱉어서 tsconfig.json에 "noEmit" 속성을 true에서 false로 바꿔줌으로써 해결했습니다.

** transpileOnly속성을 true로 주고 plugin에 ForkTsCheckerWebpackPlugin을 넣어줘도 해결이 되네요 :)

답글 달기
comment-user-thumbnail
2021년 10월 19일

mode를 development로 실행하면 해당글의 devServer 속성은
devServer: {
contentBase: path.resolve(__dirname, "public"),
host: "localhost",
port: PORT,
open: true,
hot: true,
compress: true,
historyApiFallback: true,
overlay: true,
stats: "errors-only",
},
처럼 되어있는데 webpack devServer 속성의 업데이트 때문인건지 약간 다르게 바꿔줘야하네용,

stats: "errors-only",
devServer: {
static: {
directory: path.resolve(__dirname, "public"),
},
port: PORT,
open: true,
client: {
overlay: true,
},
hot: true,
host: "localhost",
historyApiFallback: true,
compress: true,
},
stats속성은 따로 빼주어야하고, contentBase속성은 static > directory 속성으로, overlay는 client > overlay로 빼주니 되었습니다.

답글 달기
comment-user-thumbnail
2025년 2월 20일

The Top 10 Big Ass Porn Actresses often collaborate, creating memorable scenes that push the boundaries of creativity in adult films. Their partnerships frequently lead to iconic performances loved by fans.

답글 달기

관련 채용 정보