치춘짱베리굿나이스

웹팩과 웹팩 설정하기 본문

ClientSide/라이브러리

웹팩과 웹팩 설정하기

치춘 2022. 9. 18. 01:10

웹팩과 웹팩 설정하기

웹팩

공식 홈페이지

webpack

 

webpack

웹팩은 모듈 번들러입니다. 주요 목적은 브라우저에서 사용할 수 있도록 JavaScript 파일을 번들로 묶는 것이지만, 리소스나 애셋을 변환하고 번들링 또는 패키징할 수도 있습니다.

webpack.kr

웹팩이란?

공홈페이지에서부터 “~~를 번들해 보세요" 라는 캐치프레이즈가 대문짝만하게 출력되는 것에서도 알 수 있듯, 웹팩은 ‘모듈 번들러' 이다

캐치프레이즈가 한 3~5초 간격으로 바뀌는데, {이미지 | 애셋 | 스크립트 | 스타일 …} 을 번들해 보세요 예시 중 하나로 계속 슬라이딩되는 것으로 보아 프로젝트 내의 이미지, 애셋, 스크립트, 스타일도 번들이 가능한 ‘모듈' 이라고 할 수 있음을 예측가능하다

앞서 적었듯 모듈 번들러는 모듈을 잘 포장하여 하나 (또는 적은 개수) 의 파일로 만들어주는 역할을 하며, 그 중간에 발생하는 모든 부작용이나 처리해야 하는 작업을 대신 해준다

웹팩은 진입점 (Entry Point) 으로부터 의존하는 모든 모듈들을 전부 찾아 번들링해주며, 쉽게 말해 각 파일들을 타고 들어가서 그 파일들이 사용하는 모듈들을 찾고, 또 그 모듈들을 타고 들어가서 사용하는 내부 모듈들을 찾고… 를 반복하는 것이다

그래서 웹팩을 왜? 굳이?

한때는 browserify가 1등일 때도 있었는데...

  • 압도적인 인기
    • 4개의 번들러를 2015년부터 2022년까지 npm 다운로드 추이 그래프로 출력해보면 (npmtrends.com에서 볼 수 있다) 웹팩이 정말 압도적인~ 다운로드 수를 기록한 것을 볼 수 있다
    • 나머지 세 개의 번들러를 다 합쳐도 웹팩을 넘어설 수 없다
    • 인기가 많은 만큼, 자료도 방대하다 (React처럼)
  • live-reload 지원
    • 번들링 결과물을 브라우저 등으로 확인하면서 작업을 하고 있을 때, 진입점으로부터 연결된 모든 파일들 중 하나라도 변경사항이 있을 경우 다시 빌드하여 변경사항을 바로 업데이트해 준다
    • webpack-dev-server 덕분에 부가적인 외부 플러그인 없이도 라이브 리로딩이 가능하다
  • 안정성과 속도
    • 역사가 길고 사용자가 많은 만큼 기여자도 많고, 이는 안정적인 성능으로 직결된다
  • 많은 확장자들을 지원
    • JS 파일 뿐만 아니라 CSS, html 등의 마크업과 이미지, 폰트 등의 애셋도 같이 번들링해 준다
    • 이는 웹팩에서 여러가지 로더들을 지원하기 때문인데, html loader, css-loader 등 파일 유형마다 로더들이 다 존재한다
  • 스코프 문제를 해결해 준다
    • 각 모듈을 함수 (IIFE = Immediately Invoked Function Expressions) 로 감싸주어 스코프를 한 파일에 하나로 한정해 준다
    • 덕분에 (요즘은 잘 안 쓰지만) var로 변수 이름을 중복되게 선언해놔도 파일만 다르다면 충돌 걱정 없이 안전하게 번들링을 할 수 있다
  • 코드 분할 (Code splitting)
    • 하나의 번들이 아닌 여러 개의 번들로 쪼갤 수 있다
    • 중복 코드가 존재할 우려가 있는데, 이 또한 웹팩의 Optimization (최적화) 설정을 통해 별도의 라이브러리로 번들링하는 방식으로 해결한다
  • 동적 가져오기 (Dynamic Import)
    • 분할한 코드는 동적 가져오기 (다이나믹 임포트) 에 활용되는데, 만약 A라는 페이지에서 버튼을 클릭하면 B라는 컴포넌트를 보여준다고 하자 ⇒ 최초 로딩 시에 사용자에게는 A라는 페이지 내용물만 보여질 뿐, 버튼을 누르지 않으면 B라는 컴포넌트는 영원히 렌더링될 필요가 없다
    • 그렇다면 굳이? B라는 코드를 서버에서 미리 받아올 필요가 없는 것이다! B에 대한 코드까지 미리 받아오는 것은 리소스 낭비이다! 이미지도 비슷하게, 웹 페이지 하단으로 스크롤해야 보이는 이미지의 경우 사용자가 스크롤하기 전까지는 굳이 로딩을 미리 해둘 필요가 없다
    • 동적 가져오기를 사용하면, 모든 코드들을 한번에 받아올 필요 없이 필요한 코드만 먼저 받아온 후, 나중에 추가적인 코드를 받아오도록 설정할 수 있다 (= Lazy-loading)
    • 웹팩에서 코드 스플리팅을 설정한 뒤, import(파일명) 를 통해 이 코드가 필요한 시점에서 import를 하도록 해 주면 동적으로 코드를 분할하여 관리할 수 있다 (Promise를 반환하기 때문에, 적절한 비동기 처리가 필요하긴 하다)

