이전에 Vite에 대해 알아보면서 번들러에 대해 학습한적이 있었습니다. 해당 글에서 다루었듯 최근에는 CRA
가 아닌 Vite
를 이용해서 프로젝트들을 진행하고 있었고 속도에 대해 큰 만족을 가지고 있지만 다른 도구 없이 순수 리액트 환경을 구축해본 경험은 없어서 가장 많이 사용되는 번들러인 Webpack
에 대해 학습해보고 개발 환경을 구축해보려 합니다.
Webpack은 모던 JavaScript 애플리케이션을 위한 정적 모듈 번들러입니다. webpack이 애플리케이션을 처리할 때, 내부적으로는 프로젝트에 필요한 모든 모듈을 매핑하고 하나 이상의 번들을 생성하는 디펜던시 그래프를 만듭니다. -webpack 공식 문서-
공식 문서에 나와있듯이 webpack은 자바스크립트 모듈 번들러입니다.
모듈
은 프로그램을 구성하는 독립적인 요소입니다. 데이터와 함수들을 묶어서 모듈을 형성하며 파일 단위로 관리됩니다. 모듈화 프로그래밍을 통해 기능별로 파일을 나눠서 프로그래밍을 진행하면 유지보수가 쉽다는 장점이 있습니다.
몇 만줄이 넘는 코드를 하나의 js파일에서 관리한다면 해당 코드들을 수정하고 유지보수하는데 매우 불편함을 겪을 것입니다. 또한 변수, 함수명 등이 기하급수적으로 많아지기에 오류를 발생시키기도 쉬워집니다. 이러한 비효율적인 방식을 극복하기 위해 모듈을 통해 개발의 편리성을 추구할 수 있어집니다.
번들러
는 모듈별로 나누어진 여러개의 파일들을 하나의 파일로 만들어주는 역할을 합니다. 애플리케이션을 만들 때 필요한 JS 파일의 양은 상상을 초월합니다. 번들러를 사용하면 이 JS 파일들을 하나의 파일로 묶어서 관리할 수 있습니다.
JS 파일을 번들러 없이 따로 로딩하게 되면 다음과 같은 문제점들이 발생합니다.
결국 대규모 협업 개발에 유리한 module 방식을 채택하여 여러개의 파일을 나눠서 개발하고 모든 파일을 하나로 합쳐서 하나의 번들(bundle)
로 만들어내는 번들러
라는 개념이 만들어진 것입니다.
번들러의 종류는 대표적으로 webpack
, parcel
, rollup
등이 존재합니다.
Webpack
은 웹 브라우저에서 사용되는 모든 자원을 번들링해주는 번들러로써 JS 파일 뿐만 아니라 CSS, HTML, Image 등을 모듈로 로드해서 사용할 수 있습니다.
Entry
는 webpack이 내부의 의존성 그래프를 생성하기 위해 사용해야 하는 모듈입니다. webpack이 번들링을 시작할 메인 파일로 값으로 입력된 파일의 의존성을 찾아 나머지 파일을 알아냅니다.
Entry는 꼭 하나가 아닌 여러개의 entry point를 지정할 수 있는데 이때 이름(name)과 콘텐츠 해시(contents hash)를 가진 여러 개의 번들로 만들 수 있습니다.
기본값은 ./src/index.js
입니다.
module.exports = {
entry: "./src/index.tsx",
};
Ouput
속성은 생성된 번들을 내보낼 위치와 이 파일의 이름을 지정하는 방법을 webpack에 알려주는 역할을 합니다.
module.exports = {
output: {
path: path.join(__dirname, "/dist"), // 내보낼 위치
filename: "bundle.js", // 번들 파일명
},
};
webpack은 기본적으로 JavaScript와 JSON 파일만 이해합니다. Loader
를 사용하면 webpack이 다른 유형의 파일을 처리하거나, 유효한 모듈로 변환 하여 애플리케이션에서 사용하거나 의존성 그래프에 추가합니다.
module.exports = {
module: {
rules: [{ test: /\.txt$/, use: 'babel-loader' }],
},
};
Loader
는 두 가지 속성을 가집니다.
1. 변환이 필요한 파일들을 식별하는 test
속성
2. 변환을 수행하는데 사용되는 loader를 가리키는 use
속성
Babel-loader
을 사용해 최신 문법으로 작성된 JavaScript를 모든 브라우저(특히 IE)에서 해석할 수 있도록 변환할 수 있습니다. 이건 아래서 자세히 알아보겠습니다.
loader는 특정 유형의 모듈을 변환하는데 사용된다면 Plugin
을 활용하여 번들을 최적화하거나 에셋을 관리하고 환경 변수 주입등과 같은 광범위한 작업을 수행할 수 있습니다.
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html",
}),
],
};
HTMLWebpackPlugin
은 HTML 파일을 생성하고 자동으로 생성된 모든 번들을 삽입합니다.
mode
를 통해 파일을 어떻게 번들링해줄 것인지 설정할 수 있습니다.
module.exports = {
mode: "development", // production
}
production
모드는 공백 및 줄바꿈도 없애서 파일을 최소화되어 있고 development
모드는 개발을 위해 코드가 정리되어 있습니다.
webpack 사용을 위해서는 Babel
에 대해서도 알아보아야 합니다.
Babel은 JavaScript 컴파일러입니다. -Babel 공식문서-
Babel
은 자바스크립트 컴파일러로 ES6버전 이상의 JS 구문들을 구 버전인 ES5 문법으로 변환해주는 역할을 합니다.
현재는 많은 브라우저들이 ES6를 지원하지만 일부 브라우저는 여전히 ES6를 지원하지 않습니다. (특히 IE)
그렇다면 ES6 문법으로 작성한 애플리케이션을 IE같은 브라우저에서 구동하면 어떻게 될까요? 당연히 정상적으로 작동하지 않습니다. 그렇기에 일부 브라우저들을 생각해서라도 Babel
을 사용해 구 버전 문법으로 지원할 필요가 있습니다.
Babel이 하는 역할은 간단하게 다음과 같습니다.
트랜스파일링은 최신의 자바스크립트 문법을 오래된 브라우저가 이해할 수 있도록 오래된 문법으로 변환해줍니다.
폴리필은 오래된 브라우저에 네이티브로 지원하지 않는 사용자가 사용하는 메서드, 속성, API가 존재하지 않을 때 추가해줍니다.
React에서 사용되는 JSX 구문들을 변환해줍니다.
Webpack을 구성해보기 위해 CRA 환경을 구성해보겠습니다.
라이브러리 설치를 위한 package.json
을 생성해줍니다.
yarn init -y
react 환경을 위해 react, react-dom 패키지를 설치해줍니다.
yarn add react react-dom
이후 React 환경 구동에 기본적으로 필요한 index.html
, index.tsx
, App.tsx
파일을 생성해줍니다.
index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
index.tsx
import App from "./App";
import { createRoot } from "react-dom/client";
const root = createRoot(document.getElementById("root") as HTMLElement);
root.render(<App />);
App.tsx
const App = () => (
<div>App</div>
);
export default App;
TypeScript 사용을 위해 관련 설정을 해줍니다. 순수 JavaScript만 사용시 해당 설정은 넘어가셔도 됩니다.
yarn add -D typescript @types/react @types/react-dom
tsconfig.json 생성도 해줍니다.
tsc --init
tsconfig.json
{
"compilerOptions": {
"outDir": "./dist",
"target": "ES2015",
"skipLibCheck": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"jsx": "react-jsx",
"allowJs": true,
"baseUrl": ".",
"paths": {
"@components/*": ["components/*"],
"@pages/*": ["pages/*"]
},
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
}
}
webpack 설정을 위한 패키지들을 설치해줍니다.
yarn add -D webpack webpack-cli webpack-dev-server webpack-merge
yarn add -D html-webpack-plugin ts-loader
각 패키지들은 다음과 같은 역할을 수행합니다.
webpack
webpack을 사용하기 위해 기본적으로 필요한 패키지
webpack-cli
터미널에서 webpack 커맨드를 실행할 수 있게 해주는 커맨드라인 도구
webpack-dev-server
Hot reload를 가능하게 하는 개발 서버로 디스크에 저장되지 않는 메모리 컴파일을 사용하기 때문에 컴파일 속도가 빠름
webpack-merge
webpack을 development, production모드로 분리하여 구축할 수 있게 도와줌
html-webpack-plugin
html 파일을 템플릿으로 생성할 수 있게 도와주는 플러그인
ts-loader
타입스크립트 코드를 자바스크립트 코드로 변환해줌
보통은 webpack.config.js
파일에서 mode를 나눠 사용하지만 저는 공통 파일인 webpack.common.js
를 생성하여 dev, prod 모드에서 둘다 공통으로 사용되는 설정을 작성한 뒤 webpack.dev.js
와 webpack.prod.js
를 나눠서 작성하여 모드를 구분해주었습니다.
webpack.common
.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
module.exports = {
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",
},
],
},
],
},
output: {
path: path.join(__dirname, "/dist"),
filename: "bundle.js",
},
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html",
}),
],
};
webpack.dev
.js
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");
module.exports = merge(common, {
mode: "development",
devtool: "eval",
devServer: {
historyApiFallback: true,
port: 3000,
hot: true,
},
});
webpack.prod.js
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");
module.exports = merge(common, {
mode: "production",
devtool: "hidden-source-map",
});
Babel 설정을 위한 패키지들도 설치해줍니다.
yarn add -D babel-loader @babel/core @babel/preset-env
yarn add -D babel/preset-react @babel/preset-typescript
babel.config.js
를 작성해줍니다.
module.exports = {
presets: [
"@babel/preset-env",
["@babel/preset-react", { runtime: "automatic" }],
"@babel/preset-typescript",
],
};
프로그램 구동을 위한 script들을 package.json에 작성해줍니다.
"scripts": {
"dev": "webpack-dev-server --config config/webpack.dev.js --open --hot",
"build": "webpack --config config/webpack.prod.js",
"start": "webpack --config config/webpack.dev.js",
},
start 명령어 실행시 정상적으로 동작하는것을 확인할 수 있습니다.
Webpack에 대해 학습해보고 webpack을 이용하여 리액트 개발 환경을 만들어보았습니다. 추가적인 설정 없이는 webpack을 이용한 애플리케이션 구동 속도도 느리지는 않다고 느껴집니다. 다만 CRA 환경은 조금만 프로그램이 무거워져도 속도가 많이 느려지기에 Vite가 확실히 빠르다는 생각은 동일합니다.
이후 프로젝트를 webpack 환경에서 제작할지는 미지수지만 구성되는 환경과 개념들은 알고 사용해야 한다고 느껴서 webpack에 대해 알아보고 CRA없이 react 환경을 구축해보았는데 하면 할수록 배워야 하는 부분이 많다고 느낀 거 같습니다.
webpack 공식 문서
https://webpack.kr/concepts/
babel 공식 문서
https://babeljs.io/docs/
Webpack
https://velog.io/@chyori/webpack
Webpack 직접 적용하며 이해하기
https://velog.io/@suyeon9456/Webpack
프론트엔드 필수 Webpack이란?
https://velog.io/@yon3115/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%ED%95%84%EC%88%98-Webpack%EC%9D%B4%EB%9E%80
Babel 직접 적용하며 이해하기
https://velog.io/@suyeon9456/Babel
CRA없이 React+TypeScript 환경 구축하기
https://velog.io/@tnehd1998/CRA%EC%97%86%EC%9D%B4-ReactTypeScript-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0
CRA 없이 리액트 시작하기 with webpack
https://yogjin.tistory.com/entry/CRA-%EC%97%86%EC%9D%B4-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0