웹팩 알아보기 1부

업데이트:

어떤 프로젝트든 시간이 지날수록 기능은 추가되고 관련 파일은 늘어나게된다. 이러한 늘어나는 파일을 관리하는것은 꽤나 머리아픈일이다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="js/main.js"></script>
    <script src="js/profile.js"></script>
    <script src="js/mypage.js"></script>
    <script src="js/shopList.js"></script>
    <script src="js/header.js"></script>
    .
    .
    .
    .
</head>
<body>
    
</body>
</html>

html 로만 페이지를 구성한다면 모든 페이지에 공통으로 사용되는 스크립트가 있다면 매번 스크립트선언을 해줘야하고 만약 저런식으로 수십 수백개의 파일을 가져와 사용한다면 중간에 뭐하나 빠져도 제대로 알아차리기 힘들것이다. 또한 저렇게 많은 파일들중에서 중복되지않은 변수명, 함수명이 100% 완벽하게 이루어져있을것이라고 확신할 수 있을것인가?? 물론 즉시호출함수를 사용하여 모듈을 만들어 사용하거나 모듈시스템을 활용하여 관리할 수도 있을것이다.

하나의 웹 페이지를 구성하려면 html, css, js 파일들이 기본적으로 필요하고 페이지가 늘어남에 따라 구성에따라 또 유지보수를 위해 많은 파일들이 생성될것이다. 웹팩에서는 이러한 파일들 심지어 폰트파일까지 하나의 모듈로 인식하고 엔트리를 통해서 필요한 모듈을 로드하고 하나의 파일로 묶어주는 역할을 한다.

웹팩의 주요개념

  • Entry
  • Output
  • Loaders
  • Plugins

실습환경 세팅

$ mkdir webpack-test
$ cd webpack-test
$ npm init -y
$ npm install webpack webpack-cli --save-dev
$ npm install --save lodash

디렉토리를 생성하고 webpack 과 명령어를 사용하기위해 webpack-cli 를 설치

// src/index.js
import _ from 'lodash';

function component() {
    const el = document.createElement('div');
    el.innerHTML = _.join(['Hello', 'webpack'], ' ');

    return el;
}

document.body.appendChild(component());
<!-- dist/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <!-- <script src="./src/index.js"></script> -->
    <script src="main.js"></script>
</body>
</html>

우선은 수동으로 dist 폴더에 index.html 을 생성해주고 main.js 스크립트 파일을 불러옵니다. 여기서 주의해서 봐야할 부분은 스크립트 파일은 src/index.js 에만 생성되어있을뿐 어디에도 main.js 라는 파일은 없습니다.

해당 파일들이 정상적으로 동작하게 하기위해서는 webpack 으로 번들링하는 과정이 필요합니다.

$ npx webpack

asset main.js 69.5 KiB [emitted] [minimized] (name: main) 1 related asset
runtime modules 1010 bytes 5 modules
cacheable modules 532 KiB
  ./src/index.js 206 bytes [built] [code generated]
  ./node_modules/lodash/lodash.js 531 KiB [built] [code generated]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

webpack 5.31.2 compiled with 1 warning in 1707 ms

dist/main.js 파일이 생성되었고 어떤 모듈들이 사용되었는지도 나옵니다. 약간의 경고문구를 보여주고있지만 정상적으로 번들링되었고 index.html 을 실행하면 화면에 Hello webpack 이라는 문구가 보여진다.

main.js 를 열어보면 알아보기힘든 코드들이 나열되어있는데 index.js 와 lodash.js 를 번들링하여 나온 결과라고 볼 수 있다.

그냥 이렇게 사용할수도있지만 webpack.config.js 파일을 통해서 webpack 번들링에 대해서 직접적인 설정을 할수도있다.

루트디렉토리에 webpack.config.js 를 생성하고 아래의 코드를 입력한다.

const path = require('path');

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

const path = require('path'); 여기서 사용된 path 는 Node 의 내장모듈로 OS 타입에따라 서로 다른 경로구분자를 가질 수 있는데 이에대한 고민을 해결해주고 javascript 의 폴더와 파일경로를 문자열로 만들어준다.

