Webpack 시작하기

이재윤·2021년 6월 6일
1

front-end

목록 보기
2/3

본 글은 Getting Started With Webpack을 번역한 글 입니다. 오역이 있을 수 있습니다.

💻 개요

모듈화를 통해 명명 충돌(namespace collisions)를 피할 수 있고, 유지보수가 용이한 코드를 작성할 수 있다. 하지만 JavaScript가 실행되는 웹 브라우저에서는 모듈을 사용할 수가 없다.

이러한 문제를 해결하기 위해서, Webpack, Parcel, Rollup과 같은 module bundler들이 브라우저에서 번들(bundle)들을 최적화하기 위해 만들어 졌다.

💻 코드를 '번들(bundle)'하다의 의미는?

코드를 번들링(Bundling code)하는 것의 의미는 여러개의 모듈들을 하나 이상의 배포가능한 번들들(production-ready bundles)로 묶고 최적화하는 것을 의미한다. 이 글에서 언급되는 '번들'은 번들링 과정(bundling process)의 최종 결과물이라고 이해하면 쉬울 것 이다.

💻 Webpack 이란?

Webpack은 확장성이 높고 설정가능한 정적 모듈 번들러(static module bundler) 이다. 높은 확장성으로, 목적에 맞는 외부 로더(loader)나 플러그인(plugin)들을 적용시킬 수 있다.

아래의 그림과 같이, webpack은 root 파일(root entry point) 부터 살펴보며, root 파일과 직,간접적으로 연관된 종속성으로 구성된 의존성 그래프(dependency graph)를 작성한 후 결합된 모듈들의 최적화된 번들들을 만든다.

Webpack의 동작 방식을 이해하기 위해서, 몇가지 용어를 이해할 필요가 있다(Webpack Glossary). 이 용어들은 글에서 자주 언급되며, webpack 공식문서에도 자주 언급된다.

  • Chunk
    Chunk는 모듈들로 부터 나온 코드이다. 이 코드는 chunk file에 저장된다. Chunk들은 보통 코드 스플리팅(code-spliting)에 사용된다.
  • Modules
    Module들은 특정한 기능을 수행하기 위해 import한 애플리케이션의 부분(broken-down parts)이다. Webpack은 ES6, CommonJS 구문을 이용하여 모듈 생성을 지원한다.
  • Assets
    Asset은 webpack과 다른 번들러에도 자주 사용되는 용어이다. Asset은 빌드 과정(build process)동안 번들링된 정적 파일(static file) 들을 의미한다. 이 파일들은 이미지부터 폰트나, 비디오 파일과 같이 무엇이든 될 수 있다. 글을 읽어갈 수록, 다른 종류의 asset 파일들을 다루기 위해 로더(loader)들을 어떻게 사용하는지를 알 수 있다.

💻 Webpack Configuration Files

❗ 사전에 webpack-cli을 설치해야 한다.

터미널에서 webpack-cli를 사용하는 대신에, configuration 파일을 통해 프로젝트 내에서 webpack을 사용할 수 있다. 하지만 webpack 최신버전에서는, configuration 파일 없이도 프로젝트 내에서 webpack을 사용할 수 있다. package.json파일의 명령어 중 하나로 webpack을 사용할 수 있다. 이 방법에서 webpack은 프로젝트의 진입파일(entry point file)이 src디렉토리에 있는 것으로 간주한다. 진입파일을 번들링한 결과물을 dist디렉토리에 위치시킨다.

예시 package.json파일은 아래와 같다. Webpack을 configuration file없이 사용했다.

{
  "name": "webpack-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "webpack": "^5.38.1"
  },
  "devDependencies": {
    "webpack-cli": "^4.7.0"
  }
}

위 파일의 build 명령을 실행하면, webpack은 src/index.js파일을 번들링해 결과물main.js를 만들어 dist디렉토리에 위치시킨다'. 하지만 webpack은 훨씬 유연하다.--config 플래그로 configuration파일을 수정함으로써, 진입점(entry point)을 바꿀 수 있고, 결과물을 위치시키는 지점(output point)을 조정할 수도 있으며, 여러가지 기본값들을 변경할 수 있다.

아래의 예시는 위 package.json파일의 build명령을 변경한 것이다.

"build": "webpack --config webpack.config.js"

