안녕하세요! 👋 서운입니다!


1. 서론 – 왜 알아야 하나? 🙄

📖 번들링/트랜스파일링을 처음 듣는 사람에게 주는 정의

퍼블리싱만 하던 시절, 제가 처음 공부한 프레임워크는 Vue였습니다.
그때는 회사 동료의 권유로 강의를 듣기 시작했는데… 솔직히 집중도는 낮았고, CS 전공자가 아니었기에 프레임워크가 어떻게 동작하는지 전혀 알지 못했죠.

그냥 “남들이 쓰니까 나도 써야지” 수준이었고,
내부에서 어떤 일이 벌어지는지는 관심도 없었습니다.

지금은 그나마
.vue나 .tsx 파일이 번들러를 거쳐 예쁘게 정리돼서 브라우저에 뜨는 거 아닐까?”
정도는 짐작하지만,
누군가 “번들링이 뭔지 설명해봐”라고 한다면 막막합니다.

그래서 이번에 번들링과 트랜스파일링을 정리해서
“왜 알아야 하는지, 어떻게 동작하는지”를 확실히 해두려 합니다.


📌 비유로 이해하는 번들링/트랜스파일링

  • 번들링(Bundling): 여러 개의 파일을 하나로 포장하는 작업
    → 쇼핑한 물건을 장바구니에 담아 한 봉투로 묶는 것
    → 수많은 JS/CSS/이미지 파일을 묶어 브라우저가 한 번에 가져오게 함

  • 트랜스파일링(Transpiling): 최신 언어를 옛날 언어로 번역하는 작업
    → 최신 JS 문법(let, async/await)을 구형 브라우저도 알아듣는 문법으로 변환
    → 현대 한국어를 옛날 한글체로 번역하듯 의미는 같지만 형태를 바꾸는 것


제 나름대로 한문장으로 정의가 되게끔 해서 정리를 했습니다.
결국

  • 최신 문법으로 작성한 코드가 구형 브라우저에서도 동작하는 이유
  • 수십 개의 JS 모듈이 하나의 파일로 합쳐져 배포되는 이유
  • 개발 서버가 코드 수정 즉시 화면에 반영되는 이유
    이 모든 것의 핵심에는 번들링(bundling)트랜스파일링(transpiling) 이 있기 떄문에 번들링/트랜스파일링을 알아야한다고 생각합니다.

💻 “npm install 하고 build 돌리면 끝 아닌가?” → 하지만 그 과정 속에서 일어나는 일

우리가 만약 모회사에 초반입사를 했다고 가정을 해볼게요.
github에 올라가 있는 코드를 받고 보통의 프론트엔드개발이면 해당 코드를 local서버에서 돌릴수 있게 해당 디렉토리위치에서 터미널 명령어 npm install을 입력하게 될겁니다.(패키지 매니저는 npm/pnpm/yarn이 대체 가능, 참고: npx는 패키지 실행 용도)

그 후 일어나는 일들을 순차적으로 정리를 한번 했어요!

1. npm CLI가 package.json을 읽는다

  • 누가? → npm CLI(Node 설치 시 함께 제공되는 커맨드라인 프로그램)
  • 어떻게? → 현재 작업 디렉토리(process.cwd())에서 package.json을 찾고 JSON을 파싱
  • 무엇을? → "dependencies", "devDependencies"로 설치 대상과 버전 범위를 수집

2. 의존성 메타데이터 수집

  • npm 레지스트리에서 각 패키지의 정확한 버전/무결성 해시/하위 의존성 정보를 가져옴
  • 여기에 전이 의존성(transitive deps) 정보가 포함됨

3. 의존성 트리 생성 → 디스크에 반영

  • 먼저 메모리에서 루트 → 1차 → 2차 … 형태의 의존성 트리(설치 계획) 을 계산
  • 설치가 진행되면 디스크 구조로 반영됨
node_modules/
├── react/                # 1차 의존성
├── axios/                # 1차 의존성
│   └── node_modules/
│       └── follow-redirects/  # 2차 의존성
  • 중복 버전은 최대한 합치고(deduplication), 충돌 나는 버전은 분리해 저장
  • 최종 결과를 package-lock.json에 기록(정확 버전/무결성/의존 관계 고정 → 재현 가능한 설치)

4. 패키지 다운로드 및 설치 + 훅 실행

npm 캐시나 레지스트리에서 .tgz(압축 패키지) 파일을 다운로드합니다.
압축을 풀어서 node_modules/ 안에 설치합니다.
설치 도중에 "scripts" 속성에 정의된 preinstall, install, postinstall 스크립트가 있으면 실행됩니다.

위 내용을 간단하게 아래로 정리해볼수 있을 것 같습니다.

5. 💡 npm install → build 속에서 일어나는 일

  • 엔트리 탐색: src/main.ts 등 진입점에서 의존성 그래프 생성
  • 트랜스파일: TS/JSX/ESNext/SASS 등을 브라우저/런타임 호환 코드로 변환(Babel/tsc 등)
  • 번들링: 모듈들을 하나(또는 몇 개)의 번들 파일로 묶음(webpack/rollup/esbuild 등)
  • 최적화: Tree Shaking, 압축(minify), 코드 스플리팅, asset 최적화
  • 출력: dist/ 또는 build/ 폴더에 배포 가능한 산출물 생성

🔎 정리

  1. npm install
    • package.json 파싱 → 레지스트리 메타데이터 조회 → 의존성 트리 계산
    • node_modules/에 설치, package-lock.json에 트리 고정
    • 설치 스크립트 훅 실행
  2. npm run build
    • 엔트리 기준 의존성 그래프 → 트랜스파일 → 번들링 → 최적화 → dist/ 출력
  3. 트랜스파일링과 번들링관련해서 간단하게 정리를 해봤는데요 1챕터에서는 간단하게만 정리해보고 아래에서 해당내용에 대해 좀더 자세히 살펴보도록 하겠습니다.

npm install 후 과정


🔍 실제 예시: ES6 코드를 구형 브라우저에서도 동작하게 만드는 과정

