Inflearn - 프론트엔드 개발환경의 이해와 실습을 수강하며 정리한 내용입니다.
웹팩 버전은 다음과 같습니다.node @16 webpack @4 webpack-cli @3
웹팩은 로더를 사용하여 모든 파일을 모듈로 취급한다. 이 덕분에 JS
뿐만 아니라 CSS
, TS
등도 JS
에서 로드할 수 있다.
로더를 직접 만들어 사용할 수 있다. 로더 함수를 가진 파일을 만든 후 webpack.config.js
에 추가한다.
// custom-loader.js
module.exports = function myCustomLoader(content) {
console.log("custom-loader runs");
return content;
};
// webpack.config.js
const path = require("path");
module.exports = {
// (...)
module: {
rules: [
{
test: /\.js$/,
use: [path.resolve("./custom-loader.js")],
},
],
},
};
실행될 로더는 module
속성 내에 작성한다. 여기서는 .js
로 끝나는 모든 파일에 한해서 custom-loader.js
가 동작한다. 빌드를 실행하면 결과를 볼 수 있다.
> webpack
custom-loader runs
custom-loader runs
위에서 math.js
와 app.js
파일을 작성했기 때문에 custom-loader
가 두 번 동작하는 것을 볼 수 있다.
css-loader
를 사용하면 CSS 파일을 import
로 호출할 수 있다.
npm install css-loader@3
으로 설치한다. 강의에 따라 webpack@4 버전을 사용하기 때문에 호환되는 버전으로 맞췄다.
webpack.config에 css-loader
를 추가한다.
module.exports = {
// (...)
module: {
rules: [
{
test: /\.css$/,
use: ["css-loader"],
},
],
},
};
웹팩은 .css
파일을 만나면 css-loader
를 실행한다.
[./src/app.css] 277 bytes {main} [built]
[./src/app.js] 87 bytes {main} [built]
[./src/math.js] 360 bytes {main} [built]
main.js
에 CSS 코드가 추가되었지만, 브라우저에는 반영되지 않는다. HTML이 DOM으로 변환되어야 렌더링되듯이, CSS 역시 CSSOM이 되어야 화면에 나타나기 때문이다. 이를 반영하기 위해서 style-loader
를 함께 사용한다.
강의 버전에 맞춰 npm install style-loader@1
로 설치한다. 로더를 config의 module에 추가하는데, css-loader
보다 앞에 추가한다. 로더가 여러 개일 때는 뒤에서부터 앞으로 실행되기 때문이다.
module.exports = {
// (...)
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
],
},
};
빌드해 보니 스타일이 적용되었다.
이미지 같은 파일들도 로더를 사용하여 모듈로 호출할 수 있다.
body {
background-image: url(bg.png);
}
CSS에서 파일로 가져온 이미지를 모듈화하기 위해 npm install file-loader@5
하고, 모듈에 추가한다.
module.exports = {
// (...)
module: {
rules: [
{
// (...)
},
{
test: /\.png$/,
use: ["file-loader"],
},
],
},
};
빌드는 성공하지만 배경화면이 적용되지는 않는다. index.html
입장에서는 같은 폴더가 아닌 dist
에 이미지가 있기 때문이다. 해결하기 위해 옵션을 추가한다.
module.exports = {
// (...)
module: {
rules: [
{
// (...)
},
{
test: /\.png$/,
loader: "file-loader",
options: {
publicPath: "./dist/",
name: "[name].[ext]?[hash]",
},
},
],
},
};
options
에 추가한 부분를 보면, publicPath
는 빌드 후 파일이 있을 경로이고, name
은 파일의 이름을 설정하는 역할이다. 순서대로 [파일명].[확장자]?[해시]
이며, 해시는 캐시 무력화를 위해 사용한다.
해시가 없다면 브라우저는 파일이 바뀌어도 이름이 같다면 저장된 캐시를 사용한다. 때문에 빌드마다 해시를 변경하여 파일이 바뀌었음을 브라우저에 알린다.
이미지가 많으면 네트워크 자원에 사용에 부담이 생기고, 사이트 성능에 악영향을 끼칠 수 있다. 만약 한 페이지에 작은 이미지를 여러 개 사용한다면, 이미지를 Base64로 인코딩하여 소스로 넣는 Data URI Scheme
방법이 나을 수도 있다. url-loader
는 이러한 방식을 돕는다.
먼저 app.js
에 img
태그에 이미지 파일을 모듈로 불러와 소스에 넣는다.
import "./app.css";
import nyancat from "./nyancat.jpg";
document.addEventListener("DOMContentLoaded", () => {
document.body.innerHTML = `
<img src="${nyancat}" />`;
});
npm install url-loader@3
으로 설치하고 로더를 config에 추가한다.
module.exports = {
// (...)
module: {
rules: [
{
// (...)
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: "url-loader",
options: {
publicPath: "./dist/",
name: "[name].[ext]?[hash]",
limit: 20000, // 20kb
},
},
],
},
};
file-loader
와 유사하지만 용량의 limit
을 지정하여 빌드 방식을 구분할 수 있다. limit: 20000
으로 설정했으니 20kb 이하의 이미지는 base64
로 인코딩되어 main.js
에 들어가고, 그보다 큰 이미지는 file-loader
가 처리하여 위의 배경 이미지처럼 동작한다.
플러그인은 번들된 결과물을 난독화하거나 특정 텍스트를 추출하는 용도이다.
플러그인은 로더와 다르게 class
로 정의한다.
// custom-plugin.js
class CustomPlugin {
apply(compiler) {
compiler.hooks.done.tap("Custom Plugin", (stats) => {
console.log("CustomPlugin: done");
});
}
}
module.exports = CustomPlugin;
플러그인은 plugins
속성의 배열에 넣는다.
// webpack.config.js
const path = require("path");
const customPlugin = require("./custom-plugin");
module.exports = {
// (...)
plugins: [new customPlugin()],
};
빌드를 실행하면 플러그인에 적은 대로 출력되는 것이 보인다.
> webpack
CustomPlugin: done
다음은 빌드마다 빌드 날짜를 남기는 플러그인이다.
class CustomPlugin {
apply(compiler) {
const date = new Date();
compiler.plugin("emit", (compilation, callback) => {
const source = compilation.assets["main.js"].source();
compilation.assets["main.js"].source = () => {
const banner = [
"/**",
" * 이것은 BannerPlugin이 처리한 결과입니다.",
` * Build Date: ${date.getFullYear()}년 ${
date.getMonth() + 1
}월 ${date.getDate()}일`,
" */",
].join("\n");
return banner + "\n\n" + source;
};
callback();
});
}
}
module.exports = CustomPlugin;
웹팩의 Banner Webpack Plugin을 참고했다. compilation
에서 main.js
에 해당하는 소스를 받고, 빌드 날짜를 기록하는 코드와 소스를 합친다. main.js
를 보면 소스 코드 제일 앞에 배너가 적혀 있는 것을 볼 수 있다.
// dist/main.js
/**
* 이것은 BannerPlugin이 처리한 결과입니다.
* Build Date: 2023년 6월 25일
*/
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // (...)
웹팩에서 기본적으로 제공하며, 빌드 결과물에 빌드 정보나 커밋 정보 등을 추가할 수 있는 플러그인이다.
// webpack.config.js
const Webpack = require("webpack");
const childProcess = require("child_process");
module.exports = {
// (...)
plugins: [
new Webpack.BannerPlugin({
banner: `
Build Date: ${new Date().toLocaleString()}
Commit Version: ${childProcess.execSync("git rev-parse --short HEAD")}
Author: ${childProcess.execSync("git config user.name")}
`,
}),
],
};
// 결과
/*!
*
* Build Date: 2023. 6. 25. 오후 11:48:01
* Commit Version: 0a18573
*
* Author: Real-Bird
*
*
*/
/******/ (function (modules) {
/******/ // The module cache
/******/ var installedModules = {};
/******/ // (...)
child_process
는 터미널 명령어를 실행해주는 노드 모듈이다. 그것을 이용해 현재 커밋하는 깃의 버전(git rev-parse --short HEAD
)과 작성자(git config user.name
)를 적어 빌드한 결과물이다.
개발환경과 운영환경에 따라 의존적인 환경 변수를 다루기 위해 사용하는 플러그인이다.
// webpack.config.js
const Webpack = require("webpack");
const childProcess = require("child_process");
module.exports = {
// (...)
plugins: [
new Webpack.DefinePlugin({
TWO: "1+1",
}),
],
};
// app.js
console.log(TWO);
플러그인에서 정의한 TWO
는 전역으로 사용할 수 있는 코드이며, 빌드 후 실행했을 때 콘솔에 2
가 찍힌다. 만약 코드가 아닌 문자열을 출력하고 싶다면 JSON.stringify
로 감싸 문자열로 변환한다.
써드파티 플러그인으로, HTML 파일도 웹팩 빌드 과정에 포함할 때 사용한다. npm install html-webpack-plugin@3
으로 설치한다.
HTML을 엔트리 포인트가 있는 소스 폴더로 이동시키고 내부의 스크립트를 지운다.
// webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
// (...)
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
],
};
빌드하면 ./dist/
에 index.html
이 포함되어 있고, 스크립트가 추가된 것을 확인할 수 있다.
esj
문법을 이용하여 웹팩 빌드에서 HTML의 정보를 변경할 수 있다.
<title>Document<%= env %></title>
타이틀에 <%= env %>
를 추가한다.
// webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
// (...)
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
templateParameters: {
env: process.env.NODE_ENV === "development" ? "(개발용)" : "",
},
}),
],
};
templateParameters
에 HTML에 추가했던 env
를 설정한다. NODE_ENV
가 development
이면 타이틀에 (개발용)이 붙고, 아니라면 아무것도 붙지 않는다. NODE_ENV=development npm run build
로 빌드한다.
<title>Document(개발용)</title>
개발용이 추가된 것이 보인다.
문서의 주석도 제거할 수 있다. minify
옵션에 필요한 설정을 추가한다.
// webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
// (...)
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
templateParameters: {
env: process.env.NODE_ENV === "development" ? "(개발용)" : "",
},
minify: {
collapseWhitespace: true,
removeComments: true,
},
}),
],
};
collapseWhitespace
는 공백을 제거하고, removeComments
는 주석을 제거한다. 이것 역시 환경에 따라 구분하는 것이 좋다. production일 때만 활성화하고 아닐 때는 false로 설정한다.
써드파티 플러그인이며, 아웃풋 폴더의 이전 빌드 내용을 삭제해 준다. npm install clean-webpack-plugin@3
으로 설치한다.
// webpack.config.js
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
// (...)
plugins: [new CleanWebpackPlugin()],
};
테스트를 위해 ./dist/
폴더에 foo.js
를 추가하고 빌드를 실행해 보면 결과물에 foo.js
가 사라졌음을 확인할 수 있다.
써드파티 플러그인이며, 스타일 코드만 뽑아서 CSS 파일로 만들어 준다. 코드를 여러 파일로 나누는 게 좋은데, 브라우저에서는 하나의 큰 파일을 다운로드하는 것보다 여러 개의 작은 파일을 동시에 다운로드하는 것이 더 빠르기 때문이다.
npm install mini-css-extract-plugin@0
으로 설치한다.
// webpack.config.js
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" })]
: []),
],
};
production
이 아닌 환경에서는 빌드 과정을 줄이는 것이 나으므로 추가 조건을 설정한다. MiniCssExtractPlugin
을 사용할 경우에는 전용 로더를 사용하는 것이 나으므로, module
의 스타일 코드도 수정한다.
실행을 확인하기 위해 NODE_ENV=production npm run build
로 빌드한다. 결과물을 보면 이전에 인라인 스타일로 포함되었던 것이 main.css
로 생성된 것을 볼 수 있다.