loader
는 파일 단위로 처리해주었지만 plugin
은 번들된 결과물을 가지고 처리를 해줍니다.
주로 번들된 자바스크립트 코드를 난독화하거나 특정 텍스트를 추출하는 용도로 사용됩니다.
공식문서에 따르면, 플러그인은 웹팩 생태계에 핵심 요소이며, 웹팩의 compilation 과정을 활용(tap into)할 수 있는 강력한 방법을 제공합니다.
플러그인은 각 컴파일 과정에서 발생하는 주요 입네트에 연결(hook into)할 수 있습니다. 모든 단계에서 플러그인은 compiler에 대한 액세스 권한을 가지며, 해당되는 때의 compilation 과정에 대해서도 권한을 갖습니다.
로더와 달리 플러그인은 클래스로 제작하며, 클래스 안에서 apply
함수를 구현하면 됩니다. tap에 들어갈 event hook
을 지정하고, 웹팩의 내부 인스턴스 별 데이터를 조작하면 됩니다.
플러그인은 prototype
에 apply
메서드를 가지고 있는 인스턴스화된 object입니다. apply
메서드는 플러그인을 설치하는 동안, 웹팩 컴파일러에 의해 한 번 호출됩니다. 이 apply
메서드는 기본 웹팩 컴파일러에 대한 참조를 제공하며, 해당 참조를 통해 compiler callback
에 대한 액세스 권한을 부여합니다.
그럼 커스텀 플러그인을 작성해보도록 하죠. plugin
디렉토리를 만들고 MyWebpackPlugin.js
파일을 만든 뒤 아래의 코드를 작성해봅시다.
class MyWebpackPlugin {
apply(compiler) {
// Tap into the compiler to access the hooks
compiler.hooks.done.tap('My Plugin', (stats) => {
/* stats is passed as an argument when done hook is tapped. */
console.log('MyPlugin: done');
});
}
}
module.exports = MyWebpackPlugin;
compiler
객체 안에 있는 tap 함수를 사용하는 코드입니다.done
)되는 시점에 로그를 찍는 코드입니다.webpack.config.js
에 플러그인을 적용해보도록 합시다.
const MyWebpackPlugin = require("./plugin/MyWebpackPlugin");
module.exports = {
..., // 다른 웹팩 옵션들
plugins: [new MyWebpackPlugin()],
}
이제 빌드를 해봅시다.
빌드하게 되면 완료되는 시점에서 MyPlugin: done
을 출력하고 있는 것을 볼 수 있습니다.
이번에는 webpack.config.js
에서 옵션 객체를 넣을 수 있도록 플러그인을 작성해봅시다.
먼저 플러그인 클래스를 아래와 같이 고쳐봅시다.
class MyWebpackPlugin {
constructor(options = {}) {
console.log("MyPlugin: constructor", options);
}
apply(compiler) {
// Tap into the compiler to access the hooks
compiler.hooks.done.tap("My Plugin", (stats) => {
/* stats is passed as an argument when done hook is tapped. */
console.log("MyPlugin: done");
});
}
}
module.exports = MyWebpackPlugin;
그리고 webpack.config.js에 넣은 플러그인에 인자를 넣어 호출해보도록 합시다.
plugins: [new MyWebpackPlugin({ options: true })],
그러고 난 뒤에, 빌드를 실행해보면 done
이전에 생성자 함수 쪽에서 작성했던 console.log
가 먼저 실행되었음을 확인해볼 수 있습니다.
이번에는 플러그인을 통해 번들 결과에 접근해보도록 하죠. 커스텀 웹팩 파일을 다음과 같이 수정해봅시다.
class MyWebpackPlugin {
constructor(options = {}) {
console.log("MyPlugin: constructor", options);
}
apply(compiler) {
// Tap into the compiler to access the hooks
compiler.hooks.done.tap("My Plugin", (stats) => {
/* stats is passed as an argument when done hook is tapped. */
console.log("MyPlugin: done");
});
compiler.hooks.emit.tapAsync("My Plugin", (compilation, callback) => {
// Tap into the compilation instance to access the assets
const source = compilation.assets["main.js"].source();
console.log(source);
callback();
});
}
}
module.exports = MyWebpackPlugin;
다시 빌드를 해보면 다음과 같이 출력이 되었음을 확인할 수 있습니다.
즉, 커스텀 플러그인의 constructor
를 실행하고, emit.tapAsync
를 실행한 후에, done
을 차례대로 실행했음을 알 수 있습니다.
또한, source
변수에는 소스코드가 문자열 형태로 들어가 있음을 확인할 수 있었습니다.
플러그인을 통해 출력 결과에 시그니처를 추가하는 실습을 진행해봅시다. 커스텀 웹팩 파일을 다음과 같이 수정해봅시다.
(기존의 방식은 적용이 잘 되지 않았기 때문에, webpack의 소스코드 중 BannerPlugins를 찾아서 적용을 해봤습니다 - 링크)
class MyWebpackPlugin {
constructor(options = {}) {
console.log("MyPlugin: constructor", options);
}
apply(compiler) {
// Tap into the compiler to access the hooks
compiler.hooks.done.tap("My Plugin", (stats) => {
/* stats is passed as an argument when done hook is tapped. */
console.log("MyPlugin: done");
});
compiler.hooks.emit.tap("emit", (compilation) => {
// Tap into the compilation instance to access the assets
// 번들링된 소스코드
const source = compilation.assets["main.js"].source();
// console.log(source);
compilation.updateAsset("main.js", (old) => {
return {
source: () => {
const banner = [
"/**",
" * 이 소스코드는 제가 점령했습니다.",
" * 이 글을 쓴 사람은 누굴까요?",
" */",
].join("\n");
return banner + "\n\n" + source;
},
size: () => {
return source.length;
},
};
});
});
}
}
module.exports = MyWebpackPlugin;
그리고 나서 다시 빌드를 해볼까요?
빌드하고 난 뒤에 main.js
를 보게되면 저희가 작성한 글이 소스코드에 합쳐진 것을 볼 수 있습니다.
플러그인은 webpack에서 제공하는 플러그인을 사용하거나 써드 파티 라이브러리를 찾아 사용하게 됩니다.
저희가 작성했던 커스텀 플러그인과 비슷한 역할을 해주는 플러그인입니다. 결과물에 빌드 정보나 커밋 버전 같은 것을 추가해줄 수 있습니다. 또한, webpack에서 제공해주는 플러그인이기도 합니다.
먼저 git init
으로 깃을 실행하고 git add .
, git commit -m "<작성할 메시지>"
를 통해 로컬 환경에서 버전을 관리하도록 구현합니다.
그 다음 webpack.config.js의 플러그인 옵션을 추가해줍니다.
const webpack = require("webpack");
plugins: [
new webpack.BannerPlugin({
banner: () => `
Commit Version: ${childProcess.execSync("git rev-parse --short HEAD")}
Author: ${childProcess.execSync("git config user.name")}
Build Date: ${new Date().toLocaleString()}
`,
}),
],
그 다음 빌드를 진행해봅시다.
다음과 같이 main.js
에 commit version
, commit 작성자
, build date
가 추가되었음을 확인할 수 있습니다.
애플리케이션은 보통 개발환경과 프로덕션 환경을 나눠서 운영하게 됩니다. 이 때, 환경에 따라 API 서버 주소를 다르게 운영할 수 있는데 같은 소스 코드를 두 환경에 배포하기 위해서는 환경 의존적인 정보를 소스가 아닌 곳에서 관리하는 것이 좋습니다. 배포할 때마다 사람이 코드를 수정하는 것은 위험하기 때문입니다.
웹팩은 이러한 환경정보를 제공하기 위해 DefinePlugin
을 제공합니다.
webpack.config.js
에 DefinePlugin
을 추가해봅시다.
plugins: [
..., // 다른 플러그인들
new webpack.DefinePlugin({}),
],
DefinePlugin
에 빈 객체를 전달해도 코드에 기본적으로 넣어주는 값이 있습니다. 노드의 환경 정보인 process.env.NODE_ENV
이며, webpack.config.js
의 mode
속성의 값이 여기에 들어갑니다. "development"
로 설정했기 때문에 애플리케이션 코드에서 process.env.NODE_ENV
변수로 접근하게 되면 "development"
라는 값을 반환합니다.
console.log(process.env.NODE_ENV); // "development"
웹팩 컴파일 시간에 결정되는 값을 전역 상수 문자열로 애플리케이션에 주입할 수 있습니다.
플러그인을 다음과 같이 수정해봅시다.
new webpack.DefinePlugin({
TWO: "1+1",
})
1+1
이라는 코드조각을 넣었습니다.그리고 app.js
에 아래와 같이 TWO
라는 전역변수에 접근해보도록 합시다.
import * as math from "./math.js";
import "./style.css";
import nyancat from "./assets/images/nyancat.jpg";
const imgElem = document.querySelector("#image");
imgElem.src = nyancat;
console.log('TWO', TWO);
이렇게 작성한 후 빌드한 후에 html을 실행해보면 다음과 같이 2라는 값이 찍혀있음을 알 수 있습니다.
만약 코드가 아닌 값(리터럴)을 넘기려면 stringify한 뒤에 넘기면 됩니다.
먼저 webpack.config.js
의 플러그인 설정 중 DefinePlugin
을 다음과 같이 수정해봅시다.
new webpack.DefinePlugin({
VERSION: JSON.stringify("v.1.2.3"),
PRODUCTION: JSON.stringify(false),
MAX_COUNT: JSON.stringify(999),
"api.domain": JSON.stringify("http://dev.api.domain.com"),
})
app.js
에서 DefinePlugin
에서 넘겨준 전역 변수에 접근해봅시다.
import * as math from "./math.js";
import "./style.css";
import nyancat from "./assets/images/nyancat.jpg";
const imgElem = document.querySelector("#image");
imgElem.src = nyancat;
// console.log(math.sum(1, 2, 3));
// console.log('TWO', TWO);
console.log("VERSION", VERSION);
console.log("PRODUCTION", PRODUCTION);
console.log("MAX_COUNT", MAX_COUNT);
console.log("api.domain", api.domain);
빌드한 후에, html을 실행해보면 다음과 같은 결과를 얻게 됩니다.
빌드 타임에 결정된 값을 어플리케이션에 전달할 때,DefinePlugin
을 사용하면 됩니다.
HtmlWebpackPlugin
플러그인은 써드 파티 패키지입니다. HTML 파일을 후처리하는데 사용됩니다. 이를 통해 빌드 타임의 값을 넣거나 코드를 압축할 수 있습니다.
먼저 패키지를 다운로드합시다.
npm i -D html-webpack-plugin
이 플러그인으로 빌드하면 HTML 파일로 아웃풋에 생성됩니다.
먼저 index.html
파일을 src 폴더 내부로 옮기고 아래와 같이 작성합시다.
// src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>타이틀: <%= env %></title>
</head>
<body>
<img id="image" style="position: absolute; z-index: 100;"></img>
<!-- <script src="./dist/main.js"></script> -->
<!-- <script src="./src/app.js"></script> -->
<!-- <script type="module" src="./src/app.js"></script> -->
</body>
</html>
<%= env %>
는 전달받은 env 변수 값을 출력합니다. HtmlWebpackPlugin
은 이 변수에 데이터를 주입시켜 동적으로 HTML 코드를 생성합니다.webpack.config.js
에 HtmlWebpackPlugin
플러그인을 추가해줍시다.
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html", // 템플릿 경로를 지정
templateParameters: { // 템플릿에 주입할 파라미터 변수 지정
env: process.env.NODE_ENV === "development" ? "(개발용)" : "",
},
})
],
이렇게 설정을 하게 된다면 NODE_ENV=development
로 설정해서 빌드하면 빌드결과가 타이틀: (개발용)
으로 나오고 NODE_ENV=production
으로 설정해서 빌드하면 빌드결과가 타이틀:
로 나옵니다.
먼저 NODE_ENV=development npm run build 명령어로 빌드해봅시다.
그럼 다음과 같이 index.html
이 생성됩니다.
<title>
을 확인해보면 타이틀: (개발용)
이 생겼음을 확인할 수 있습니다.
또한, <script defer src="main.js"></script>
도 추가되었습니다.
이번에는 NODE_ENV=production npm run build
명령어로 빌드해봅시다.
그럼 다음과 같이 index.html
이 생성됩니다.
이번에는 production 환경에서는 파일을 압축하고, 불필요한 주석을 제거하는 옵션을 추가해봅시다.
먼저 webpack.config.js
의 HtmlWebpackPlugin
을 다음과 같이 변경해봅시다.
new HtmlWebpackPlugin({
template: "./src/index.html", // 템플릿 경로를 지정
templateParameters: {
// 템플릿에 주입할 파라미터 변수 지정
env: process.env.NODE_ENV === "development" ? "(개발용)" : "",
},
minify:
process.env.NODE_ENV === "production"
? {
collapseWhitespace: true, // 빈칸 제거
removeComments: true, // 주석 제거
}
: false,
}),
NODE_ENV=production npm run build
명령어로 빌드를 하면 다음과 같이 빌드됨을 확인할 수 있습니다.
주석도 제거되고, 파일의 공백을 압축시켰음을 볼 수 있습니다.
브라우저 캐시로 인해 정적파일 배포시, 즉각적으로 브라우저에 반영되지 않는 경우가 있어 이를 예방하기 위한 옵션도 존재합니다.
webpack.config.js
의 HtmlWebpackPlugin
을 다음과 같이 변경해봅시다.
new HtmlWebpackPlugin({
template: "./src/index.html", // 템플릿 경로를 지정
templateParameters: {
// 템플릿에 주입할 파라미터 변수 지정
env: process.env.NODE_ENV === "development" ? "(개발용)" : "",
},
hash: true, // 빌드할 때마다 해시값을 붙여줌
minify:
process.env.NODE_ENV === "production"
? {
collapseWhitespace: true, // 빈칸 제거
removeComments: true, // 주석 제거
}
: false,
}),
그 다음에 npm run build
명령어로 빌드를 해보면 다음과 같이 빌드할 때 생성되는 해시값을 정적파일 로딩 주소의 쿼리스트링으로 붙여서 HTML을 생성합니다.
CleanWebpackPlugin
은 빌드 이전 결과물을 제거하는 플러그인입니다. 빌드 결과물은 아웃풋 경로에 모이는데 과거에 빌드했던 파일이 남아있을 수 있습니다. 이러한 현상을 CleanWebpackPlugin
을 통해 해결할 수 있습니다.
먼저 패키지를 설치합시다.
npm install -D clean-webpack-plugin
그 다음에 webpack.config.js
의 플러그인에 CleanWebpackPlugin
을 추가해줍시다.
plugins: [
..., // 다른 플러그인들
new CleanWebpackPlugin(),
]
이후, 빌드하면 빌드하기 전에 이전 파일들을 삭제한 후에 빌드하게 됩니다.
CSS 파일이 점점 많아지면 하나의 자바스크립트 결과물로 만들기보다는 번들 결과에서 스타일시트 코드만 뽑아서 별도의 CSS 파일로 만들어 역할에 따라 파일을 분리하는 것이 좋습니다.
왜냐하면 큰 파일 하나를 다운로드 받는 것보다 여러 개의 작은 파일을 동시에 다운로드하는 것이 더 효율적이기 때문입니다.
개발 환경에서는 CSS를 하나의 모듈로 처리해도 상관없지만 프로덕션 환경에서는 분리하는 것이 더 효과적이며, MiniCssExtractPlugin
은 CSS를 별도의 파일로 뽑아내는 플러그인이다.
먼저 패키지를 설치해보도록 합시다.
npm install -D mini-css-extract-plugin
그 다음에 webpack.config.js
의 플러그인에 MiniCssExtractPlugin
을 추가해줍시다.
plugins: [
..., // 다른 플러그인들
...(process.env.NODE_ENV === "production"
? [new MiniCssExtractPlugin({ filename: `[name].css` })] // 프로덕션 환경일 때만 사용
: [
function () {
this.hooks.done.tap("done", (stats) => {
if (stats.compilation.errors && stats.compilation.errors.length) {
console.log("Build Error");
process.exit(1);
}
});
},
]),
]
filename
에 설정한 값으로 아웃풋 경로에 CSS 파일이 생성됩니다.여태껏 css-loader
에 의해 js 모듈로 변경된 스타일시트를 적용하기 위해 style-loader
을 사용했었는데, 이제는 production 환경에서는 별도의 CSS 파일로 추출되는 플러그인을 적용했으므로 이를 위한 loader
가 필요합니다.
때문에 아래와 같이 production
일 때와 아닐 때를 분기처리해서 로더를 추가해봅시다.
module: {
rules: [
{
test: /\.css$/, // .css로 끝나는 파일을 찾아라
use: [
process.env.NODE_ENV === "production"
? MiniCssExtractPlugin.loader // 프로덕션 환경
: "style-loader",
"css-loader",
], // css-loader를 사용해라
},
... // 다른 로더 설정
}
이제 NODE_ENV=production npm run build
명령어로 빌드를 해보면 다음과 같이 css는 분리되어 있고, index.html
에는 css를 로딩하는 코드가 추가되었습니다.
(난독화하는 플러그인은 주석처리하거나 제거해둔 상태로 실행해야 결과를 더 잘 확인할 수 있습니다)
웹팩은 ECMAScript2015에 등장한 모듈 시스템을 쉽게 사용할 수 있도록 해주며, 정적인 리소스들을 모듈로 제공해줄 수 있도록 해주므로 일관성 있게 개발할 수 있게 됩니다.
출처