webpack을 활용해 간단한 자바스크립트 프로젝트 번들링 해보기

2020. 10. 22

webpack (웹팩)

현재 웹 시장에서는 bundler(번들러)를 아주 유용하게 사용하고 있다. 대표적으로 webpack, parcel, rollup 등의 번들러들이 있는데, 그 많은 번들러들 중 웹팩은 가장 많이 사용되고 있으며 최고의 자리를 지키고 있는 번들러다.

최고의 자리를 지키는 이유에는 몇가지가 있는데 대표적인 이유는 바로 생태계가 넓으며, 광범위한 커스터마이징이 가능하다는 점이다.

가장 많이 사용되고있는 만큼 생태계는 그만큼 넓을 수 밖에 없고 plugin을 통해 특정 기능을 내가 원하는대로 커스터마이징 할 수 있다는 것이 가장 장점이라고 볼 수 있다.

bundle (번들)

bundle의 뜻이 무엇일까?

바로 묶음이다. 여기에 -er이 붙어주면 묶어주는 역할을 하는 bundler가 된다. 여기에 진행형이 붙는다면 bundling인 것이다. 이 역할을 webpack이라는 도구가 해주게 된다.

그렇다면 도대체 뭘 묶어주는걸까?

바로 브라우저에서 사용할 파일들이다. 이 파일들은 HTML, CSS, JS, 이미지 등이 포함되는데 이는 전부 HTTP 통신을 통해 요청되어 리소스를 가져오게 된다.

그렇다면 저 파일들이 적을 땐 크게 상관이 없지만 규모가 커져서 파일의 요청이 많아지게 된다면 통신량이 많아지게 되고, 이는 당연히 성능 저하의 큰 원인이 될 것이다.

또한 Common.js와 AMD등을 통해 모듈이라는 개념이 등장하면서 이후 지속적으로 JS의 규모와 중요성이 대두되었고 이 또한 함께 관리해주어야 할 대상이 된것이다... 😨

즉, 이렇게 많은 파일모듈을 한데 모아서 사용할 수 있도록 해주도록 도와주는 것이 번들러이며, 번들러는 위와 같은 현상을 해결해준다. 또한 이런 기능 외에 번들러별로 가지고 있는 기능들을 활용하여 더욱 편리하고 효율적인 번들링을 할 수 있게 해준다.

그래서 대표적인 웹팩을 통해 간단한 예제를 만들어보며 주요 기능에 대해 살펴보려고 한다.

프로젝트 생성

먼저 예제를 구현할 프로젝트를 생성한다.

npm init -y

package.json

{
  "name": "webpack-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

최초 프로젝트를 생성하면 위와 같이 생성된다.

이제 웹팩을 프로젝트에 설치해야한다.

webpack 설치

웹팩을 활용하기 위해선 webpackwebpack-cli 두 가지가 필요하다.

결과물에서 동작하는게 아닌 영향을 주는 빌드에 활용되는 친구들이니 dev로 추가한다.

 npm i --save-dev webpack@4.44.2 webpack-cli

웹팩이 5 버전이 추가됨에 따라 이전 버전인 4 버전으로 예제를 만들어 보았다.

설치가 완료되면 패키지에는 위 두 가지가 추가가 되어있을 것이다. 추가가 되었다면 루트 디렉토리에 webpack.config.js를 생성한다.

앞으로 알아볼 프로퍼티들은 전부 webpack.config.js에 추가되는 부분이니 이점을 참고하면 된다.

자 이제 설치한 웹팩을 본격적으로 사용해보자

Entry

entry는 웹팩이 파일을 읽기 시작하는 시작점이다. 이 파일을 시작으로 하여 번들링을 실행한다.

module.exports = {
  entry: './src/index.js',
}

src/index.js를 시작점으로 하여 번들링을 하겠다는 것을 의미한다.

이제 시작을 했으니 끝이 있어야한다.

Output

빌드가 되서 최종적으로 번들링 파일이 나오는 지점을 설정한다. 여기서는 pathfilename을 통해 경로와 파일명을 정할 수 있다.

const path = require('path')

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

dist란 폴더의 경로에 bundle.js라는 파일명으로 나온다는 의미이다.

시작과 끝 설정을 했으니 이제 진짜 파일이 나오는지 테스트를 해보자!

package.json

{
  "name": "webpack-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.44.2",
    "webpack-cli": "^4.1.0"
  }
}

scripts에 build라는 명령어를 만들고 webpack을 입력해준다.

build를 실행하게 되면 프로젝트에서는 webpack.config.js를 읽고 작성한대로 번들링을 해줄 것이다.

src/index.js

console.log('hello webpack!!')

그리고 src폴더와 index.js를 만들어 간단한 콘솔 하나를 찍어준다.

npm run build

위 명령어를 입력하고 잠깐 기다리면...!

webpack success

위와 같은 안내창이 뜨면서 성공했다는 희소식을 들려준다.

이제 정말 dist 폴더 내에 bundle.js라는 파일이 생성됐나 보면..!

dist

짜잔!!!! 진짜로 나왔다!

아까 index.js에서 작성했던 콘솔이 있었는데 정말 번들링 된 파일에 들어가있는지 확인해보자

dist/bundle.js

bundled

파일의 맨 끝에 보면 아까 작성했던 콘솔이 잘 들어가 있다.

추가적으로 웹팩이 제공하는 기능들을 더 살펴보자

Loader

Loader(로더)는 개발자가 사용하기 편하도록 만들어 놓은 기능인데, 하나의 모듈로 만들어 내는데에 기존에는 JS 파일들만 읽어내지만 특정 파일을 지정 그리고 특정 이펙트를 주어 해당되는 파일을 원하는데로 결과물에 반영할 수 있다.