아무래도 리액트랑 비슷하게, 압도적인 사용자 수에서 비롯한 잘 갖추어진 커뮤니티와 공식문서 덕에 웹팩 쪽에 조금 손이 더 간다

설정 파일을 만들더라도 레퍼런스가 많은 경우가 좋지 않나 하고… ㅎ

웹팩 설정해보기

1. 웹팩 설치

빈 폴더를 준비한다 (텅~~)

나는 깃허브에 올릴 예정이기 때문에 레포지토리로 준비했다

 

$> npm init
$> yarn init

 

npm (또는 yarn) 을 사용할 수 있도록 환경을 초기화한다

이 과정은 package.json의 내용을 (설치 의존성을 제외한 정보 - 프로젝트 이름, 버전, 라이센스 등) 초기화해 준다

 

$> npm i -D webpack webpack-cli
$> yarn add -D webpack webpack-cli

웹팩을 설치하자

-D 플래그를 이용하여 개발 환경 (dev 환경) 에서만 사용할 수 있도록 설치한다

webpack이 웹팩 본체, webpack-cli는 프롬프트에서 웹팩 관련 명령어를 사용하게 도와준다

 

웹팩이 잘 설치되면, package.json에 내용이 덧붙여지고 package-lock.json이 추가되며 패키지의 의존성들이 모두 기록된다

2. 웹팩 설정 파일 초기화 (webpack.config.js)

webpack.config.js 파일을 생성하자

VS코드에서 아이콘 관련 확장을 쓰다 보니 웹팩 설정 파일은 자동으로 인식해서 웹팩 아이콘으로 표시해준다

이 파일이 웹팩의 설정 관련 내용을 담고 있는 파일이고, 따라서 이 설정 파일에 적힌 대로 동작한다

 

// webpack.config.js
const path = require("path");

module.exports = {

};

일단은 내용물을 이렇게 초기화해 보자

package.json 안에서 “type”: “module” 키-값 쌍을 추가함으로써 ES6 모듈 설정을 하거나, webpack.config.mjs 파일로 확장자를 바꾸면 ES6 모듈 파일이 되어 require문 대신 import / export를 사용할 수 있다

이제 module.exports 안의 내용물을 하나씩 채워볼 것이다

3. 웹팩의 4가지 핵심 요소 추가하기

