본문 바로가기
JavaScript

웹팩(Webpack) 이해하기

by Vintz 2021. 11. 16.
반응형

처음 리액트를 배울 때 이런 생각이 들었다.

 

"이렇게까지 어려운 문법을 배우면서 굳이 써야하나? 내 개인 프로젝트에 쓰일 정도로 편하긴 한가?"

 

반은 억지로 하다보니 따라치기 수준의 학습밖에 하지 못했다. 그러다 Vanilla JS로 SPA 만들기같은 과제를 두 번이나 하게 되면서 리액트를 쓰는 이유를 알게 되었다. 아니, 좋아하게 되었다. 리액트에선 많은 것들이 추상화 되어 있었고 굉장히 편리한 도구였다. 이제는 "고작 이 문법으로 이렇게나 많은 것들을 할 수 있다니"로 생각이 바뀌게 되었다.

 

그렇다면 CRA에서 기본으로 깔려있는 이 웹팩도 알아볼 필요가 있다. 분명 이유가 있을 것이다. 내 평일은 물론이고 주말까지 시간을 순삭하게 만든 웹팩을 한번 살펴보자.

✏️ 웹팩을 배우면서 제가 개인적으로 느꼈던 필요성과 사용법 그리고 리액트의 생각 정리도 담겨져 있습니다. 웹팩의 탄생 배경과 깊이 있는 설명을 원한다면 이 글을 추천드립니다.

웹팩(Webpack)이란?

웹팩은 여러개의 파일을 하나의 파일로 합쳐주는 번들러(bundler)다. 

 

하나의 시작점(entry point)을 기준으로 의존적인 모듈들을 전부 찾아내서 결과물을 생성한다. 근데 이게 왜 필요할까?

https://webpack.kr/

개인 프로젝트만 하더라도 만드는데 굉장히 많은 자원들이 쓰인다. .html, .js, .css 등 이러한 파일들의 수가 많다. 특히나 모든 렌더링을 브라우저에게 위임하는 CSR의 경우, 뷰(컴포넌트)별로 나누기 때문에 그 파일 수가 더 많을 것이다. 이렇게 파일이 많으면 요청수도 많아지고, 그만큼 사용자에게 보여지는 시간이나 서비스를 이용할 수 있는 시간이 길어질 수 밖에 없다.

 

이런 문제를 해결하기 위한 노력, 즉 브라우저에서 서버로 요청하는 파일 숫자를 줄여주는 노력을 웹팩이 해줄 수 있다.

서로 의존적인 모듈(자원)들을 각각 요청하는 것에서
이렇게 하나의 모듈로 만들어 압축도 해주고, 요청수를 줄일 수 있다

뿐만 아니라 압축도 해주고 모듈 시스템(ES2015)을 지원하지 않는 브라우저도 동작하게 해준다던지, 웹팩 개발 서버를 통해 프론트엔드 개발환경을 구축할 수도 있다.

  • 자원의 요청수를 줄여 네트워크 비용 감소
  • 압축을 통한 로딩 속도 개선
  • 크로스 브라우징 해결
  • 프론트엔드 개발환경 구축

따라서 웹팩을 배우면 위 내용들의 부담을 쉽게 덜어낼 수 있다. 배우는 비용에 비해 문제를 해결할 수 있는 것들이 굉장히 많은 것을 알 수 있다.

 

이제야 왜 리액트 CRA에서 기본으로 깔려있는지, 웹팩이 왜 핫한지 알 것 같다.

번들링 하기

먼저 번들 작업을 하는 webpack과 웹팩 터미널 도구인 webpack-cli를 설치한다.

npm install -D webpack webpack-cli
명령어에서 -D는 "devDependencies"를 의미한다. 운영 시 사용하지 않는 모듈은 -D로 설치한다. 

개발환경에서만 사용하는 devDependencies

번들링 시 웹팩 터미널 도구로 여러 옵션들을 지정할 수 있지만 하나의 설정 파일로 관리하는게 더 편리하다.

// webpack.config.js 📂

