오늘은 React에서 SSR을 구현을 목적으로 리액트에서 적용되는 webpack에 대해 학습하고 CRA에서 적용되는 webpack설정을 따라하면서 webpack에 대해서 자세하게 공부해보도록 하겠습니다.
우선 완성된 코드는 >> https://github.com/LE123123/webpack-reactconfig 에서 확인하실 수 있습니다.
package.json
// package.json
{
"name": "webpack-react",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.17.5",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"babel-loader": "^8.2.3",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.6.0",
"html-loader": "^3.1.0",
"html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.5.3",
"node-sass": "^7.0.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"sass-loader": "^12.6.0",
"style-loader": "^3.3.1",
"webpack": "^5.69.1",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.7.4"
},
"scripts": {
"start": "webpack-dev-server --config ./config/webpack.config.dev --hot",
"build": "webpack --config ./config/webpack.config.prod"
}
}
우선 관련 의존성은 위와 같으며 start, build script는 아래에서 설명하겠습니다.
src Folder
// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import Root from "./Root";
ReactDOM.render(<Root />, document.getElementById("root"));
// src/Root.js
import React from "react";
import "./style.scss";
const Root = () => {
return <h3 className="title">Hello, Reac_v2</h3>;
};
export default Root;
// src/style.css
.title {
color: #2196f3;
font-size: 52px;
text-align: center;
}
// src/style.scss
$fontColor: #2196f3;
$fontSize: 52px;
.title {
color: $fontColor;
font-size: $fontSize;
text-align: center;
}
src안의 파일들은 아래와 같다 우선 Root컴포넌트를 간단하게 브라우저에 "Hello, React_v2"를 띄우게 하였고 h3태그의 class는 title로 놓아서 css나 sass로 스타일링 할 수 있게 하였습니다,
webpack.config
// config/webpack.config.dev.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const webpack = require("webpack");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "build"),
},
mode: "dvelopment",
devServer: {
static: {
directory: path.resolve(__dirname, "build"),
},
port: 9000,
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: "/node_modules",
use: ["babel-loader"],
},
{
test: /\.html$/,
use: [
{
// html파일을 읽었을 떄 html-loader를 실행하여 웹팩이 이해할 수 있게 한다.
loader: "html-loader",
options: { minimize: true },
},
],
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html", // public/index.html 파일을 읽는다.
filename: "index.html",
}),
// 웹팩에서 제공하는 DefinePlugin은 모든 자바스크립트 코드에서 접근이 가능한 전역 변수를 선언하기 위해서 사용되는 플러그인입니다.
// 현재 개발환경이 "development"임을 명시
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("development"),
}),
new MiniCssExtractPlugin({
filename: "style-test.css",
}),
new CleanWebpackPlugin(),
],
};
// config/webpack.config.prod.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const webpack = require("webpack");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "build"),
},
mode: "production",
devServer: {
static: {
directory: path.resolve(__dirname, "build"),
},
port: 9000,
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: "/node_modules",
use: ["babel-loader"],
},
{
test: /\.html$/,
use: [
{
// html파일을 읽었을 떄 html-loader를 실행하여 웹팩이 이해할 수 있게 한다.
loader: "html-loader",
options: { minimize: true },
},
],
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html", // public/index.html 파일을 읽는다.
filename: "index.html",
}),
// 웹팩에서 제공하는 DefinePlugin은 모든 자바스크립트 코드에서 접근이 가능한 전역 변수를 선언하기 위해서 사용되는 플러그인입니다.
// 현재 개발환경이 "development"임을 명시
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("development"),
}),
new MiniCssExtractPlugin({
filename: "style-test.css",
}),
new CleanWebpackPlugin(),
],
};
우선 webpack의 config파일은 위 2개로 개발과 프로덕션용 2개로 나누었습니다.
이제 옵션을 차근차근 설명하자면
< entry >
우선 entry로 저는 ./src/index.js로 설정하였습니다. 그 이유는 리액트의 시작부분 즉ReactDOM.render를 해서 "root"div에 삽입하는 파일이 index.js이기 때문입니다. 즉 index.js파일에서 시작하여 차근차근 번들링을 시작하는 것 입니다.
< output >
그 다음은 번들링 된 파일이 저장될 output입니다. output.filename은 bundling될 파일의 이름을 설정 한것이고 저는 ./config/build/bundle.js에 결과물이 나오게 하기 위해 "bundle.js"라는 값을 주었습니다. 또한 그 경로는 절대경로로 path.resolve(__dirname, "build") 를 주었습니다.
< module >
module은 그냥 개발 환경을 적어주면 된다.
< module >
그 다음은 webpack설정에서 가장 중요한 규칙을 설정하는 부분이다. mode는 rule객체를 집어넣을 수 있고 rule에는 많은 규칙 객체들을 넣을 수 있습니다.
- babel-loader ( /\.(js|jsx)$/ regex를 위한 loader )
- babel-loader는 우리가 잘 알다싶이 ES6+버전 이상의 자바스크립트나 JSX, 타입스크립트 코드를 하위 버전의 자바스크립트 코드로 변환 시켜 IE나 다른 브라우저에서 동작할 수 있도록 하는 역할을 합니다. 또한 우리는 webpack에서 babel을 사용해서 번들링 할 수 있는데 별도의 .babelrc파일을 두어 설정할 수 있습니다.
// .babelrc
{
"presets": ["@babel/preset-react" ,"@babel/preset-env"]
}
- .babelrc에는 name, version, babel.presets, babel.plugins옵션을 집어넣을 수 있습니다. 우선 우리가 코드에 집어넣은 preset에 대해 설명하겠다.
- babel foundation에서는 plugin들을 포함한 번들(plugin들을 모아놓은 파일이라고 생각하면 된다) 다양한 babel preset들이 있는데 babel foundation에서 제공하는 공식 preset과 Airbnb같은 곳에서 제공하는 비공식 preset들이 있다. 심지어 우리가 만들 수도 있다.
babel이 7로 업데이트 되면서 [scoped package](https://babeljs.io/docs/en/next/v7-migration#scoped-packages)로 전환했다. 간단히 이야기하면 기존의 비공식적인 package들과 네이밍 컨벤션에 문제가 있어서 바꾸게 되었다.
공식 preset은:
- @babel/preset-env
- @babel/preset-flow
- @babel/preset-react
- @babel/preset-typescript
또한 Stage-X(Experimental Presets)들이 있었는데 공식문서를 보면
Deprecated
As of Babel 7, we've decided to deprecate the Stage-X presets and stop publishing them. Because these proposals are inherently subject to change, it seems better to ask users to specify individual proposals as plugins vs. a catch all preset that you would need to check up on anyway. Check out our blog for more context.
쓰지말라고 함을 알 수 있다.
즉 정리하면 우리는 지금 ES6+ syntax와 JSX문법들을 compile할 것이므로 @babel/preset-env와 @babel/preset-react를 사용했다.
* 또 쓰다가 생각났는데 나는 @babel/polyfill을 쓰고 싶으므로 그냥 index.js상단에 import "@babel/polyfill"을 넣어주도록 하겠다.
// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import Root from "./Root";
import "@babel/polyfill";
ReactDOM.render(<Root />, document.getElementById("root"));
- html-loader ( /\.html$/ regex를 위한 loader )
- html-loader는 간단히 html파일을 읽을 떄 쓸 것이다. 우리는 아래에서 plugin으로 HtmlWebpackPlugin을 사용할 것인데. ./public/index.html파일을 template으로 하여 inded.html파일을 build파일에 같이 만들어 낼 것이기 때문에 템플릿을 읽어서 우리의 니즈에 맞게 쓸려면 html-loader가 꼭 필요한 것이다. 또한 option에 minimize: true를 주면 html파일을 일자로 최소화 해줘서 문자열로 반환한다.
- MiniCssExtractPlugin.loader, css-loader ( /\.css$/ regex를 위한 loader 2개 )
- 우리는 inline형식으로 css를 불러올 것이 아니기 때문에 외부 파일로 extract하기 위해 MiniCssExtractPlugin을 사용한 것이다. 또한 jsx와 강이 자바스크립트를 이용한 마크업에 스타일링을 입힐 때는 import "global.css"와 같이 css파일을 import합니다. impot한다는 말은 css파일을 하나의 모듈로 취급하여 js, tsx파일에서 불러와 사용한다는 것인데 css파일을 모듈로 부르기 위해서 사용하는 로더가 css-loader이다.
- 또한 css-loader의 중요한 특징 중 하나는 컴포넌트 스타일링 방법에서 A.css, B.css가 동일한 클래스 이름을 갖더라도 실제 브라우저에 적용되는 클래스명은 난수 처리가 되어 클래스 이름이 충돌이 되지 않는다.
- 또한 css-loader로 webpack의 의존성 트리에 css파일을 불러왔다면 html의 <style>태그를 삽입하여 DOM에 CSS를 입혀야 하는데 이때 사용하는 것이 stylelaoder인데 우리는 외부파일에서 직접 추가 했으므로 사용할 필요가 없다. 만약 필요하다면 babel은 오른쪽에서 왼쪽으로 loader를 해석하므로 css-loader의 왼쪽에 styleloader를 적용하면 된다.
- 또한 css-loader의 중요한 특징 중 하나는 컴포넌트 스타일링 방법에서 A.css, B.css가 동일한 클래스 이름을 갖더라도 실제 브라우저에 적용되는 클래스명은 난수 처리가 되어 클래스 이름이 충돌이 되지 않는다.
- 우리는 inline형식으로 css를 불러올 것이 아니기 때문에 외부 파일로 extract하기 위해 MiniCssExtractPlugin을 사용한 것이다. 또한 jsx와 강이 자바스크립트를 이용한 마크업에 스타일링을 입힐 때는 import "global.css"와 같이 css파일을 import합니다. impot한다는 말은 css파일을 하나의 모듈로 취급하여 js, tsx파일에서 불러와 사용한다는 것인데 css파일을 모듈로 부르기 위해서 사용하는 로더가 css-loader이다.
- MiniCssExtractPlugin.loader, sass-loader ( /\.scss$/ regex를 위한 loader 2개 )
- MiniCssExtractPlugin.loader는 설명했으니까 생략하고 sass-loader또한 css와 비슷한 개념이기 때문에 생략하겠다
<plugin>
이제 plugin인데 plugin의 개념은 loader로 할 수 없는 것 즉 부가적인것을 담당하는 것이라고 보면 된다. 여기서 사용된 것을 짧게 설명하겠다.
- webpack.DefinePlugin
- 이는 그냥 js파일에서 전역으로 지금 환경을 알아낼 수 있는 process.env.NODE_ENV객체를 생성해 주는 것이라고 보면 된다. 나 같은 경우는 안하면 계속 브라우저 콘솔에서 오류가 떠서.. 그냥 적어주었다 ( 어디서 사용하고 있는 거겠지..? )
- CleanWebpackPlugin
- 이는 build파일을 계속 할 떄 파일을 깔끔히 지우고 다시 build해주는 plugin이다. 가장 먼저하기 위해 plugin의 가장 아래쪽에 적어주었는데 plugin의 실행 순서는 아래 -> 위 순서이다 ( loader는 오른쪽 -> 왼쪽 )
< devServer >
이는 그냥 devserver의 메모리에 build파일을 저장해서 hot-reloading을 해서 빌드된 결과물을 빨리 확인하게 해줄 수 있는 모듈이다.
webpack.config파일에 devserver객체를 넣어주고 static객체 안에 directory에 serving될 build파일을 적어주면 해당 build파일이 서빙된다. 또한 devServer객체 안에 port를 설정해줄 수 있다.
'React > ReactJs' 카테고리의 다른 글
SPA기반 SSR 구현하기 - 2 (0) | 2022.02.19 |
---|---|
SPA기반 SSR구현하기 - 1 (0) | 2022.02.19 |
React - Code-spliting, Lazy Reloading (0) | 2022.02.19 |
useMemo, useCallback, ( React.memo ) (0) | 2022.02.18 |
Webpack 설정 (0) | 2022.02.17 |