하루에 한 문제

웹팩 플러그인 본문

Dev/JavaScript

웹팩 플러그인

dkwjdi 2021. 4. 27. 21:13

플러그인의 역할

로더가 파일 단위로 처리하는 반면 플러그인은 번들된 결과물을 처리한다.

번들된 자바스크립트 코드를 난독화 한다거나 특정 텍스트를 추출하는 용도로 사용한다.

 

 

커스텀 플러그인 만들기

myplugin.js

class MyPlugin {
  apply(compiler) {
    compiler.hooks.done.tap("My Plugin", stats => {
      console.log("MyPlugin: done")
    })
  }
}

module.exports = MyPlugin

로더와 다르게 플러그인은 클래스로 제작한다. apply 함수를 구현하면 되는데 이 코드에서는 인자로 받은 compiler 객체 안에 있는 tap() 함수를 사용하는 코드다. 플러그인 작업이 완료되는(done) 시점에 로그를 찍는 코드인것 같다.

 

플러그인을 웹팩 설정에 추가한다.

 

webpack.config.js

const MyPlugin = require("./myplugin")

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

웹팩 설정 객체의 plugins 배열에 설정한다.

클래스로 제공되는 플러그인의 생성자 함수를 실행해서 넘기는 방식이다.

 

웹팩으로 빌드해 보자.

  • 로그가 찍힌걸 보니 플러그인이 동작했다.
  • 그런데 파일이 여러 개인데 로그는 한 번만 찍혔다.
  • 즉 플러그인은 하나의 번들링된 결과물을 대상으로 동작한다!!!!

 

 

myplugin.js

class MyWebpackPlugin {
  apply(compiler) {
    compiler.plugin('emit', (compilation, callback) => {
      const source = compilation.assets['main.js'].source();
      compilation.assets['main.js'].source = () => {
        const banner = [
          '/**',
          ' * 이것은 BannerPlugin이 처리한 결과입니다.',
          ' * Build Date: 2021-04-27',
          '      */'
        ].join('\n');
        return banner + '\n' + source;
      }

      callback();
    })
  }
}
  
module.exports = MyWebpackPlugin;

 

배너 문자열과 기존 소스 코드를 합친 문자열을 반환하게 source()를 재정의했다.

빌드하고 결과물을 확인해보면 다음과 같다.

 

자주 사용하는 플러그인

 

1. BannerPlugin

MyPlugin와 비슷한 것이 BannerPlugin이다. 결과물에 빌드 정보나 커밋 버전같은 것을 추가할 수 있다.

 

webpack.config.js

const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.BannerPlugin({
      banner: '이것은 배너다',
    })
  ]
  • 생성자 함수에 전달하는 옵션 객체의 banner 속성에 문자열을 전달한다.
  • 웹팩 컴파일 타임에 얻을 수 있는 정보, 가령 빌드 시간이나 커밋정보를 전달하기위해 함수로 전달할 수도 있다.

 

 

2.DefinePlugin

어플리케이션은 개발환경과 운영환경으로 나눠서 운영한다. 가령 환경에 따라 API 서버 주소가 다를 수 있다. 같은 소스 코드를 두 환경에 배포하기 위해서는 이러한 환경 의존적인 정보를 소스가 아닌 곳에서 관리하는 것이 좋다. 배포할 때마다 코드를 수정하는 것은 곤란하기 때문이다.

 

webpack.config.js

const webpack = require("webpack")

export default {
  plugins: [new webpack.DefinePlugin({})],
}
  • 빈 객체를 전달해도 기본적으로 넣어주는 값이 있다.
  • 노드 환경정보인 process.env.NODE_ENV 인데 웹팩 설정의 mode에 설정한 값이 여기에 들어간다.
  • "development"를 설정했기 때문에 어플리케이션 코드에서 process.env.NODEENV 변수로 접근하면 "development" 값을 얻을 수 있다.

app.js

console.log(process.env.NODE_ENV) // "development"

이 외에도 웹팩 컴파일 시간에 결정되는 값을 전역 상수 문자열로 어플리케이션에 주입할 수 있다.

 

new webpack.DefinePlugin({
  TWO: "1+1",
})