예전 회사에서 퍼블리싱을 할 때는 외주 업체 요구사항에 맞춰 구형 브라우저 호환성을 직접 챙겨야 했습니다.
하지만 프레임워크를 사용하면 이런 작업을 트랜스파일링이 대신 처리해주기 때문에, 개발자가 일일이 맞출 필요가 줄어듭니다.

즉 현대 자바스크립트(ES6+) 문법은 최신 브라우저에서는 잘 동작하지만,
구형 브라우저(예: IE11)에서는 실행되지 않기 때문에 그 역할을 해주는 것이 트랜스파일링 입니다.

아래는 우리가 평소에 쓰는 ES6코드가 어떻게 변환되는지 과정을 통한 예시 입니다.

1) 원본 코드 (ES6+)

// ES6 화살표 함수 & let
let numbers = [1, 2, 3];
let doubled = numbers.map(n => n * 2);

console.log(doubled); // [2, 4, 6]

👉 최신 크롬, 파이어폭스에서는 문제없이 실행됩니다.
하지만 IE11에서는 화살표 함수(=>), let을 이해하지 못해 에러가 납니다.


2) Babel로 트랜스파일링된 코드 (ES5)

"use strict";

var numbers = [1, 2, 3];
var doubled = numbers.map(function (n) {
  return n * 2;
});

console.log(doubled); // [2, 4, 6]
  • let → var 로 변환
  • 화살표 함수 n => n 2 → 일반 함수 function(n) { return n 2; }

👉 이렇게 변환되면 구형 브라우저도 이해 가능합니다.

3) 필요한 경우 Polyfill까지 추가

ES6의 Promise, Array.includes 같은 기능은 단순히 문법 변환만으로는 안 됩니다.
이럴 땐 polyfill을 주입해야 합니다.

import "core-js/stable";
import "regenerator-runtime/runtime";

👉 이런 polyfill 덕분에, 브라우저가 모르는 기능도 사용할 수 있게 됩니다.

📖 여기서 Polyfill이란?

트랜스파일링은 문법만 변환합니다.
하지만 Promise, Array.includes처럼 아예 기능 자체가 없는 경우에는 트랜스파일링만으로 해결되지 않습니다.

이럴 때 필요한 것이 바로 Polyfill입니다.
Polyfill은 브라우저나 실행 환경이 아직 지원하지 않는 기능을 흉내 내어 동작하게 해주는 코드 조각입니다.

말 그대로 “틈새를 메우는 폼(Poly-fill)” 역할을 합니다.
최신 자바스크립트 기능이 구형 브라우저에서는 없을 수 있는데, 그 “빈 공간”을 채워서 마치 원래 있었던 것처럼 쓸 수 있게 해주는 거죠.

🔍 예시 1: Array.includes

최신 JS에는 includes 메서드가 있지만, IE 같은 구형 브라우저는 이걸 모릅니다.

// 최신 문법
['a', 'b', 'c'].includes('b'); // true

👉 IE11에서는 에러 발생
Polyfill을 추가하면, includes가 없는 환경에서 직접 구현해줍니다

if (!Array.prototype.includes) {
  Array.prototype.includes = function (search) {
    return this.indexOf(search) !== -1;
  };
}

→ 이제 구형 브라우저에서도 includes를 쓸 수 있음.

🔍 예시 2: Promise

IE11에는 Promise 객체가 없습니다.
Polyfill을 추가하면 다음처럼 구현된 코드가 자동으로 삽입됩니다:

import "core-js/stable";
import "regenerator-runtime/runtime";

→ 이렇게 하면 Promise, async/await 같은 기능이 IE11에서도 동작합니다.


📌 정리

  • 트랜스파일링 : 최신 문법을 구형 문법으로 변환 → 브라우저 호환성 확보 (예: let → var)
  • Polyfill : 브라우저가 아예 모르는 기능을 흉내 내서 추가해주는 것 (예: Promise, includes)
  • 이 과정 덕분에 npm run build로 뽑아낸 번들이 모든 브라우저에서 안전하게 실행됩니다.
  • 둘 다 합쳐야 완전한 호환성 확보 가능


2. 모듈 시스템의 이해

프론트엔드 코드가 커지면, 파일을 여러 개로 나눠 관리해야 합니다.
이때 서로 다른 파일 간 코드를 어떻게 불러오고, 실행 시 묶을지 정해주는 방식이 바로 모듈 시스템 입니다.

CommonJS(CJS) vs ES Module(ESM)

CommonJS(CJS), ES Module(ESM) 왜 비교할까요?

1. 역사적인 맥락

  • Node.js 초창기에는 모듈 시스템이 없었음 → CommonJS가 사실상 표준으로 굳어졌죠.
  • 이후 브라우저 표준으로 ESM이 등장하면서, 지금은 두 체계가 공존하고 있어요.
  • 그래서 우리가 쓰는 npm 패키지들도 어떤 건 CJS, 어떤 건 ESM으로 배포돼요.

2. 실무에서의 혼란

  • React 같은 프레임워크 패키지를 보면, 어떤 파일은 require/module.exports, 또 다른 파일은 import/export로 되어 있어요.
  • 그래서 프로젝트 세팅 시 type: "module" 옵션, Babel, 번들러 설정이 필요해지는 거예요.
  • 즉,"왜 import 했는데 에러가 나고 require 하니까 되는 거야?"같은 상황을 이해하려면 비교가 꼭 필요합니다.

3. 번들러와의 연결 고리

  • 번들러(Webpack, Vite)는 CJS와 ESM 양쪽 형식을 모두 지원해야 함
  • 우리가 npm install로 가져오는 패키지가 CJS일 수도, ESM일 수도 있기 때문이죠
  • 따라서 CJS ↔ ESM 차이를 알아야 번들러가 왜 필요한지도 자연스럽게 이해됩니다.

CommonJS와 ES Module을 비교하는 이유는 단순히 문법 차이를 아는 데 그치지 않습니다.
두 방식은 자바스크립트 역사 속에서 차례로 등장했고, 지금도 공존하고 있기 떄문에 실무에서 혼란의 원인이 됩니다.

