Build/Build

Webpack 다시 보기

iam102 2024. 3. 24. 16:05

Webpack을 사용할 때 이전에 미리 Webpack과 함께 Babel, 각종 loader들을 설정하여 만들어둔 package를 가져와 사용하고 현재는 Vite를 사용하다 보니 Webpack을 직접 다룬지 오래되어 리프레시 겸 다시 찾아보게 되었습니다. 지난번 Babel 포스팅처럼 간단히 정리하려고 합니다.

 

Webpack?

Webpack은 여러 개의 JavaScript module을 하나(또는 여러개)의 파일로 만드는 bundler입니다. 클라이언트 개발에서 module의 개념이 등장한 이후 bundler의 존재도 뗄 수가 없으며 이제는 필수가 되었습니다. 그중 Webpack은 많은 개발자로부터 선택받은 bundler입니다.

State of JS 2022 Build Tools 인지도 부분(https://2022.stateofjs.com/en-US/libraries/build-tools/)
State of JS 2022 Build Tools 사용 부분(https://2022.stateofjs.com/en-US/libraries/build-tools/)

 

Webpack Config

Webpack의 config 설정을 위해 루트 디렉토리에 webpack.config.js 파일을 생성합니다. 하나씩 config option을 설정해 보겠습니다.

※ Node v20, Webpack v5 환경에서 진행하겠습니다.

Entry

entry는 Webpack에서 bundle로 묶기 위해 여러 모듈의 디펜더시 그래프를 만드는데 이 디펜더시 그래프의 시작점을 명시하는 속성입니다. 쉽게 말해 bundle을 묶기 위한 시작점이 되는 파일명을 명시하는 속성입니다. 필요에 따라 여러 개의 시작점을 둘 수 있습니다.

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

 

Output

output은 bundle을 내보낼 위치와 파일명을 지정하는 속성입니다.

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

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.join(__dirname, '/dist'),
    filename: '[name]_[hash].js',
    clean: true,
  },
}

 

bundle을 내보낼 위치는 path에 명시하며 내보내는 bundle 파일명은 filename에 명시합니다. clean 옵션을 true로 설정할 경우 bundle 생성 시 기존에 생성된 bundle 파일을 지우고 bundle을 생성합니다.

 

Loaders

Webpack은 기본적으로 JS, JSON 파일만 인식하기 때문에 그 이외의 파일은 변환을 해줘야 합니다. 해당 변환을 위해 파일 확장자에 맞는 loader를 사용해야 하며 package 구성에 따라 loader 설정을 맞게 해줘야 합니다.

Webpack의 loader 설정에서는 변환이 필요한 파일을 식별하는 test와 변환을 수행하는 loader을 가리키는 use 속성을 기본적으로 가집니다.

 

예시로 React, TypeScript, Sass를 사용하는 환경에서 loader 설정을 해보겠습니다.

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

module.exports = {
  entry: './src/index.tsx',
  output: {
    path: path.join(__dirname, '/dist'),
    filename: '[name]_[hash].js',
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_module/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              "@babel/preset-env",
              "@babel/preset-react",
              "@babel/preset-typescript"
            ],
          }
        },
      },
      {
        test: /\.s[ac]ss$/i,
        use: [
          'style-loader',
          'css-loader',
          'sass-loader',
        ],
      },
      {
        test: /\.(png|jpe?g|webp)$/,
        type: 'asset/resource',
      },
    ],
  },
}

 

우선 ts, tsx를 변환하기 위해 babel-loader를 사용했습니다. 변환하는 과정에서 ts를 js로 변환하기 위해 @babel/preset-typescript이 필요하며 따로 타입 검사를 하지 않기 때문에 빠릅니다. babel 설정 파일에 presets을 명시하지 않았다면  options에 presets을 등록해서 사용해야 합니다. ts-loader도 사용이 가능하며 변환 시 타입 검사를 같이 하지만 이 때문에 속도가 느릴 수 있습니다. esbuild-loader도 사용이 가능하며 esbuild의 경우 Go 언어로 작성되어 있어 앞선 babel-loaderts-loader 보다 속도가 빨라 좋은 선택지가 될 수 있습니다. 프로젝트의 상황에 맞게 선택해서 사용하면 되겠습니다.

 

style sheet 변환을 위해 style-loadercss-loader가 필요하며 추가로 sass 사용 시 sass-loader를 추가해 줍니다. css-loader는 css를 읽어 js에서 사용 가능하게 변환하고 style-loadercss-loader로 읽은 css를 dom에 style 태그로 넣어줍니다.

 