위에서 --config 플래그를 추가했으며 새로운 webpack 설정을 갖는 webpack.config.js를 지정했다.

webpack.config.js는 아직 존재하지 않는다. 따라서 프로젝트 디렉토리에 만들어 주어야 하며 아래의 코드를 파일에 붙여넣어야 한다.

// webpack.config.js

const path = require("path")

module.exports = {
  entry : "./src/entry",
  output : {
    path: path.resolve(__dirname, "dist"),
    filename: "output.js"
  }
}

위 파일은 여전히 JavaScript파일을 번들링하기 위한 webpack의 설정이지만, webpack의 기본 경로가 아닌 사용자 지정 진입(entry), 결과(output) 파일의 경로를 지정했다.

Webpack configuration 파일에 대해 몇가지 알아야 할 사항:

  • Webpack configuration 파일은 CommonJS module로 작성된 JavaScript 파일이다.

  • Webpack configuration 파일은 몇가지 프로퍼티를 가진 객체를 export 한다. 각각의 프로퍼티들은 코드를 번들링할 때 webpack을 구성하는 옵션으로 사용된다.

    • mode
      설정에서, 이 옵션은 번들링동안 NODE_ENV를 지정하기 위해 사용된다. production이나 development를 값으로 가질 수 있다. 값이 지정되지 않았다면, 기본값인 none으로 설정된다. webpack이 mode에 따라 asset들을 다르게 번들링하는 것은 중요하다. 예를들어, 개발 모드에서 webpack은 자동으로 번들들을 캐싱해 번들링 시간을 줄이고 최적화 한다. 자세한 사항은 webpack 공식문서의 mode section 부분에 나와있다.

💻 Webpack Concepts

Webpack CLI나 configuration 파일을 통해서 webpack을 구성할 때, 옵션으로 적용되는 4가지의 주요 개념(concept)들이 있다. 이 글의 다음 부분에서는 이 개념들에 대해 살펴볼것이며, 데모 프로젝트에 적용해 볼 것이다.

아래에 설명된 개념들이 다른 모듈 번들러들과 몇 가지 유사점을 공유한다는 점에 주목할 필요가 있다. 예를 들어 Rollup의 configuration 파일의 경우, 의존성 그래프(dependency graph)에 진입점(entry point)를 지정하기 위해 input 필드를 지정하며, output 객체는 어떤 방식으로 chunk들을 만들고 만들어진 chunk들을 어디에 위치시킬지 정한다. plugins 객체 또한 외부 플러그인들을 추가하기 위해 존재한다.

🎁 ENTRY

Configuration 파일에서 entry 필드는 의존성 그래프(dependency graph)를 빌드하는 시작점의 파일 경로를 포함한다. 이 입력 파일(entry file)에서 웹 팩은 진입점에 직접 또는 간접적으로 의존하는 다른 모듈로 이동한다.

설정의 진입점(entry point)는 단일 파일명을 가진 single entry 유형일 수 있다. 예시는 아래와 같다.

// webpack.configuration.js

module.exports = {
  mode:  "development",
  entry : "./src/entry" 
}

진입점(entry point)은 여러 진입 파일의 경로들을 포함하는 배열을 가진 multi-main entry 유형일 수도 있다. 예시는 아래와 같다.

// webpack.configuration.js

const webpack = require("webpack")

module.exports = {
  mode: "development",
  entry: ['./src/entry', './src/entry2']
}
🎁 OUTPUT

이름 그대로, 설정의 output 필드는 만들어진 번들이 어디에 위치할지를 결정한다. 이 필드는 여러 모듈을 배치한 경우에 유용하다. Webpack이 만든이름을 사용하는 대신, 사용자가 파일이름을 지정할 수 있다.

// webpack.configuration.js
const webpack = require("webpack")
const path = require("path")

module.exports = {
  mode: "development",
  entry: "./src/entry",
  output: {
    filename: "webpack-ouput.js",
    path: path.resolve(__dirname, "dist"),
  }
}
🎁 LOADERS

기본적으로, webpack은 애플리케이션의 JavaScript 파일들만 이해할 수 있다. 하지만, webpack은 모듈로 import된 모든 파일들을 의존성(dependency)으로 간주하며, 그것들을 의존성 그래프(dependency graph)에 추가한다. 이미지, CSS, JSON 이나 CSV에 저장된 데이터들과 같은 정적 자원(static resource)들을 처리하기 위해서, webpack은 loader사용하여 이런 파일들을 번들에 "로드(load)" 한다.