또한 우리가 사용하는 패키지와 번들러가 왜 이런 두 형식을 모두 지원해야 하는지 이해하려면, 이 비교가 꼭 필요합니다.


📦 CommonJS (CJS)란?

  • Node.js 초기(2009년 즈음)에 만들어진 자바스크립트 모듈 표준 제안 형식 중 하나
  • 당시 브라우저에는 모듈 시스템이 없어서, “서버(Node.js)에서 모듈을 어떻게 관리할까?”라는 고민에서 나온 규격
  • 이름 그대로 “공통으로 쓰자(Common)!” 라는 의도로 만들어진 모듈 정의 방식
  • require, module.exports 키워드 사용
  • 동기 로딩 → 실행 시점에 파일을 불러옴

🔎 CommonJS를 어디서 확인할 수 있냐면

  • React, Vue 같은 프레임워크도 npm 패키지입니다.
  • 실제 node_modules 안을 열어보면 CommonJS 문법을 확인할 수 있어요.
    예: node_modules/react/index.js
'use strict';

if (process.env.NODE_ENV === 'production') {
  module.exports = require('./cjs/react.production.min.js');
} else {
  module.exports = require('./cjs/react.development.js');
}

👉 여기서 module.exports랑 require 보이죠?
React도 배포된 빌드 파일은 CommonJS 형태로 제공됩니다.


📌 CommonJS 특징과 문법

1. require / module.exports 문법

// math.js
function add(a, b) {
  return a + b;
}
module.exports = add;

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

👉 CommonJS (CJS)란 자바스크립트 모듈을 작성하고 불러오는 방식(규격) 이에요.
즉, require와 module.exports 문법을 사용하는 게 바로 CommonJS 스타일 코드입니다.

2. 동기 로딩

  • require()가 호출되면, 그 순간 바로 파일을 읽고 실행합니다.
  • 서버(Node.js) 환경에서는 빠른 파일 접근이 가능하니 문제 없죠.
  • 하지만 브라우저 환경에서는 “네트워크 요청 = 느림” → 비효율입니다.

3. Node.js 기본 모듈 시스템 (ESM 도입 전까지)

  • Node.js는 오랫동안 CJS만 지원 → 현재도 수많은 npm 패키지가 CJS 기반으로 배포됨

📦 ES Module (ESM)란?

  • 2015년(ES6)부터 자바스크립트 표준 모듈 시스템으로 채택
  • 최신 브라우저와 Node.js 모두 지원
  • import, export 키워드 사용
  • 비동기 로딩 → 병렬로 모듈을 가져올 수 있어 브라우저 환경에 적합

📌 특징과 문법

// math.js
export function add(a, b) {
  return a + b;
}

// main.js
import { add } from "./math.js";
console.log(add(2, 3)); // 5

👉 ES Module은 표준 모듈 형식이에요.
즉, importexport 문법을 사용하는 게 ESM 스타일 코드라는 뜻입니다.


🔍 CommonJS vs ES Module 차이

구분CommonJS (CJS)ES Module (ESM)
문법require, module.exportsimport, export
로딩 방식동기 (실행 시 불러옴)비동기 (병렬 로딩 가능)
주로 사용Node.js (옛날)브라우저 & Node.js (최신)
등장 시기2009년경2015년 (ES6)

📌 정리

  • CommonJS는 Node.js중심의 옛 모듈 형식이고, ES Module은 현대 자바스크립트 표준 모듈 형식입니다.
  • 자금은 ESM이 점점 기본으로 자리 잡고 있지만, CJS 기반 패키지도 여전히 많아서 실무에서는 두 형식을 모두 이해할 필요성이 있습니다.

require vs import

앞에서 CommonJS(CJS)와 ES Module(ESM)을 왜 비교해야 하는지, 그리고 두 체계가 공존하는 이유를 살펴봤습니다, 이 두개의 형식중점에 있는 require, import를 비교해보려고 합니다.

“require vs import” 차이를 꼭 알아야 하는 이유는 단순히 문법 차이 때문이 아니라, 실제 프로젝트 세팅, 에러 상황, 최적화와 직결되기 때문이에요.

❓정확히 왜 알아야 할까?

1. 실무에서 혼란을 줄이기 위해서

  • 어떤 패키지는 CommonJS(CJS)로, 어떤 패키지는 ES Module(ESM)로 배포됩니다.
  • 그래서 require만 되거나, import만 되는 경우가 있습니다.
  • 예를 들어:
// 어떤 패키지는 이렇게 해야 불러와지고
const express = require("express"); // CJS

// 어떤 패키지는 이렇게 해야 불러와집니다
import React from "react"; // ESM
  • 두 방식을 구분하지 못하면 "SyntaxError: Cannot use import statement outside a module"같은 에러에 막히게 됩니다.

2. 번들러·런타임 설정 때문

  • Node.js에서 ESM을 쓰려면 package.json"type": "module"을 명시해야 합니다.
  • 반대로 CJS만 지원하는 환경에서 import를 쓰면 동작하지 않죠.
  • Bable, Webpack, Vite 같은 번들러도 이 차이를 고려해서 변환 규칙을 적용합니다.

👉 따라서 “require vs import” 차이를 이해하면 빌드 도구 설정 문제를 빠르게 파악할 수 있습니다.

3. 최적화 측면

  • require(CJS)
    - 동기 로딩 -> 실행 시점에서 모듈을 불러와요
    • 모듈 경로를 런타임에만 알 수 있음 -> Tree Shaking 가 불가능 해집니다.
  • import(ESM)
    - 정적 구조 -> 빌드 시점에 모듈 관계를 파악 가능해지죠.
    • Dead Code Elimination(사용하지 않는 코드 제거), Tree Shaking 최적화가 가능해집니다.

👉 성능 최적화 관점에서 import 방식이 유리합니다.

📌 정리

