Webpack 웹팩 알아보기(2) : plugin

2020-07-16

플러그인(Plugin)

로더가 파일 단위로 처리하는 반면, 플러그인은 번들된 결과물을 처리한다. 번들된 자바스크립트를 난독화 한다거나 특정 텍스트를 추출하는 용도로 사용한다.

공식홈페이지의 Basic Plugin Architecture를 따라해보자.

hello-world-plugin.js

class HelloWorldPlugin {
  apply(compiler) {
    // plugin이 종료 되었을 때 실행된다.
    compiler.hooks.done.tap('Hello World Plugin', stats => {
      console.log('Hello World!')
    })
  }
}

module.exports = HelloWorldPlugin

loader가 함수형으로 정의된 것과 다르게, plugin은 클래스형으로 정의된다.

webpack.config.js

const HelloWorldPlugin = require('./hello-world-plugin')

module.exports = {
  plugins: [new HelloWorldPlugin()],
}

설정을 마치고 npm run build를 하면 'Hello World!'가 찍힌 것을 볼 수 있다.

$ npm run build

Hello World!
Hash: cabe67579d243965d759
Version: webpack 4.43.0
Time: 1349ms
Built at: 2020-07-15 23:46:13
                                  Asset      Size  Chunks             Chunk Names
bg.png?5c6d3b633991b51295c68b34d8b94c8b  1.17 MiB          [emitted]
                                main.js    22 KiB    main  [emitted]  main
Entrypoint main = main.js
[./node_modules/css-loader/dist/cjs.js!./src/app.css] 581 bytes {main} [built]
[./src/app.css] 517 bytes {main} [built]
[./src/app.js] 186 bytes {main} [built]
[./src/bg.png] 64 bytes {main} [built]
[./src/howdy.png] 2.93 KiB {main} [built]
    + 3 hidden modules

그럼 이제 웹팩이 번들링한 결과물에는 어떻게 접근할까?

이때 compilercompilation이 필요하다. 이들의 역할을 이해하는 것이 웹팩 엔진을 확장하는 중요한 첫 걸음이다. 이 둘을 외, 중요한 것은 plugins API 문서를 보면된다.

compiler

compiler 모듈은 모든 옵션이 CLI 혹은 Node API를 통해 전달되는 compilation instance를 생성하는 메인 엔진이다.

compiler의 Hooks는 아래와 같다.

compiler.hooks.someHook.tap('MyPlugin', params => {
  /* ... */
})

hook 타입에 따라 tapAsynctapPromise가 될 수도 있다.

compilation

compilation 모듈은 compiler에 의해 사용되며, 새로운 compilations를 만들거나 build 할 때 사용한다.

compilation.hooks.someHook.tap(/* ... */)

compiler와 마찬가지로 hook 타입에 따라 tapAsynctapPromise를 사용할 수 있다.

tap, tapAsync, tapPromise

tap은 동기로 동작하는 것에 사용하며, tapAsynctapPromise는 이름에서도 알 수 있듯이 비동기를 처리할 때 사용한다.

tapAsync

class HelloAsyncPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync(
      'HelloAsyncPlugin',
      (compilation, callback) => {
        // Do something async...
        setTimeout(function() {
          console.log('Done with async work...')
          callback()
        }, 1000)
      }
    )
  }
}

module.exports = HelloAsyncPlugin

tapPromise

class HelloAsyncPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapPromise('HelloAsyncPlugin', compilation => {
      // return a Promise that resolves when we are done...
      return new Promise((resolve, reject) => {
        setTimeout(function() {
          console.log('Done with async work...')
          resolve()
        }, 1000)
      })
    })
  }
}

module.exports = HelloAsyncPlugin

인자로 받는 것이 약간 다르다.


다시 본론으로 돌아와, 번들링한 결과물을 보기 위해 compiler를 사용해보겠다.

class HelloWorldPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync(
      'HelloWorldPlugin',
      (compilation, callback) => {
        const source = compilation.assets['main.js'].source()
        console.log(source)
      }
    )
  }
}

제대로 번들링이 되었는지 확인하기 위해 npm run build를 하면 해독하기 힘든 형태의 텍스트가 터미널에 찍히는 걸 볼 수 있다.


자주 사용하는 플러그인

웹팩 자체에서 제공하는 플러그인 중에, 자주 사용되는 것이 몇 가지 있다.

BannerPlugin

배너플러그인은 build 된 것의 제일 상단에 작성한 내용을 보여준다.

webpack.config.js

const webpack = require('webpack');

module.exports = {
  ...,
  plugins: [new webpack.BannerPlugin({
    banner: `
      Build Date: ${new Date().toLocaleDateString()}
    `
  })],
}

이렇게 설정 한후, npm run build를 하면 '배너 플러그인'이 dist/main.js의 제일 상단에 있는 것을 볼 수 있다.

배너플러그인은 해당 파일의 작성 시간, 담당자 등 표기할 때 많이 사용한다.

DefinePlugin

DefinePlugin은 컴파일 시간에 전역 변수를 생성하게 해준다.

어플리케이션은 개발(development)환경과 운영(production)환경이 나뉘어서 운영 된다. 이때 사용하는 API 주소가 다를 수 있는데, 이를 DefinePlugin으로 정보를 저장할 수 있다.

현재 환경을 알기 위해서는 process.env.NODE_ENV로 접근하면 어떤 환경인지 알 수 있다.

webpack.config.js

module.exports = {
  ...,
  plugins: [new webpack.DefinePlugin({})],
}

이를 설정하고, 아무 .js에서 console.log(process.env.NODE_ENV)를 하고 npm run build 후, HTML 파일을 열어 확인해보면 'development'가 나오는 것을 확인할 수 있다.

환경에 따른 API를 변수로 저장하자면 아래와 같이 할 수 있다.

module.exports = {
  ...,
  plugins: new webpack.DefinePlugin({
    DEVELOPMENT_API: JSON.stringify('http://localhost:5000'),
    PRODUCTION_API: JSON.stringify('http://123.456')
  })],
}

그 후, 다시 console로 찍어보면 정상적으로 나오는 것을 알 수 있다.

이는 Node 런타임에서 process.env에 저장되는 환경 변수를 전역 변수로 등록해주는 EnvironmentPlugin이랑 자주 쓰인다.

CleanWebpackPlugin

CleanWebpackPlugin은 빌드 이전의 결과물을 제거해주는 플러그인이다.

$ npm install -D clean-webpack-plugin

webpack.config.js

const { CleanWebpackPlugin }= require('clean-webpack-plugin');

module.exports = {
  ...,
  plugin: [
    new CleanwebpackPlugin()
  ]
}

설정 후, npm run build를 하면 /dist 폴더에 있는 것이 없어지고 새로 build 되는 것을 확인할 수 있다.


이 외에도, 번들한 CSS, JS파일을 각각의 HTML 파일에 태그로 추가해주는 HtmlWebpackPlugin, , 번들 결과에서 CSS 코드만 뽑아 별도의 파일로 분리해주는 MiniCssExtractPlugin이 있다.


참고