const path = require('path');

module.exports = {
  entry: {
    main: './src/index.js',
  },
  output: {
    path: path.resolve('./dist'),
    filename: '[name].js',
  },
  mode: 'development', // 개발 모드
};

파일명은 webpack.config.js로 설정하여 관리할 수 있다.

  • mode: 웹팩의 실행 모드를 설정한다. 개발 버전인 'development'와 운영 버전인 'production'을 지정한다.
    • Webpack v4+부터는 'production' 모드로 번들링 할 경우 기본으로 압축을 해준다.
    • 주석과 공백 등을 제거해주는데, console.log()는 제거하지 않는다.
  • entry: 시작점 경로를 지정한다.
    • 위의 예시에선 index.js를 기준으로 의존적인 모듈들을 전부 찾아내고 결과물을 생성한다.
  • output: 번들링 결과물을 위치할 경로다.
    • Node.js의 path 모듈로 './dist' 절대경로를 반환한다. (output.path는 절대경로를 사용한다)
    • filename: 결과물의 이름을 설정한다. 시작점의 키(key)가 main이므로 main.js 파일이 생성된다.

이제 웹팩 실행을 위한 npm 커스텀 명령어를 추가한다.

// package.json 📂

{
  "scripts": {
    "build": "webpack --mode=production"
  }
}

그럼 이제 npm run build 번들링 작업을 지시할 수 있다.

웹팩 번들 결과

로더(Loaders)

웹팩에서 말하는 모듈이라는 개념은 자바스크립트 모듈에만 국한되지 않고 웹 앱을 구성하는 모든 자원을 의미한다. JavaScript, HTML, CSS, 이미지, 폰트까지도 모두 모듈로 보기 때문에 import 구문을 사용하면 자바스크립트 코드 안으로 가져올 수 있다.

 

이는 웹팩의 로더 덕분에 가능한 것이다. 꽤나 중요한 개념으로 다가온다.

이것이 가능한 이유는 웹팩의 로더 덕분이다. 로더는 타입스크립트 같은 다른 언어를 자바스크립트 문법으로 변환해 주거나 이미지를 data URL 형식의 문자열로 변환한다. 뿐만아니라 CSS 파일을 자바스크립트에서 직접 로딩할수 있도록 해준다. - 김정환님 블로그

import 구문으로 CSS 불러오기

// index.js 📂

import './index.css';

로더를 통해 .css 파일을 import 구문으로 불러오기 위해선 CSS를 모듈로 변환해주는 css-loader와 자바스크립트로 변경된 스타일을 동적으로 돔에 추가해주는 style-loader를 설치해야한다.

npm install -D css-loader style-loader

그리고 웹팩 설정에 로더를 추가한다.

// webpack.config.js 📂

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/, // .css 확장자로 끝나는 모든 파일을 의미한다
        use: ['style-loader', 'css-loader'], // ✅ style-loader를 앞에 추가하자. 아니면 에러가 난다
      },
    ],
  },
}

위의 예시처럼 배열로 설정하면 뒤에서부터 앞 순서로 로더가 동작한다. 따라서 위 설정은 .css 확장자로 끝나는 모듈을 읽고나서 css-loader를 적용하고 그 후에 style-loader를 적용한다.

main.js - 빌드 한 결과 css-loader덕분에 CSS 코드가 자바스크립트 코드로 변환 되었고, style-loader가 이 코드를 동적으로 돔에 추가해준다.

이 밖에 자주 사용하는 로더는 다음과 같다.

  • @babel/core, @babel/preset-env, babel-loader: 크로스 브라우징을 해결해준다. ES6+ 문법들에 polyfill을 넣어주는식으로 구형 브라우저랑 호환을 시켜준다.
    • 폴리필(polyfill)은 웹 개발에서 기능을 지원하지 않는 웹 브라우저 상의 기능을 구현하는 코드를 뜻한다.
    • 바벨을 사전이라 생각하자. 그리고 웹팩은 그 사전을 통해 번역을 해주는 도구로 볼 수 있다.
  • file-loader: CSS 뿐만 아니라 소스코드에서 사용하는 모든 파일을 모듈로 사용하게끔 해준다.
  • url-loader: 작은 이미지나 폰트 파일을 base64로 인코딩하여 문자열 형태로 코드에 인라인화 해준다.
    • 여러개의 작은 파일들은 http 요청을 하느니 차라리 문자열로 인코딩해서 보내는 것이 나을 수도 있다.
    • 하지만 용량은 실제로 33% 정도 증가한다고 한다.(응?)

