
CRA를 이용 하지 않고 웹팩으로 리액트 프로젝트 세팅을 한다.
velog의 시리즈중 react(with CRA) & react(without CRA) 는 자바스크립트 기반본 시리즈는 React & typescript를 이용한 프로젝트에 대해 기록 한다.
ESM(ECMAScript Modules)을 사용해야 하는 이유는 현대 JavaScript 환경의 표준이기 때문이며, 다음과 같은 기술적·실용적 이점이 있습니다:
ESM은 JavaScript의 공식 모듈 시스템입니다 (ECMAScript 2015부터 표준).
CommonJS(require, module.exports)는 Node.js만의 비표준 방식입니다.
브라우저, Deno, Node.js 모두 ESM을 지원하며, ESLint, TypeScript, Webpack, Vite 등 주요 도구들도 ESM을 기본으로 전환 중입니다.
ESM은 import/export가 정적으로 분석 가능하여:
트리 쉐이킹(Tree shaking)이 가능 → 사용하지 않는 코드를 제거해서 번들 크기 줄임
자동 코드 완성 및 타입 추론 정확도 향상
// 정적 분석 가능
import { add } from './utils.js';
vs
// 동적 분석 불가
const utils = require('./utils'); // 런타임까지 분석 불가
ESM은 브라우저 환경에서 <script type="module"> 사용 가능
Node.js에서도 import()로 동적 import 가능 → 지연 로딩, 코드 분할 가능
Node.js 20+ 및 대부분의 현대 툴링은 ESM 중심으로 움직이고 있음
일부 npm 패키지들은 더 이상 CommonJS를 지원하지 않거나, ESM만 배포함
React, Vite, Astro 등 프론트엔드 도구들도 ESM 기반
ESM은 모듈 최상단에서 await 사용 가능 (top-level await)
CommonJS에서는 불가능
// ESM
const data = await fetch(...);
항목 설명
.js, .mjs, type: "module" 문제 Node.js에서 ESM을 사용하려면 파일 확장자 또는 package.json 설정 필요
동적 import 제한 import() 사용 시에는 비동기 코드가 필요
일부 구버전 패키지 호환성 오래된 CommonJS 전용 패키지는 직접 호환처리 필요
선택 사용 시기
✅ ESM (import, export) 신규 프로젝트, 모던 툴체인 사용 시 권장
⚠️ CommonJS (require) 기존 Node.js 레거시 시스템 유지 시
mkdir ProjectName && cd ProjectName
yarn init
// 본인 입맛에 맞게 답변을 하면되고 귀찮으면 enter 또는 yarn init -y
yarn init v1.22.19
question name (ProjectName):
question version (1.0.0):
question description:
question entry point (index.tsx):
question repository url:
question author:
question license (MIT):
question private:
success Saved package.json
Done in 6.64s.
위 과정을 통해 Project root > package.json이 생성된다.
{
"name": "ProjectName",
"version": "1.0.0",
"main": "index.tsx",
"license": "MIT"
}
yarn add react react-dom
yarn add -D typescript @types/react @types/react-dom
yarn add -D webpack webpack-cli webpack-dev-server html-webpack-plugin ts-loader terser-webpack-plugin ignore-loader
webpack: javaScript application staic module buddler
웹팩을 사용하면 응용 프로그램을 구성 요소로 분할하고 이를 종속성 관리를 통해 정적 에셋으로 변환하여 번들링할 수 있다.
webpack-cli: webpack commnd interface
웹팩 빌드 및 관련명령어를 터미널에서 사용할 수 있다.
webpack-dev-server: dev server
실시간으로 변경되는 소스를 확인 하고 디버깅 할수 있다.
html-webpack-plugin: 웹팩 빌드 시 자동으로 HTML 파일을 생성.
웹팩 빌드에서 생성된 JavaScript나 CSS 파일을 HTML 파일에 자동으로 추가할 수 있습니다.
webpack-merge: webpack-config merge
ex) webpack.common.js , webpack.dev.js, webpack.prod.js
tip) 하나의 config에서 분기를 통해 처리 하는 것보다 파일 나누는게 해보니 나음.
ts-loader: typescript loader
terser-webpack-plugin; 빌드시 [name].LICENCE.txt 등과 같이 불필요한 파일 생성 방지
ignore-loader: .md파일과 같이 빌드에 필요 없는 파일을 무시 하는 기능
clean-webpack-plugin: 빌드 결과물 정리 플러그인
webpack.config > output: {clean: true} 옵션 추가를 통해 할수 있다
낡은 것은 버리기로 한다. 혹 필요 할수도 있으니...
JavaScript 최신 문법을 구형 브라우저에서도 실행 가능한 ES5로 변환 도구.
yarn add -D @babel/core @babel/preset-env @babel/preset-react babel-loader @babel/preset-typescript
@babel/core: Babel Core Package
@babel/preset-env: 이 preset은 최신 JavaScript 문법을 사용하여 작성된 코드를 지원할 브라우저 환경에 맞게 변환해줍니다. 브라우저 지원 목록을 설정하고 필요한 폴리필을 추가하여 최신 문법을 구형 브라우저에서도 실행 가능하게 만들어줍니다.
@babel/preset-react: 이 preset은 React 애플리케이션에서 JSX 문법을 사용할 수 있게 해줍니다. JSX는 JavaScript 내에서 HTML을 작성할 수 있게 해주는 문법으로, React 애플리케이션을 개발할 때 필수적인 도구입니다.
babel-loader: Webpack에서 바벨을 사용 하기 위한 로더
@babel/preset-typescript: typescript preset
bash code: 직접 하나씩 생성 해보길 권장
touch tsconfig.json && mkdir config && touch config/webpack.common.js && touch config/webpack.dev.js && touch config/webpack.prod.js
간결한 폴더 트리를 위해 설정관련 폴더(config)를 만들고 안에 추가한다.
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},
"typeRoots": ["src/@types", "./node_modules/@types"],
"target": "ESNext",
"jsx": "react",
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"strictNullChecks": false,
"noImplicitAny": true,
"skipLibCheck": true,
"allowJs": true,
"outDir": "./build",
"strictBindCallApply": false,
"sourceMap": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"plugins": [{ "transform": "ts-transformer-keys/transformer" }]
},
"include": ["src", "jsoneditor-react"],
"exclude": ["node_modules", "build", "public"]
}
yarn tsc --init 명령어를 통해 생성해보기 권한다.
위 코드는 필자 입맛에 맞게 구성한 가장 기본값이다.
tsc --init을 통해 생성시 "jsx": "react" 구문을 반드시 추가 해준다.
"jsx": "react",
## ./config/babel.config.js
구 브라우져 지원시 추가
module.exports = {
presets: ["@babel/preset-react", "@babel/preset-env"],
};
import path from "path";
import HtmlWebpackPlugin from "html-webpack-plugin";
import webpack from "webpack";
const Webpack = (env, argv, custom) => {
const { appName, version, minify } = custom;
console.log(appName, "build version : ", version, JSON.stringify(argv), JSON.stringify(env), JSON.stringify(custom));
return {
entry: { main: "./src/index.tsx" },
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".css", ".scss"],
alias: { "@": path.resolve(process.cwd(), "src") },
symlinks: false,
cacheWithContext: false
},
performance: {
maxEntrypointSize: 10240000,
maxAssetSize: 10240000
},
module: {
rules: [
{
test: /\.(ts|tsx|js|jsx)$/,
use: "ts-loader",
exclude: /node_modules/
},
{
test: /\.md$/,
use: "ignore-loader"
}
]
},
plugins: [
new webpack.ProvidePlugin({ React: "react" }),
new HtmlWebpackPlugin({
title: appName,
template: "./public/index.html",
favicon: "./public/favicon.ico",
minify
})
]
};
};
export default Webpack;
import { merge } from "webpack-merge";
import common from "./webpack.common.js";
const Webpack = (env, argv) => {
const custom = {
appName: argv.name || "client",
version: env.version || "v0.0.1",
minify: {
collapseWhitespace: true,
removeComments: true
}
};
return merge(common(env, argv, custom), {
mode: "development",
devtool: env.debug ? "inline-source-map" : "eval",
output: {
publicPath: "/",
clean: true
},
devServer: {
host: "localhost",
port: argv.port || 80,
historyApiFallback: true,
open: true,
client: {
overlay: {
errors: true,
warnings: false
},
logging: "info"
}
}
});
};
export default Webpack;
import path from "path";
import TerserPlugin from "terser-webpack-plugin";
import { merge } from "webpack-merge";
import common from "./webpack.common.js";
const Webpack = (env, argv) => {
const version = env.version || "v0.0.1";
const buildDir = env.dist ? path.resolve(process.cwd(), env.dist) : "build";
return merge(common(env, argv, { version }), {
mode: "production",
output: {
publicPath: "auto",
path: buildDir,
filename: `${version}/[name].js`,
clean: true
},
optimization: {
minimize: true,
minimizer: [new TerserPlugin({ extractComments: false })]
}
});
};
export default Webpack;
"name": "projectname",
"version": "1.0.0",
"description": "project description",
"main": "index.js",
"type": "module",
"author": "MIT",
"license": "MIT",
"private": true,
"engines": {
"node": "22.15.0",
"yarn": "1.22.22"
},
"scripts": {
"clean": "rm -rf node_modules && yarn cache clean && yarn",
"start": "yarn webpack-dev-server --port=80 --progress --config config/webpack.dev.js --name=app ",
"build": "yarn webpack --progress --config config/webpack.prod.js --name=app --env version=v0.0.1 dist=build"
},
bash code: 직접 하나씩 생성 해보길 권장
mkdir -p public && mkdir -p src && touch src/index.tsx && touch src/App.tsx && touch public/index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<link rel="shortcut icon" sizes="any" href="favicon.ico" />
<link rel="icon" sizes="any" href="favicon.ico" type="image/x-icon" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Expires" content="0" />
<meta http-equiv="Pragma" content="no-cache" />
<title>REACT</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
import React from "react";
const App = () => {
return <div>This is App.jsx</div>;
};
export default App;
import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
// StricMode 개발 초기에 구성 요소의 일반적인 버그를 찾을 수 있습니다.
<StrictMode>
<App />
</StrictMode>
);
yarn dev
yarn start
yarn run v1.22.22
$ yarn webpack-dev-server --port=80 --progress --config config/webpack.dev.js --name=app
$ C:\work\sources\react-webpack2025\node_modules\.bin\webpack-dev-server --port=80 --progress --config config/webpack.dev.js --name=app
app build version : v0.0.1 {"port":80,"progress":true,"config":["config/webpack.dev.js"],"name":"app","env":{"WEBPACK_SERVE":true}} {"WEBPACK_SERVE":true} {"appName":"app","version":"v0.0.1","minify":{"collapseWhitespace":true,"removeComments":true}}
<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:80/, http://[::1]:80/
<i> [webpack-dev-server] Content not from webpack is served from 'C:\work\sources\react-webpack2025\public' directory
<i> [webpack-dev-server] 404s will fallback to '/index.html'
<i> [webpack-dev-middleware] wait until bundle finished: /
asset main.js 1.19 MiB [emitted] (name: main)
asset favicon.ico 15 KiB [emitted]
asset index.html 565 bytes [emitted]
runtime modules 26.3 KiB 12 modules
modules by path ./node_modules/ 1.09 MiB
modules by path ./node_modules/webpack-dev-server/client/ 84.8 KiB 8 modules
modules by path ./node_modules/webpack/hot/*.js 5.17 KiB 4 modules
modules by path ./node_modules/react-dom/ 947 KiB 4 modules
modules by path ./node_modules/react/ 44.2 KiB 2 modules
modules by path ./node_modules/scheduler/ 12 KiB
./node_modules/scheduler/index.js 194 bytes [built] [code generated]
./node_modules/scheduler/cjs/scheduler.development.js 11.9 KiB [built] [code generated]
./node_modules/events/events.js 14.5 KiB [built] [code generated]
./node_modules/ansi-html-community/index.js 4.16 KiB [built] [code generated]
modules by path ./src/*.tsx 2.34 KiB
./src/index.tsx 1.96 KiB [built] [code generated]
./src/App.tsx 387 bytes [built] [code generated]
app (webpack 5.99.8) compiled successfully in 2549 ms
<i> [webpack-dev-middleware] wait until bundle finished: /
assets by path *.js 1.19 MiB
asset main.js 1.19 MiB [emitted] (name: main)
asset main.826510bdba39566a87f1.hot-update.js 860 bytes [emitted] [immutable] [hmr] (name: main)
asset favicon.ico 15 KiB [emitted]
asset index.html 565 bytes [emitted]
asset main.826510bdba39566a87f1.hot-update.json 28 bytes [emitted] [immutable] [hmr]
Entrypoint main 1.19 MiB = main.js 1.19 MiB main.826510bdba39566a87f1.hot-update.js 860 bytes
cached modules 1.09 MiB [cached] 24 modules
runtime modules 26.3 KiB 12 modules
app (webpack 5.99.8) compiled successfully in 115 ms

├─ config
~~├─ babel.config.js~~
├─ webpack.common.js
├─ webpack.dev.js
├─ webpack.pro.js
├─ node_modules
├─ public
├─ favicon.ico
└─ index.html
├─ src
├─ App.jsx
└─ index.js
├─ package.json
├─ jsconfig.json
└─ yarn.lock
{
"name": "react-webpack2025",
"version": "1.0.0",
"description": "react practics",
"main": "index.js",
"type": "module",
"author": "MIT",
"license": "MIT",
"private": true,
"engines": {
"node": "22.15.0",
"yarn": "1.22.22"
},
"scripts": {
"clean": "rm -rf node_modules && yarn cache clean && yarn",
"start": "yarn webpack-dev-server --port=80 --progress --config config/webpack.dev.js --name=app ",
"build": "yarn webpack --progress --config config/webpack.prod.js --name=app --env version=v0.0.1 dist=build"
},
"dependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
"devDependencies": {
"@types/react": "^19.1.4",
"@types/react-dom": "^19.1.4",
"html-webpack-plugin": "^5.6.3",
"ignore-loader": "^0.1.2",
"terser-webpack-plugin": "^5.3.14",
"ts-loader": "^9.5.2",
"typescript": "^5.8.3",
"webpack": "^5.99.8",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.1"
}
}