// webpack.config.js
module.exports = {
    entry: {},
    output: {},
    module: {
        rules: []
    },
    plugins: {}
}

entry, output, module = loader, plugins 키를 각각 추가하고, 값은 빈 객체로 남겨두자

이 각각의 키가 무엇을 의미하는지 알아보자

 

1. entry

단어 그대로 진입점을 의미한다

웹팩이 어느 파일부터 의존 관계 탐색을 시작할 지 명시해주는 부분이며, 웹팩은 이 진입점에서 시작하여 모듈들 사이의 의존 관계를 나타내는 ‘의존성 그래프' (Dependency Graph) 를 재귀적으로 작성한다

예를 들어 A 모듈이 B 모듈을 불러와 사용하고 있고, B 모듈이 C와 D 모듈을 참조하고 있으며, C 모듈은 E라는 이미지 파일을 사용한다고 생각해보자

  • 진입점을 A로 설정하면, 웹팩은 A 모듈부터 탐색하여 A 모듈이 어느 모듈에 의존하는지 알아낸다 (의존한다는 것은 해당 모듈을 필요로 하며 내부에서 사용하고 있다는 의미이다)
  • A 모듈은 B 모듈을 불러와 사용하므로, A 모듈은 B 모듈에 의존적이고, 따라서 웹팩은 B를 의존성 그래프에 기록하고, 다음 탐색 목표는 B가 된다
  • B 모듈에 도달하면, B가 의존하는 모듈은 C와 D이므로 C와 D를 의존성 그래프에 기록하고, C와 D를 다음 탐색 목표로 삼는다 (DFS 같은 느낌으로, 재귀적으로 깊이 탐색한다)
  • C 모듈은 이미지 애셋인 E를 사용하므로, E에 의존적이다 ⇒ E를 의존성 그래프에 기록하고, E가 의존하는 모듈이나 애셋이 없으므로 해당 경로에서의 탐색을 중단하고 D 모듈으로 넘어간다
  • D 모듈이 의존하는 다른 모듈이나 애셋이 없으므로 해당 경로에서의 탐색을 중단한다

의존성 그래프를 간단하게 다이어그램으로 그려서 표현하면 위와 같다 (열심히그렸다)

https://github.com/pahen/madge 이 툴을 사용하면 현재 폴더의 의존성 그래프를 시각화해 준다고 한다

 

// webpack.config.js
entry: "./src/index.js" // 1개일 때

entry: ["./src/index.js", "./src/test.js"] // 여러개일 때

entry: {
    index: "./src/index.js", 
    test: "./src/test.js" // 여러개이고, 이름을 붙여주어 서로 다른 번들을 만들고자 할 때
}

진입점은 한 개가 될 수도 있고, 여러 개가 될 수도 있다

진입점이 여러 개일때, 배열로 작성하면 진입점만 여러개인 하나의 파일로 번들링되고, 객체로 이름을 붙여 작성하면 후술할 방법을 통해 여러 개의 파일로 번들링할 수 있다

만약 SPA가 아닌 여러 페이지 어플리케이션을 제작한다면 하나의 자바스크립트 파일로 동작하는 것이 아닌, 각 html 파일마다 스크립트 파일을 개별로 넣어 주어야 하므로 객체로 작성하는 방식을 택하면 된다

 

2. output

entry가 진입점을 의미한다면, output은 번들링된 파일의 경로와 파일명을 지정한다

번들링된 파일을 모아둘 폴더의 경로와, 파일명을 명시하자

// webpack.config.js
output: {
    path: "./dist" // 상대경로
    filename: "bundle.js"
}

path는 폴더의 경로, filename은 번들링 파일명이다

 

// webpack.config.js
const path = require("path");

...

output: {
    path: path.join(__dirname, "dist"),
    ...
}

위에서 module.exports 까지만 만들어줬을 때 최상단에 path 라이브러리를 import했었는데, 그 이유가 여기서 나온다

