자바스크립트 코드가 점점 복잡해지면 하나의 파일에 모든 코드를 작성하는 것은 유지보수 측면에서 매우 비효율적입니다. 이럴 때 자바스크립트 코드를 여러 파일로, 논리적 단위로 분리하면 개발과 디버깅이 훨씬 수월해집니다. 하지만 자바스크립트 파일을 분리할 때 잘못하면 코드가 동작하지 않는 문제가 발생할 수 있습니다. 이 글에서는 자바스크립트 모듈화의 핵심 개념과 실제 적용 방법에 대해 알아보겠습니다.
목차
- 자바스크립트 파일 분리 시 발생하는 문제
- 전통적인 방식의 자바스크립트 모듈화
- 현대적 방식: ES 모듈 시스템
- 번들러를 활용한 모듈 관리
- 실전 예제: 웹사이트에 모듈화 적용하기
- 디버깅 팁
- 마무리
자바스크립트 파일 분리 시 발생하는 문제
자바스크립트 파일을 분리할 때 가장 흔히 발생하는 문제들은 다음과 같습니다:
1. 스코프 문제
각 자바스크립트 파일은 자체 스코프를 가집니다. 한 파일에서 선언한 변수나 함수를 다른 파일에서 직접 접근할 수 없습니다.
// file1.js
const username = "Alice";
function greet() {
console.log(`Hello, ${username}!`);
}
// file2.js
greet(); // ReferenceError: greet is not defined
console.log(username); // ReferenceError: username is not defined
2. 로딩 순서 문제
HTML 문서에서 스크립트 태그의 로딩 순서에 따라 종속성 문제가 발생할 수 있습니다.
<!-- 잘못된 순서 -->
<script src="app.js"></script> <!-- app.js는 utils.js의 함수를 사용 -->
<script src="utils.js"></script>
3. 전역 네임스페이스 오염
여러 파일에서 같은 이름의 변수나 함수를 정의하면 충돌이 발생합니다.
// file1.js
const config = { theme: "dark" };
// file2.js
const config = { debug: true }; // 이전 config를 덮어씀
전통적인 방식의 자바스크립트 모듈화
ES6 모듈 이전에는 다음과 같은 방법으로 모듈화를 구현했습니다:
1. 즉시 실행 함수(IIFE)
// module.js
(function() {
// 비공개 변수와 함수
let privateVar = "비공개 데이터";
// 전역으로 노출할 API
window.MyModule = {
getPrivateData: function() {
return privateVar;
},
doSomething: function() {
console.log("Something done with " + privateVar);
}
};
})();
// 사용
MyModule.doSomething();
2. 네임스페이스 패턴
// namespace.js
var MyApp = MyApp || {};
MyApp.utils = {
formatDate: function(date) {
// 날짜 형식화 로직
return date.toLocaleDateString();
}
};
MyApp.ui = {
showAlert: function(message) {
alert(message);
}
};
// 사용
MyApp.utils.formatDate(new Date());
MyApp.ui.showAlert("Hello!");
3. 스크립트 로딩 순서 관리
HTML에서 스크립트를 올바른 순서로 로드합니다:
<!-- 올바른 순서 -->
<script src="utils.js"></script>
<script src="models.js"></script>
<script src="views.js"></script>
<script src="controllers.js"></script>
<script src="app.js"></script>
현대적 방식: ES 모듈 시스템
ES6에서 도입된 모듈 시스템은 자바스크립트 코드 구조화의 표준이 되었습니다.
1. 기본 내보내기와 가져오기
// math.js
export default {
add: (a, b) => a + b,
subtract: (a, b) => a - b
};
// app.js
import Math from './math.js';
console.log(Math.add(5, 3)); // 8
2. 명명된 내보내기와 가져오기
// utils.js
export function formatDate(date) {
return date.toLocaleDateString();
}
export function formatCurrency(amount) {
return `$${amount.toFixed(2)}`;
}
// app.js
import { formatDate, formatCurrency } from './utils.js';
console.log(formatDate(new Date()));
console.log(formatCurrency(9.99));
3. HTML에서 모듈 사용하기
<script type="module" src="app.js"></script>
ES 모듈을 사용할 때 주의할 점:
type="module"
속성이 필요합니다.- 모듈은 항상 엄격 모드(
'use strict'
)로 실행됩니다. - 모듈은 CORS 정책을 따릅니다 (로컬 파일에서 직접 실행 시 문제 발생).
- 모듈은 기본적으로 defer 속성처럼 동작합니다.
번들러를 활용한 모듈 관리
대규모 프로젝트에서는 Webpack, Rollup, Parcel 같은 번들러를 사용하면 모듈 관리가 더 쉬워집니다.
Webpack을 사용한 예
- 설치하기
npm init -y
npm install webpack webpack-cli --save-dev
- 웹팩 설정 (webpack.config.js)
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
mode: 'development'
};
- 사용 예
// src/utils.js
export function greeting(name) {
return `Hello, ${name}!`;
}
// src/index.js
import { greeting } from './utils.js';
document.body.textContent = greeting('World');
- 빌드 및 HTML에 포함
npx webpack
<script src="dist/bundle.js"></script>
실전 예제: 웹사이트에 모듈화 적용하기
대부분의 웹사이트는 다음과 같은 자바스크립트 구성 요소를 가집니다:
- UI 상호작용
- 데이터 관리
- API 통신
- 유틸리티 함수
이러한 요소를 모듈로 분리하여 구성해 보겠습니다.
1. 프로젝트 구조 설정
/js
/modules
ui.js
api.js
data.js
utils.js
main.js
index.html
2. 모듈 코드 작성
// js/modules/utils.js
export function formatDate(date) {
return new Date(date).toLocaleDateString();
}
export function debounce(func, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}
// js/modules/api.js
export async function fetchData(url) {
try {
const response = await fetch(url);
return await response.json();
} catch (error) {
console.error('API 호출 중 오류 발생:', error);
throw error;
}
}
// js/modules/data.js
import { fetchData } from './api.js';
let cache = {};
export async function getUserData(userId) {
if (cache[userId]) {
return cache[userId];
}
const data = await fetchData(`/api/users/${userId}`);
cache[userId] = data;
return data;
}
// js/modules/ui.js
import { formatDate } from './utils.js';
import { getUserData } from './data.js';
export function renderUserProfile(userId, elementId) {
const element = document.getElementById(elementId);
if (!element) return;
element.innerHTML = '<div class="loading">로딩 중...</div>';
getUserData(userId)
.then(user => {
element.innerHTML = `
<div class="profile">
<h2>${user.name}</h2>
<p>가입일: ${formatDate(user.joinDate)}</p>
<p>이메일: ${user.email}</p>
</div>
`;
})
.catch(error => {
element.innerHTML = `<div class="error">사용자 정보를 불러오는 중 오류가 발생했습니다.</div>`;
});
}
3. 메인 애플리케이션 파일 작성
// js/main.js
import { renderUserProfile } from './modules/ui.js';
import { debounce } from './modules/utils.js';
// DOM이 로드된 후 실행
document.addEventListener('DOMContentLoaded', () => {
const userIdInput = document.getElementById('user-id');
const searchButton = document.getElementById('search-user');
searchButton.addEventListener('click', () => {
const userId = userIdInput.value.trim();
if (userId) {
renderUserProfile(userId, 'user-profile');
}
});
// 검색어 입력 시 디바운스 적용
userIdInput.addEventListener('input', debounce(() => {
const userId = userIdInput.value.trim();
if (userId && userId.length > 3) {
renderUserProfile(userId, 'user-profile');
}
}, 500));
});
4. HTML에 통합
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>사용자 프로필 조회</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h1>사용자 프로필 조회</h1>
<div class="search-container">
<input type="text" id="user-id" placeholder="사용자 ID 입력">
<button id="search-user">검색</button>
</div>
<div id="user-profile"></div>
</div>
<script type="module" src="js/main.js"></script>
</body>
</html>
디버깅 팁
모듈화된 자바스크립트 코드를 디버깅할 때 유용한 팁입니다:
1. 브라우저 개발자 도구 활용
크롬 개발자 도구의 ‘Sources’ 탭에서는 모듈화된 코드를 파일별로 볼 수 있습니다. 중단점(breakpoint)을 설정하여 코드 실행을 추적할 수 있습니다.
2. 모듈 로딩 확인
네트워크 탭에서 모든 자바스크립트 모듈이 제대로 로드되었는지 확인합니다.
3. 모듈 내보내기/가져오기 검증
콘솔에서 모듈 객체를 출력하여 예상한 속성과 메서드가 포함되어 있는지 확인합니다.
import * as utils from './utils.js';
console.log(utils); // 모든 내보내기 확인
4. 오류 메시지 분석
모듈 관련 오류 메시지는 대개 다음과 같은 패턴입니다:
Failed to resolve module specifier
– 경로 문제Unexpected token 'export'
– 모듈로 로드되지 않은 파일에서 export 사용X is not defined
– 모듈을 올바르게 가져오지 않음
5. CORS 오류 대응
로컬에서 모듈을 테스트할 때 CORS 오류가 발생할 수 있습니다. 이를 해결하려면 로컬 서버를 사용해야 합니다:
# Node.js를 사용한 간단한 서버
npx serve
마무리
자바스크립트 코드를 모듈화하면 다음과 같은 장점이 있습니다:
- 코드 구조 개선: 논리적으로 관련된 코드를 함께 그룹화
- 재사용성 증가: 모듈을 여러 프로젝트에서 재사용 가능
- 의존성 관리 개선: 모듈 간 명확한 종속성 선언
- 네임스페이스 충돌 방지: 각 모듈은 자체 스코프를 가짐
- 테스트 용이성: 독립적인 모듈은 단위 테스트하기 쉬움
현대적인 웹 개발에서는 ES 모듈을 기본으로 사용하고, 대규모 프로젝트에서는 번들러를 추가하는 것이 좋습니다. 이를 통해 자바스크립트 코드를 체계적으로 관리하고 유지보수성을 크게 높일 수 있습니다.
답글 남기기