미새문지

24.10.11 day78 모듈 번들러 본문

개발 TIL

24.10.11 day78 모듈 번들러

문미새 2024. 10. 11. 21:43
728x90

모듈 번들러

모듈 번들러는 현대 웹 개발에서 매우 중요한 도구로, 모듈화된 자바스크립트 파일들을 하나의 최적화된 파일로 묶는 역할을 한다.

여기서 모듈이란, 여러 기능들에 관한 코드가 모여있는 하나의 파일이며, 이를 서로 조합해 더 큰 프로그램을 만들 수 있다.

 

모듈의 사용처

  • 유지보수성 : 기능들이 모듈화가 잘 되어있다면, 의존성을 그만큼 줄일 수 있기 때문에 어떤 기능을 개선한다거나 수정할 때 훨씬 편하게 할 수 있다.
  • 네임스페이스화 : 자바스크립트에서 전역변수는 전역공간을 가지기 때문에 코드의 양이 많아질수록 겹치는 네임스페이스가 많아질 수 있지만, 모듈로 분리하면 모듈만의 네임스페이스를 갖기 때문에 그 문제가 해결된다.
  • 재사용성 : 똑같은 코드를 반복하지 않고 모듈로 분리시켜서 필요할 때마다 사용할 수 있다.

예전엔 자바스크립트의 공식 스펙이 브라우저만 지원했었다고 한다. 그걸 데스크탑 애플리케이션이나 서버사이드 자바스크립트인 node.js에서 지원하기 위해 노력했다는데, 그렇게 만들어진게 CommonJS이다.

 

CommonJS의 특징

require 함수

 

  • 모듈을 가져오기 위한 함수이며 require('모듈이름')의 형태로 다른 파일에 정의된 모듈을 불러올 수 있다.
  • 예를 들어, math.js라는 파일에 정의된 함수를 불러올 때 이렇게 사용할 수 있다.
// math.js
function add(a, b) {
  return a + b;
}

module.exports = add;

// main.js
const add = require('./math');
console.log(add(2, 3)); // 5

 

 

module.exports

  • 현재 모듈에서 내보낼 내용을 정의하는 객체이며, 모듈을 외부에서 사용할 수 있도록 내보내기 위해 module.exports를 사용한다.
  • 위 예제에서 module.exports = add;로 add함수를 다른곳에서 사용할 수 있게 내보내고 있다.
  • CommonJS는 모듈별로 module.exports 객체가 존재하며, 이를 통해 외부에서 해당 모듈의 기능을 사용할 수 있다.

동기 방식

  • CommonJS는 모듈을 동기적으로 불러오는데, 즉, require 함수가 호출될 때 해당 모듈이 즉시 로드되고 실행된다.
  • 이 동작 방식은 서버 환경(Node.js)에서는 문제가 없지만, 브라우저에서는 비동기 로드가 필요한 경우에는 적합하지 않을 수 있다.

로컬 범위

  • CommonJS 모듈에서 정의된 변수나 함수는 해당 모듈 내부에서만 유효하며, 다른 모듈과의 충돌을 방지할 수 있다.
  • 이는 모듈 간에 전역 네임스페이스를 오염시키지 않으면서 각 모듈의 코드를 안전하게 분리할 수 있다.

 

 

모듈 번들러가 해결하는 문제들

의존성 관리

  • 웹 애플리케이션을 개발할 때 여러 모듈이 서로 의존하는 경우가 많다.
  • 예를 들어, 모듈 A가 모듈 B에 의존하고, B는 C에 의존하는 식으로 이어질 수 있는데, 이 때 각 모듈이 정확한 순서로 로드되지 않으면 코드가 제대로 동작하지 않는다.
  • 이에 모듈 번들러는 이러한 의존성 관계를 자동으로 분석하고 올바른 순서로 번들링해주는 역할을 해준다.