로더는 ES 코드 트랜스파일링(transpiling)부터 스타일처리, ESLint를 이용한 코드 린팅(linting)까지 다양한 것들을 할 수 있을만큼 유연하다.

애플리케이션에 로더를 적용하는 세가지 방법이 있다. 그중 한가지는 파일에 직접 import하는 방법이다. 예를 들어, 이미지 크기를 줄이기 위해서 아래의 예시처럼 image-loader 로더를 파일에 직접 사용할 수 있다.

// main.js
import ImageLoader from 'image-loader'

로더를 사용하는 또다른 방법은 webpack configuration 파일을 사용하는 것이다. 이 방법을 사용하면, 로더를 적용하고 싶은 파일 종류를 지정할 수 있는 등 로더를 이용하여 많은 것을 할 수 있다. 이 방법은 rules 배열을 만들고 객체에 로더를 지정한다. 각 객체는 로더를 적용할 asset들과 일치하는 정규식을 가진 테스트 필드를 가지고 있다.

예를 들어, 이전 예시에서 직접 import 했던 image-loader공식문서에 나온 기본 옵션을 webpack 설정파일에 적용해 사용할 수 있다. 예시는 아래와 같다.

// webpack.config.js

const webpack = require("webpack")
const path = require("path")

module.exports = {
  mode: "development",
  entry: "./src/entry",
  output: {
    filename: "webpack-ouput.js",
    path: path.resolve(__dirname, "dist")
  },
  module: {
    rules: [
      {
        test: /\.(jpe?g|png|gif|svg)$/,
        use: [
          'img-loader'
        ]
      }
    ]
  }
}

위의 예시에 image-loader를 포함하고 있는 객체의 test 필드를 주목해라. jp(e)g, png, gif 및 svg 형식의 모든 이미지 파일과 일치하는 정규식을 찾을 수 있다.

로더를 사용하는 마지막 방법은 CLI 환경에서 --module-bind 플래그를 이용하는 것이다.

Awesome-webpack의 README는 webpack과 함께 사용할 수 있는 로더들의 목록이 나와있고, 로더들은 수행하는 작업의 유형으로 그룹화되어 있다.

  • Responsive-loader
    반응형 사이트나 앱에 맞는 이미지를 추가하고자 할때 유용한 로더이다. 이 로더는 하나의 이미지로부터 여러 크기의 이미지들을 만들고, 적절한 화면크기에 사용할 이미지와 일치하는 srcset을 반환한다.

  • Babel-loader
    최신 ECMA문법을 ES5로 트랜스파일링 하기위해 사용된다.

  • GraphQL-loader
    만약 GraphQL 사용자(enthusiast)라면, GraphQL 스키마(schema), 쿼리(queries)와 뮤테이션(mutations)을 포함하는 .graphql 파일을 로드할때, 이 로더가 유용할 것이다.

🎁 PLUGINS

플러그인을 사용하면 webpack 컴파일러가 번들 모듈에서 생성된 chunk들에 대한 작업을 수행할 수 있다. webpack은 task runner가 아니지만, 플러그인을 이용하면 코드가 번들링될때 로더가 하지 못하는 사용자 지정작업을 할 수 있다.

Webpack 플러그인의 예로는 webpack에 내장된 ProgressPlugin이 있다. 이 플러그인은 컴파일동안 사용자 지정 진행과정을 콘솔에 출력하는 기능을 제공한다.

const webpack = require("webpack")
const path = require("path")

module.exports = {
  mode: "development",
  entry: "./src/entry",
  output: {
    filename: "webpack-output.js",
    path: path.resolve(__dirname, "dist")
  },
  module: {
    rules: [
      {
        test: /\.(jpe?g|png|gif|svg)$/i,
        use: [
          "img-loader"
        ]
      }
    ]
  },
  plugins: [
    new webpack.ProgressPlugin({
      handler: (percentage, message) => {
      	console.info(percentage, message)
      }
    })
  ]
}

컴파일이 진행되는 동안 진행율과 메시지를 출력하는 handler 함수(handler function) 을 추가했다.

