여러 제품이나 코드, 프로그램을 묶어서 패키지로 제공하는 행위를 의미하며 프론트엔드 개발자는 사용자에게 웹 애플리케이션을 제공하기 위한 파일 묶음이라고 볼 수 있다. 사용자가 브라우저를 열고 주소를 입력하면, 해당 주소에서 프론트엔드 개발자가 번들링한 여러 파일을 받게 되고 이 파일을 브라우저가 실행하여 웹 애플리케이션을 사용자에게 제공하게 된다.
내가 작성한 HTML, CSS, JavaScript 파일을 그대로 전송할 경우 아래와 같은 상황으로 어려움을 처할 수 있으므로 번들링이 필요하게 되었다.
➡ 파일간의 충돌 방지
➡ 성능 최적화
➡ 보안 향상
Webpack은 얘플리케이션 배포를 위해서 가장 많이 사용하는 번들러이며 여러 개의 파일을 하나의 파일로 합쳐주는 모듈 번들러를 의미한다. 모듈 번들러는 HTML, CSS, JavaScript 등의 자원을 전부 각각의 모듈로 보고 조합해서 하나의 묶음으로 번들링(빌드)하는 도구이다.
모던 웹으로 발전하면서 JavaScript 코드의 양이 증가하였고 대규모의 의존성 트리를 가지고 있는 대형 웹 애플리케이션이 등장하면서 세분화된 모듈 파일이 증가하였다. 모듈 단위의 파일들을 브라우저에 띄워야 하는데 자바스크립트 언어의 특성으로 각 변수들의 스코프 문제, 각 자원을 호출할 때에 생겨나는 네크워크 쪽의 코스트도 신경써야했다.
➡ 위의 상황에 대한 복잡성을 대응하기 위해서 하나의 시작점(ex. React App의 index.js)으로부터 의존성을 가지는 모듈을 모두 추적하여 하나의 결과물을 만들어내는 모듈 번들러가 등장하게 되었다.
웹팩에서의 모듈은 자바스크립트 모듈뿐만 아니라 HTML, CSS나 .jpg, .png 같은 이미지 파일들도 전부 포함된다. 웹팩의 주요 구성 요소인 로더(loader)를 통해 다양한 파일도 번들링이 가능하다
빌드와 번들링
빌드 : 개발이 완료된 앱을 배포하기 위해 하나의 폴더(directory)로 구성하여 준비하는 작업을 말한다
(React 앱에서 npm run build 커맨드를 통해 React build 작업이 진행되고, index.html 파일에 압축되어 배포에 최적하된 상태를 제공한다)
번들링 : 파일을 묶는 작업을 말하며 파일은 의존적 관계이 있는 파일들(import, export)이나 내부적으로 포함 되어 있는 모듈을 의미한다. 번들링은 모듈 간의 의존성 관계를 파악해 그룹화 하는 작업이라고 할 수 있다.
➡ 웹팩이 필요한 이유는 웹 페이지를 구성하는 코드의 양이 많아질수록 웹 페이지의 로딩 속도와 성능은 저하가 되며 웹 애플리케이션의 빠른 로딩 속도와 높은 성능을 위해서 필요하다. 유저는 하나의 웹사이트가 3초 이내에 뜨지 않는다면 웹페이지의 이탈율이 많이 높아지게 된다.
➡ 웹팩이 없다면 각 자원들을 일일히 서버에 요청해 받아야 하는데, 웹팩이 있다면 같은 타입의 파일들을 묶어서 요청 및 응답을 받을 수 있으므로 네트워크 코스트가 획기적으로 줄어든다.
➡ Webpack loader를 사용하면 일부 브라우저에서 지원하지 않는 JavaScript ES6의 문법들을 ES5로 변환해주는 babel-loader를 사용할 수 있게 된다.
➡ 웹팩4 버전 이상부터는 Develoment, Production 모드를 지원하며 Production 모드로 번들링을 할 경우에는 코드 난독화, 압축, 최적화(Tree Shaking) 작업을 지원하기도 한다.
웹팩은 상용화 된 프로그램을 사용자가 더욱 쾌적한 환경에서 보안까지 신경쓰면서 노출될 수 있으므로 웹팩의 필요성이 높은 편이라고 할 수 있다.
module.exports = {
target: ["web", "es5"],
entry: "./src/script.js",
output: {
path: path.resolve(__dirname, "docs"),
filename: "app.bundle.js",
clean: true
},
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
exclude: /node_modules/,
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "src", "index.html"),
}),
new MiniCssExtractPlugin(),
],
optimization: {
minimizer: [
new CssMinimizerPlugin(),
]
}
};
Target
Webpack은 다양한 환경과 target을 컴파일한다.
target의 기본값은 web이며 적용하지 않으면 기본 값으로 적용된다.
web외에도 다양한 환경을 컴파일 할 수 있으며 esX를 넣으면 지정된 ECMAScript 버전으로 컴파일할 수 있다.
// es5버전으로 컴파일
module.exports = {
target: ["web", "es5"],
};
Entry(엔트리)
Webpack에서의 entry는 개발자가 작성한 코드의 시작점이다.
Entry 속성은 Entry point라고도 하며, webpack이 내부의 디펜던시 그래프를 생성하기 위해 사용하는 모듈이다. Webpack은 Entry point를 기반으로 직간접적으로 의존하는 다른 모듈과 라이브러리를 찾아낼 수 있다.
아래의 예시에서 작성된 기본 값은 ./src/index.js이지만 Entry속성을 설정하여 다른 Entry point나 여러 Entry point를 지정할 수 있다.
//기본 값
module.exports = {
...
entry: "./src/index.js",
};
//지정 값
module.exports = {
...
entry: "./src/script.js",
};
Output(출력)
Output은 생성된 번들을 내보낼 위치와 이 파일의 이름을 저장하는 방법을 웹팩에 알려주는 역할을 한다.
const path = require('path');
module.exports = {
...
output: {
path: path.resolve(__dirname, "docs"), // 절대 경로로 설정
filename: "app.bundle.js",
clean: true
},
};
Loader(로더)
Webpack은 기본적으로 JavaScript와 JSON 파일만 이해하므로 loader를 사용하여 Webpack이 다른 유형의 파일을 처리하거나 유효한 모듈로 변환해준다.
module.exports = {
...
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
exclude: /node_modules/,
},
],
},
};
Plugins(플러그인)
플러그인을 사용하면 번들을 최적화하거나 에셋을 관리, 환경변수 주입 등의 광범위한 작업을 수행할 수 있다.
플러그인을 사용하려면 require( )를 통해 플러그인을 먼저 요청하고 플러그인 배열에 사용하려는 플러그인을 추가해야 한다. 플러그인은 여러 번 사용하도록 설정할 수 있으므로 new 연산자를 사용해 호출하여 플러그인의 인스턴스를 만들어줘야 한다.
const webpack = require('webpack');
// 생성된 모든 번들을 자동으로 삽입하여 애플리케이션용 HTML 파일을 생성
const HtmlWebpackPlugin = require("html-webpack-plugin");
// css를 별도의 파일로 추출해 CSS를 포함하는 JS 파일 당 CSS파일을 작성
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "src", "index.html"),
}),
new MiniCssExtractPlugin(),
],
};
Optimization(최적화)
Webpack은 버전 4부터 선택한 항목에 따라 최적화를 실행한다.
대표적으로 minimize와 minimizer등을 사용하며 최적화하기 위해 다양한 옵션이 있다.
// mini-css-extract-plugin에 관련된 번들을 최소화
module.exports = {
...
optimization: {
minimizer: [
new CssMinimizerPlugin(),
]
}
};
디렉터리 생성
cd ~/Desktop
mkdir fe-sprint-webpack-tutorial
cd fe-sprint-webpack-tutorial
npm init
현재 디렉터리 상황과 맞게 package.json 파일을 생성해준다.
{
"name": "fe-sprint-webpack-tutorial",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": ""
}
웹팩 설치하기
만든 결과물을 웹팩을 이용하여 하나로 합친다. npm으로 webpack, webpack-cli를 설치하며 두개를 같이 설치해주는것이 좋다. 실제 프로젝트에 사용하지 않기 때문에 devDependency 옵션을 설정하고 설치한다.
webpack은 번들링을 원하는 파일을 먼저 확인하고, import한 라이브러리나 코드가 있으면 해당 코드도 모두 인식하여 하나의 번들 안으로 모두 넣는다
npm install -D webpack webpack-cli
웹팩 config 파일 작성
웹팩 config는 웹팩 설정파일로 파일에 entry와 output 정보를 적을 수 있다. 최근의 웹팩은 설정파일이 없어도 작동할 수 있지만, 웹팩 설정을 다룰줄 알면 더욱 다양한 외부 리소스를 사용하기 편하다.
// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'), // './dist'의 절대 경로를 리턴
filename: 'app.bundle.js',
},
};
npx webpack 명령어를 입력하면 dist/app.bundle.js 파일에서 최소화된 코드를 볼 수 있다.
node dist/app.bundle.js로 실행하여 번들링한 코드도 결과가 잘 나오는지 확인해본다.
npx webpack
npm run build 설정하기
npm script에 build를 작성하면 npm run build 스크립트로 언제든지 번들링을 할 수 있다.
{
"name": "fe-sprint-webpack-tutorial",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"devDependencies": {
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0"
}
}
CSS파일을 JavaScript 파일에 포함시키기
npm으로 css-loader와 style-loader를 설치하고 webpack.config.js를 조정한다.
npm i -D css-loader style-loader
// webpack.config.js
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "app.bundle.js",
},
module: {
rules: [
{
// 파일명이 .css로 끝나는 모든 파일에 적용
test: /\.css$/,
// 배열 마지막 요소부터 오른쪽에서 왼쪽 순으로 적용
// 먼저 css-loader가 적용되고, styled-loader가 적용
use: ["style-loader", "css-loader"],
// node_modules는 제외
exclude: /node_modules/,
},
],
},
};
번들에 HTML 포함시키기
html-webpack-plugin을 설치 후 webpack.config.js 파일에 플러그인을 적용한다.
src/index.html파일을 번들링하여 dist/index.html 파일을 새로 생성한 것을 확인할 수 있다.
npm i -D html-webpack-plugin
const path = require("path");
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "app.bundle.js",
},
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
exclude: /node_modules/,
},
],
},
plugins: [new HtmlWebpackPlugin({
template: path.resolve(__dirname, "src", "index.html")
})]
};
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
module.exports = {
entry: './src/index.js',
devServer: {
static: './dist'
},
output: {
path : path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
clean: true
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env"],
["@babel/preset-react", {runtime: "automatic"}]
],
plugins: ["react-hot-loader/babel"]
}
}
},
{
test: /\.css$/,
exclude: /node_modules/,
use: [MiniCssExtractPlugin.loader, "css-loader"]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "public", "index.html")
}),
new MiniCssExtractPlugin(),
new BundleAnalyzerPlugin({
//분석 모드 설정
analyzerMode: "static",
//빌드하고 바로 열건지 정해줌
openAnalyzer: false,
//파일 만들지 결정
generateStatsFile: true,
//파일 이름
statsFilename: "bundle-report.json",
})
],
optimization: {
minimizer: [
new CssMinimizerPlugin()
],
runtimeChunk: 'single'
},
mode: "development"
}
github page 배포하기
.gitignore 파일을 만들어서 적어주면 node modules와 같은 올리지 않아도 되는 파일들을 설정할 수 있다.
babel-loader 설정하기
웹팩은 jsx문법이나 TypeScript와 같이 js를 확장한 문법을 변환을 못하므로 바벨로 설정을 해준다
babel-loader : ES6으로 작성된 문법을 ES5로 변환시켜주는 로더이다.
@babel/core : 바벨을 이용해서 코드를 변환하는데 필요한 기능들을 제공하므로 필수로 설치해야한다.
@babel/preset-env : 언어를 변환할 때 필요한 플러그인들로 특정한 변환 규칙들을 모아놓은 것이다.
@babel/preset-react : jsx 문법을 js로 변환하는 플러그인 모음이다.
npm install -D babel-loader @babel/core @babel/preset-env @babel/preset-react
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env"],
["@babel/preset-react", {runtime: "automatic"}]
],
}
}
},
]
}
}
작동 모드 설정하기
mode 옵션을 사용하여 웹팩에 내장된 최적 기능을 사용할 수 있으며 none(없음), development(개발모드), production(생산모드)으로 설정할 수 있다.
module.exports = {
mode : 'development',
}
mode : 'none'
mode : 'development'
mode : 'production'
Output 관리하기
clean을 true로 설정해주면 번들링 할 때마다 dist 디렉터리를 정리해준다.
app.bundle.js 가 아니라 [name].bundle.js로 적어주면 output이 동적으로 변하도록 생성할 수 있다. '[name].[hash].js'로 작성하면 번들이 해쉬값으로 들어와서 계속해서 바뀌는 것을 확인할 수 있다. 코드가 변경되지 않았다면 해쉬값이 변경되지 않는다.
module.exports = {
output: {
path : path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
clean: true
},
}
app.bundle.js
[name].[hash].js
Asset 관리하기
CSS에 minify를 적용하며 require로 가져와야한다.
css-extract-plugin : css 파일을 분리시키며 style-loader와 같이 쓰지 않는다.
css-minimizer-webpack-plugin : css 파일을 최적화한다.
npm install -D mini-css-extract-plugin css-minimizer-webpack-plugin
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
use: [MiniCssExtractPlugin.loader, "css-loader"]
}
]
},
plugins: [
new MiniCssExtractPlugin(),
],
optimization: {
minimizer: [
new CssMinimizerPlugin()
],
}
개발용 서버
라이브 서버와 비슷한 기능을 하는 webpack-dev-server, react-hot-loader 를 적용할 수 있다.
npm install -D webpack-dev-server
//package.json 파일에 추가
"scripts": {
"dev-server": "webpack serve --open",
}
react-hot-loader는 react 18버전부터는 적용이 안되므로 react 17버전으로 낮춘다음에 사용할 수 있다.
// react 18버전 사용시 react 17버전으로 변경
npm install react@17 react-dom@17
// react-hot-loader 설치
npm install react-hot-loader
//App.js 파일 수정
import { hot } from 'react-hot-loader';
.
.
.
export default hot(module)(App);
//index.js 파일 수정
import React from 'react';
import { render } from 'react-dom';
import './index.css';
import App from './App';
// 기존 내용
// const root = ReactDOM.createRoot(document.getElementById('root'));
// root.render(
// <React.StrictMode>
// <App />
// </React.StrictMode>
// );
//변경 내용
const container = document.getElementById('root');
render(<App />, container);