즉, 내가 원하는 파일을 선정하여 번들러가 파일을 읽어낼 때 특정 이펙트를 주어 번들링에 반영된다는 것이다. 더 쉽게 말하자면 번들링의 과정에 영향을 미친다는 것이다.

대표적으로 CSS와 Babel이 있는데, 이 두 가지를 예시로 살펴보자

먼저 CSS를 반영하기 위해서는 CSS 파일이 있어야 하기 때문에 src 디렉토리에 index.css 파일을 생성한다.

src/index.css

.webpack {
  color: blue;
  font-size: 30px;
}

src/index.js

그리고 CSS 파일을 index.js에서 import 해준다.

import './index.css'

console.log('hello webpack!!')

이제 본격적으로 CSS관련된 로더를 사용하기 아래 두 가지를 설치한다.

npm i --save-dev css-loader style-loader

그리고 아래와 같이 작성한다.

const path = require('path')

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [{ test: /\.css$/, use: ['style-loader', 'css-loader'] }],
  },
}

module 내 rules 프로퍼티를 사용하는데 여기에는 배열로 2개 이상의 객체가 들어갈 수 있다.

이 객체에서는 testuse 프로퍼티가 나뉜다.

  • test: 규칙을 적용할 대상 (정규표현식 사용)
  • use: 특정 이펙트
  • exclude: 제외할 대상

위 두가지를 활용하면 번들링 할 때 CSS를 함께 읽을 수 있다. 이게 어떻게 반영되는지는 추후 plugin에서 알 수 있게 된다.

바벨(babel)은 ES6 이상의 자바스크립트 문법을 트랜스파일링(transpiling)을 통해 하위 브라우저에서도 읽을 수 있는 ES5 이하의 문법으로 바꾸어주는 트랜스파일러(transpiler)역할을 한다.

바벨의 유래를 간단하게 설명하자면, 성경에서 나오는 바빌로니아의 바벨탑을 건축하면서 하늘에 다가가며 한 가지의 말을 사용하고 있었는데 예수가 이를보고 분노하여 언어를 제각각으로 만들고 다 흩뜨려 놓았다고 하는 유래가 있다고 한다.

그래서 바벨의 의미는 현재 여러가지로 흩어져있는 문법을 하나로 통일해주는 역할을 한다고 보면 된다.

바벨을 사용하려면 아래 세 가지가 필요하다.

  • babel-loader: js 파일을 읽을 때 core를 참조하여 트랜스파일링
  • @bable/core: 바벨의 중요 자료들
  • @bable/preset-env: 트랜스파일링을 어떤 문법으로 할지 지정 (기본은 ES5)
npm install --save-dev babel-loader @babel/core @babel/preset-env

설치가 완료 되었다면 아래와 같이 작성한다.

const path = require('path')

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
}

대상은 js 파일을 대상으로 하며 node_modules에 있는 파일들은 따로 트랜스파일링 할 필요가 없으니 exclude 처리 해준다.

또한 로더는 바벨을 사용할 것이고 기본 preset(ES5)로 적용할 것이기 때문에 기존 그대로 가져와 사용해준다.

이렇게 되면 작성한 ES6가 빌드 후 전체 ES5 문법으로 변경되어 번들링된다.

plugin

플러그인은 여러가지 추가적인 기능들을 활용하여 번들링을 할 수 있는데요, 로더와 가장 큰 차이점으로는 로더는 번들링되는 과정에 대해 영향을 미치지만, 플러그인은 번들링된 결과에 대해 영향을 준다.

대표적으로 HtmlWebpackPlugin을 통해 살펴보자. 이는 HTML 파일을 번들링된 js 파일에 자동으로 적용 후 파일을 함께 생성해준다. 그러기 위해선 src 폴더에 index.html을 예시로 하나 생성한다.

src/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>webpack</title>
  </head>
  <body>
    <span class="webpack">webpack</span>
  </body>
</html>

먼저 외부 플러그인이기 때문에 설치를 해준다.

npm i --save html-webpack-plugin

설치를 완료했으면 아래와 같이 작성한다.

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
  plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
}

설치한 플러그인을 가져온 후 new 생성자를 통해 생성한다. 여러가지 프로퍼티가 있지만 template를 통해서 번들링 될 대상을 선택해준다.

그리고 다시 빌드를 통해 번들링을 하면 index.html 파일이 생성 되었을 것이다.

dist final

그럼 이제 저 index.html 을 실행하여 기존에 찍었던 콘솔이 브라우저에 나오는지 확인해보자

webpack browser

번들링 된 js 파일을 자동으로 적용해줘서 콘솔이 잘 나오는건 알겠는데... 갑자기 아까 파일을 생성하고 import 했던 CSS까지 적용이 되어있다. 왜 그럴까? 🤔

dist/bundle.js

webpack success

번들링 된 js 파일을 살펴보면, push 메소드를 통해 아까 선언한 css가 적용되어있는 것을 확인할 수 있다.

즉, style-loadercss-loader를 사용하면 로더를 통해 번들링 되는 과정에서 HTML 파일에 스타일이 직접 작성 된다는 것을 알 수 있다.

이 방법 외에 MiniCssExtractPlugin을 통해 CSS 파일을 만드는 방법도 물론 존재하니 필요에 의해서 커스터마이징을 해도 된다.

지금까지 완전 간단한 예제를 만들어 보며 웹팩의 핵심기능을 통해 번들링을 해보았는데, 이보다 훨씬 더 많은 기능들이 있으니 개발자의 필요에 따라 커스터마이징 하며 다루면 된다. 🛠