아래는 awesome-webpack README에 나와있는 유용한 몇가지 플러그인이다.

  • Offline-plugin
    이 플러그인은 service workersAppCache를 이용하여 webpack관리 프로젝트의 오프라인 환경을 제공한다.

  • Purgecss-webpack-plugin
    이 플러그인은 컴파일 중에 응용프로그램 내에서 사용되지 않는 CSS를 제거하므로 webpack 프로젝트를 최적화하는 데 유용하다.

지금까지, 상대적으로 작은 애플리케이션을 위한 webpack 설정을 살펴보았다. 애플리케이션에서 webpack을 이용해 특정 작업을 수행하는 방법을 알아볼 것이다.

💻 다중환경(Multiple Environments) 다루기

애플리케이션에서, 개발이나 배포환경에서 webpack 설정을 다르게 할 필요가 있다. 예를들어, 배포환경의 지속적인 통합 파이프라인에서 새로운 배포를 할때마다 webpack이 사소한 경고 메시지를 출력하길 원치 않을 수도 있다.

webpack과 커뮤니티에서 권장한 것처럼 이를 달성하기 위한 몇가지 방법이 있다. 첫 번째 방법은 객체를 리턴하는 함수를 export하도록 설정파일을 변경 하는 것이다. 이 방법에서, webpack 컴파일러에 의해 현재 환경이 함수의 첫번째 파라미터로 전달되고, 다른 옵션들이 두 번째 옵션에 전달된다.

이 방법은 현재 환경에 따라 다르게 처리하고 싶은 몇가지 작업이 있을때 유용하다. 하지만 복잡한 설정들을 가지고 있는 규모가 큰 애플리케이션의 경우, 설정들은 결국 많은 조건문들로 구성될 것이다.

아래의 코드는 function을 사용하여 동일한 파일에서 productiondevelopment 환경을 다루는 방법의 예시를 보여준다.

//webpack.config.js

module.exports = function (env, args) {
  return {
    mode: env.production ? 'production' : 'development',
    entry: './src/entry',
    output: {
      filename: 'webpack-output.js',
      path: path.resolve(__dirname, 'dist')
    },
    plugins: [
      env.development && (
        new webpack.ProgressPlugin({
          handler: (percentage, message) => {
            console.log(percentage, message)
          }
        })
      )
    ]
  }
}

위의 코드에서 export된 함수를 살펴보면, 함수에 전달된 env 파라미터가 값을 바꾸기위해 삼항연산자와 어떻게 사용되었는지를 확인해 볼 수 있다. 첫 번째로 webpack의 모드를 바꾸기위해 사용되었고, 그 다음으로는 개발모드에서만 ProgressPlugin을 사용하기 위해 사용되었다.

producitondevelopment 환경을 다루는 다른 세련된 방법은 두 환경을 위한 다른 설정 파일을 만드는 것이다. 파일들을 만들면, 애플리케이션을 번들링할때 package.json에서 다른 명령어로 파일들을 사용할 수 있다.
예시는 아래와 같다:

{
  "name": "webpack-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "bundle:dev": "webpack --config webpack.dev.config.js",
    "bundle:prod": "webpack --config webpack.prod.config.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "webpack": "^5.38.1"
  },
  "devDependencies": {
    "img-loader": "^4.0.0",
    "webpack-cli": "^4.7.0"
  }
}

위의 package.json파일에는 두 개의 스크립트 명령이 있으며, 각각 다른 설정 파일을 사용하여 애플리케이션 asset들을 번들로 묶을 때 특정 환경을 처리한다. npm run bundle:dev 명령을 사용해 개발 모드에서 애플리케이션을 번들링 하거나, npm run bundle:prod 명령을 사용해 배포 모드에서 번들링 할 수 있다.

두 번째 방법을 사용하면, 함수로 부터 반환된 설정 객체에 조건문들을 넣지않아도 된다. 하지만 여러개의 설정 파일들을 관리해야 한다.

💻 Configuration File 쪼개기

지금까지 우리의 webpack 설정파일은 38줄의 코드들로 이루어져 있습니다. 이는 단일 loader와 plugin을 갖는 애플리케이션에 적절하다.