path는 보통 현재 경로를 __dirname으로 가져와 dist 폴더 (또는 원하는 폴더명) 를 join하여 작성한다

CommonJS가 아닌 es module (es6) 에서는 __dirname이 존재하지 않으므로 따로 path.resolve() 를 통해 현재 경로를 가져와야 한다

 

// webpack.config.js
entry: {
    index: "./src/index.js", 
    test: "./src/test.js" // 이름: "진입점경로"
}
...
output: {
    path: path.join(__dirname, "dist"),
    filename: "[name].bundle.js" // 이 부분
}

 

filename은 이렇게도 설정 가능한데, [name] 부분이 좀 특이하다

앞서 entry 에서 진입점을 설정할 때, 객체를 통해 진입점 각각에 이름을 붙여줬는데 그 이름을 여기서 사용한다

위의 예시처럼 진입점의 이름이 index라면, 해당 진입점 (./src/test.js)으로부터 의존성 그래프를 작성하여 번들링된 파일은 index.bundle.js가 된다

진입점의 이름이 test라면 test.bundle.js 가 될 것이다

여러 번들 파일을 사용해야 한다면 참고하자

 

3. loader

앞서 서술했듯 웹팩은 자바스크립트 파일 외에도 다양한 자료형의 파일들을 번들링할 수 있다

이를 위해 로더를 설정해줘야 하는데, 웹팩이 생 이미지 파일 (png, jpg) 이나 마크업 (html), 스타일 (css) 등 다른 확장자 파일들을 모듈로 인식만 하지 실제로 읽어들이지는 못하기 때문에 각 파일 종류마다 서로 다른 로더를 사용하여 파일을 읽어들이고 적절한 조치를 취해 번들링해준다

말 그대로 여러 파일을 ‘불러올 (로딩)' 수 있도록 도와주는 부품이라고 볼 수 있다

이 로더들은 module을 키로 갖는 객체에 명시해주면 된다

 

// webpack.config.js
module: {
    rules: []
},

위에 보면 module 안에 rules라는 배열이 들어있는데, 실질적으로 ‘어느 확장자의 파일’ 을 읽을 때 ‘어떤 로더를 호출’ 하고, ‘어떤 설정'을 사용할 것인지 명시하는 배열이다

객체에서 rules 대신 loader 라는 이름을 사용하기도 하는데, 이는 웹팩 1.x 버전의 방식이고 현재는 rules를 쓴다

확장자의 종류가 다양하기 떄문에, 확장자 각각에 대한 설정은 객체로 정리하여 rules에 저장한다

여기서 설정한 로더들은 번들링 단계에서 지정한 확장자의 파일을 만났을 때 호출되어 파일을 적절한 형태로 변환하고 같이 번들링한다

웹팩 핸드북에 나와있는 것처럼 CSS 로더를 적용해 보자

 

$> npm i css-loader -D

웹팩 전용 CSS 로더를 설치한다

마찬가지로 개발 단계에서만 사용하므로 (배포 단계에서는 또 번들링을 할 필요가 없으므로 쓰지 않는다) -D 플래그를 붙인다

// webpack.config.js
module: {
    rules: [
        {
            test: /\.css$/,
      use: ["css-loader"],
    },
  ],
},

rules 배열에 객체를 하나 추가한 뒤, 객체에 testuse 프로퍼티를 추가하자

  • test는 로더를 적용할 파일 정보를 명시하며, 주로 정규표현식이 사용된다
    • $ 기호가 문자열의 끝을 의미하므로, 이름의 맨 끝이 .css인 파일들에 적용하겠다는 의미이다
  • use는 적용할 로더의 이름을 적는다
    • use 대신 loaderloaders를 사용하기도 한다
    • loader는 로더가 단 하나일 때 loader: “css-loader” 와 같이 사용
    • loaders는 로더가 여러 개일 때 배열로 loaders: [{ loader: “css-loader” }, …] 와 같이 loader를 여러 개 적어 사용
    • use에 명시된 로더가 여러 개일 경우, 오른쪽에서 왼쪽 순으로 적용되므로, Sass 등 전처리가 필요한 경우 [’css-loader’, ‘sass-loader’] 와 같이 먼저 적용되어야 하는 로더를 배열의 오른쪽에 배치한다

 

