Frontend/Today I Learned

[JS] Module 기초 정리

joycie416 2024. 8. 8. 18:17

firebase 등을 사용할 때 import { ... } from "./..../file.js"과 같은 내용을 본 적일 있을 것이다. 혹은 html에서 script 태그를 사용할 때 type='module'이라고 지정하는 경우가 있다. 느낌적으로 해당 파일에서 함수를 불러오는 것을 알 수 있다.

 

(ES6 기준에서 작성합니다.)

 

Module 기본 사용법

ES6에서는 `export` 키워드를 사용해 모듈을 만들고, 다른 파일에서 사용할 수 있게 한다. 모듈은 재사용할 수 있는 함수, 객체 또는 원시값을 의미한다.

// math.js
export const add = (a,b) => a + b;
export const multiply = (a,b) => a*b;
export const ONE = 1;
export const numbers = {
  one : 1,
  two : 2,
  three : 3,
};

 

위와 같이 math.js 파일에서 모듈화된 것을 가져와 사용하려면 다음과 같이 하면 된다.

// app.js
import { add, multiply, ONE, sample } from './math.js';

console.log(add(2, 3));      // 5
console.log(multiply(2, 3)); // 6
console.log(ONE);            // 1
console.log(sample.two);     // 2

 

모듈을 사용하는 이유

1. 종속성 관리가 쉬움

html에서 <script>를 사용해 js 파일을 로드합니다. 이와 같은 방식은 파일 간의 종속성과 로딩 순서를 수동으로 관리해야합니다. 이러한 방식은 규모가 커질수록 종속성을 추적하고 관리하기 어려워지며, 실수로 인해 버그가 발생할 수 있습니다.

<!-- 전통적인 스크립트 로딩 방식 -->
<script src="file.js"></script>
<script src="file2.js"></script> <!-- 이 스크립트는 file.js에 의존 -->
<script src="file3.js"></script> <!-- 이 스크립트는 file.js와 file2.js에 의존 -->

 

모듈 시스템을 사용하면 파일 간 종속성을 내부적으로 선언하므로, 파일 로드하는 순서에 신경 쓰지 않아도 된다. (모듈 로더가 종속성을 해석하고 올바른 순서로 스크립트를 묶어줌)

// ES6 사용 예시
import func1 from './file.js'
import func2 from './file1.js' // 자동으로 file.js에 대한 의존성 처리
import options from './file2.js' // 모든 의존성이 충족되면 실행

 

2. 코드 캡슐화, 충돌 방지

모듈은 자체적인 스코프를 가져 모듈 외부에서 모듈 내부 변수에 직접 접근할 수 없다. 따라서 전역 변수의 오염을 방지할 수 있고, 변수 이름이 충돌하는 것도 막아준다.

// module1.js
export function conflict() {
  console.log('Module 1의 함수');
}

// module2.js
export function conflict() {
  console.log('Module 2의 함수');
}

// app.js
import { conflict as func1 } from './module1.js';
import { conflict as func2 } from './module2.js';

func1(); // "Module 1의 함수"
func2(); // "Module 2의 함수"

 

3. 효율적인 코드 로딩

앞서 이야기했듯이 모듈 시스템을 통해 필요한 기능만 선택적으로 불러올 수 있고, 초기 로딩 시간을 단축할 수 있다. 코드 스플리팅을 사용하면 사용자의 현태 요구에 따라 필요한 코드만 동적으로 로드할 수 있다. 이러한 지연 로딩(lazy-loading)은 대규모 어플리케이션에서 성능과 자원 사용 최적화에 효과적이다.

// 동적으로 모듈 로드하기 (예: 사용자가 특정 기능을 활성화했을 때)
button.addEventListener('click', event => {
  import('./heavyModule.js')
    .then(module => {
      module.heavyFunction();
    })
    .catch(err => {
      console.error("모듈 로딩에 실패했습니다.", err);
    });
});

 

내보내기와 가져오기

내보내기

시작할 때 한 것처럼 그때 그때 export할 수도 있지만, 파일 맨 끝에 `{}`사용해 한번에 필요한 것들을 export할 수 있다.

// math.js
export const add = (a,b) => a + b;

const multiply = (a,b) => a*b;
const ONE = 1;
const numbers = {
  one : 1,
  two : 2,
  three : 3,
};

export {muliply, ONE, numbers};

 


위와 같은 export 방법은 named export라고 하며, 이와 다른 export로 default export로 단 한번 지정할 수 있다.

// utils.js
export default function square(x) {
  return x * x;
}

 

불러오기

named export는 import할 때 `{}`로 감싸야 하지만, default export는 `{}`로 감싸지 않아도 된다. default export는 새로운 이름을 지정해 바로 불러올 수 있다. 단, 이름을 as를 이용해 바꿀 때는 `{}`로 감싸줘야 한다.

// named export
import { add, multiply, ONE, sample } from './math.js';

// default export
import sqr from './util.js'
import {default as sqr} from './util.js'

 

 

위에서 잠깐 봤듯이 import를 사용해 불러올 때 `as`를 `{}` 안에 사용해 원하는대로 이름을 변경할 수 있다.

import { multiply as mul } from './math.js';
console.log(mul(3, 4));  // 12

 

전체 모듈 내용 가져오기

아래와 같이 `*`을 사용해 한번에 모든 모듈을 가져올 수 있다.

// app.js
import * as MathFunctions from './math.js';

console.log(MathFunctions.add(10, 5));       // 15
console.log(MathFunctions.multiply(10, 5));  // 50

 


덤. 파이썬 import와의 비교

파이썬에서도 다른 파일에 정의한 함수를 import를 통해 불러올 수 있는데, 아래 두 코드는 같은 뜻을 가진다.

// javascript
import { multiply as mul } from './math.js';
// python
from math import multiply as mul