entry: './src/index.js', entry 는 시작점을 나타내며 해당 시작점을 기준으로 의존성을 갖고있는 모든 모듈에 대해서 하나로 합쳐준다.

output 에서 filename 은 어떤 파일명으로 번들링할것인지 지정하는것이고
path: path.resolve(__dirname, 'dist'), 부분은 어느 루트에 번들링된 파일을 생성할것인지 경로를 지정하게된다.

  • __dirname : 현재 실행중인 폴더경로
  • __filename : 현재 실행중인 파일경로

즉 path 모듈을 활용하여 현재 실행중인 폴더경로와 dist 를 연결하여 파일 경로를 지정할 수 있다.

이제 새로작성한 환경설정에 맞게 실행해보려고하는데 그전에 이미 dist에 생성된 main.js 파일을 제거하고 다시 실행해보려고한다.

$ npx webpack --config webpack.config.js

asset main.js 69.5 KiB [emitted] [minimized] (name: main) 1 related asset
runtime modules 1010 bytes 5 modules
cacheable modules 532 KiB
  ./src/index.js 206 bytes [built] [code generated]
  ./node_modules/lodash/lodash.js 531 KiB [built] [code generated]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

webpack 5.31.2 compiled with 1 warning in 1700 ms

처음과 동일하게 실행이되었다.

하지만 매번 저 긴 명령어를 입력할수 없으니 package.json 에 스크립트명령어를 추가하려고한다.

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
},

$ npm run build

파일관리

entry 와 output 을 정의했으니 이제 파일관리핵심인 loaders 에 대해서 알아보려고한다. webpack 은 어쨋든 모든 파일들에 대해서 모듈로 인식한다고했는데 지금까지한건 js 가 서로 의존성을 가진것들이 묶여서 하나의 파일로 사용할 수 있게끔 번들링해주는것으로 보았다. 그렇다면 css 나 이미지 폰트파일들은 어떤식으로 처리할 수 있을까?

<!-- dist/index.html -->
- <script src="main.js"></script>
+ <script src="bundle.js"></script>
// webpack.config.js
output: {
    filename: 'bundle.js',
}

우선 코드의 일부를 bundle.js 로 수정하고 진행하려고한다.

css loader

스타일파일을 웹팩에서 사용하기위해서는 추가 패키지를 설치해야한다.

$ npm install --save-dev style-loader css-loader sass-loader sass

그리고 웹팩의 설정파일에 내용을 추가한다.

// webpack.config.js
module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.(s[ac]ss|css)$/i,
        use: [
          "style-loader",
          "css-loader",
          "sass-loader",
        ]
      }
    ]
  }
};

로더순서는 위와같이 유지해야하는데 이 규칙을 따르지않으면 webpack 오류가 발생할 수 있다.

위와같이 패키지설치와 webpack 환경설정이 끝나면

// src/index.js
import _ from 'lodash';
import './style.css';
import './style2.scss';

function component() {
    const el = document.createElement('div');
    el.innerHTML = _.join(['Hello', 'webpack'], ' ');
    el.classList.add('hello');
    return el;
}

document.body.appendChild(component());

와 같이 사용할 수 있다.

npm run build

> webpack-test@1.0.0 build /Users/yongsuek/Study/Analysis/webpack-test
> webpack

asset bundle.js 73 KiB [emitted] [minimized] (name: main) 1 related asset
runtime modules 1010 bytes 5 modules
orphan modules 734 bytes [orphan] 2 modules
cacheable modules 541 KiB
  modules by path ./src/ 1.61 KiB
    ./src/index.js + 2 modules 1020 bytes [built] [code generated]
    ./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/style.css 315 bytes [built] [code generated]
    ./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/style2.scss 320 bytes [built] [code generated]
  modules by path ./node_modules/ 540 KiB
    ./node_modules/lodash/lodash.js 531 KiB [built] [code generated]
    ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js 6.67 KiB [built] [code generated]
    ./node_modules/css-loader/dist/runtime/api.js 1.57 KiB [built] [code generated]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

