우선 이전 블로그에서 React의 개발 환경을 갖춘 바 있다. 이에 기반해서 유용한 plugin에 대해 소개 하고자 한다. 우선 완성된 코드를 여러분들에게 보여주고 이에대한 설명을 진행하도록 하겠다.
Source Code
< components >
// components/Home.js
import React, { useState } from "react";
import "../styles/Home.css";
import loadable from "@loadable/component";
import testImg from "../public/test.svg";
const LoadableComponent = loadable(() => import("./Loadable"));
const Home = () => {
const [visible, setVisible] = useState(false);
const onButtonHandler = () => {
setVisible((prev) => !prev);
console.log("clicked");
};
return (
<div className="homeContainer">
Hello world!
<hr />
{visible && <LoadableComponent />}
<button onClick={onButtonHandler}>Please Click Here~</button>
<img src={testImg} alt="sex" width="200px" height="200px" />
</div>
);
};
export default Home;
// components/Loadable.js
import React from "react";
const Loadable = () => {
return <div>Loadable Component is Here!</div>;
};
export default Loadable;
< config >
// config/webpack.config.dev.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const webpack = require("webpack");
module.exports = {
mode: "development",
entry: {
app: path.resolve(__dirname, "..", "./index.js"),
},
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].[chunkhash].js",
chunkFilename: "[name].[chunkhash].js",
},
devServer: {
static: {
directory: path.join(__dirname, "dist"),
},
port: 9000,
open: true,
compress: true,
},
devtool: "inline-source-map",
module: {
rules: [
{
test: /\.html$/,
exclude: "/node_modules",
use: ["html-loader"],
},
{
test: /\.(js|jsx)$/,
exclude: "/node_modules",
use: {
loader: "babel-loader",
},
},
{
test: /\.css$/,
exclude: "/node_modules",
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
{
test: /\.scss$/,
exclude: "/node_modules",
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
},
{
// write image files under 10k to inline or copy image files over 10k
test: /\.(jpg|jpeg|gif|png|svg|ico)?$/,
use: [
{
loader: "url-loader",
options: {
limit: 10000,
fallback: "file-loader",
name: "images/[name].[ext]",
},
},
],
},
{
// write files under 10k to inline or copy files over 10k
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
{
loader: "url-loader",
options: {
limit: 10000,
fallback: "file-loader",
name: "fonts/[name].[ext]",
},
},
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "..", "./public/index.html"),
filename: "index.html",
}),
new MiniCssExtractPlugin({
filename: "static/css/[name].css",
}),
new WebpackManifestPlugin({
fileName: "assets.json",
}),
new CleanWebpackPlugin(),
],
};
// config/webpack.config.prod.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const webpack = require("webpack");
const childProcess = require("child_process");
const removeNewLine = (buffer) => {
return buffer.toString().replace("\n", "");
};
module.exports = {
mode: "production",
module: "esnext",
entry: {
app: path.resolve(__dirname, "..", "./index.js"),
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: "initial",
name: "vendor",
enforce: true,
},
},
},
},
output: {
path: path.resolve(__dirname, "..", "./dist"),
filename: "[name].js",
chunkFilename: "[name].js",
publicPath: "/dist/",
},
module: {
rules: [
{
test: /\.html$/,
exclude: "/node_modules",
use: ["html-loader"],
},
{
test: /\.(js|jsx)$/,
exclude: "/node_modules",
use: {
loader: "babel-loader",
},
},
{
test: /\.css$/,
exclude: "/node_modules",
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
{
test: /\.scss$/,
exclude: "/node_modules",
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
},
{
// write image files under 10k to inline or copy image files over 10k
test: /\.(jpg|jpeg|gif|png|svg|ico)?$/,
use: [
{
loader: "url-loader",
options: {
limit: 10000,
fallback: "file-loader",
name: "images/[name].[ext]",
},
},
],
},
{
// write files under 10k to inline or copy files over 10k
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
{
loader: "url-loader",
options: {
limit: 10000,
fallback: "file-loader",
name: "fonts/[name].[ext]",
},
},
],
},
],
},
plugins: [
new webpack.BannerPlugin({
banner: `
Build Date :: ${new Date().toLocaleString()}
Commit Version :: ${removeNewLine(
childProcess.execSync("git rev-parse --short HEAD")
)}
Auth.name :: ${removeNewLine(
childProcess.execSync("git config user.name")
)}
Auth.email :: ${removeNewLine(
childProcess.execSync("git config user.email")
)}
`,
}),
new webpack.DefinePlugin({
TWO: JSON.stringify("1+1"),
}),
new HtmlWebpackPlugin({
template: "./public/index.html",
filename: "index.html",
}),
new MiniCssExtractPlugin({
filename: "static/css/[name].css",
}),
new WebpackManifestPlugin({
fileName: "assets.json",
}),
new CleanWebpackPlugin(),
],
};
< dist >
번들링 된 결과물 이 들어갈 자리
< public >
// public/index.html
<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="UTF-8" />
<title>WEBPACK4-Example</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Roboto+Serif:ital,wght@1,100&display=swap"
rel="stylesheet"
/>
</head>
<body>
<noscript>스크립트가 작동되지 않습니다!</noscript>
<div id="root"></div>
</body>
</html>
+ RobotoSerif.Regular.tff, test.jpg, test.svg ( 테스팅 글꼴이랑 사진들 )
< styles >
// styles/Global.css
body {
font-family: "Roboto Serif", sans-serif;
}
// styles/Home.css
.homeContainer {
background-color: bisque;
}
< root directory >
// .babelrc
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-syntax-dynamic-import"
]
}
// App.js
import React from "react";
import Home from "./components/Home";
import "./styles/Global.css";
const App = () => {
return (
<div className="App">
<Home />
</div>
);
};
export default App;
// index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"));
// package.json
{
"name": "webpackexample",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "npx webpack serve --config ./config/webpack.config.dev.js",
"build": "webpack --config ./config/webpack.config.prod.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@babel/core": "^7.17.5",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"@babel/runtime": "^7.17.2",
"@loadable/component": "^5.15.2",
"mini-css-extract-plugin": "^2.5.3",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"styled-components": "^5.3.3",
"url-loader": "^4.1.1"
},
"devDependencies": {
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.17.0",
"babel-loader": "^8.2.3",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.6.0",
"file-loader": "^6.2.0",
"html-loader": "^3.1.0",
"html-webpack-plugin": "^5.5.0",
"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",
"webpack-manifest-plugin": "^4.1.1"
}
}
Webpack Plugin
webpack plugin에 대해 간단히 모르는 사람을 위해 설명하겠다. 각각의 파일을 해석하는 데 들어가는 로더와 달리 번들된 결과물을 처리하는 것이 플러그인이다. 5개의 파일이 있다고 하면 로더는 각각의 파일을 모듈화 할 떄 호출되고(5번), 플로그인은 최종 1번 호출된다.
자주 쓰이는 플러그인
< Banner Plugin >
번들링된 파일의 상단에 배너(텍스트)를 달아주는 플러그인이다. webpack 기본 플러그인이기에 따로 설치는 필요없다. 빌드시 데이터들을 자동으로 기록해주는 방식으로 사용하면 유용할 듯 하다.
// dist/static/css/vendor.css
/*!
*
* Build Date :: 2022. 2. 27. 오후 10:55:36
* Commit Version :: 9dac27e
* Auth.name :: LE123123
* Auth.email :: heart20021010@gmail.com
*
*/
.homeContainer {
background-color: bisque;
}
body {
font-family: "Roboto Serif", sans-serif;
}
참고로 벤더 dist파일에 생성된 vendor파일은 청크간이나 번들링 된 파일간의 겹치는 패키지들을 별도의 파일로 추출해주는 코드입니다.
중복을 최소화 하기 위함입니다. => SplitChunks 옵션 사용 ( config/webpack.config.prod.js )
< Define Plugin >
각종 환경 변수들을 빌드시에 지정할 수 있는 플러그인이다. 개발 환경에 따라 서버 URL등의 정보가 바뀌는 경우가 많은데 그럴 때 미리 지정해두면 실수를 방지할 수 있다.
< HTML Template Plugin >
HTML을 동적으로 생성할 수 있는 플러그인이다. 해당 플러그인을 사용하면 index.html에서 js file을 로드하는 부분을 삭제해도 된다.
templateParameters, minify등의 옵션으로 생성될 html파일에 원하는 처리를 해줄 수 있다.
NODE_ENV === 'development'이면 title에 (" 개발용 ")이라는 접미사를 붙여주고 production인 경우 minify(공백, 줄바꿈, 주석 삭제)를 수행한다.
new HtmlWebpackPlugin({
template: "./public/index.html",
filename: "index.html",
templateParameters: {
env: process.env.NODE_ENV === "development" ? "( 개발 용 )" : "( 배포 용 )",
},
minify:
process.env.NODE_ENV === "production"
? {
collapseWhitespace: true,
removeComments: true,
}
: false,
}),
< Clean Webpack Plugin >
빌드되고 사용되지 않는 찌꺼기 파일들이나 캐시처럼 남아있는 파일들을 빌드 전에 삭제해주는 플러그인이다.
const { CleanWebpackPlugin } = require("clean-webpack-plugin")
module.exports = {
plugins: [
new CleanWebpackPlugin(),
}
< Mini Css Extract Plugin >
webpack 기본 설정은 css파일이 js파일에 같이 번들링 됩니다. js파일이 비대해질 경우 그만큼 페이지 로딩 시간이 길어집니다. 이럴 때에는 js파일과 css파일을 분리하는 편이 도움이 됩니다.
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
process.env.NODE_ENV === 'production'
? MiniCssExtractPlugin.loader
: 'style-loader',
'css-loader',
],
},
],
},
plugins: [
...(process.env.NODE_ENV === 'production'
? [new MiniCssExtractPlugin({ filename: '[name].css' })]
: []),
],
}
Mini Css Extract Plugin은 플러그인 뿐 아니라 loader에도 손을 봐주어야 한다 (style-loader 삭제, MCEP 생성자 loader 추가)
이 외에도 다양한 유용한 플러그인을 소개한 git페이지를 소개하겠다.
https://github.com/webpack/docs/wiki/list-of-plugins
사용자의 needs에 따라 유동적으로 추가하고 빼서 webpack을 다루는 실력을 늘리면 될 거 같다.!!
나의 코드와 직접 작성한 코드를 비교해 보면 공부가 수월해 질 것이다.
< 결과물
'React > ReactJs' 카테고리의 다른 글
React - mongoDB ( mongoose ) (0) | 2022.03.12 |
---|---|
React - koa FrameWork (0) | 2022.03.09 |
HTML - History API (0) | 2022.02.23 |
React - ServerSideRendring (0) | 2022.02.22 |
React - React.lazy & Suspense & Loadable Components (0) | 2022.02.22 |