TWO라는 전역 변수 1+1 이란 코드 조각을 넣었다. 실제 어플리케이션 코드에서 이것을 출력해보면 2가 나올 것이다.

(하지만 Window객체에 저장되는것은 아니다!)

 

app.js

console.log(TWO) // 2

코드가 아닌 값을 입력하려면 JSON.stringify()를 통해 문자열화 한 뒤 넘긴다.

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"),
})

appjs

console.log(VERSION) // 'v.1.2.3'
console.log(PRODUCTION) // true
console.log(MAX_COUNT) // 999
console.log(api.domain) // 'http://dev.api.domain.com'

 

3.HtmlWebpackPlugin

공부할 때 계속해서 HttpWebpackPlugin이라고 오타를 내서 좀 기억에 남는 플러그인이다;

이번엔 써드 파티 패키지에 대해 알아보자. HtmlWebpackPluginHTML 파일을 후처리하는데 사용한다.

빌드 타임의 값을 넣거나 코드를 압축할수 있다.

 

우선 패키지를 다운한다.

npm install -D html-webpack-plugin

이 플러그인으로 빌드하면 HTML파일로 아웃풋에 생성될 것이다. index.html 파일을 src/index.html로 옮긴뒤 다음과 같이 작성해 보자.

 

src/index.html

<!DOCTYPE html>
<html>
  <head>
    <title>타이틀<%= env %></title>
  </head>
  <body>
    <!-- 로딩 스크립트 제거 -->
    <!-- <script src="dist/main.js"></script> -->
  </body>
</html>
  • 타이틀 부분에 ejs 문법을 이용하는데 <%= env %> 는 전달받은 env 변수 값을 출력한다.
  • HtmlWebpackPlugin은 이 변수에 데이터를 주입시켜 동적으로 HTML 코드를 생성한다.
  • 뿐만 아니라 웹팩으로 빌드한 결과물을 자동으로 로딩하는 코드를 주입해 준다.
  • 때문에 스크립트 로딩 코드도 제거했다. 

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' ? '(개발용)' : '',
      },
    })
  ]
}
  • 환경 변수에 따라 타이틀 명 뒤에 "(개발용)" 문자열을 붙이거나 떼거나 하도록 했다.
  • NODEENV=development 로 설정해서 빌드하면 빌드결과 "타이틀(개발용)"으로 나온다.
  • NODEENV=production 으로 설정해서 빌드하면 빌드결과 "타이틀"로 나온다.

 

개발 환경과 달리 운영 환경에서는 파일을 압축하고 불필요한 주석을 제거하는 것이 좋다.

 

webpack.config.js