하지만 큰 규모의 애플리케이션의 경우, webpack 설정 파일은 다수의 loader들과 plugin들을 갖기 때문에 길어질 수 있다. 설정 파일을 깨끗하고 가독성있게 유지하기 위해서, 여러파일에 걸쳐 작은 객체로 나누고 webpack-merge 패키지를 이용해 설정 객체들을 하나의 파일로 합칠 수 있다.

우리의 webpack 프로젝트에 적용하기 위해, 하나의 설정 파일을 각각 loader, plugin, 두 개의 파일이 하나로 합쳐질 기본 설정을 갖는 파일로 나눠야 한다.

webpack.plugin.config.js 파일을 만들고, 아래의 코드를 붙여 넣어라.

// webpack.plugin.config.js
const webpack = require('webpack')

const plugin = [
	new webpack.ProgressPlugin({
      handler: (percentage, message) => {
      	console.info(percentage, message);
      }
    })
]

module.exports = plugin

위의 파일은 webpack.configuration.js 파일로부터 분리해낸 하나의 플러그인이다.
다음으로 webpack loader를 위한 webpack.loader.config.js 파일을 만들어라.

const loader = {
	module: {
    	rules: [
          {
          	test: /\.(jpe?g|png|gif|svg)$/i,
            use: [
              'img-loader'
            ]
          }
        ]
    }
}

위 코드에서 img-loader를 분리된 파일로 옮겼다.

마지막으로 기본적인 인풋, 아웃풋 설정과 위에서 만든 두개의 파일이 들어갈 webpack.base.config.js 파일을 만들어라.

// webpack.base.config.js
const path = require('path')
const merge = require('webpack-merge')

const plugins = require('./webpack.plugin.config')
const loaders = require('./webpack.loader.config')

const config = merge(loader, plugins, {
  mode: 'development',
  entry: './src/entry',
  output: {
  	filename: 'webpack-output.js',
    path: path.resolve(__dirname, 'dist')
  }
})

module.exports = config

위 파일을 살펴보면, 처음의 webpack.config.js와 비교했을때 간결해진 것을 확인할 수 있다. 이제 설정파일의 중요한 세 부분은 작은 파일들로 분리가 되었으며, 따로 관리할 수 있다.

💻 빌드 파일 최적화 (Optimizing Large Builds)

애플리케이션을 계속 개발하면서, 기능적인 측면에서나 크기측면에서나 점점 커질 것이다. 이렇게 되면, 새로운 파일들이 생성되며, 예전 파일들은 변경될 것이고, 외부 패키지들이 설치된다. 그리고 이는 webpack이 생성하는 번들의 크기를 키우는 결과를 초래한다.

기본적으로, webpack은 자동으로 사용자를 대신해 mode가production으로 설정되어 있다면, 번들의 크기를 최적화한다. 예를 들어, webpack(webpack 4+)이 번들의 크기를 최적화하고 줄이기 위해 사용하는 기술은 Tree-Shaking이다. 이 기술은 사용하지 않는 코드를 제거하는데 사용된다. 번들링 중 간단한 단계에서, import와 export문은 번들로 부터 사용하지 않는 모듈들을 제거하기전에 탐지하는데 사용된다.

optimazation 객체를 추가함으로써 수동으로 번들을 최적화 할 수 도 있다. Webpack 공식문서의 optimization section 부분은 optimazation 의 필드들이 나와있다. 여러개의 필드중 한가지를 살펴보자

  • minimize
    이 boolean 필드는 webpack이 번들 크기를 최소화하는데 사용된다. 기본적으로 webpack은 함께 제공되는 TerserPlugin을 이용해 크기를 최소화한다.

minimizer 배열 필드를 이용해 다른 최소화 도구를 사용할 수 있다. 아래는 Uglifyjs-webpack-plugin를 사용한 예시이다.

// webpack.config.js
const Uglify = require('uglifyjs-webpack-plugin')

module.export = {
	optimization: {
    	minimize: true,
      	minimizer: [
          new Uglify({
            cache: true,
            test: /\.js(\?.*)?$/
          })
        ] 
    }
}

위의 uglifyjs-webpack-plugin은 두개의 중요한 옵션이 사용되었다. 첫 번째로 cache를 가능하게 한다는 것의 의미는 이미 존재하는 파일들에게 새로운 변화가 있을때만 최소화한다는 것이다. 두 번째로, test옵션은 최소화하고 싶은 파일들을 특정할 수 있다.

0개의 댓글