코딩/JS

JS 모듈 패턴 + Dynamic Import

최만규 2025. 5. 15. 12:16
728x90

 

 

개요

 

규모가 큰 프로젝트일수록, 코드들을 유지보수하기 좋게 잘게 쪼개는 것이 중요해진다.

 

JavaScript 모듈 패턴은 코드의 캡슐화, 재사용성, 의존성 관리를 위해 자바스크립트에서 사용되는 대표적인 패턴 중 하나이다.

 

이 개념은 자바스크립트가 ES6 이전까지 공식적인 모듈 시스템이 없던 시절부터, 클로저(Closure)와 IIFE(즉시 실행 함수 표현식) 등을 활용해 네임스페이스 충돌 방지, 정보 은닉(Private 변수) 등을 위해 사용되었다.

 

본격적으로 모듈 패턴과 어떻게 쓰이는지에 대해서 알아보자

 


 

모듈 패턴이란?

 

자바스크립트에서 변수와 함수를 하나의 모듈로 묶고, 외부에 필요한 것만 노출하는 방식이다.

 

이 패턴의  주요 목적으로는 

  • 캡슐화(encapsulation): 내부 구현을 숨기고 필요한 것만 외부에 공개
  • 네임스페이스 충돌 방지: 전역(global) 오염 방지
  • 의존성 관리: 필요한 모듈만 불러와 사용

 


 

📌 IIFE 기반 모듈 패턴 (ES5 이전 방식)

const CounterModule = (function () {
  let count = 0; // private 변수

  function increment() {
    count++;
    console.log(count);
  }

  function decrement() {
    count--;
    console.log(count);
  }

  return {
    increment,   // public 메서드
    decrement
  };
})();

CounterModule.increment(); // 1
CounterModule.decrement(); // 0
console.log(CounterModule.count); // undefined (private!)

 

위에서 말했듯이 ES5 이전엔 공식적인 모듈 시스템이 없길래 IIFE를 통해 모듈의 효과를 얻었다.

 

즉시 실행함수를 통해 스코프를 제한해, 변수 충돌을 방지할 수 있다.

 

🔎  count는 외부에서 접근할 수 없고, increment / decrement만 접근 가능 → 정보 은닉

🔎  내부 상태를 보호하면서 필요한 기능만 외부에 노출하는 전형적인 모듈 패턴

 


 

📌 ES6 모듈 시스템 (현대적 방식)

// counter.js
let count = 0;

export function increment() {
  count++;
  console.log(count);
}

export function decrement() {
  count--;
  console.log(count);
}
// main.js
import { increment, decrement } from './counter.js';

increment(); // 1
decrement(); // 0

 

🔍  import/export 키워드를 사용하여 명시적으로 모듈을 정의

🔍  트리 쉐이킹(Tree Shaking)을 통해사용하지 않는 모듈은 빌드 시 제거 가능

🔍  정적 분석 가능: 코드 분석 도구, 타입 검사, 자동완성 등 지원

🔍  엄격한 스코프로 전역 오염 방지

 


 

📌  CommonJS 모듈 (Node.js 환경)

// counter.js
let count = 0;

function increment() {
  count++;
  console.log(count);
}

module.exports = { increment };
// main.js
const { increment } = require('./counter');
increment(); // 1

 

ES6버전 이후 부터는 import, export를 사용했지만 이전 CommonJS의 경우 require, module.exports를 사용했다.

 

이 방식은 ES6버전 과는 다르게 동기 방식으로 로딩되고 Node.js에서 기본환경으로 활용된다.

 

ES6의 import/export 방식이 사용자가 필요한 모듈 부분만 선택해 로드가 가능하기에 성능이 우수하고 메모리 절약도 가능하다는 장점이 존재한다.

 


 

📌 비교 정리

구분방식, 장점, 단점

IIFE 모듈 패턴 즉시 실행 함수 ES5 지원, 캡슐화 재사용/관리 어려움
ES6 모듈 import/export 정적 분석, 트리 쉐이킹, 표준화 구형 브라우저 지원 이슈
CommonJS require/module.exports Node.js에서 표준 동적 로딩 불가 (정적 분석 불리)

 


 

 

부록 : 유연한 모듈 사용(dynamic import)

 

파일의 맨 위에서 모듈들을 import하면 파일 내 다른 코드들이 실행되기 전에 해당 모듈이 로드된다.

 

어떤 상황에서는 특정 조건에서만 특정 모듈을 로드해야 할 때가 있는데 이런 경우에 dynamic import를 통해 필요한 상황에 import를 하도록 한다.

 

1. 조건부 로딩이 필요한 경우 (사용자의 상태, 권한, 환경에 따라 모듈을 조건부로 가져오고 싶을 때)

if (user.isAdmin) {
  const adminModule = await import('./admin-tools.js');
  adminModule.init();
}

 

import문은 정적 조건문 내부엔 사용 불가하지만 import()함수는 가능하다.

 

 

2. 초기 로딩 속도 개선 (코드 스플리팅) 

button.addEventListener('click', async () => {
  const { openModal } = await import('./modal.js');
  openModal();
});

 

페이지 진입 시, 불필요한 코드까지 한꺼번에 로딩하지 않도록 사용한다.(모달, 차트, 관리자 도구)

 

3. SSR이 불가한 클라이언트 전용 모듈

if (typeof window !== 'undefined') {
  const Chart = (await import('chart.js')).default;
  new Chart(ctx, config);
}

 

브라우저 API를 사용하는 라이브러리의 경우 서버 사이드 렌더링 중 오류가 나기에 런타임에서만 로딩되도록 하는 경우 사용할 수 있다.

 

Dynmic import를 사용하면 필요할 때만 로드할 수 있다.

 

 

In Next.JS

const DynamicComponent = dynamic(() => import('./Chart'), { ssr: false });

 

유사하게 Next.js에서 SSR 비활성화를 하는 방법도 있다.

 

 

 

 

참고

https://velog.io/@danmin20/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%AA%A8%EB%93%88%ED%8C%A8%ED%84%B4%EC%9D%B4%EB%9E%80

 

자바스크립트 모듈패턴이란? (1)

자바스크립트의 모듈패턴에 대해 알아보자

velog.io

 

https://patterns-dev-kr.github.io/design-patterns/module-pattern/

 

Module 패턴

📜 원문: patterns.dev - module pattern 코드베이스가 커져갈 수록 코드들을 유지보수하기 좋게 쪼개는 것이 중요해진다. 모듈 패턴이 이 때 코드들을 재사용 가능하면서도 작게 나눌 수 있게 해 준다.

patterns-dev-kr.github.io

 

728x90