// webpack.config.js
use: [
    { loader: 'css-loader' },
    { loader: 'sass-loader', options: { ... } }
]

use를 객체 배열로 만들어 로더의 이름 뿐만 아니라 옵션을 명시해 줄 수도 있다

 

그 외에도

  • exclude는 제외할 파일들을 배열로 명시한다
  • options는 로더의 옵션을 작성한다 (객체)
    • query는 현재 deprecated되었으므로 options로 사용한다
  • layer는 모듈이 존재하는 레이어 위치를 명시한다

등 추가 설정이 가능하다

어찌 보면 제일 복잡한 설정 파트가 되지 않을까 싶다

자기가 어떤 종류의 파일들을 프로젝트에 사용할 것인지 알고 있는 것도 중요해 보인다

 

4. plugins

자바스크립트 외의 파일을 불러와 전처리 후 번들링할 수 있도록 도와주는 것이 로더였다면, 플러그인은 그 외의 잡다한 추가 기능들을 제공한다

결국 로더랑 플러그인이 둘이 비슷한 거 아닌가? 생각할 수 있는데,

  • 로더는 번들 생성 중에 각 파일을 해석하고 처리 및 변환한다
  • 플러그인은 번들이 생성된 후에 추가적인 업무를 처리한다

사용하고 싶은 플러그인이 없다면 굳이 작성할 필요가 없다

 

// webpack.config.js
const testPlugin = require("testPlugin");
const testPlugin2 = require("testPlugin2");

...
plugins: [new testPlugin(), new testPlugin2()],
...

로더는 굉장히 설정해줄 것들이 많았는데, 플러그인은 require 또는 import로 불러와서 생성자를 통해 인스턴스화한 객체만 넣어주면 되어서 간단하다

 

$> npm i -D html-webpack-plugin
$> npm i -D mini-css-extract-plugin
// webpack.config.js
...
plugins: [
  new HtmlWebpackPlugin({ template: "./public/index.html" }),
  new MiniCssExtractPlugin(),
],

보통 많이 사용하는 플러그인으로 html-webpack-pluginmini-css-extract-plugin이 있다

html-webpack-plugin

  • 웹팩을 통해 번들링을 완료한 뒤 해당 번들을 가져와 사용하는 html 파일을 생성해 준다
  • template 옵션은 설정한 템플릿으로 파일을 구성해 주므로, 기본 html 형식이 마음에 들지 않으면 사용자가 템플릿을 지정해줄 수 있다

mini-css-extract-plugin

  • 웹팩을 통해 css를 번들링한 뒤 css를 불러와 사용하는 자바스크립트 파일별로 css를 분리해 준다
  • html-webpack-plugin과 함께 사용하면 html 파일을 생성해줄 때 css를 link 태그로 연결도 해준다

4. 그외 값들 설정하기

// webpack.config.js
...
mode: "development"
...

mode는 현재 프로젝트가 개발 단계인지, 배포 단계인지 결정한다

  • development (개발)
    • 아주 빠르게 빌드된다
  • production (배포)
    • 배포를 위해 최적화 단계를 거쳐 빌드된다 (조금 느림)
  • none (둘 다 아님)
    • 기능 없이 그냥 빌드된다
    중 하나로 값을 지정할 수 있으며, 기본값은 production으로 되어 있다

각각의 경우마다 웹팩이 서로 다른 방식으로 번들링해 주므로 현재 상태에 맞게 지정하자

process.env.NODE_ENV 값으로 배포 단계인지 개발 단계인지 알 수 있으므로 이를 이용하는 것도 좋다

 

// webpack.config.js
...
name: "test-config"
...