플러그인(Plugin)

플러그인은 좀 더 추가적인 작업을 위해 쓰인다. 많은 것들을 사용해보진 않았지만 이 기능 역시 웹팩을 더욱 강력하게 만들어준다는 것은 확실한 것 같다. 그리고 특이하게 new 생성자 함수를 실행해서 넘긴다. 나같은 경우엔 .html 파일을 복사하는 작업에 사용을 했다.

npm install -D html-webpack-plugin

플러그인을 설치 후 이번에도 웹팩 설정에 다음과 같이 추가 해주자.

// webpack.config.js 📂

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

module.exports = {
  plugins: [new HtmlWebpackPlugin({ template: './public/index.html' })], // 템플릿 경로를 지정
}

이 플러그인과 함께 빌드하면 .html 파일로 아웃풋에 생성이 된다. 뿐만 아니라 빌드한 결과물을 자동으로 로딩하는 코드를 주입해주기 때문에 스크립트 로딩 코드도 작성할 필요가 없어졌다.

그래서 리액트 CRA의 index.html에 .css나 .js를 불러오는 코드가 없었던 것이다. 이제야 궁금증이 좀 해소되었다. 웹팩 덕분이었다.

빌드 하기전 index.html
빌드 후 아웃풋에 index.html이 생성되었고 main.js를 불러오는 태그가 자동으로 주입이 되었다. 웹팩..대단하다

이 밖에도 mini-css-extract-plugin이 있다. 스타일시트가 점점 많아지면 하나의 자바스크립트 결과물로 만드는 것이 부담이 될 수 있다. 번들 결과에서 스타일시트 코드만 뽑아서 별도의 .css 파일로 만들어 역할에 따라 파일을 분리하는 것이 좋다.

 

브라우저에서 큰 파일 하나를 내려받는 것보다 여러개의 작은 파일을 동시에 다운로드하는 것이 더 좋은 방법일 수 있다. 따라서 운영 환경에서는 분리하는 것이 효과적이다.

 

해당 플러그인은 CSS 파일을 별도 파일로 추출(extract)한다.

 

다시말해 CSS 코드가 포함된 JS 파일별로 CSS 파일을 생성한다.

와..웹팩 너 정말 대단하구나

아직 웹팩의 기능 중 반의 반도 못쓴 것 같다. 이정도의 분량으로도 굉장히 많은 것을 할 수 있게 되었다. 웹팩은 ES6의 모듈 시스템을 쉽게 사용하게 해주며 여러개로 흩어진 파일, 즉 엔트리 포인트를 시작으로 서로 연결되어 있는 모듈들을 하나로 합쳐준다.

 

또한 웹팩에서의 모듈은 자바스크립트 모듈 뿐만 아니라 CSS, 이미지, 폰트까지도 모듈로 제공해주기 때문에 개발의 일관성을 유지할 수 있다.

 

이런 웹팩을 잘 익혀 놓으면 편리한 프론트엔드 개발환경을 구축할 수 있을 것 같다는 생각이 든다. 

 

다음 글에선 웹팩 개발 서버(webpack-dev-server)에 대해 글을 쓸 예정이다.

참고

프론트엔드 개발환경의 이해: 웹팩(기본) - 김정환블로그
웹팩이란? - 웹팩 핸드북
웹팩5(Webpack) 설정하기 - 제로초 블로그
CSS 파일 개별 추출 - Webpack 러닝 가이드
Loaders - 웹팩 공식문서
반응형