asset의 경우 Webpack v5부터 url-loader, file-loader, raw-loader 로더 대신에 Webpack의 asset module을 사용합니다. asset 처리 방식에 따라 asset module을 맞게 사용하면 되겠습니다. 

 

Plugins

Webpack에서 플러그인을 사용하여 bundle 최적화, 에셋 관리, 환경 변수 주입 등 추가로 다른 작업을 수행하게 합니다. plugins도 프로젝트의 상황에 맞춰 사용하면 되겠습니다. 

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.tsx',
  output: {
    path: path.join(__dirname, '/dist'),
    filename: '[name]_[hash].js',
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_module/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              "@babel/preset-env",
              "@babel/preset-react",
              "@babel/preset-typescript"
            ],
          }
        },
      },
      {
        test: /\.s[ac]ss$/i,
        use: [
          'style-loader',
          'css-loader',
          'sass-loader',
        ],
      },
      {
        test: /\.(png|jpe?g|webp)$/,
        type: 'asset/resource',
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
}

 

예시로 사용한 html-webpack-plugin의 경우 생성된 bundle 파일을  index.html에 맵핑해주는 플러그인입니다. 추가로 사용할 플러그인이 있다면 예시처럼 추가해서 사용하면 되겠습니다.

 

Mode

Webpack이 구동될 환경을 명시하며 development, production, none이 있으며 none으로 설정 시 webpack 내부 설정에 따릅니다.

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const isProduction = process.env.NODE_ENV === 'production';

module.exports = {
  mode: isProduction ? 'production' : 'development',
  entry: './src/index.tsx',
  output: {
    path: path.join(__dirname, '/dist'),
    filename: '[name]_[hash].js',
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_module/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              "@babel/preset-env",
              "@babel/preset-react",
              "@babel/preset-typescript"
            ],
          }
        },
      },
      {
        test: /\.s[ac]ss$/i,
        use: [
          'style-loader',
          'css-loader',
          'sass-loader',
        ],
      },
      {
        test: /\.(png|jpe?g|webp)$/,
        type: 'asset/resource',
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
}

 

DevServer

개발 시 webpack-dev-server를 사용하기 위해 설치 및 설정이 필요합니다.

npm install webpack-dev-server webpack-cli --dev
# or
yarn add webpack-dev-server webpack-cli --dev
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const isProduction = process.env.NODE_ENV === 'production';

module.exports = {
  mode: isProduction ? 'production' : 'development',
  entry: './src/index.tsx',
  output: {
    path: path.join(__dirname, '/dist'),
    filename: '[name]_[hash].js',
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_module/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              "@babel/preset-env",
              "@babel/preset-react",
              "@babel/preset-typescript"
            ],
          }
        },
      },
      {
        test: /\.s[ac]ss$/i,
        use: [
          'style-loader',
          'css-loader',
          'sass-loader',
        ],
      },
      {
        test: /\.(png|jpe?g|webp)$/,
        type: 'asset/resource',
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
  devServer: {
    host: '0.0.0.0',
    port: 3000,
    hot: true,
    historyApiFallback: true,
  },
}

 

사용할 host와 port를 지정하는 것 외에도 Hot Module에 대한 설정 및 페이지 라우팅 시 컴포넌트를 동적으로 로딩하는 설정 등 여러 옵션을 추가로 설정할 수 있습니다.

 

추가사항

Webpack의 기본적인 내용과 설정에 대해 알아보았습니다. 실제로 프로젝트에서 사용 시 bundle 사이즈를 줄이기 위한 Code Spliting(Chunk, Dynamic Imports), CSS 파일 및 Resource 최적화 등을 고려해야 하고 어떤 loader를 어떻게 사용할 것인지에 대한 고민도 필요합니다. 최적화에 관련된 내용은 다음에 기회가 된다면 포스팅해 보겠습니다.

 

마무리

최근 작업 중인 프로젝트에서는 Vite를 사용하다 보니 번들러를 세세하게 설정할 일이 많이 없었습니다. 하지만 오랜만에 Webpack을 설정하면서 번들러가 동작하는 방식이나 효율에 대해 고민하는 시간을 가지며 실제 작업 시 다른 번들러를 사용하더라도 최적화나 빌드 시간에 대한 효율을 높일 수 있도록 계속해서 찾아보며 공부해야겠습니다. 해당 포스팅과 관련하여 작성된 package 깃허브 링크 첨부하며 마무리하겠습니다.


참고 자료

https://webpack.kr/concepts/