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는 React는 라이브러리가 아닌 프레임워크라 부르게 만드는 이유 중 하나입니다. 이미 촘촘하게 짜여진 환경에 따라야하며, 사용자가 커스텀하기엔 큰 부담이 따르거든요.
👉 하지만 언젠가는 직접 환경설정을 해야하는 날이 올 것이기 때문에, CRA에만 의존해서는 안 되겠다는 생각이 들었습니다.
아래 설명 부분부터는 말투가 좀 바뀔겁니다..
사실 보일러플레이트가 뭐 별거 있겠나(사실 별거임). 이미 수많은 블로그에 다양한 정보가 넘쳐난다.
하지만 블로그마다 환경 설정이 조금씩 다르다. 근데 다 제대로 동작한다.
🤦♂️ 이 미묘한 차이가 초보자 입장에서 아주 골치다.
의문이 들지만 명확한 설명은 없다. 보통의 환경 설정을 설명하는 블로그 글은 대부분 이 파일은 이렇게 작성하세요에서 그친다.. 다 그렇다는건 아니지만!
일단 한 번 따라해봐! 그럼, 짠! 동작해!
👉 가능하면 각종 설정 파일들(package.json, tsconfig ..)의 각 속성들이 어떤 의미인지 최대한 설명하겠다.. 이후 글에는 내가 진행하면서 궁금하고 찾아본 사소한(?) 내용들은 ❓로 표시하고 찾아낸 내용을 기술할 예정이다. 그러다보니 뭐 이런거까지 언급하냐 싶은 내용이 많을 수 있으니 참고해주시길..
npm init -y
❓ -y 는 뭐지?
npm i react react-dom
npm i -D typescript @types/react @types/react-dom
❓ 왜 typescript 관련 설치는 -D를 붙이나?
이 파일이 존재하는 경로가 TypeScript 프로젝트의 root 경로가 된다.
compilerOptions, files, include, exclude, extends, compileOnSave 등의 속성을 작성할 수 있다. 이에 대한 설명은 간단하게만 작성하고, 자세한 내용은 검색!
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"]
}
❓ 이 많은 옵션이 다 필요한건가?
참고 링크
여기 상당히 잘 정리된 4개의 문서가 있다. 각자 공부해보면서 자신만의 tsconfig.json 파일을 만들어보자
이제 모든 프로젝트를 번들링해줄 웹팩 설정을 해보자
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 옵션으로 설치한다.
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는 뭘까?
❓ NODE_ENV는 뭘까?
❓ Chunk(청크)는 뭘까?
❓ path.resolve vs path.join
❓ resolve 필드는 어떤 역할을 할까?
import Calender from "@jjunyjjuny/react-calendar"
import a from 'src/App'
❓ transpileOnly는 어떤 속성일까
fork-ts-checker-webpack-plugin
라는 플러그인으로 타입 체크를 한다.❓ HtmlWebpackPlugin는 어떤 역할을 할까?
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
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를 사용해 설치하는 방법에 대해 포스팅하겠습니다.
위 내용에서 오류를 발견하시면 댓글 부탁드립니다!
참고한 아티클들
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로 빼주니 되었습니다.
똑같이 따라했는데 "Typescript emitted no output for [경로] at makeSourceMapAndFinish, successLoader,Object.loader" 라는 에러를 뱉어서 tsconfig.json에 "noEmit" 속성을 true에서 false로 바꿔줌으로써 해결했습니다.
** transpileOnly속성을 true로 주고 plugin에 ForkTsCheckerWebpackPlugin을 넣어줘도 해결이 되네요 :)