new HtmlWebpackPlugin({
  minify: process.env.NODE_ENV === 'production' ? {
    collapseWhitespace: true, // 빈칸 제거
    removeComments: true, // 주석 제거
  } : false,
}
  • 환경변수에 따라 minify 옵션을 켰다.
  • NODE_ENV=production npm run build로 빌드하면 아래처럼 코드가 압축된다. 물론 주석도 제거 되었다.
  • window는  npm install --save-dev cross-env 를 설치한 뒤 아래의 그림처럼 script를 고친다.

 

 

정적파일을 배포하면 즉각 브라우져에 반영되지 않는 경우가 있다.

그래서 필자는 프로젝트 하면서 "이거 고쳤는데 왜 반영이 안되지??" 라는 말을 많이했었고, 친구가 "캐시삭제해"라고 해줘서 반영된 결과를 확인한적이 있다.

이렇게 브라우져 캐쉬가 원인일 경우가 있는데 이를 위한 예방 옵션도 있다.

webpack.config

new HtmlWebpackPlugin({
  hash: true, // 정적 파일을 불러올때 쿼리문자열에 웹팩 해쉬값을 추가한다
})

hash: true 옵션을 추가하면 빌드할 시 생성하는 해쉬값을 정적파일 로딩 주소의 쿼리 문자열로 붙여서 HTML을 생성한다.

 

4. CleanWebpackPlugin

CleanWebpackPlugin빌드 이전 결과물을 제거하는 플러그인이다. 빌드 결과물은 아웃풋 경로에 모이는데 과거 파일이 남아 있을수 있다. 이전 빌드내용이 덮여 씌여지면 상관없지만 그렇지 않으면 아웃풋 폴더에 여전히 남아 있을 수 있다.

 

임시로 아웃풋 폴더에 foo.js 파일을 만든 후 다시 빌드해 보자...... 파일이 남아 있다

이러한 현상을 CleanWebpackPlugin으로 해결해 보자. 먼저 패키지를 설치한다.

 npm install -D clean-webpack-plugin

웹팩 설정을 추가한다.

 

webpack.config.js

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

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

빌드 결과 foo.js가 깨끗히 사라졌다. 아웃풋 폴더인 dist 폴더가 모두 삭제된후 결과물이 생성되었기 때문이다.

 

5. MiniCssExtractPlugin

  • 스타일시트가 점점 많아지면 하나의 자바스크립트 결과물로 만드는 것이 부담일 수 있다.
  • 번들 결과에서 스트일시트 코드만 뽑아서 별도의 CSS 파일로 만들어 역할에 따라 파일을 분리하는 것이 좋다.
  • 브라우져에서 큰 파일 하나를 내려받는 것 보다, 여러 개의 작은 파일을 동시에 다운로드하는 것이 더 빠르다.

 

개발 환경에서는 CSS를 하나의 모듈로 처리해도 상관없지만 프로덕션 환경에서는 분리하는 것이 효과적이다. MiniCssExtractPlugin은 CSS를 별로 파일로 뽑아내는 플러그인이다.

 

 

먼저 패키지를 설치한다.

npm install -D mini-css-extract-plugin

웹팩 설정을 추가한다.

 

webpack.config.js

const MiniCssExtractPlugin = require("mini-css-extract-plugin")

module.exports = {
  plugins: [
    ...(process.env.NODE_ENV === "production"
      ? [new MiniCssExtractPlugin({ filename: `[name].css` })]
      : []),
  ],
}
  • 프로덕션 환경일 경우만 이 플러그인을 추가했다. 
  • filename에 설정한 값으로 아웃풋 경로에 CSS 파일이 생성될 것이다.
  • 개발 환경에서는 css-loader에의해 자바스크립트 모듈로 변경된 스타일시트를 적용하기위해 style-loader를 사용했다.
  • 반면 프로덕션 환경에서는 별도의 CSS 파일으로 추출하는 플러그인을 적용했으므로 다른 로더가 필요하다.
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          process.env.NODE_ENV === "production"
            ? MiniCssExtractPlugin.loader // 프로덕션 환경
            : "style-loader", // 개발 환경
          "css-loader",
        ],
      },
    ],
  },
}

플러그인에서 제공하는 MiniCssExtractPlugin.loader 로더를 추가한다.

NODE_ENV=production npm run build로 결과를 확인해보자.

dist/main.css가 생성되었고 index.html에 이 파일을 로딩하는 코드가 추가되었다.

 

 

 

 

ECMAScript2015 이전에는 모듈을 만들기 위해 즉시실행함수와 네임스페이스 패턴을 사용했다. 이후 각 커뮤니티에서 모듈 시스템 스펙이 나왔고 웹팩은 ECMAScript2015 모듈시스템을 쉽게 사용하도록 돕는 역할을 한다.

엔트리포인트를 시작으로 연결되어 었는 모든 모듈을 하나로 합쳐서 결과물을 만드는 것이 웹팩의 역할이다. 자바스크립트 모듈 뿐만 아니라 스타일시트, 이미지 파일까지도 모듈로 제공해 주기 때문에 일관적으로 개발할 수 있다.

 

참고

jeonghwan-kim.github.io/series/2019/12/10/frontend-dev-env-webpack-basic.html#51-%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8%EC%9D%98-%EC%97%AD%ED%95%A0

'Dev > JavaScript' 카테고리의 다른 글

스코프 (ECMAScript 2015 )  (0) 2021.04.28
바벨 (babel)  (1) 2021.04.28
웹팩 로더  (0) 2021.04.27
프론트엔드 개발에 Node.js가 필요한 이유  (1) 2021.04.26
자바스크립트 런타임 : 콜스택과 메모리 힙  (0) 2021.04.20
Comments