name은 이 설정의 이름을 지정한다

5. 바벨 설정하기

Babel · The compiler for next generation JavaScript

 

Babel · The compiler for next generation JavaScript

The compiler for next generation JavaScript

babeljs.io

바벨탑을 아는가? 하나의 언어만을 쓰던 인간들이 탑을 쌓아 천국에 가려고 했다가 어쩌구… 거기서 이름을 따오지 않았을까 싶다

바벨은 자바스크립트 트랜스파일러로, 트랜스파일러란 같은 언어의 코드를 문법적으로 변환해서 컴파일하는 것을 의미한다

컴파일러는 아예 기계어로 변환해주고, 트랜스파일러는 문법만 변환해준다는 차이가 있다

예시

  • 타입스크립트 (자바스크립트 문법에 타입만 추가) 를 자바스크립트로 변환
  • SASS (.scss) 를 css로 변환
  • ES6 자바스크립트 문법을 ES5로 변환

어찌 보면 로더들이 트랜스파일러의 역할을 겸한다고 볼 수 있겠다

바벨이 필요한 이유는 ES6을 지원하지 않는 브라우저 (인터넷 익스플로러……….) 가 있기 때문으로, es6 코드들 (Promise, import / export, let / const 등) 을 es5 기준으로 변환하여 모든 브라우저 환경에서 자바스크립트 코드가 구동되도록 해주는 것이 바벨의 궁극적인 역할이다

 

$> npm i -D @babel/core @babel/cli @babel/preset-env
$> npm i -D babel-loader

바벨을 설치하자

@babel/core가 바벨의 본체로, 핵심 기능들을 담당한다

@babel/cli는 CLI 환경에서 바벨을 이용하여 컴파일할 수 있게 도와준다

@babel/preset-env는 ES6을 ES5로 변환하기 위한 프리셋들이 저장되어 있다

babel-loader는 웹팩과 연결해주는 로더로, 번들링 시에 바벨을 적용하도록 한다

 

// babel.config.json
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "browsers": ["last 2 version", "> 10% in KR", "not dead"]
        },
        "debug": true
      }
    ]
    // "@babel/preset-react" // React 사용 시
  ],
  "plugins": [["@babel/plugin-proposal-class-properties", { "loose": true }]]
}

babel.config.json 를 생성하자

해당 프로젝트 전체에 이 설정을 적용할 것이다

presets은 프리셋을 설정할 수 있으며, 배열 형태로 그 안에 문자열 (프리셋 패키지) 나 배열 (추가 설정) 을 넣을 수 있다

@babel/preset-env가 속한 배열이 하나의 프리셋 묶음이다

  • @babel/preset-env를 적용시켜줌과 동시에 추가 옵션으로
  • target으로 바벨을 적용시킬 브라우저 범위를 지정하고
  • debug는 디버그용 콘솔로그를 출력해 준다

배열로 되어있으므로 다른 프리셋이 있다면 배열에 추가해주면 된다

리액트를 사용할 경우를 대비하여 @babel/preset-react 를 추가해 놓았다

 

플러그인으로는 @babel/plugin-proposal-class-properties 를 등록해주었는데, ES6에서 클래스를 만들었을 때 클래스 내부 속성을 생성자 밖에서 초기화하거나 정적으로 지정해줄 경우 플러그인 없이는 오류가 난다고 한다

loose: true 옵션을 통해 최종적으로 번들링되는 코드의 양을 줄일 수 있다

// webpack.config.js
...
module: {
    rules: [
        ...
        {
            test: /\.js$/,
            exclude: /node_modules/,
            use: ['babel-loader']
        }
        ...
    ],
    ...
},
...

바벨 로더를 웹팩 설정에 추가하여 로더를 사용할 수 있게끔 하자

모든 .js 파일에 적용하기 위해 .js를 붙였는데, 리액트를 사용한다면 .jsx? 로 설정하면 .js, .jsx 모두 인식한다 (물음표는 앞에 적힌 글자가 0개 혹은 1개일 때를 의미)