결국 우리가 requireimport 차이를 알아야 하는 이유는 단순히 문법 때문이 아닙니다.
우리가 사용하는 패키지가 CJS인지 ESM인지에 따라 에러가 날 수 있고,
번들러와 런타임 설정에도 직접적인 영향을 주며,
나아가 최적화 가능 여부까지 달라지기 떄문입니다.

📌 require vs import (비교 표 버전)

구분require (CJS)import (ESM)
문법const pkg = require("pkg")import pkg from "pkg"
동작 시점런타임(실행 중) 모듈 불러옴컴파일 시점에 모듈 구조 파악
로딩 방식동기 로딩 → 호출 시 즉시 실행비동기 로딩 → 병렬 처리 가능
최적화Tree Shaking 불가 (사용하지 않는 코드도 포함)Tree Shaking 가능 (안 쓰는 코드 제거)
환경Node.js 기본(옛날 방식)브라우저 표준, 최신 Node.js 지원
대표 사례const express = require("express")import React from "react"

📌 정리

require는 Node.js에서 오래도록 쓰인 동기 로딩 방식이고, import는 현대 자바스크립트 표준인 비동기 로딩 방식입니다.
결국 두 방식은 "실행 시점에 모듈을 불러오느냐, 아니면 빌드 단계부터 모듈 관계를 파악하느냐"의 차이로 요약할 수 있습니다.


⚡ 동기vs비동기 로딩 차이

동기, 비동기 로딩 차이를 알게되면 프로젝트 성능, 에러 상황, 번들링 최적화가 어떻게 진행이 되는지 연결되어 생각할수 있습니다.

📌 동기 vs 비동기 로딩 차이를 알아야 하는 이유

1. 브라우저 성능

  • 동기 로딩(CJS/require)
    • require() 가 실행되는 순간, 그 모듈이 로딩될 떄까지 나머지 코드들이 멈춥니다.
    • 서버(Node.js)는 파일 접근이 빠르니까 큰 문제가 없지만,
    • 브라우저에서는 네트워크 요청이 막히면서 페이지 로딩 지연* 문제가 생깁니다.
  • 비동기 로딩(ESM/import)
    • 모듈로 병렬로 가져오고, 준비되면 실행합니다.
    • 브라우저 환경에서 초기 로딩 속도가 훨씬 빨라지고, 사용성도 좋아집니다.

2. 코드 최적화(Tree Shaking)

  • CJS(require)는 실행 시점에 어떤 모듈이 필요한지 알 수 있어서, 빌드 도구가 미리 최적화를 하기 어렵습니다.
  • ESM(import)은 정적으로 분석 가능하기 떄문에, 안 쓰는 코드를 미리 제거(Three Shaking)할 수 있어 결과 번들 크기가 작아집니다.

3. 실무에서 자주 겪는 에러

  • import를 쓰려 했는데 Node.js설정이 CJS라서 "SyntaxError: Cannot use import statement outside a module"에러가 발생합니다.
  • require로 불러왔는데 브라우저에서 동작 안하는 경우가 있습니다.

👉 이런 문제들을 빠르게 파악하려면 동기/비동기 로딩 차이를 이해하고 있어야 합니다.

1. 동기 로딩 (Synchronous Loading)

  • 특징
  • 코드 실행이 순차적(직렬) 으로 진행됩니다.
  • 이전 작업이 끝나야 다음 작업이 실행되죠.
  • CommonJS(require)가 대표적입니다.
  • 장점: 흐름을 이해하기 쉽고 디버깅이 단순해지죠
  • 단점: 네트워크 요청이나 큰 파일을 불러올 떄 전체 코드 실행이 블로킹(멈춤)됩니다.
// CommonJS - 동기 로딩
const fs = require("fs"); // 파일 로딩이 끝날 때까지 다음 줄 실행 안 됨
console.log("이 줄은 require가 끝난 후 실행됨");

2. 비동기 로딩 (Asynchronous Loading)

  • 특징
    • 여러 작업을 병렬로 처리 가능합니다.
    • 기다리는 동안 다른 코드 실행 가능합니다.
  • ES Module(import)가 대표적이죠.
  • 장점: 브라우저 환경에서 초기 로딩 속도가 빨라지고, 효율적입니다.
  • 단점: 코드가 비동기 흐름이어서 설계가 조금 더 복잡해질 수 있습니다.
// ES Module - 비동기 로딩
import { readFile } from "fs/promises";

async function run() {
  const data = await readFile("text.txt", "utf-8");
  console.log(data);
}
run();

3. 차이 비교

구분동기 로딩 (CJS / require)비동기 로딩 (ESM / import)
실행 방식직렬(순서대로 실행)병렬(동시에 실행 가능)
코드 진행이전 작업 끝날 때까지 멈춤기다리는 동안 다른 코드 실행 가능
환경Node.js 초기 기본브라우저 표준, 최신 Node.js
성능작은 코드에는 무난대규모 앱, 네트워크 요청 많은 경우 유리
최적화Tree Shaking 불가Tree Shaking 가능

📖 정리

CommonJS의 require는 동기 로딩이기 때문에 서버 환경에선 괜찮지만, 브라우저에서는 성능 병목이 생깁니다.
반대로 ES Module의 import는 비동기 로딩이 가능해 브라우저 환경에 적합하며, 빌드 최적화(Tree Shaking)까지 지원합니다. 결국 최신 어플리케이션을 만든다고 하면 비동기 로딩을 지양하는 것이 환경과 성능 측에서 좋다고 생각이 듭니다.



🌍 브라우저 환경 vs Node.js 환경에서의 모듈 동작

자바스크립트는 원래 브라우저 전용 언어로 시작했습니다.
하지만 Node.js가 등장하면서 서버 환경에서도 자바스크립트를 쓰게 되었고, 그 과정에서 모듈 시스템(하나의 큰 프로그램을 여러 개의 독립적이고 재사용 가능한 부분, 즉 모듈(module)로 분리하여 관리하는 방식)이 두 갈래로 발전했씁니다.

  • 브라우저: HTML 기반, 네트워크 요청을 통해 스크립트를 불러오는 구조 -> 비동기 로딩(ESM)에 최적화
    Node.js: 파일 시스템 기반, 빠른 로컬 파일 접근 가능 -> 동기 로딩(CJS) 도 큰 무리 없음

