CRA(Create React App) ๊ทธ๋ฆฌ๊ณ Vite, Rollup์ ๊ฐ์ ๋ฒ๋ค๋ง ๋๊ตฌ ์์ด babel๊ณผ webpack์ ํ์ฉํ์ฌ ์ง์
React
+TypeScript
๊ฐ๋ฐ ํ๊ฒฝ์ ๊ตฌ์ถํด๋ณด๋ ๋ฐฉ๋ฒ์ ๋ํด์ ์ ๋ฆฌํ์์ต๋๋ค.
1. Package Manager & Package Configuration
// package.json ํ์ผ ์์ฑ
$ yarn init
// react & react-dom ์ค์น
$ yarn add react react-dom
// index.html ๊ตฌ์ฑ
$ mkdir public && public/ && touch index.html
// src ๋๋ ํ ๋ง ์์ฑ ๋ฐ React์ Entry Point๊ฐ ๋ Index.tsx ํ์ผ ์์ฑ
$ mkdir src && cd src/ && touch Index.tsx
// Root Component๊ฐ ๋ App.tsx ํ์ผ ์์ฑ
$ touch App.tsx
$ cd ..
1-1. index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#fff" />
<title>WithoutCRA-TS</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
1-2. index.tsx
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
1-3. App.tsx
ํ ์คํธ๋ฅผ ์ํด ๋ค์๊ณผ ๊ฐ์ ์ฝ๋๋ฅผ ์์ฑํ์์ต๋๋ค.
import { useState } from 'react';
const commonBtnStyle = {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100px',
height: '36px',
fontSize: '16px',
fontWeight: 'bold',
borderRadius: '9999px',
};
const followBtnStyle = {
...commonBtnStyle,
backgroundColor: 'black',
color: 'white',
};
const unfollowBtnStyle = {
...commonBtnStyle,
backgroundColor: 'white',
color: 'black',
border: '1px solid #cfd9de',
};
const App = () => {
const [following, setFollowing] = useState(false);
return (
<div>
<button style={following ? followBtnStyle : unfollowBtnStyle} onClick={() => setFollowing(!following)}>
{following ? 'follow' : 'unfollow'}
</button>
<ul>
<li>red</li>
<li>blue</li>
<li>yellow</li>
<li>orange</li>
<li>green</li>
</ul>
</div>
);
};
export default App;
2. TypeScript Configuration
// TypeScript & React์์ TypeScript๋ฅผ ์ง์ํ๊ธฐ ์ํ @types/react & @types/react-dom ์ค์น
$ yarn add typescript @types/react @types/react-dom --dev
// TypeScript ์ด๊ธฐํ & tsconfig.json ํ์ผ ์์ฑ
$ tsc --init
2-1. tsconfig.json
{
"compilerOptions": {
/* Modules */
"baseUrl": ".",
"paths": {},
"module": "esnext",
"moduleResolution": "nodenext",
"resolveJsonModule": true,
/* Type Checking */
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"strict": true,
/* Language and Environment */
"target": "es6",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"jsx": "react-jsx",
/* Interop Constraints */
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
/* Emit */
"downlevelIteration": true,
"inlineSources": true,
"preserveConstEnums": true,
"removeComments": false,
"sourceMap": true,
/* Completeness*/
"allowJs": true,
"skipLibCheck": true
},
"include": ["src"]
}
3. Babel Configuration
// babel ์ค์น
$ yarn add -D @babel/core @babel/preset-env babel-loader
// Typescript babel ์ค์น
$ yarn add -D @babel/preset-react @babel/preset-typescript
// babel.config.js ํ์ผ ์์ฑ
$ touch babel.config.js
3-1. babel.config.js
Babel์ JavaScript TransFiler(Compiler)๋ก ES6 ์ด์์ ์ต์ ๋ฌธ๋ฒ์ ๋ง์ ๋ธ๋ผ์ฐ์ ์์ ํธํํ๊ธฐ ์ํด ES5 ์ดํ์ ์ด์ ๋ฌธ๋ฒ์ผ๋ก ์์ฑํ ๊ฒ์ฒ๋ผ ์์ค์ฝ๋ ๋ด์ Syntax ํํ๋ฅผ ๋ณ๊ฒฝํด์ฃผ๋ ์ญํ ์ ํฉ๋๋ค.
ES6์ ๋์ ๋ Arrow Function์ ES6 ์ด์ ์ ๋ฌธ๋ฒ์ ์ง์ํ๋ ๋ธ๋ผ์ฐ์ ์์๋ ์ดํดํ์ง ๋ชปํ๊ธฐ ๋๋ฌธ์, Babel์ ํตํด ํธ๋์ค ํ์ผ๋งํ๋ ๊ณผ์ ์ด ํ์ํฉ๋๋ค.
module.exports = api => {
// this 'cache' option makes it possible to optimize the build process performance by caching config function execution result like 'presets' and 'plugins'
api.cache(true);
const presets = ['@babel/preset-react', '@babel/preset-env', '@babel/preset-typescript'];
const plugins = [];
return {
presets,
plugins,
};
};
4. Webpack Configuration
// webpack ํ์ ๊ตฌ์ฑ ์ค์น
$ yarn add -D webpack webpack-cli webpack-dev-server
// webpack์ ํ๋ฌ๊ทธ์ธ ๊ด๋ จ ๊ตฌ์ฑ ์ค์น
$ yarn add html-webpack-plugin clean-webpack-plugin ts-loader --dev
// webpack.config.js ํ์ผ ์์ฑ
$ touch webpack.config.js
4-1. webpack.config.js
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/index.tsx',
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
module: {
rules: [
{
test: /\.tsx$/,
use: ['babel-loader', 'ts-loader'],
},
{
test: /\.(png|jpe?g|gif)$/,
use: [
{
loader: 'file-loader',
},
],
},
],
},
plugins: [
new HTMLWebpackPlugin({
template: './public/index.html',
}),
new CleanWebpackPlugin(),
],
optimization: { minimizer: [] },
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
devServer: {
static: './dist',
historyApiFallback: true,
port: 3000,
hot: true,
},
};
babel-loader : webpack์ด ๋ฒ๋ค๋งํ
bundle.js
๋ฅผpublic/index.html
์ ๋ถ์ธ ํ ์ต์ข ์ ์ธ html ํ์ผ์ dist ๋๋ ํ ๋ฆฌ์ ๊ฐ์ด ๋ง๋ค์ด์ฃผ๋ ์ญํ ์ ํ๋ค.
ts-loader : ์ถ๊ฐ์ ์ผ๋ก typescript ์ฆ, tsx๋ ์ฝ๊ธฐ ์ํด
ts-loader
๋ฅผ ํ์ฉํ๋ค.
HTMLWebpackPlugin : webpack์ด ๋ฒ๋ค๋งํ
bundle.js
๋ฅผpublic/index.html
์ ๋ถ์ธ ํ ์ต์ข ์ ์ธ html ํ์ผ์dist
๋๋ ํ ๋ฆฌ์ ๊ฐ์ด ๋ง๋ค์ด์ฃผ๋ ์ญํ ์ ํ๋ค.
devServer
static
:output.path
์ค์ ์์resolve
๋ฉ์๋๋ฅผ ํตํดdist
๋๋ ํ ๋ฆฌ ๊ฒฝ๋ก์ build ํ๋๋ก ์ค์ ํ์๋ค. ์ด๋ ์ฆ,webpack-dev-server
์๊ฒdist
๋๋ ํฐ๋ฆฌ์ ๋ฒ๋ค๋ ํ์ผ์localhost:3000
์์ ์ ๊ณตํ๋๋ก ํ๋ ๊ฒ์ ์๋ฏธํ๋ค.port
: ๊ฐ๋ฐ ์๋ฒ ํฌํธ๋ฅผ ์๋ฏธhistoryApiFallback
: 404 ์๋ต ์index.html
๋ก redirect ํ๋ ์์ฑhot
:dist
๋๋ ํ ๋ฆฌ ๋ด ์ ๋ฐ์ดํธ๊ฐ ๋ฐ์ํ ๋๋ง๋ค ๋ฐํ์๋ง๋ค ์ ๋ฐ์ดํธ ํด์ฃผ๋ ์ญํ ์ ํ๋ค.
โก๏ธ
npx webpack server
๋ช ๋ น์ด๋ก ์คํํ์ฌlocalhost:3000
์๋ฒ๋ฅผ ์คํํ๋ค.
4-2. build
โก๏ธ
npx webpack
๋ช ๋ น์ด๋ฅผ ์คํํ์ฌ ๋น๋ํ๋ค.
์๋์ ๊ฐ์ด package.json
์ script
๋ช
๋ น์ด๋ฅผ ์ง์ ํด์ค๋ค๋ฉด ์์ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ์๋ฒ๋ฅผ ์คํํ๊ฑฐ๋ ๋น๋ํ์ง ์์๋ ๋๋ค.
// package.json
"scripts": {
"dev": "webpack-dev-server --config webpack.config.js --open --hot",
"build": "webpack --config webpack.config.js"
},
$ npx webpack
๋น๋๋ฅผ ์คํํ๋ฉด, ์๋์ ๊ฐ์ด dist
๋๋ ํ ๋ฆฌ๊ฐ ์์ฑ๋๋ฉฐ ํด๋น ๋๋ ํ ๋ฆฌ ์์๋ ๋ฒ๋ค๋ ์๋ฐ์คํฌ๋ฆฝํธ ํ์ผ์ธ bundle.js
์ index.html
์ด ์์ต๋๋ค.
5. Project Execution
// ํ๋ก์ ํธ ์คํ
$ yarn dev
5. Extra Options
5-1. .prettierrc
// prettier ์ค์น
$ yarn add -D --exact prettier
// webpack์ ํ๋ฌ๊ทธ์ธ ๊ด๋ จ ๊ตฌ์ฑ ์ค์น
$ touch .prettierrc
์๋์ ์ค์ ์ ์์ฃผ ์ฌ์ฉํ๋ prettier ์ต์ ์ ๋๋ค.
// .prettierrc
{
"singleQuote": true,
"bracketSpacing": true,
"bracketSameLine": true,
"arrowParens": "avoid",
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "css",
"printWidth": 120,
"insertPragma": false,
"singleAttributePerLine": false,
"proseWrap": "always",
"quoteProps": "as-needed",
"trailingComma": "all",
"useTabs": true,
"semi": true
}
5-2. .eslintrc.js
// eslint ์ค์น
$ yarn add -D eslint
// eslint์ prettier๋ฅผ ํจ๊ป ์ฌ์ฉํ๊ธฐ ์ํ ์ค์ ๋ฐ import
$ yarn add -D eslint-config-prettier eslint-plugin-import eslint-plugin-prettier eslint-plugin-simple-import-sort
// eslint์ React-Hooks ํจ๊ป ์ฌ์ฉ
$ yarn add -D eslint-plugin-react-hooks
์ดํ์ ๋ถ๊ฐ์ ์ธ
.gitignore
,.env
์ค์ ๋ค์ ์ถ๊ฐํ ์ ์์ต๋๋ค.
6. Conclusion
์ง๊ธ๊น์ง Vite, Rollup
์ ๊ฐ์ ๋ฒ๋ค๋ง ๋๊ตฌ ์์ด babel
๊ณผ webpack
์ ํ์ฉํ์ฌ React + Typescript
๊ฐ๋ฐ ํ๊ฒฝ์ ๊ตฌ์ถํ๋ ๊ณผ์ ์ ์ ๋ฆฌํด ๋ณด์์ต๋๋ค. ์ง์ํด์ Vite์ NextJS์ ๊ฐ์ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํจ์ ๋ฐ๋ผ ์ง์ ๊ฐ๋ฐ ํ๊ฒฝ์ ๊ตฌ์ฑํด๋ณด๋ ๊ฒฝํ์ ํ ์ ์ด ์์์ต๋๋ค.
Webpack
์ ๊ฒฝ์ฐ ์๋์ ์ผ๋ก Vite
์ ๊ฐ์ ๋ฒ๋ค๋ง ๋๊ตฌ์ ๋นํด ํ๋ก์ ํธ ๊ท๋ชจ๊ฐ ์ปค์ก์ ๋ ์คํ ๋๋ ๋น๋ ์๋๊ฐ ๋๋ฆด ์ ์์ง๋ง, ์ง์ ์ค์ ํ๋ ๊ฒฝํ์ ํด๋ณด๋ฉด์, ๊ฐ๊ฐ์ ์ต์
์ด ์ด๋ค ๊ฒ์ ์๋ฏธํ๊ณ , ํ๋ก์ ํธ์ ์ฑ๊ฒฉ์ ๋ง๊ฒ ์ต์
๋ค์ ์ปค์คํฐ ๋ง์ด์งํ์ฌ ํ๋ก์ ํธ๋ฅผ ๊ตฌ์ฑํ ์ ์๋ ๊ฒฝํ์ ํ ์ ์์ด ์ข์์ต๋๋ค.