리액트 웹 애플리케이션을 웹팩으로 번들링하는 과제를 진행하는 과정을 기록으로 남겨보려고 한다. 저번 세션에서 진행한 레퍼런스 코드를 복사해와서 진행했다. (처음에는 clone해와서 시작했는데, 페이지를 배포를 진행하기 위해서 fork해온 리포지토리에서 빌드를 완성하고 push하다가 오류가 생겨서 처음부터 다시 시작했다는 슬픈 사연.. 🥲)
npm i -y
npm i react react-dom
npm i -D webpack webpack-cli
바닐라 js로 작성된 프로젝트는 번들링할때 이 단계에서 웹팩 config파일에서 entry와 output만 설정하고 번들링할 수 있었지만, JSX문법으로 작성된 리액트 코드는 바벨을 통해서 브라우저가 이해할 수 있는 JavaScript로 변환해서 번들링해줘야 한다. 이걸 처리해주는 것이 바벨 로더!
npm i -D babel-loader @babel/core @babel/preset-env @babel/preset-react
//webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js', // 빌드 시작 위치
output: {
// 빌드결과 저장 위치
filename: 'app.bundle.js',
path: path.resolve(__dirname, 'docs'),
clean: true,
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env'],
['@babel/preset-react', { runtime: 'automatic' }],
],
},
},
},
],
},
};
styl-loader
와 css-loader
로 번들링하면 header태그 안에서 internal 방식으로 번들링 되는 것과 달리 external 방식으로 CSS 번들링하는 플러그인인 css-minimizer-webpack-plugin
를 설치했다.
npm i -D css-minimizer-webpack-plugin
npm i -D mini-css-extract-plugin
mini-css-extract-plugin
은 css 파일을 한줄로 압축한다.//webpack.config.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
entry: './src/index.js', // 빌드 시작 위치
output: {
// 빌드결과 저장 위치
filename: 'app.bundle.js',
path: path.resolve(__dirname, 'docs'),
clean: true,
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env'],
['@babel/preset-react', { runtime: 'automatic' }],
],
},
},
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
exclude: /node_modules/,
},
],
},
optimization: {
minimizer: [
new CssMinimizerPlugin(), // css를 줄여주는 플러그인
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'style.css',
}),
],
};
// index.js
import style from './index.css'
index.js 파일에 css파일이 import 된 것을 확인하고 빌드한다.
npm install -D html-webpack-plugin
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
entry: './src/index.js', // 빌드 시작 위치
output: {
// 빌드결과 저장 위치
filename: 'app.bundle.js',
path: path.resolve(__dirname, 'docs'),
clean: true,
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env'],
['@babel/preset-react', { runtime: 'automatic' }],
],
},
},
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
exclude: /node_modules/,
},
],
},
optimization: {
minimizer: [
new CssMinimizerPlugin(), // css를 줄여주는 플러그인
],
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
}),
new MiniCssExtractPlugin({
filename: 'style.css',
}),
],
};
여기까지하면 docs 폴더에 번들 결과 파일이 잘 생성된다. (최소한의 웹팩 설정!)
레퍼런스 코드를 보면서 추가적으로 유용한 설정을 추가했다.
웹팩 설정 파일에 mode 속성을 추가해서 사용할 수 있다. 기본값은 production(배포 모드)이고, 웹팩 모듈 번들링 과정에서 코드를 최적화해서 용량을 줄인다. 반면에 development(개발 모드)는 안전한 로컬 환경에서 개발한다고 가정하고, 편리한 개발을 위해서 어느 파일에서 에러가 발생했는지도 알려주는 최적화되지 않은 번들 파일을 제공한다.
레퍼런스 코드에서는 웹팩 설정파일을 두가지 모드로 설정하기 위해서 webpack-merge를 설치한다.
npm install -D webpack-merge
기본 설정 파일을 머지한 각각의 개발/배포 환경에 필요한 설정파일을 따로 만들어준다.
webpack-dev-server
도 같이 설치해서 변경된 코드를 개발 서버에 반영해서 보여줄 수 있도록 설정을 추가했다.
npm i -D webpack-dev-server
// webpack.config.dev.js
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.config.base');
const path = require('path');
module.exports = merge(baseConfig, {
mode: 'development',
devServer: {
static: {
directory: path.resolve(__dirname, 'docs'),
},
port: 3001,
hot: true,
},
});
// webpack.config.prod.js
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.config.base');
module.exports = merge(baseConfig, {
mode: 'production',
});
package.json scripts에 추가
"scripts": {
"build": "webpack --config webpack.config.prod.js",
"dev": "webpack server --open --config webpack.config.dev.js",
},
webpack-dev-server처럼 저장할때마다 변경사항을 적용시며주고, 리액트의 상태를 유지해주는 플러그인 설치한다.
react-refresh-webpack-plugin
npm i -D @pmmmwh/react-refresh-webpack-plugin react-refresh
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.config.base');
const path = require('path');
const ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
module.exports = merge(baseConfig, {
mode: 'development',
devServer: {
static: {
directory: path.resolve(__dirname, 'docs'),
},
port: 3001,
hot: true,
},
plugins: [new ReactRefreshPlugin()],
});
코드에 문제가 없는지 검사하기 위해서 설치
npm install -D eslint eslint-plugin-react @babel/eslint-parser
//pacakge.json scripts에 추가
"lint": "eslink ./src",
웹 접근성에 대해서 지켜야하는 부분을 알려주는 eslint rule 추가 설치
npm install -D eslint-plugin-jsx-a11y
따로 설정파일을 작성해야 한다. (레퍼런스 코드라서 옵션에 대해서는 더 공부해봐야 함.. )
// .eslintrc.js
module.exports = {
parser: "@babel/eslint-parser",
env: {
browser: true,
commonjs: true,
es6: true,
node: true,
},
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:jsx-a11y/recommended",
],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2018,
sourceType: "module",
},
settings: {
react: {
version: "18.2.0",
},
},
plugins: ["react"],
rules: {
"react/react-in-jsx-scope": 0,
"react/jsx-uses-react": 0,
"react/prop-types": 0,
},
};
코드 형식 통일(vs extension으로만 알았는데 이렇게 개발환경에 추가할 수 있다!!)
npm install -D prettier
//pacakge.json scripts에 추가
"pretty": "prettier --write ./",
따로 설정파일을 만들어줘야 한다.
// .prettierrc.js
module.exports = {
singleQuote: true,
jsxSingleQuote: true,
};
https://seul-dev.github.io/react-webpack-build-prac/
{
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@babel/core": "^7.18.9",
"@babel/eslint-parser": "^7.18.9",
"@babel/preset-env": "^7.18.9",
"@babel/preset-react": "^7.18.6",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
"babel-loader": "^8.2.5",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.0.0",
"eslint": "^8.20.0",
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-react": "^7.30.1",
"html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.6.1",
"prettier": "^2.7.1",
"react-refresh": "^0.14.0",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.9.3",
"webpack-merge": "^5.8.0"
},
"name": "react-webpack-build-prac",
"version": "1.0.0",
"main": "webpack.config.js",
"scripts": {
"build": "webpack --config webpack.config.prod.js",
"dev": "webpack server --open --config webpack.config.dev.js",
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslink ./src",
"pretty": "prettier --write ./"
},
"author": "",
"license": "ISC",
"description": ""
}
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
entry: './src/index.js', // 빌드 시작 위치
output: {
// 빌드결과 저장 위치
filename: 'app.bundle.js',
path: path.resolve(__dirname, 'docs'),
clean: true,
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env'],
['@babel/preset-react', { runtime: 'automatic' }],
],
},
},
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
exclude: /node_modules/,
},
],
},
optimization: {
minimizer: [
new CssMinimizerPlugin(), // css를 줄여주는 플러그인
],
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
}),
new MiniCssExtractPlugin({
filename: 'style.css',
}),
],
};
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.config.base');
const path = require('path');
const ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
module.exports = merge(baseConfig, {
mode: 'development',
devServer: {
static: {
directory: path.resolve(__dirname, 'docs'),
},
port: 3001,
hot: true,
},
plugins: [new ReactRefreshPlugin()],
});
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.config.base');
module.exports = merge(baseConfig, {
mode: 'production',
});
CRA가 얼마나 편리한 것인지 느꼈다.🥲 손쉽게 사용가능하던 리액트 프로젝트 개발환경을 처음부터 하나하나 설정해나가보니 create-react-app 한 줄의 강력함을 느꼈다. 그래도 내가 필요한 설정들을 계속 추가하는 느낌으로 설치+ 설정 저장을 반복하다보니 웹팩의 큰 개념에는 제법 익숙해질 수 있었던 시간이었다. 커스텀 개발환경을 구축해볼 수 있었다.
이번 과제에서 favicon, manifest가 번들링되지 않았는데 이 부분은 아래 블로그를 참고해서 다음에 다시 해결해보는걸로!!
블로그)webpack plugin을 이용한 favicon, manifest 추가 방법