👉 그래서 같은 자바스크립트라도 어느 환경에서 실행하느냐에 따라 모듈 동작 방식이 다릅니다.
실무에서는 우리가 작성한 코드가 브라우저에서 실행될 수도 있고, Node.js(백엔드, 빌드 도구, 번들러)에서 실행될 수도 있기 떄문에, 두 환경의 차이를 정확히 이해하는 것이 중요합니다, 그래야 사용 가능한 문법과 설정을 달리 사용할수 있기 때문입니다.


1. 브라우저 환경

  • 브라우저는 기본적으로 ES Module(ESM) 만 지원합니다.
  • HTML에서 <script type="module"> 속성을 사용하면 import/export 문법을 인식할 수 있습니다.
  • CommonJS(require, module.exports)는 브라우저가 직접 이해하지 못합니다. -> 반드시 번들링/트랜스파일링 이 필요하죠
<!-- 브라우저 ESM 예시 -->
<script type="module">
  import { add } from "./math.js";
  console.log(add(2, 3));
</script>

👉 브라우저는 네트워크 요청을 통해 모듈을 가져오기 때문에, 비동기 로딩 인 ESM이 적합합니다.


2. Node.js 환경

  • Node.js는 초창기부터 CommonJS(CJS) 를 기본 모듈 시스템으로 사용했습니다.
  • 그래서 require/module.exports 문법이 지금도 널리 쓰이고 있습니다.
  • Node.js 최신 버전(14+)부터는 ESM도 지원합니다.
  • package.json"type":"module"을 설정해야 import/export를 쓸 수 있습니다.
  • 또는 .mjs 확장자를 사용하면 자동으로 ESM으로 인식합니다.
// Node.js - CommonJS
const fs = require("fs");
console.log("CJS:", fs.existsSync("app.js"));

// Node.js - ESM
import fs from "fs";
console.log("ESM:", fs.existsSync("app.js"));

👉 즉, Node.js에서는 CJS와 ESM이 모두 동작하지만, 프로젝트 설정에 따라 어떤 걸 쓰는지가 달라집니다.


3. 📖 정리

구분브라우저Node.js
기본 지원ESM(import/export)CJS(require/module.exports)
CJS 사용❌ 직접 지원 불가 → 번들링 필요✅ 기본 지원
ESM 사용<script type="module">✅ ("type": "module" 또는 .mjs)
적합한 이유네트워크 요청 기반 → 비동기 로딩 유리서버 환경 파일 접근 빠름 → 동기 로딩도 무리 없음

브라우저는 기본적으로 ESM만 이해하고, CommonJS는 번들러 없이는 사용할 수 없습니다.
반대로 Node.js는 원래 CJS만 지원했지만, 최신 버전에서는 ESM도 지원합니다.
따라서 브라우저 = ESM 중심, Node.js = CJS 중심 + ESM 지원이라는 차이가 있습니다.



❓ 왜 번들링이 필요할까? (네트워크 요청 최소화)

브라우저 환경에서는 모듈이 많아질수록 네트워크 요청이 늘어나 병목 현상이 발생합니다.
번들러는 의존성 그래프를 분석해 하나의 번들 파일로 합쳐주기 떄문에, 브라우저는 최소한의 요청만으로 앱을 실행할 수 있습니다.

조금 더 자세한 내용은 아래 번들러 챕터에서 서술하도록 하겠습니다!!


📦 번들러가 해결하는 방식

  • 번들러(Webpack, Vite 등)는 의존성 그래프를 분석합니다.
    • 프로젝트의 진입점(entry)에서 시작해
    • 각 모듈이 어떤 모듈을 import하는지 전부 추적합니다.
  • 그 결과, 수십~수백 개의 파일을 하나(또는 몇 개)의 번들 파일로 묶어줍니다.
  • Tree Shaking, 코드 압축(minify), 코드 스필리팅 같은 최적화 기능도 함께 적용됨
    👉 이렇게 하면 브라우저는 bundle.js같은 파일만 요청하면 되므로
    네트워크 병목을 피하고 더 빠르게 실행할 수 있습니다.

🔎 예시: 번들링 전vs후

번들링 전

/src
  ├── utils.js
  ├── api.js
  ├── component.js
  └── main.js

👉 브라우저가 각 파일을 하나하나 요청해야 함 (네트워크 요청 증가)

번들링 후

/dist
  └──  bundle.js

👉 브라우저는 bundle.js 하나만 요청하면 됨 (네트워크 요청 최소화)


📖 정리

번들링이 필요한 가장 큰 이유는 네트워크 요청을 최소화하기 위해서 입니다.
작은 모듈 수백 개를 각각 불러오는 대신, 번들러가 하나의 파일로 묶어주면 브라우저는 빠르고 효율적으로 코드를 실행할 수 있습니다.

번들링이 되는 환경과 과정



3. 트랜스파일링(Transpiling)

트랜스파일링이란 최신 자바스크립트 문법(ES6+, ESNext)을 하위 버전(ES5 등)으로 변환하는 과정을 말합니다.
쉽게 말해, 최신 언어를 구형 환경에서도 동작하도록 번역하는 작업입니다.


대표적인 트랜스파일러/도구들

1. Babel

  • ESNext -> ES5 변환 + Polyfill 삽입까지 가능합니다.
  • 플러그인/프리셋 생태계가 크고, 사실상 트랜스파일링의 메인격으로 자리를 잡았습니다.
    2. TypeScript Compiler (tsc)
  • 원래는 TypeScript -> Javascript 변환기지만,
  • 설정("target":"ES5")에 따라 ESNext JS -> 구버전 JS 코드로도 트랜스파일링이 가능합니다.
    3. SWC(Speedy Web Compiler)
  • Rust로 구현된 초고속 트랜스파일러
  • Babel보다 훨씬 빠름 -> Next.js, Vite 일부 기능에서 사용
    4. esbuild
  • Go 언어 기반으로 만들어진 빌드 도구
  • 번들링 + 트랜스파일링을 동시에 지원 (매우 빠름)