exclude는 해당 로더를 적용하고 싶지 않은 파일을 정규식으로 적으면 된다

node_modules 안의 내용물까지 전부 바벨을 적용시키면 기하급수적으로 오래 걸리기도 하고, 컴파일 단계에서 어차피 하나로 압축되므로 제외해주자

6. devtool 지정하기

// webpack.config.js
...
devtool: "eval",
...

devtool은 소스맵을 어떻게 생성할지 결정해주며, 다양한 옵션이 있다

소스맵은 번들링한 파일과 원본 파일을 연결해 주는 역할을 한다

js 파일 여러 개를 하나의 파일로 번들링한 상태에서는 오류가 발생한 위치를 번들링한 파일 기준으로 알려준다

이런 상황에는 함수 이름도 다 바뀌어있으므로 원본 함수의 이름이 무엇이고 어떤 파일에 위치해 있는지 알 방도가 없기 때문에, 개발자 도구 등에서는 소스맵을 이용하여 원본 함수 기준으로 오류를 보여준다

 

소스맵 생성 방식에 따라 빌드 속도와 소스맵 품질에 차이가 있으므로 배포 단계인지, 개발 단계인지 등 여러 상황에 맞추어 다른 devtool을 지정해주면 좋다

내부적으로는 플러그인을 적용해주는 방식으로 동작한다

  • (none)
    • devtool을 지정하지 않는 옵션으로, 소스맵 생성을 하지 않기 때문에 가장 빠르다
    • 배포 단계에선 소스맵이 필요없으므로 배포 단계에선 devtool을 적용하지 않는다
  • eval
    • 빌드가 빠르고 빌드 후 재빌드 과정이 매우 빠르므로 개발 단계에서 주로 사용한다
  • eval-cheap-source-map, eval-cheap-module-source-map
    • 빌드 속도와 빌드 퀄리티가 균형잡혀 있다
    • 개발 단계에서 사용하면 좋다
  • eval-source-map
    • 빌드 속도가 매우 느리지만 재빌드는 그나마 빠르다
    • 고퀄리티 소스맵이 필요한 개발 단계에서 사용하면 괜찮다 (개발 과정에선 재빌드 횟수가 많기 때문)
  • source-map
    • 빌드와 재빌드 속도가 매우 느린 대신, 소스맵의 질이 매우 좋다
    • 고퀄리티 소스맵이 필요한 배포 단계에서 사용하면 괜찮다 (배포 단계에는 재빌드를 할 필요가 없기 때문)

7. dev-server 지정하기

$> npm i -D webpack-dev-server

개발 시에 가장 중요한 건 속도이다

그리고 매번 코드 하나를 바꿀 때마다 일일히 빌드 명령어를 입력하는 것은 상당히 귀찮고 속도도 느려진다

이것을 방지하기 위해 개발 서버를 이용해볼 것이다

먼저 웹팩 개발 서버 webpack-dev-server를 설치하자

 

// webpack.config.js
...
devServer: {
    static: { directory: path.join(__dirname, "dist") },
    port: 9000,
}

 

webpack.config.jsdevServer 키를 추가하자

여기서 개발 서버 설정을 할 수 있다

기본적으로 서버에서 띄울 파일들의 경로 (static.directory)를 dist 폴더로, 포트번호 (port) 를 9000으로 설정해 주었다

몇몇 예제에서는 static.directory 부분이 contentBase로 되어있는데, 언젠가 수정되었는지 웹팩에서 인식을 못 해서 오류를 뱉어버린다

이 설정값대로라면 앞으로 localhost:9000 으로 접속하면 dist 폴더에 있는 빌드 결과물을 바로 볼 수 있다

 

// webpack.config.js
...
devServer: {
    static: { directory: path.join(__dirname, "dist") },
    port: 9000,
    open: true,
    compress: true,
    host: "localhost",
}

