플러그인
웹팩에서 알아야할 마지막 기본 개념이 플러그인이다.
로더가 파일 단위로 처리하는 반면 플러그인은 번들된 결과물을 처리한다.
번들된 자바스크립트를 난독화 한다거나 특정 텍스트를 추출하는 용도로 사용한다.
이것도 사용하기에 앞서 동작 원리를 이해하기 위해 플러그인을 직접 만들어 보자.
커스텀 플러그인 만들기
웹팩 문서의 Writing a plugin을 보면 클래스로 플러그인을 정의하도록 한다.
헬로월드 코드를 가져다 그대로 실행해보자.
// my-webpack-plugin.js
class MyWebpackPlugin {
apply(compiler) {
compiler.hooks.done.tap('My Plugin', stats => {
console.log('MyPlugin: done');
})
}
}
module.exports = MyWebpackPlugin;
로더와 다르게 플러그인은 클래스로 제작한다.
apply
함수를 구현하면 되는데 이 코드에서는 인자로 받은 compiler의 tap 함수를 사용했다.
플러그인 작업이 완료되는(done) 시점에 로그를 찍는 코드인 것 같다.
위와 같이 MyWebpackPlugin
클래스를 만들었다.
클래스의 맨 앞글자는 보통 대문자로 쓴다.
그리고 이 플러그인의 메서드 apply
를 하나 만들었다.
그리고 이 apply
메서드를 만들게되면 웹팩은 compiler라는 객체를 파라미터로 주입해준다.
그러면 이 compiler라는 객체에 hooks.done.tap
으로 접근할 수 있는데, 여기에 인자를 두개 넣었다.
‘My Plugin’이라는 인자와 콜백함수를 전달한다.
이 콜백함수는 플러그인이 완료되었을 때 동작하는 플러그인이라고 보시면 된다.
플러그인이 잘 동작했는지를 알기위해 console.log에 MyPlugin: done 이라는 문자열을 전달했다.
그럼 이 커스텀 플러그인을 웹팩 설정에 추가해보자.
// webpack.config.js
const path = require('path');
const MyWebpackPlugin = require('./my-webpack-plugin');
module.exports = {
mode: "development",
entry: {
main: "./src/app.js"
},
output: {
path: path.resolve('./dist'),
filename: "[name].js"
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
],
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'url-loader',
options: {
publicPath: './dist',
name: '[name].[ext]?[hash]',
limit: 20000, // 20kb
}
}
]
},
plugins: [
new MyWebpackPlugin(),
]
}
npm run build
이렇게 콘솔로 찍히는 것을 확인할 수 있다.
로더가 src 폴더에 있는 여러개 파일에 대해서 ‘각각’ 실행했다면, 플러그인은 이를 하나로 합쳐놓은 번들 파일에 대해서 딱 한번 실행되는 것을 확인할 수 있다.
번들된 결과 처리하기
그러면 어떻게 번들된 결과물에 접근할 수 있을까?
웹팩 내장 플러그인 BannerPlugin 코드를 참고하자.
// my-webpack-plugin.js
class MyWebpackPlugin {
apply(compiler) {
// 플러그인이 종료되었을 때 실행되는 함수
// compiler.hooks.done.tap('My Plugin', stats => {
// console.log('MyPlugin: done');
// })
compiler.plugin('emit', (compilation, callback) => { // compiler.plugin()
const source = compilation.assets['main.js'].source();
console.log(source);
// compilation.assets['main.js'].source = () => {
// const banner = [
// '/**',
// ' * 이것은 BannerPlugin이 처리한 결과입니다.',
// ' * Build Date: 2019-10-10',
// ' */'
// ].join('\n');
// return banner + '\n\n' + source;
// }
callback();
})
}
}
module.exports = MyWebpackPlugin;
compiler 객체에 plugin이라는 메서드(함수)가 있고 인자로 ‘emit’ 이라는 문자열과 콜백 함수를 넘겨준다.
콜백함수가 실행될 때 번들된 결과물에 접근할 수 있는 것이다.
이 콜백 함수는 compilation, callback 이렇게 두 개의 인자를 받는데,
compilation
를 통해서 웹팩이 번들링한 결과물에 접근할 수 있다.
compilation.assets('main.js').source();
그 중에 main.js 파일의 소스 코드를 가져오는 함수이다.
일단 위와 같이 주석처리를 한 다음에 npm run build
를 실행해보자.
그러면 번들된 main.js
의 내용이 콘솔창에 뜬다.
너무 길어서 일부만 적었다.
아래와 같이 main.js
의 내용이 쭈르륵 뜬다.
$ npm run build
> webpack-middle-class@1.0.0 build D:\webpack\webpack-middle-class
> webpack
(node:13228) DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead
/******/ (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;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
...
이렇게 compilation
객체를 이용해서 웹팩이 번들링한 결과물에 접근할 수 있다라는 사실을 알게됐다.
우리는 이걸 웹팩 내장 플러그인 중 하나인 BannerPlugin을 통해 알아냈습니다.
그럼 이번엔 이걸 한번 해보자.
번들한 결과물에 내용을 더 추가해보자.
웹팩으로 빌드한 시간을 주석으로 달아놓는 로직을 추가해보도록 하자.
// my-webpack-plugin.js
class MyWebpackPlugin {
apply(compiler) {
// 플러그인이 종료되었을 때 실행되는 함수
// compiler.hooks.done.tap('My Plugin', stats => {
// console.log('MyPlugin: done');
// })
compiler.plugin('emit', (compilation, callback) => { // compiler.plugin()
const source = compilation.assets['main.js'].source();
compilation.assets['main.js'].source = () => {
const banner = [
'/**',
' * 이것은 BannerPlugin이 처리한 결과입니다.',
' * Build Date: 2019-10-10',
' */'
].join('\n');
return banner + '\n\n' + source;
}
callback();
})
}
}
module.exports = MyWebpackPlugin;
compilation.assets
의 main.js
라는 키를 통해서 source()
함수에 접근할 수 있었다.
main.js
파일이 갖게될 소스 내용을 source()
메서드로 얻어낼 수 있었다.
그런데 그 아래 다시 source()
라는 메서드를 재정의했다.
그래서 이 source
메서드가 하는 역할은..
banner
를 하나 만들고, 이렇게 만든 banner
문자열 플러스(+) 줄바꿈하고(‘\n\n’) 다시 원본 source를 합한(+) 이 문자열을 return
하는 함수이다.
이렇게하면 원본 코드 맨 상단에 주석문자열이 추가될 것이다.
이렇게 해서 번들 결과가 어떻게 바뀌는지 한번 확인해보겠다.
npm run build
위와 같이 번들된 결과에 후처리를 한 것을 확인할 수 있다.
다시한번 위 그림을 보자.
웹팩이 하는 역할을 나타내는 그림인데, 웹팩의 로더는 위 그림에 있는 모듈로 연결된 각 파일들을 처리한다.
그래서 오른쪽처럼 하나로 만들어주는데, 그 직전에 plugin 이라는 녀석이 개입해서 아웃풋으로 만들어질 번들링에 후처리를 해주게된다.
우리가 커스텀으로 만들었던 것은 아웃풋 내용물 상단에 주석으로 내요을 추가하는 플러그인이었다.
이것이 바로 플러그인의 역할이다.
실제 플러그인을 직접 만드는 경우는 거의 없다.
우리가 실무에서 필요한 플러그인은 이미 제공이 되어있고 ‘어떤 플러그인을 사용할지’ 아는 것이 더 중요하다.