🙄 왜 아직도 Babel을 쓸까?

1. 광범위한 호환성과 안정성

  • Babel은 2014년(6to5 시절)부터 시작된, 가장 오래되고 안정적인 트랜스파일러입니다.
  • 구형 브라우저(특히 IE11)까지 지원할 수 있는 거의 유일한 도구였기 때문에, 레거시 지원이 필요한 프로젝트에서는 지금도 필수죠
  • esbuild, SWC는 빠르지만, 브라우저 호환성 세세한 설정은 Babel만큼 치밀하지 않음.

2. 풍부한 플러그인 & 프리셋 생태계

  • Babel은 플러그인/프리셋이 압도적으로 많습니다.
  • 예시:
    • @babel/preset-env: 환경별로 필요한 문법 변환/폴리필 자동 적용
    • @babel/preset-react: JSX → JS 변환
    • @babel/preset-typescript: TS → JS 변환
  • 즉, React, Vue, TS, 최신 문법 실험까지 Babel 하나로 다 커버 가능하죠.

3. 정밀한 제어 (Config Power)

  • Babel은 세부 설정이 매우 강력합니다.
  • 어떤 문법을 변환할지, 어떤 브라우저 타겟을 지원할지 아주 구체적으로 조정 가능합니다.
  • 반대로 esbuild, SWC는 “빠르고 단순”을 지향해서 이런 디테일이 부족할 수 있습니다.

4. 빌드 체인 통합

  • Webpack, Rollup 같은 기존 번들러와 오랫동안 깊이 통합돼 있어서
    -babel-loader (Webpack)
    • rollup-plugin-babel
      같은 생태계가 이미 자리 잡은 상황이죠
  • 큰 규모의 기존 프로젝트는 Babel을 빼기가 쉽지 않아요.

5. 새로운 문법 실험 지원

  • Stage-0~3 단계의 실험적 문법을 플러그인으로 바로 적용 가능
  • 예: optional chaining, nullish coalescing도 Babel 덕분에 표준화되기 전부터 쓸 수 있었음.
  • 최신 문법을 “안정적으로 먼저 체험”하려면 Babel이 필요.

👉 결론

Babel은 esbuild, SWC보다 속도는 느릴 수 있지만,

  • 구형 브라우저까지 커버하는 호환성
  • 방대한 플러그인 생태계
  • 세밀한 설정 제어력
  • 오래된 프로젝트와 번들러 생태계의 기본 통합성
    덕분에 여전히 많이 쓰이고 있습니다.
    즉, 새로운 도구들이 속도와 단순함을 강점으로 한다면, Babel은 안정성과 범용성을 강점으로 한다고 볼 수 있습니다.

🔧 Babel이 하는 일

1. Babel은 무엇인가?

  • Babel은 자바스크립트의 트랜스파일러(Transpiler) 입니다.
  • 최신 자바스크립트(ES6, ES2020, ESNext 등) 코드를 구형 환경(예: ES5, IE11)에서도 실행할 수 있도록 변환합니다.
  • 단순히 브라우저 호환성뿐 아니라, 새로운 문법을 미리 체험(early adoption) 할 수 있게 해줍니다.
    → 예: 아직 모든 브라우저가 지원하지 않는 optional chaining(obj?.prop) 같은 문법도 Babel을 거치면 안전하게 사용할 수 있습니다.

2. Babel의 동작 방식

👉 Babel은 크게 세 단계를 거칩니다.

  1. Parsing (파싱)
  • 우리가 작성한 최신 JS 코드를 읽어서 AST(Abstract Syntax Tree, 추상 구문 트리) 로 변환합니다.
  • 예: const sum = (a, b) => a + b; → 트리 구조로 분석
  1. Transforming (변환)
  • AST를 탐색하면서 최신 문법을 하위 문법으로 변환합니다.
  • 이 과정에서 플러그인(plugins) 이 사용되죠.
  • 예:@babel/plugin-transform-arrow-functions는 화살표 함수를 일반 함수로 변환
  1. Generating (코드 생성)
  • 변환된 AST를 다시 코드 문자열로 출력됩니다.
  • 최종 결과는 구형 브라우저에서도 이해할 수 있는 ES5 코드로 변환되죠.

3. Babel의 주요 기능

  1. 최신 문법 변환 (Syntax Transform)
  • 예: let, const -> var
  • 화살표 함수 -> 일반 함수
  • 클래스 문법 -> 함수 기반 코드
  1. polyfill 주입 (Built-ins)
  • 단순히 문법만 변환하면 안 되는 경우, 기능 자체를 추가(polyfill) 해야합니다.
  • 예:
    • Promise (구형 브라우저에 없음)
    • Array.prototype.includes
    • async/await (regenerator-runtime 필요)
  • 보통 core-js, regenerator-runtime을 함께 사용해 자동으로 polyfill 삽입
  1. 플러그인 & 프리셋 시스템
  • Babel은 플러그인 기반이라 필요한 기능만 끼워 넣을 수 있어요.
  • 예:
    • @babel/preset-env → 브라우저 환경에 맞춰 필요한 변환/폴리필만 자동 적용
    • @babel/plugin-transform-react-jsx → React JSX 문법을 React.createElement 코드로 변환됩니다.
  1. 실제 프로젝트에서 Babel은 어떻게 쓰일까?
  • 단독으로 쓰이기보다는 번들러(Webpack, Vite, Rollup 등)와 함께 사용됩니다.
  • 번들러가 프로젝트 전체 파일을 읽어올 때, Babel을 통해 코드 변환을 거치도록 설정합니다.
  • 예:Webpack 설정에서 babel-loader 사용
  • 이렇게 하면 npm run build 할 때 자동으로 트랜스파일링이 적용됨

💻 실제 코드 변환 예시

원본 코드 (ES6+)