추가 옵션으로

  • open: 서버를 실행시킬 때마다 자동으로 브라우저 창을 열어준다
  • compress: 번들링 파일들을 gzip으로 압축할 것인지 여부이다
  • host: 서버를 실행할 때의 호스트 주소이다

더 많은 옵션은 https://webpack.js.org/configuration/dev-server/ 이곳에서 볼 수 있다

 

// package.json
...
"scripts": {
    "start": "webpack serve --mode=production",
    "start:dev": "webpack serve --mode=development",
    "build": "webpack --mode=production",
    "build:dev": "webpack --mode=development"
},
...

package.json에도 웹팩 서버를 켜는 명령과 웹팩으로 번들링하는 명령을 추가하자

:dev가 붙으면 개발 버전으로 번들링되며, webpack servewebpack dev server를 켠다

 

위에 설정한 명령어를 치면 무언가가 주르르르륵 뜨면서 컴파일이 완료되었다고 나온다

웹팩이 추적하는 파일의 내용 하나를 바꿀 때마다 재컴파일이 되며, 파일 내부에 오류가 있을 경우 컴파일 오류를 바로 뱉어주므로 디버깅이 수월해진다

8. dev-middleware 설정하기

$> npm i -D webpack-dev-middleware

미들웨어는 웹팩으로 번들링한 파일을 서버로 내보내주는 역할을 한다

웹팩 개발 서버 내에서도 사용하는 패키지지만, 웹팩 개발 서버 외의 다른 서버에서 미들웨어 (전처리 용도) 로 웹팩을 사용하고 싶을 때 직접 설치해서 사용하면 된다

대표적인 용례로 제작한 서버 (express 등) 에 코드를 내보낼 때 등이 있다

우선 위의 명령어로 설치한다

 

// webpack.config.js
...
output: {
    path: path.join(__dirname, "dist"), // 번들링 파일 저장 폴더
  filename: "bundle.js", // 번들링 파일 이름
  publicPath: "/",
}
...

output 객체에 publicPath를 하나 추가해주자

해당 경로에 파일을 내보내줄 것이다

 

// server.js (서버의 진입점 파일)

...
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
...

app.use(
  webpackDevMiddleware(compiler, {
    publicPath: "/", // webpack.config.js의 output.publicPath
  })
);

이제 서버 쪽 코드에서 app.use를 통해 미들웨어를 설정해 주자

webpack-dev-middleware를 불러와서 미들웨어 설정을 해 준다

설정 부분에서 publicPath는 앞서 설정한 output.publicPath와 똑같이 설정해준다

앞으로 서버를 켜면 웹팩이 자동으로 빌드되는 것을 볼 수 있다

결론

사실 이번에 웹팩을 정리하면서 Create-React-App이 얼마나 소중한지 깨닫게 되었다…

웹팩 요소 하나하나를 정리하면서 설정해주는 건 쉽지 않은 일 같다 (눈물)


참고자료

[JS][WEBPACK] 1. 웹팩이란 무엇인가

웹팩이 필요한 이유 | 웹팩 핸드북

우리는 Webpack이 왜 필요할까?

Webpack을 이용한 코드 스플리팅

webpackを使い倒す - Thujikun blog

웹팩 이해하기 - 2

[Webpack] 알아두면 쓸데있는 웹팩지식

Loaders | 웹팩

빌드 결과 자동 주입

Webpack - 웹팩 4 뽀개기 (개발하기 sourcemap, 개발서버 setting(webpack dev server)

Webpack Dev Server | 웹팩 핸드북

웹팩(Webpack) 밑바닥부터 설정하기

'ClientSide > 라이브러리' 카테고리의 다른 글

axios - instance 사용하기  (0) 2023.04.18
Cypress로 첫 E2E 테스트 수행하기  (0) 2023.04.18
commander  (0) 2022.08.02
Victory  (0) 2022.05.30
구글 애널리틱스  (0) 2022.05.29
Comments