리액트를 공부하고, 리액트 프로젝트를 진행하며 모듈 시스템을 이용해 컴포넌트 주도 개발을 할 때 Webpack과 같은 번들러의 역할이 중요하다는 것을 알게 되었다. 이 글을 통해서 리액트 개발 시 웹팩이 필요한 이유와 웹팩 설정 방법을 정리해본다.
현재 모던 웹 환경은 모듈 시스템 및 컴포넌트 주도 개발에 한계를 가지고 있다. 그 이유는 다음 2가지로 정리할 수 있다.
CanIUse 모듈 시스템
대부분의 모던 브라우저는 모듈 시스템을 지원하지만, IE와 같은 일부 구형 브라우저에서는 모듈 시스템을 지원하지 않고 있다. 물론 모듈을 사용하기 이전에는 즉시 실행 함수를 통해 모듈 패턴을 사용했지만, 파일 단위로 관심사의 분리를 할 수 있고 전역 스코프를 오염시키지 않는 모듈 시스템을 도입하는 것이 개발 경험(DX) 관점에서 훨씬 더 좋기 때문이다.
최근 웹/앱의 기능이 복잡해지면서, 컴포넌트 및 페이지가 복잡하기 때문에 반드시 이를 처리할 수 있는 빌드 환경이 제공되어야 한다.
1) 모듈 번들링
번들링은 서로 관련 있는 모듈 파일들을 목적에 따라 하나의 번들링 파일로 묶어 관리하므로, 서버에 여러 개의 파일을 요청할 필요가 없기 때문에 서버 요청 횟수를 줄여주는 장점이 있다.
2) 트리쉐이킹
위의 번들링된 파일의 단점은 모듈의 개수가 늘어나거나, 용량이 커지면 하나로 합쳐진 번들링 파일 역시 크기가 커진다는 단점이 있어 서버 요청 시 부하가 커진다.
따라서 앱을 빌드할 때, 현재 코드 상에서 사용하지 않는 코드를 제거하는 트리쉐이킹을 통해 파일이 과도하게 커지는 것을 방지하여 성능 최적화를 이룰 수 있다
3) 코드 분할 (Code splitting)
사용자가 페이지를 로드할 경우, 모든 페이지를 다운 받을 필요는 없다. 혹은 여러 개의 chunk들이 하나의 모듈을 공유할 경우, 해당 모듈을 모든 chunk point에 번들링 될 필요는 없다.
따라서 코드를 적절히 분할할 경우 사용자가 처음 페이지에 접근할 경우 서버에 필요한 페이지만 요청하고, 이후 다른 페이지에 접근할 경우 그 때 추가 요청을 보내 성능적이 이득을 이룰 수 있다.
4) 코드 최적화, 소스맵
코드를 번들링하면, 번들링 된 파일은 사람이 읽고 이해하기 어렵다.
따라서 반드시, 해당 파일의 소스맵을 통해 해당 코드가 어느 파일에 있는지를 명시해야 한다. 이를 통해 디버깅 및 유지 보수가 유용하기 때문이다.
모듈 시스템을 이용하여 컴포넌트 주도 개발을 하기 위해서, 위의 4가지의 앱 환경이 필요하지만 현재 웹 브라우저 환경에서는 이런 시스템을 지원하지 않는다
Babel의 역할은 트랜스파일링이다. 즉 ES6+ 문법을 하위 호환을 위해 ES5 문법으로 변환하거나, JSX 문법을 React.createElement로 변환시켜주는 역할을 한다.
Webpack은 이렇게 변환된 모듈 파일을 번들링 또는 코드 분할 및 최적화를 하여 build 하거나, devServer를 제공한다.
npm install webpack webpack-cli --save-dev
웹팩을 사용하기 위한 webpack 패키지와 CLI 환경에서 사용하기 위한 webpack-cli를 설치한다.
현재 프로젝트 폴더 루트에 webpack.config.js를 만들어 보자
const path = require('path');
const webpackConfig = ({ development, production }) => {
return {
target: ['web', 'es5'],
mode: development ? 'development' : 'production',
cache: true,
devtool: development ? 'eval-cheap-source-map' : false,
resolve: {
extensions: ['.jsx', '.js', '.json', '.wasm'],
},
{
...
}
...
};
code-splitting
, tree-shaking
을 지원한다const HtmlWebpackPlugin = require('html-webpack-plugin');
const ROOT_DIR = process.cwd();
const webpackConfig = ({ development, production }) => {
return {
...
entry: {
main: {
import: path.resolve(ROOT_DIR, 'src/index.jsx'),
dependOn: 'vender',
},
vender: ['react', 'react-dom'],
},
output: {
path: path.resolve(ROOT_DIR, development ? 'dist' : 'public'),
filename: 'js/[name].js',
clean : true,
},
module: {
rules: [
{
test: /\.jsx?$/i,
exclude: /node_modules|public/,
use: 'babel-loader',
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(ROOT_DIR, 'public/index.html'),
title: '랜덤 카운트 업 - React Count Up',
}),
],
};
};
string | [string] | object가 올 수 있음
모듈이 여러 개일 경우, entry 객체 안에 여러 개의 chunk
를 명시할 수 있음 (Code splitting)
💡 주의 - 여러 개의 chunk에서 공통적으로 사용되는 모듈의 경우 chunk마다 번들링 되어 중복이 발생
dependOn
(중복 제거 방법)
→ 기본적으로 모든 chunk는 번들링 시, 자신이 사용하는 모듈을 포함하여 번들링 되므로 용량이 커짐
→ 하지만 해당 옵션을 사용하면, 하나의 chunk entry를 이용해 다른 chunk에 모듈을 공유할 수 있음
→ 위에서는 번들링된 main.js 가 vender.js의 모듈을 의존하는 상황이므로, 반드시 두 파일을 동시에 html 파일에서 로드해야 함
절대 경로
를 사용해야 하므로, process.cwd
를 사용해야 current working directory를 기준으로 path를 설정한다clean : true
옵션을 줄 경우, 번들 시 이전 버전을 지우고 다시 생성HtmlWebpackPlugin
을 사용한다.// package.json
"scripts": {
"start": "npm run dev -- --open", // devServer 실행 (open 옵션)
"dev": "webpack serve --env development",// devServer 실행
"bundle": "webpack --env development", // bundling
"build": "webpack build --env production" // build
},
npm install -D babel-loader @babel/core @babel/preset-en
.babelrc
파일을 설정할 수 있다{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
@babel/preset-env
: babel-loader가 변환할 때 사용할 여러 플러그인 모음@babel/preset-react
: JSX 문법을 변환하기 위한 플러그인npm install webpack-dev-server --save-dev
// webpack.config.js
const webpackConfig = ({ development, production }) => {
return {
...
devServer: {
static: ['public'],
port: 3000,
compress: true,
client: {
logging: 'info',
overlay: true,
},
},
...
}
}
// package.json alias 설정하기
Usage: webpack serve|server|s [entries...] [options]
// options
--env : configuration(webpack.config.js 내 webpackConfig)가 함수일 경우, 인수로 값을 전달할 수 있음 (예 : --env production => 인수 : { production : true }
--open : devServer 가 구동하면서 브라우저 창을 자동으로 실행