// ES6 화살표 함수 & const
const sum = (a, b) => a + b;

Babel 변환 후 (ES5)

"use strict";

var sum = function sum(a, b) {
  return a + b;
};

👉 여기서 const → var, 화살표 함수 → 일반 함수로 변환된 것을 볼 수 있습니다.


📌 정리

트랜스파일링: 최신 문법을 하위 버전으로 변환 → 브라우저 호환성 확보

Babel은 단순히 “최신 문법을 옛날 문법으로 바꿔주는 도구”를 넘어서,
(1) 파싱 → (2) 변환 → (3) 코드 생성의 과정을 거쳐 동작하며,
플러그인과 프리셋을 통해 우리가 어떤 환경(브라우저, Node.js)을 타겟으로 할지 맞춤 설정할 수 있습니다.
덕분에 개발자는 최신 문법을 마음껏 활용하면서도, 구형 브라우저 호환성을 확보할 수 있습니다.

트랜스파일링: 최신 문법을 하위 버전으로 변환 → 브라우저 호환성 확보
👉 이 과정을 통해 우리가 작성한 최신 자바스크립트 코드가 구형 브라우저에서도 안전하게 실행됩니다.



4. 번들링(Bundling)

번들링(Bundling) 이란 프로젝트 안에 흩어져 있는 여러 모듈 파일을 하나(또는 몇 개)의 큰 파일로 합치는 과정을 말합니다.

👉 쉽게 말해, 수많은 JS, CSS, 이미지 모듈들을 “하나의 묶음”으로 만들어 브라우저가 효율적으로 불러올 수 있게 해주는 작업입니다.


⚡️ 왜 필요한가?

자바스크립트 프로젝트가 커지면 파일이 수십, 수백 개로 나뉘게 됩니다.
이 파일들을 브라우저가 각각 개별 요청으로가져온다면 어떤 문제가 생길까요?

  • 네트워크 요청이 많아져 로딩 속도 저하
  • 브라우저가 동시에 처리할 수 있는 요청 수(병렬 요청 제한)에 걸림 -> 네트워크 병목이 발생하게 됩니다.
  • 사용자는 화면이 늦게 뜨거나, 기능이 지연되는 경험을 하게 됩니다.
  • 브라우저 환경에서는 단순히 <script type="module">로 모든 ESM 파일을 불러오는 방식만으로는 한계가 있습니다.
    👉 이런 문제들을 해결하려는 핵심이 바로 번들링(Bundling)입니다.

👉 그래서 번들러가 의존성 그래프를 분석 → 하나(또는 몇 개)의 파일로 묶어서 제공해주는 겁니다.


🛠️ 주요 번들러 비교

1. Webpack

  • 가장 오래되고 널리 쓰이는 번들러
  • 설정 자유도가 높아 거의 모든 요구사항을 커버 가능
  • 하지만 설정이 복잡하고, 학습 곡선이 있음
  • 오래된 프로젝트나 엔터프라이즈 규모에서 여전히 강세

2. Vite

  • 차세대 번들러이자 개발 서버 도구
  • ESM 기반으로 동작 → 브라우저가 직접 모듈을 불러와 빠른 개발 환경 제공
  • HMR(Hot Module Replacement) 속도가 빠름 → 개발 생산성 극대화
  • Vue, React 프로젝트에서 점점 표준처럼 자리 잡는 중

3. Rollup

  • 라이브러리 번들링에 강점
  • 출력물이 가볍고 최적화가 잘 되어 있어 npm 라이브러리 제작에 많이 사용됨
  • Webpack보다 단순하지만, 앱 개발보다는 라이브러리 배포용으로 더 적합

4. esbuild

  • Go 언어로 작성된 초고속 번들러
  • 빌드 속도는 경쟁자 대비 최강 수준
  • 설정이 단순하지만, 플러그인/에코시스템은 Babel·Webpack보다는 약한 편
  • Vite, Next.js 내부에서도 트랜스파일링 엔진으로 esbuild를 활용

📂 번들링 전/후 파일 구조 비교

번들링 전

/src
  ├── utils.js
  ├── api.js
  ├── component.js
  └── main.js

👉 브라우저는 여러 파일을 각각 요청해야 합니다.

번들링 후

/dist
  └── bundle.js

👉 브라우저는 bundle.js 하나만 요청하면 됩니다.


📖 정리

번들링은 단순히 파일을 합치는 것을 넘어서,

  • "의존성 관리"
  • "코드 최적화(Tree Shaking, 압축, 코드 스플리팅)"
  • "개발 편의 기능(HTR, 빌드 환경 통합)
    까지 담당하는 중요한 과정입니다.
    다양한 번들러가 존재하지만, 프로젝트의 성격에 따라 적합한 도구를 선택하는 것이 핵심입니다."


5. 번들러 동작 과정

번들링이란 단순히 "여러 파일을 합치는 것"으로만 끝나지 않습니다.
사실 그 안에는 진입점 탐색 -> 의존성 그래프 생성 -> 변환 -> 묶음 출력이라는 일련의 과정이 숨어 있어요.
이 과정을 이해하면, 우리가 흔히 접하는 로더(loaders), 플러그인(plugins), 그리고 Tree Shaking 같은 개념도 자연스럽게 연결됩니다.

아래는 그 동작 과정을 순서대로 정리해봤습니다.


🔑 1. 진입점(entry)

번들러는 프로젝트의 시작 파일(엔트리 포인트)에서 탐색을 시작합니다.

예: src/main.js 또는 src/index.tsx

이 파일을 기준으로 import, require를 따라가며 어떤 모듈들이 필요한지 추적합니다.

👉 즉, “어디서부터 읽어야 할까?”라는 출발점 역할을 하는 게 엔트리입니다.


🗂️ 2. 의존성 그래프 생성

  • 번들러는 엔트리에서 출발해 모든 모듈 간의 의존 관계를 그래프로 만듭니다.
  • “이 모듈은 누구를 import했고, 그 모듈은 또 누구를 import했는가”를 끝까지 따라갑니다.
  • 최종적으로 프로젝트 전체를 하나의 큰 그래프로 표현할 수 있습니다.

