React

SetUp Without CRA or Bundle Tools

August 11, 2023

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

  1. static : output.path ์„ค์ •์—์„œ resolve ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด dist ๋””๋ ‰ํ† ๋ฆฌ ๊ฒฝ๋กœ์— build ํ•˜๋„๋ก ์„ค์ •ํ•˜์˜€๋‹ค. ์ด๋Š” ์ฆ‰, webpack-dev-server์—๊ฒŒ dist ๋””๋ ‰ํ„ฐ๋ฆฌ์˜ ๋ฒˆ๋“ค๋œ ํŒŒ์ผ์„ localhost:3000์—์„œ ์ œ๊ณตํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.
  2. port : ๊ฐœ๋ฐœ ์„œ๋ฒ„ ํฌํŠธ๋ฅผ ์˜๋ฏธ
  3. historyApiFallback: 404 ์‘๋‹ต ์‹œ index.html๋กœ redirect ํ•˜๋Š” ์†์„ฑ
  4. 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์ด ์žˆ์Šต๋‹ˆ๋‹ค.

current composition

5. Project Execution

// ํ”„๋กœ์ ํŠธ ์‹คํ–‰
$ yarn dev

yarn dev result

browser result



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 ์™€ ๊ฐ™์€ ๋ฒˆ๋“ค๋ง ๋„๊ตฌ์— ๋น„ํ•ด ํ”„๋กœ์ ํŠธ ๊ทœ๋ชจ๊ฐ€ ์ปค์กŒ์„ ๋•Œ ์‹คํ–‰ ๋˜๋Š” ๋นŒ๋“œ ์†๋„๊ฐ€ ๋Š๋ฆด ์ˆ˜ ์žˆ์ง€๋งŒ, ์ง์ ‘ ์„ค์ •ํ•˜๋Š” ๊ฒฝํ—˜์„ ํ•ด๋ณด๋ฉด์„œ, ๊ฐ๊ฐ์˜ ์˜ต์…˜์ด ์–ด๋–ค ๊ฒƒ์„ ์˜๋ฏธํ•˜๊ณ , ํ”„๋กœ์ ํŠธ์˜ ์„ฑ๊ฒฉ์— ๋งž๊ฒŒ ์˜ต์…˜๋“ค์„ ์ปค์Šคํ„ฐ ๋งˆ์ด์ง•ํ•˜์—ฌ ํ”„๋กœ์ ํŠธ๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝํ—˜์„ ํ•  ์ˆ˜ ์žˆ์–ด ์ข‹์•˜์Šต๋‹ˆ๋‹ค.


Profile picture

Personal Blog by Hyukmin Kwon

A Space to document my steady growth with code.