# Webpack 구성 파일

## 구성 파일  <a href="#configuration" id="configuration"></a>

Webpack 구성 파일을 프로젝트 루트(root) 위치에 생성하고 진입, 진출 파일 경로, [모드](https://yamoo9.gitbook.io/webpack/webpack/config-webpack-dev-environment/webpack-mode) 등을 설정합니다.

{% tabs %}
{% tab title="webpack.config.js" %}

```javascript
// Node.js 모듈 path 불러오기
const path = require('path')

// CommonJS 방식의 모듈 내보내기
module.exports = {
  // 엔트리 파일 설정
  entry: './src/index.js',
  // 아웃풋 파일 출력 설정
  output: {
    // 파일 이름
    filename: 'main.js',
    // 경로
    path: path.resolve(__dirname, './dist'),
  },
  // 번들링 모드 설정
  mode: 'development',
}
```

{% endtab %}
{% endtabs %}

## 모듈 번들링 <a href="#bundling-modules" id="bundling-modules"></a>

`webpack` 명령을 터미널에 입력해 모듈 번들링을 수행합니다.

```bash
npx webpack
```

{% hint style="danger" %}
만약 명령 실행 후, 다음과 같은 오류 메시지가 출력된다면? 출력 경로 설정이 잘못된 것입니다.

```bash
[webpack-cli] 
# 잘못된 구성 객체입니다.
Invalid configuration object. 
# Webpack API 스키마(Schema)와 일치하지 않는 구성 객체를 사용해 초기화 되었습니다.
Webpack has been initialized using a configuration object that does not match the API schema.
   # 구성.출력.경로 설정인 './dist' 공급 값은 절대 경로가 아닙니다! 
 - configuration.output.path: The provided value "./dist" is not an absolute path!
      # 출력 디렉토리 설정은 반드시 **절대 경로**로 처리해야 합니다.
   -> The output directory as **absolute path** (required).
```

이 문제는 출력 경로 설정을 다음과 같이 절대 값으로 설정하면 손쉽게 해결할 수 있습니다.

```javascript
path: path.resolve(__dirname, './dist'),
```

{% endhint %}

## NPM 스크립트 <a href="#npm-script" id="npm-script"></a>

&#x20;프로젝트 구성 파일의 `scripts` 항목에 `bundle` 명령을 추가 등록한 다음 값으로 `webpack` 명령을 추가합니다.

{% tabs %}
{% tab title="package.json" %}

```javascript
"scripts": {
  "bundle": "webpack"
}
```

{% endtab %}
{% endtabs %}

&#x20;그러면 터미널에서 NPM 명령 `bundle`로 Webpack 모듈 번들링을 수행할 수 있습니다.

```bash
npm run bundle
```

## 모드 별, 구성 파일 <a href="#config-by-mode" id="config-by-mode"></a>

구성할 내용이 많아지고, [모드](https://yamoo9.gitbook.io/webpack/webpack/config-webpack-dev-environment/webpack-mode)에 따라 조건 처리해야 할 경우 구성 파일은 상당히 복잡해집니다. 이런 경우 개발용, 배포용 구성 파일을 각각 만들어 효율적으로 관리할 수도 있습니다.

{% tabs %}
{% tab title="디렉토리 구조" %}

```bash
.
├── config/
│   ├── webpack.dev.js  # 개발용 Webpack 구성 파일
│   └── webpack.prod.js # 배포용 Webpack 구성 파일
├── src/
├── package-lock.json
└── package.json
```

{% endtab %}

{% tab title="config/webpack.dev.js" %}

```javascript
const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

/* -------------------------------------------------------------------------- */

module.exports = {
  // 입력 파일
  entry: {
    main: './src/index.js',
  },

  // 출력 파일
  output: {
    path: path.resolve(__dirname, '../dist'),
    // 파일 이름 브라우저 캐싱 처리
    filename: '[name].js',
  },

  // 감시 설정 (package.json 스크립트로 제어)
  // watch: true,

  // 모드 설정
  mode: 'development',

  // 소스맵 설정
  devtool: 'inline-source-map',

  // 모듈 설정
  module: {
    // 로더 규칙
    rules: [
      // 이미지 파일 로더
      {
        test: /\.(png|jpe?g|gif|svg|webp)$/i,
        use: {
          loader: 'file-loader',
          options: {
            name: '[name].[ext]',
          },
        },
      },
      // 스타일 파일 로더
      {
        test: /\.(sa|sc|c)ss$/i,
        exclude: /node_modules/,
        use: ['style-loader', 'css-loader', 'sass-loader'],
      },
      // 스크립트 파일 로더
      {
        test: /\.js$/i,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env', '@babel/preset-react'],
            plugins: ['@babel/plugin-proposal-class-properties'],
            sourceMap: true,
          },
        },
      },
    ],
  },

  // 플러그인 설정
  plugins: [
    // 환경 변수 등록
    new webpack.EnvironmentPlugin({
      NODE_ENV: 'development',
    }),
    // 빌드 된 결과물 정리
    new CleanWebpackPlugin(),
    // 빌드 결과 HTML 문서 자동 주입
    new HtmlWebpackPlugin({
      // 템플릿 설정
      template: './src/template/template.ejs',

      // 자동 주입 해제
      inject: false,

      // 압축 설정
      minify: true,

      // 문서 메타
      meta: {
        'theme-color': '#4285f4',
        'description': 'Webpack 러닝 가이드 실습을 진행합니다.',
      },

      // 사용자 정의 옵션
      templateParameters: {
        title: 'Webpack 러닝 가이드',
        lang: 'ko-KR',
      },
    }),
  ],
}
```

{% endtab %}

{% tab title="config/webpack.prod.js" %}

```javascript
const os = require('os')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const CSSMQPackerPlugin = require('css-mqpacker-webpack-plugin')
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin')

// 개발 모드 구성 가져오기
const devConfig = require('./webpack.dev')

// 배포 모드 구성 설정
const prodConfig = {
  entry: {
    ...devConfig.entry,
    'polyfills': './src/polyfills/polyfills.js',
    'detect.polyfills': './src/polyfills/detect.polyfills.js',
  },
  output: {
    ...devConfig.output,
    filename: '[name].[contenthash].js',
    publicPath: '',
  },
  module: {
    rules: [
      ...devConfig.module.rules.filter((rule) => {
        if (Array.isArray(rule.use)) {
          return !rule.use.includes('css-loader')
        }
        return rule
      }),
      // MiniCssExtractPlugin.loader 설정 (대체)
      {
        test: /\.(sa|sc|c)ss$/i,
        exclude: /node_modules/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
      },
    ],
  },
  plugins: [
    ...devConfig.plugins,
    // CSS 추출
    new MiniCssExtractPlugin({
      linkType: false,
      filename: '[name].[contenthash].css',
      chunkFilename: '[id].[contenthash].css',
    }),
    // PNG — 이미지 포멧 최적화
    new ImageMinimizerPlugin({
      minimizerOptions: {
        plugins: ['pngquant'],
      },
    }),
    // WEBP — 이미지 포멧 최적화
    new ImageMinimizerPlugin({
      deleteOriginalAssets: true,
      filename: '[name].webp',
      minimizerOptions: {
        plugins: ['imagemin-webp'],
      },
    }),
    // JPE?G — 이미지 포멧 최적화
    new ImageMinimizerPlugin({
      // 약 8kb 이상 파일만 최적화 적용
      filter: (source) => (source.byteLength >= 8192 ? true : false),
      minimizerOptions: {
        plugins: [['jpegtran', { progressive: true }]],
      },
    }),
    new ImageMinimizerPlugin({
      // 약 8kb 미만 파일만 최적화 적용
      filter: (source) => (source.byteLength < 8192 ? true : false),
      minimizerOptions: {
        plugins: [['jpegtran', { progressive: false }]],
      },
    }),
  ],
}

/* -------------------------------------------------------------------------- */

module.exports = {
  entry: prodConfig.entry,
  output: prodConfig.output,
  mode: 'production',
  devtool: 'source-map',

  // 모듈 설정
  module: prodConfig.module,

  // 플러그인 설정
  plugins: prodConfig.plugins,

  // 최적화 설정
  optimization: {
    // 압축
    minimize: true,
    // 미니마이저
    minimizer: [
      // 플러그인 인스턴스 생성
      new CssMinimizerPlugin({
        // CPU 멀티 프로세서 병렬화 옵션 (기본 값: true)
        // os.cpus() → CPU 코어 정보를 포함하는 배열 객체 반환
        parallel: os.cpus().length - 1,
      }),
      // 미디어쿼리 그룹 병합
      new CSSMQPackerPlugin({
        sort: true,
      }),
    ],
  },
}
```

{% endtab %}

{% tab title="webpack.backup.config.js" %}

```javascript
const os = require('os')
const path = require('path')
const webpack = require('webpack')

const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const CSSMQPackerPlugin = require('css-mqpacker-webpack-plugin')
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

/* -------------------------------------------------------------------------- */

// 개발 모드 감지
const isDevMode = process.env.NODE_ENV.includes('dev')

/* -------------------------------------------------------------------------- */

// 개발,배포 모든 모드에서 사용되는 Webpack 플러그인 목록(배열)
const plugins = [
  // 환경 변수 등록
  new webpack.EnvironmentPlugin({
    // process.env.NODE_ENV가 정의되지 않은 경우, 'development' 사용
    NODE_ENV: 'development',
  }),
  // 빌드 된 결과물 정리
  new CleanWebpackPlugin(),
  // 빌드 결과 HTML 문서 자동 주입
  new HtmlWebpackPlugin({
    // 템플릿 설정
    template: './src/template/template.ejs',

    // 자동 주입 해제
    inject: false,

    // 압축 설정
    minify: true,

    // 문서 타이틀

    // 문서 메타
    meta: {
      'theme-color': '#4285f4',
      'description': 'Webpack 러닝 가이드 실습을 진행합니다.',
    },

    // 사용자 정의 옵션
    templateParameters: {
      title: 'Webpack 러닝 가이드',
      lang: 'ko-KR',
    },
  }),
]

// 개발,배포 모든 모드에서 사용되는 엔트리 파일 설정
let entry = {
  main: './src/index.js',
}

// 배포 모드 설정
if (!isDevMode) {
  // 배포용 엔트리 파일(들) 추가
  entry = {
    ...entry,
    'polyfills': './src/polyfills/polyfills.js',
    'detect.polyfills': './src/polyfills/detect.polyfills.js',
  }

  // 배포용 플러그인 추가
  plugins.push(
    // CSS 추출
    new MiniCssExtractPlugin({
      linkType: false,
      filename: '[name].[contenthash].css', // 'style.css' 설정도 가능
      chunkFilename: '[id].[contenthash].css',
    }),
    // PNG — 이미지 포멧 최적화
    new ImageMinimizerPlugin({
      minimizerOptions: {
        plugins: ['pngquant'],
      },
    }),
    // WEBP — 이미지 포멧 최적화
    new ImageMinimizerPlugin({
      deleteOriginalAssets: true,
      filename: '[name].webp',
      minimizerOptions: {
        plugins: ['imagemin-webp'],
      },
    }),
    // JPE?G — 이미지 포멧 최적화
    new ImageMinimizerPlugin({
      // 약 8kb 이상 파일만 최적화 적용
      filter: (source) => {
        if (source.byteLength >= 8192) {
          return true
        }

        return false
      },
      minimizerOptions: {
        plugins: [['jpegtran', { progressive: true }]],
      },
    }),
    new ImageMinimizerPlugin({
      // 약 8kb 미만 파일만 최적화 적용
      filter: (source) => {
        if (source.byteLength < 8192) {
          return true
        }

        return false
      },
      minimizerOptions: {
        plugins: [['jpegtran', { progressive: false }]],
      },
    })
  )
}

/* -------------------------------------------------------------------------- */

module.exports = {
  // 입력 파일
  entry,

  // 출력 파일
  output: {
    path: path.resolve(__dirname, './dist'),
    // 파일 이름 브라우저 캐싱 처리
    filename: '[name].[contenthash].js',
    // publicPath: 'dist/',
  },

  // 감시 설정 (package.json 스크립트로 제어)
  // watch: true,

  // 모드 설정
  mode: isDevMode ? 'development' : 'production',

  // 소스맵 설정
  devtool: isDevMode ? 'inline-source-map' : 'source-map',

  // 플러그인 설정
  plugins,

  // 모듈 설정
  module: {
    // 로더 규칙
    rules: [
      // 이미지 파일 로더
      {
        test: /\.(png|jpe?g|gif|svg|webp)$/i,
        use: {
          loader: 'file-loader',
          options: {
            // name: '[name].[ext]',
            name: '[name].[contenthash].[ext]',
          },
        },
      },
      // 스타일 파일 로더
      {
        test: /\.(sa|sc|c)ss$/i,
        exclude: /node_modules/,
        use: [
          isDevMode ? 'style-loader' : MiniCssExtractPlugin.loader,
          'css-loader',
          'sass-loader',
        ],
      },
      // 스크립트 파일 로더
      {
        test: /\.m?js$/i,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env', '@babel/preset-react'],
            plugins: ['@babel/plugin-proposal-class-properties'],
            sourceMap: true,
          },
        },
      },
    ],
  },

  // 최적화 설정
  optimization: {
    // 압축
    minimize: isDevMode ? false : true,
    // 미니마이저
    minimizer: [
      // 플러그인 인스턴스 생성
      new CssMinimizerPlugin({
        // CPU 멀티 프로세서 병렬화 옵션 (기본 값: true)
        // os.cpus() → CPU 코어 정보를 포함하는 배열 객체 반환
        parallel: os.cpus().length - 1,
      }),
      // 미디어쿼리 그룹 병합
      new CSSMQPackerPlugin({
        sort: true,
      }),
    ],
  },
}
```

{% endtab %}
{% endtabs %}

## 참고 <a href="#reference" id="reference"></a>

{% embed url="<https://webpack.js.org/guides/getting-started/#using-a-configuration>" %}
사용자 정의 구성 파일
{% endembed %}
