eject
로 숨겨져있던 babel, webpack, jest 설정 파일을 custom 할 수 있다.✨ CRA 없이 Webpack + React + TypeScript + Babel + Prettier + ESLint로 프로젝트 초기 설정을 해보자
ReactDOM
에 전달render()
함수에서 DOMElement와 ReactElement를 인자로 받아 서브트리로 Element를 렌더링mkdir react-boilerplate
cd react-boilerplate
yarn
(js 패키지 관리자)을 사용해서 필요한 패키지들을 설치하고자 한다.yarn init
yarn add react react-dom
yarn add -D typescript @types/react @types/react-dom
npx tsc --init
include
에 지정한 파일 파일 목록을 제외시킬 수 있다. (include
에 지정하지 않은 경우 적용되지 않음)target
tsc
가 최종적으로 컴파일하는 결과물의 문법 형태를 결정es5
를 선택하면 화살표함수가 function으로 변환jsx
react-jsx
: import React 없이 React 사용 가능lib
"DOM"
을 지정해주면 DOM 관련 API 타입을 사용 가능outDir
include
로 지정된 파일들의 결과물이 저장되는 폴더를 정의noEmit
true
로 설정하면 결과 파일이 나오지 않음 (단순 타입 체크용)strict
true
로 설정하면 타입 검사 옵션 중 strict 관련된 것을 모두 true로 만듦module
moduleResolution
"node"
로 지정해주면 node.js
가 사용하는 방식으로 모듈을 찾음baseUrl
paths
baseUrl
기준으로 다시 매핑 가능 (절대경로로 사용)isolatedModules
"true"
로 설정하면 프로젝트 내의 모든 파일을 모듈로 만들기를 강제esModuleInterop
CommonJS
의 export와 ES6
의 export가 다른데, 이 문제를 해결하기 위해 "true"
로 설정skipLibCheck
.d.ts
파일에 타입 정의가 잘못되어있어 오류가 날 수 있는데, 이를 true
로 설정해 .d.ts
파일의 타입 검사를 생략 가능{
"compilerOptions": {
"target": "es2016",
"jsx": "react-jsx",
"module": "esnext",
"moduleResolution": "node",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true, // 파일명에 대소문자 구분하지 않아도 되는 기능
"strict": true,
"skipLibCheck": true
},
"include": ["src"]
}
webpack-dev-server
사용historyApiFallback
historyApiFallback
이 설정한 url을 포함하는 url에 접근했을 때 index.html
을 서빙해주는 효과hot
compress
source-map
: 가장 기본적인 옵션으로 map 파일을 만들어 url에 파일 경로를 추가eval-
: JS의 함수 eval()을 사용해 sourceMap을 생성 (각 모듈을 따로 실행시켜 수정된 모듈만 재빌드하기 때문에 빠름)inline-
: map 파일을 만들지 않고 주석에 파일을 data URL로 작성해두어 bundle.js 파일 내에 포함cheap-
: 라인 넘버만 매핑하고 라인에서 몇 번째 글자인지 매핑하지 않음import
또는 load
할 때 모듈의 소스코드를 변형시키는 전처리 과정을 수행css-loader
: JS에서 css 파일을 불러들이도록 도와주는 loaderstyle-loader
: HTML 파일의 style 안으로 css를 삽입해주는 loadersass-loader
: JS에서 SASS 파일을 불러들이도록 도와주는 loaderpostcss-loader
: css 후처리를 도와주는 플러그인들을 한 환경을 만들어주는 도구ts-loader
: typescript(es6) → javascript(es6)babel-loader
: javascript(es6) → javascript(es5)html-webpack-plugin
: HTML 파일을 템플릿으로 생성할 수 있게 도와주는 플러그인copy-webpack-plugin
: 디렉토리를 copy해 dist에 들어갈 수 있게 도와주는 플러그인으로 이미지 같은 정적 파일을 하나의 디렉토리에 넣어 관리하기 편함webpack-bundle-analyzer
: 번들 크기를 크게 만드는 원인을 파악할 수 있도록 도와주는 플러그인mini-css-extract-plugin
: CSS코드가 포함된 JS 파일별로 CSS 파일을 생성하는 플러그인style-loader
사용autoprefixer
: -webkit
, -ms
등을 사용하지 않고 이전 브라우저에 스타일을 지정할 수 있게 도와주는 플러그인preset
-env
: 최신 JS를 사용할 수 있도록 해주는 번들-react
: react JSX 문법을 사용할 수 있도록 해주는 번들@babel/polyfill
은 decrecatedyarn add -D webpack webpack-cli webpack-dev-server
yarn add -D html-webpack-plugin copy-webpack-plugin webpack-bundle-analyzer
yarn add -D babel-loader ts-loader
development 버전과 production 버전의 파일을 따로 작성하기 위해 merge 패키지가 필요하다.
yarn add -D webpack-merge
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.tsx",
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist"),
clean: true, // 내보내기 전에 output 디렉토리 정리
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx"],
},
module: {
rules: [
{
test: /\.tsx?$/i,
use: [
{
loader: "babel-loader",
},
{
loader: "ts-loader",
},
],
exclude: /node_modules/,
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "public", "index.html"),
}),
],
};
path.resolve vs path.join
- 인자로 전달받은 경로를 합쳐 문자열 형태의 path를 반환
join
은 전달받은 인자의 왼쪽부터,resolve
는 오른쪽부터 합친다.resolve
는 합치기를 진행하다가/
를 만나면 절대경로로 인식하고 나머지 경로를 무시__dirname
은 현재 실행중인 경로
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");
module.exports = merge(common, {
mode: "development",
devtool: "eval-source-map",
devServer: {
historyApiFallback: true,
hot: true,
compress: true,
port: 8000,
},
});
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
module.exports = merge(common, {
mode: "production",
devServer: {
historyApiFallback: true,
hot: true,
compress: true,
port: 9000,
},
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: "static", // 분석 파일 html 보고서를 dist 폴더에 저장
openAnalyzer: false, // 실행 시 분석 창을 따로 열지 않음
generateStatsFile: true, // 분석 파일 json을 dist 폴더에 저장
statsFilename: "bundleStats.json", // 분석 파일 json 이름
}),
],
});
"scripts": {
"dev": "NODE_ENV=development webpack-dev-server --config webpack.dev.js",
"start": "NODE_ENV=production webpack-dev-server --config webpack.prod.js",
"build": "NODE_ENV=production webpack --config webpack.prod.js"
},
yarn dev
: 개발환경 webpack dev serveryarn start
: production 환경 webpack dev serveryarn build
: production 환경 build 파일 생성yarn add -D @babel/core @babel/preset-env @babel/preset-react @babel/plugin-transform-runtime @babel/runtime-corejs3
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
]
]
}
Code Formatter
로 개발자가 작성한 코드를 정해진 포맷을 따르도록 변환해주는 도구 중 하나arrowParens
: 화살표 함수가 하나의 매개변수를 받을때 괄호 사용 방식bracketSpacing
: 객체 리터럴의 괄호 양끝에 공백 삽입 여부endOfLine
: EoF 방식htmlWhitespaceSensitivity
: HTML 공백 감도jsxBracketSameLine
: JSX 마지막 >
를 다음 줄로 내릴 것인가jsxSingleQuote
: JSX에 single Quotation 사용 여부printWidth
: 줄바꿈 폭 길이proseWrap
: 마크다운 텍스트의 줄바꿈 방식quoteProps
: 객체 속성에 따옴표 적용 방식semi
: 세미콜론 사용 여부singleQuote
: single 따옴표 사용 여부tabWidth
: 탭 너비trailingComma
: 여러 줄을 사용할 때 콤마 사용 방식useTabs
: 탭 사용 여부parser
: 사용할 파서filePath
: parser를 유추할 수 있는 파일을 지정rangeStart
: 포맷을 적용할 파일의 시작 라인rangeEnd
: 포맷을 적용할 파일의 끝 라인requirePragma
: 파일 상단에 미리 정의된 주석을 작성하고 Pragma로 포맷팅 사용 여부insertPragma
: 미리 정의된 format marker의 사용 여부env
browser
, node
plugins
extends
parser
parserOptions
rules
off
or 0 : 규칙을 사용하지 않음, warn
or 1 : 규칙을 경고로 사용, error
or 2 : 규칙을 오류로 사용settings
ignorePatterns
node_modules
나 .
로 시작하는 파일 외에 다른 파일을 무시하고 싶은 경우 사용overrides
eslint-config-prettier
: ESLint의 formatting관련 설정 중 Prettier와 충돌되는 부분을 비활성화eslint-plugin-import
: ES2015+ import/export 구문의 린트를 지원eslint-plugin-jsx-a11y
: JSX내의 접근성 문제에 대해 즉각적인 AST 린팅 피드백eslint-plugin-react
: React 관련 린트를 지원eslint-plugin-react-hooks
: React hooks의 린트를 지원eslint-config-airbnb
: airbnb 린트 사용@typescript-eslint/eslint-plugin
: TS 관련 린트를 지원@typescript-eslint/parser
: TS용 ESLint 파서yarn add -D prettier eslint
yarn add -D eslint-config-prettier eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks eslint-config-airbnb eslint-config-airbnb-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser
{
"arrowParens": "avoid",
"bracketSpacing": true,
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "css",
"jsxBracketSameLine": false,
"jsxSingleQuote": false,
"printWidth": 100,
"tabWidth": 2,
"semi": true,
"useTabs": false,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"trailingComma": "es5"
}
module.exports = {
env: {
browser: true,
node: true,
},
ignorePatterns: ["*.js"],
plugins: ["react", "react-hooks", "import", "jsx-a11y", "@typescript-eslint", "html"],
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:import/recommended",
"plugin:jsx-a11y/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"airbnb-typescript",
"airbnb/hooks",
"prettier",
],
parserOptions: {
ecmaFeatures: {
jsx: true, // Enable parsing JSX
},
ecmaVersion: 12,
sourceType: "module",
project: "./tsconfig.json",
},
rules: {
"no-empty": "warn",
"no-console": "warn",
"react/react-in-jsx-scope": "off", // import React from 'react'
"@typescript-eslint/naming-convention": [
"error",
{
selector: "variable",
format: ["camelCase", "PascalCase", "UPPER_CASE"],
},
{ selector: "function", format: ["camelCase", "PascalCase"] },
{ selector: "typeLike", format: ["PascalCase"] },
],
},
};
<!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>React Boilerplate</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
import ReactDOM from "react-dom/client";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
root.render(<App />);
import styled from "styled-components";
const Container = styled.div`
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
`;
const Text = styled.p<{ color: string }>`
margin: 0;
color: ${({ color }) => color};
`;
function App() {
return (
<Container>
<Text color="#587cf7">React</Text>
<Text color="#d2fa64">Boiler</Text>
<Text color="#ff6c45">plate</Text>
</Container>
);
}
export default App;
yarn dev
명령어를 실행해보면 잘 동작하고 있는 것을 확인 가능하다!!!참고자료
https://datalater.github.io/posts/react-boilerplate/
https://yujo11.github.io/React/React-TS-Webpack-세팅/
https://velog.io/@tnehd1998/CRA없이-ReactTypeScript-환경-구축하기
https://velog.io/@jjunyjjuny/React-TS-boilerplate-제작기-환경-구성
babel 참고자료
https://programmingsummaries.tistory.com/401
tsconfig 참고자료
https://velog.io/@sooran/tsconfig.json-제대로-알고-사용하기
ESLint 참고자료
https://www.daleseo.com/eslint-config/
https://tech.kakao.com/2019/12/05/make-better-use-of-eslint/
https://poiemaweb.com/eslint
https://eslint.org/docs/latest/rules/
Prettier 참고자료
https://velog.io/@treejy/React-ESLint와-Prettier가-무엇이며-왜-필요하고-어떻게-사용하는지
https://adjh54.tistory.com/20