HTTP 요청 최적화

  • 브라우저가 각 모듈 파일을 개별적으로 요청하면, 수많은 HTTP 요청이 발생해 네트워크 성능에 부담을 주는데, 모듈 번들러는 여러 개의 모듈을 하나의 파일로 합쳐서 한번에 요청하기 때문에 HTTP 요청 수를 줄이고 성능을 개선할 수 있다.
  • 이러한 방식을 번들링이라고 한다.

ES6+ 스펙 지원

  • 최신 자바스크립트(ES6+ 스펙)에는 import, export와 같은 모듈 시스템이 포함되어 있지만, 이전 버전인 구형 브라우저는 이를 지원하지 않을 수 있다.
  • 모듈 번들러는 이러한 최신 문법을 구형 브라우저에서도 동작할 수 있도록 트랜스파일링(babel과 같은 도구를 사용해 ES5로 변환)을 지원해준다.

모듈 번들러는 단순히 파일을 하나로 합치는 것 외에도 다양한 기능을 제공하는데,

코드에서 불필요한 공백이나 주석을 제거하고, 변수명을 축약하여 파일 크기를 줄여주는 최소화

실제로 사용되지 않는 코드를 번들에서 제거해 최적화해주는 트리쉐이킹

이미지, 폰트, CSS와 같은 비자바스크립트 리소스들도 번들에 포함하고 최적화할 수 있게 처리해주는 추가 기능들이 있다.

 

대표적인 모듈 번들러

대표적인 모듈 번들러에는 Webpack, Parcel, Rollup 등이 있다.

 

여기서 webpack은 리액트로 개발할 때 필수적인 도구 중 하나이며, 프로젝트를 효율적으로 관리하고 빌드하는 모듈 번들러이다. 기본적인 리액트 프로젝트를 설치할때는 webpack이 알아서 설치되어 세팅되서 나오지만, 대규모 프로젝트나 직접적인 세팅이 필요할 경우 리액트 라이브러리만 따로 설치하고 webpack을 직접 세팅해야 한다고 하더라.

리액트는 컴포넌트 기반의 구조로 많은 파일들이 상호 의존관계를 가지는데, webpack은 이러한 파일들을 플러그인과 로더를 사용해 처리해준다. 

 

parcel은 빠르고 간편한 모듈 번들러로, 설정 없이도 작동하는 "제로 설정"이란게 특징이라고 한다. 

parcel은 복잡한 설정 파일 없이 바로 사용 가능하기 때문에, 제로 설정은 파일 유형을 자동으로 감지하여 적절한 로더를 적용시키는 parcel의 특징이다.

특히 parcel이 빠른 이유는 내부적으로 병렬 처리와 파일 캐싱을 지원해 빌드 속도가 매우 빠르기 때문인데, 초기 빌드 뿐만 아니라 이 후 변경 사항이 있어도 빠르게 처리해준다.

그리고 parcel은 HMR이라고 변경된 코드만 즉시 반영할 수 있는 Hot Module Replacement 기능을 지원하여, 개발 중 리프레시 없이 변경된 부분을 빠르게 확인할 수 있다.

이러한 이유로 parcel은 복잡한 설정 없이 빠르게 프로젝트를 시작하고 싶은 개발자들에게 적합한 도구이다.

 

rollup은 라이브러리나 패키지를 번들링하는 데 특화된 도구이며, 특히 트리 쉐이킹 기능이 강력해, 불필요한 코드를 최소화하는 데 유리한 모듈 번들러이다.

ES모듈을 효율적으로 처리하며, 가벼운 번들을 생성하고 트리셰이킹을 통해 불필요한 코드를 줄이는 것이 장점이다.

webpack이 세팅이 까다로워서 그런지 그에 비해 rollup은 프로젝트 세팅이나 플러그인 관리가 단순하며 라이브러리 패키징에 특화된 도구라고 볼 수 있다.

그래서 rollup은 주로 라이브러리 개발자가 많이 사용하는 경향이 있다.

 

동작 방식