👉 이 그래프 덕분에 번들러는 프로젝트 전체 구조를 파악할 수 있고, 불필요한 모듈을 제거하거나 최적화할 수 있습니다.


🔄 3. 변환(Transform)

  • 우리가 작성한 코드(JSX, TS, SCSS, 최신 JS 문법 등)는 브라우저가 그대로 이해하지 못합니다.
  • 이때 번들러는 로더(loaders) 를 이용해 각 파일을 변환합니다.
    • babel-loader: JSX/ESNext → 브라우저 호환 JS
    • ts-loader: TypeScript → JavaScript
    • css-loader, sass-loader: CSS/SCSS → JS에서 불러올 수 있게 변환

👉 즉, 로더는 파일 확장자별로 어떻게 읽고 변환할지 정의하는 도구입니다.


📦 4. 묶음 출력(Bundle Output)

  • 변환된 모든 모듈을 하나(또는 코드 스플리팅으로 나눈 여러 개)의 번들 파일로 묶습니다.
  • 예: dist/bundle.js, dist/chunk-vendors.js
  • 이 과정에서 플러그인(plugins)이 개입해 추가 작업을 합니다.
    • 예: HtmlWebpackPlugin → HTML에 번들 자동 삽입
    • MiniCssExtractPlugin → CSS 파일 별도 분리

👉 플러그인은 말 그대로 “추가 기능 확장 도구”라, 번들링 결과물을 원하는 형태로 커스터마이징할 수 있게 해줍니다.


🌲 5. Tree Shaking & Dead Code Elimination

  • Tree Shaking: 사용하지 않는 코드를 번들에서 제거하는 최적화 기법
  • Dead Code Elimination: 실행될 가능성이 없는 코드를 제거

아래는 정적 분석 가능한 코드 예시 (ESM) 입니다

// utils.js
export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }

// main.js
import { add } from "./utils";
console.log(add(2, 3));

👉 subtract 함수는 사용되지 않았으므로 최종 번들에 포함되지 않습니다.

  • ESM(import/export)처럼 정적 분석이 가능한 코드여야 Tree Shaking이 제대로 작동합니다.

아래는 정적 분석이 불가능한 코드 예시 (CJS)입니다.

// main.js
const moduleName = "./math.js";
const math = require(moduleName);

console.log(math.add(2, 3));

👉 require 안에 변수가 들어가 있으니까, 실행해보기 전에는 어떤 파일을 가져올지 알 수 없음

moduleName 값이 실행 도중에 바뀔 수도 있기 때문에,

정적 분석 단계에서는 “math.js를 쓴다”는 걸 확정할 수 없습니다.

→ 이런 경우는 Tree Shaking 같은 최적화가 불가능해요.

📖 정리

Entry → Dependency Graph → Transform(Loaders) → Bundle Output(Plugins)

최종 단계에서 Tree Shaking 같은 최적화로 크기를 줄이고 성능을 높이죠

즉, 번들러는 단순한 “파일 합치기 도구”가 아니라, 전체 코드를 분석·변환·최적화하는 빌드 파이프라인입니다.
아래는 위 번들러 동작과정을 다이어그램으로 이해하기 쉽도록 그려봤습니다.

번들링 과정



😭 1부를 마무리하며

처음으로 진짜 내가 뭘 모르는지 하나하나 물어보고 블로그 찾아보면서 정리하며 적어본 포스트네요, 처음 스터디 포스트라 그런가 내용도 많고 내가 매일 쓰던 기본 개념들중 모르는 부분이 너무 많아 하나하나씩 다 GPT에게 물어보고 맞는지 틀린지 블로그를 보며 정리하느라 시간도 오래 걸리고 개념정리를 이번 블로그를 쓰면서 확실히 이해를 하는 과정을 거쳤습니다.

여기까지 번들링과 트랜스파일링의 기본 개념,
그리고 npm install -> build 과정 속에서 어떤 일이 일어나는지를 알아봤습니다!

정리하자면,

  • npm install을 입력하면 npm cli이 트랜스파일링과 번들링이 어떤식으로 진행되는지
  • 트랜스파일링은 최신 문법을 구형 환경에서도 실행 가능하게 만들어주는 과정이라는 것!
  • 번들링은 수많은 모듈을 하나(또는 몇 개)의 파일로 묶어 네트워크 요청을 최소화하는 과정이라는 것!

👉 즉, 우리가 매일처럼 쓰는 npm run build 뒤에는 "호환성 확보"와 "성능 최적화" 라는 두 축이 동시에 작동하고 있는 것을 알수 있었습니다.


다음 2부에서는

  • 트랜스파일러 + 번들러 조합
  • 실무 최적화 팁
  • 정리 및 개인 인사이트

등을 정리해볼 예정입니다. 🚀

번들러와 트랜스파일링 - 2



참고 한 블로그 내용

JavaScript 번들러의 이해 — (1) JavaScript 모듈
JavaScript 번들러의 이해 — (3) 번들러 개론
JavaScript 번들러의 이해 — (4) Webpack 및 다른 번들러들
webpack


p.s

중간에 확인해주시고 오타 지적, 복수내용 확인해주신

너무 너무 감사합니다!!🙏

profile
방향성을 찾고싶은 프론트엔드개발자

8개의 댓글

comment-user-thumbnail
2025년 8월 26일

번들러 완벽정리 ㅇㅅ ㅇ ,,!

1개의 답글
comment-user-thumbnail
2025년 8월 27일

번들러 뉴비에게 너무 유익한 내용입니다. 정리 감사합니다!

1개의 답글
comment-user-thumbnail
2025년 8월 27일

항상 관심있던 주제였는데 정리를 잘 해주셔서 흐름이 이해가 갑니다!
잘 읽었습니다!

1개의 답글
comment-user-thumbnail
2025년 8월 29일

너무 친절하고 상세하게 잘 설명해주신 것 같아요, 좋은 글 감사합니다.

1개의 답글