webpack 5.31.2 compiled with 1 warning in 1935 ms

근데 빌드할때마다 경고가 계속 나오는데 어떤 모드로 실행할건지 옵션을 지정해주지않아서 발생한다. 따라서 모드를 develop 환경으로 설정하려고한다.

// package.json
"build": "webpack --mode development"

// 또는 webpack.config.js
module.exports = {
    mode: 'development',
}

스크립트 명령어에 --mode development 를 붙여주면된다.

file-loader

원래 이미지나 다른 파일들을 모듈로 사용하려면 css 처럼 file-loader 패키지를 설치하여 진행해야하지만 webpack5 부터는 자체 자산모듈로 세팅이 가능하다.

{
    test: /\.(png|svg|jpg|jpeg|gif)$/i,
    type: 'asset/resource',
},
...
import Profile from './profile.jpeg';

function component() {
    const el = document.createElement('div');
    const profile = new Image();
    profile.src = Profile;
    el.appendChild(profile);
    return el;
}

document.body.appendChild(component());


$ npm run build

dist 폴더에 새로운 이미지파일이 생성되었는데 파일명이 일반적인 파일명이 아닌 웹팩으로 처리된 파일명이 출력된다.

글꼴

글꼴또한 내장 모듈로 이미지파일과 동일하게 처리가능하다.

{
    test: /\.(woff|woff2|eot|ttf|otf)$/i,
    type: 'asset/resource',
},

출력관리

지금까지는 index.html 에 수동으로 파일을 관리했지만 이 파일이 늘어나고 여러가지 번들을 웹팩이 만들기 시작하면 관리하기가 매우 까다로워질 수 있다. 그래서 출력과 관련된 설정을 진행하려고한다.

// create src/print.js
export default function printMe() {
    console.log('output print.js');
}

// src/index.js
...
import printMe from './print.js';

function component() {
    ...
    const btn = document.createElement('button');
    ...

    btn.innerHTML = 'click me';
    btn.onclick = printMe;

    el.appendChild(btn);
    return el;
}

document.body.appendChild(component());
<!-- dist/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="./print.bundle.js"></script>
</head>
<body>
    <script src="./index.bundle.js"></script>
</body>
</html>
const path = require('path');

module.exports = {
  entry: {
    index: './src/index.js', // 진입점 수정
    print: './src/print.js', // 진입점 추가
  },
  output: {
    filename: '[name].bundle.js', // output 파일 네임 변경
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    ...
  }
};

위와같이 수정했을때 번들파일의 추가나 네이밍 변경사항이 생기면 index.html 에서 수동으로 수정작업을 해야만할것이다. 이걸 해결하기위해 HtmlWebpackPlugin 을 설정할 수 있다.

$ npm install --save-dev html-webpack-plugin
module.exports = {
...
  plugins: [
    new HtmlWebpackPlugin({
      title: 'Output Management',
    }),
  ],
...
}
$ npm run build
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Output Management</title>
  <meta name="viewport" content="width=device-width, initial-scale=1"><script defer src="index.bundle.js"></script><script defer src="print.bundle.js"></script></head>
  <body>
  </body>
</html>

title 태그의 내용이 바뀌었고 script 파일의 위치나 구성도 바뀐걸 볼 수있다. 즉 이제는 index.html 을 수동으로 작업해주지않아도 빌드를 통해서 자동으로 변경사항을 적용하는것이 가능해진다.

지금껏 빌드를 여러번 진행해왔다면 /dist 폴더에 사용하지않는 파일까지 쌓여있는걸 볼수있는데 이를 정리하기위해 웹팩에 코드한줄을 추가하면된다.

{
    output: {
        ...
        clean: true,
    }
}

빌드를 실행하면 오랜된파일과 사용하지않는 파일들은 정리가되고 빌드후 사용될 파일만 남고 모두 정리가된다.

댓글남기기