모듈 번들러의 동작 방식은 세 가지로 분류할 수 있다.

  1. 엔트리 포인트(Entry Point): 번들러가 시작할 파일을 지정하며, 이 파일은 애플리케이션의 진입점으로, 해당 파일에서 다른 모든 의존 모듈을 추적해 번들링을 시작한다.
  2. 그래프 생성: 번들러는 엔트리 포인트에서 시작해 각 모듈 간의 의존성을 그래프로 만들어 추적하며, 이 때 각 모듈의 import/require를 분석해 모든 모듈의 관계를 파악한다
  3. 번들 생성: 모듈 그래프를 바탕으로 하나의 파일로 합치거나, 필요에 따라 여러 개의 파일로 나누어 최적화된 번들을 생성하는데, 번들러는 이 과정에서 미니피케이션, 트리 쉐이킹 등 다양한 최적화 작업도 수행한다.

 

리액트에서 웹팩을 작성하려면 일단 리액트 프로젝트를 생성해주는 커맨드를 쓰면 안된다.

기존에 쓰던 npx create-react-app이 아니라 직접 폴더를 만들고 그 안에서 초기화를 시켜줘야 한다.

mkdir my-react-app
cd my-react-app
npm init -y

 

리액트의 웹팩과 관련된 패키지들을 설치해주는데, 전부 웹팩 실행을 위한 플러그인과 로더들이다.

npm install react react-dom
npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev
npm install babel-loader @babel/core @babel/preset-env @babel/preset-react --save-dev
npm install style-loader css-loader --save-dev
npm install file-loader --save-dev

 

이 후 webpack.config.js파일을 생성해주면 된다. 이건 지피티가 짜준 웹팩의 기본 예시이다.

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

module.exports = {
  entry: './src/index.js', // 리액트 앱의 진입 파일
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    clean: true, // 빌드 시 이전 번들 파일 제거
  },
  mode: 'development', // 개발 모드 (배포 시에는 'production'으로 변경)
  module: {
    rules: [
      {
        test: /\.js$/, // .js 파일을 Babel을 사용해 트랜스파일
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
      {
        test: /\.css$/, // CSS 파일 로더 설정
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.(png|jpg|gif|svg)$/, // 이미지 파일 로더 설정
        use: {
          loader: 'file-loader',
          options: {
            name: '[name].[hash].[ext]',
            outputPath: 'images',
          },
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html', // 템플릿 HTML 파일 설정
      filename: 'index.html',
    }),
  ],
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    port: 3000, // 개발 서버 포트
    hot: true, // 핫 모듈 리플레이스먼트(HMR) 활성화
  },
  resolve: {
    extensions: ['.js', '.jsx'], // 리액트 파일 확장자 처리
  },
};

 

이제 리액트 코드인 JSX를 변환하기 위해 바벨 설정을 추가해줘야 한다.

프로젝트 루트 폴더에 .babelrc를 생성해서 코드를 추가해준다.

{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}

이건 JSX를 변환하는 바벨 프리셋을 적용시켜주는건데, 여기서 바벨이란 최신 자바스크립트(ES6+)와 JSX 같은 문법을 구형 브라우저에서도 이해할 수 있도록 변환해주는 자바스크립트 컴파일러이다.

리액트와 같은 최신 프론트엔드 프레임워크는 ES6+의 새로운 문법이나 JSX와 같은 확장 문법을 사용하기 때문에, 이를 브라우저에서 제대로 실행하려면 트랜스파일링 과정이 필요한데 여기서 바벨이 이를 해결해준다.

 

이 후 기본적으로 존재했었던 app.js와 index.js같은 기본 세팅 파일이 없기 때문에 싹 다 만들어줘야 한다.

만들었으면 해당 프로젝트에서 웹팩을 실행해야 하는데, 이를 위해 package.json의 scripts부분에 웹팩 실행 스크립트를 추가해준다.

{
  "scripts": {
    "start": "webpack serve --mode development",
    "build": "webpack --mode production"
  }
}

 

그리고 npm start를 하면 해당 코드를 컴파일해준 후 브라우저로 전달해 화면이 제대로 출력되는 것 같다.

728x90