리액트를 처음 배울 때 간략하게 접하고 이건 다신 볼일 없겠지 하고 지나갔던 웹팩이 다시 돌아왔다. 바닐라 자바스크립트로 개발을 하려니까 필요해서...
CRA 이전의 웹팩의 기본 형태, 바닐라 자바스크립트로 웹팩을 빌드하는 방법에 대해 알아보자. webpack5 버전을 사용하고 CSS loader, Dev Server, Babel 등의 설정 방법을 다뤄볼 것이다.

위와 같이 module을 사용하면 여러 개의 js파일을 하나로 모을 수 있다.

문제는 모든 JavaScript 파일을 개별 모듈로 불러올 경우, 파일 하나당 하나의 HTTP 요청이 발생한다. 이는 네트워크 지연 시간을 증가시키고, 웹 페이지의 로딩 시간이 늘어나게 한다.
웹팩을 사용하면 이러한 모듈을 하나의 번들로 합쳐서 HTTP 요청을 최소화할 수 있다.
npm init -y
npm i -D webpack webpack-cli
// webpack.config.js
const path = require('path');
module.exports = {
mode: 'development', // 배포할땐 production
entry: {
app: './src/index.js',
},
output: {
filename: '[name].[contenthash].js',// [name]은 entry 객체의 key를 참조
path: path.resolve(__dirname, 'dist'),
},
};
filename에 [contenthash]을 붙이는 이유
웹 브라우저는 서버로부터 받은 자원(HTML, CSS, JavaScript 파일 등)을 캐시(임시 저장소)에 저장하고, 동일한 자원을 요청할 때 캐시에 저장된 자원을 재사용한다. 그러나 이러한 캐시 기능 때문에 개발자가 서버의 자원을 업데이트 했을 때 문제가 생길 수 있다. 이미 캐시에 저장된 자원이 있으면 브라우저는 서버에 요청하지 않고 기존 캐시의 자원을 사용하기 때문.
이를 해결하기 위해 filename에 해시값을 추가해 캐시가 무효화 되도록 한다. 파일 내용이 바뀌면 해시값도 바뀌고, 해시값이 바뀌어서 파일명이 바뀌면 브라우저는 이를 새로운 자원으로 인식하고 서버에 새 자원을 요청한다.
// package.json
{
"scripts": {
"build": "webpack"
},
}
npm i -D html-webpack-plugin clean-webpack-plugin
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
app: './src/index.js',
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new HtmlWebpackPlugin({
title: 'test',
filename: 'index.html',
template: './src/index.html',
// template 파일을 참조해서 output path에 index.html을 생성한다
}),
new CleanWebpackPlugin(),// build할 때마다 dist 폴더를 초기화
],
};
npm i -D style-loader css-loader
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
app: './src/index.js',
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
assetModuleFilename: 'images/[hash][ext][query]',
// 옵션_ 이미지 파일이 많을 경우 위와 같이 dist에 폴더를 생성하고 저장할 수 있음.
},
module: {
rules: [
{// css 파일을 js에 import해서 쓸 수 있음.
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{// Webpack의 asset module 기능을 이용하여 이미지 파일들을 처리
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
],
},
plugins: [
new HtmlWebpackPlugin({
title: 'test',
filename: 'index.html',
template: './src/index.html',
}),
new CleanWebpackPlugin(),
],
};
리액트도 아닌데 Babel이 필요해?
비록 JSX 문법이 포함되지 않는 프로젝트라도, Babel을 Webpack 환경에 포함시키면 중요한 이점을 얻을 수 있다. Babel은 고급 JavaScript 기능을 이전 버전의 JavaScript로 변환함으로써, 어떠한 브라우저 호환성 문제 없이 최신 언어 기능들을 활용할 수 있게 해주기 때문!
npm i -D babel-loader @babel/core @babel/preset-env
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
app: './src/index.js',
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
assetModuleFilename: 'images/[hash][ext][query]',
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
title: 'test',
filename: 'index.html',
template: './src/index.html',
}),
new CleanWebpackPlugin(),
],
};
devServer는 파일 시스템의 변경을 감지하고 자동으로 번들을 재생성하고, 브라우저를 새로고침한다. 그러나 중요한 점은, devServer가 실시간으로 반영하는 이런 변경 사항들은 실제 'dist' 디렉토리의 번들 파일에는 적용되지 않는다. 즉, devServer는 개발 과정에서만 실시간 변경을 반영하며, 최종적인 빌드 결과물인 번들 파일에는 영향을 미치지 않는다. 따라서 실제 배포를 위한 최종 번들을 생성하려면 별도로 npm run build와 같은 명령을 이용하여 번들링 과정을 진행해야한다.
npm i -D webpack-dev-server
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
app: './src/index.js',
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
assetModuleFilename: 'images/[hash][ext][query]',
},
devServer: {
static: {
directory: path.resolve(__dirname, 'dist'),
},
port: 3000,
open: true, // 서버가 시작될 때 브라우저 자동열림
hot: true, // Hot Module Replacement(HMR) 활성화, 새로고침 없이 변경된 모듈 재로드
compress: true, // 모든 항목을 gzip 압축으로 제공, 네트워크 전송 시간 단축
// historyApiFallback: true, // SPA(Single Page Application)에서 중요. true로 설정하면, 404 응답 대신 index.html 페이지를 제공하여 클라이언트 측 라우팅이 가능. 이를 통해 브라우저에서 경로가 변경되어도 항상 루트 인덱스 페이지를 제공할 수 있음.
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
title: 'test',
filename: 'index.html',
template: './src/index.html',
}),
new CleanWebpackPlugin(),
],
};
{
"scripts": {
"build": "webpack",
"start": "webpack serve"
},
}
https://www.youtube.com/watch?v=IZGNcSuwBZs
https://webpack.kr/concepts/