모듈

se-een·2023년 4월 6일
0
post-thumbnail

이 글은 '이웅모'님의 '모던 자바스크립트 Deep Dive' 책을 통해 공부한 내용을 정리한 글입니다. 저작권 보호를 위해 책의 내용은 요약되었습니다.

모듈

모듈이란 애플리케이션을 구성하는 개별적 요소로서 재사용 가능한 코드 조각을 말한다. 일반적으로 모듈은 기능을 기준으로 파일 단위로 분리한다. 즉, 자신만의 파일 스코프(모듈 스코프)를 가진다.

파일 스코프를 갖는 모듈의 자산(변수, 함수 등)은 기본적으로 비공개 상태이다. 즉, 캡슐화되어 있기에 다른 모듈에서 접근이 불가하다.

모듈은 애플리케이션이나 다른 모듈에 의해 재사용되어야 의미가 있으므로 이를 명시적으로 선택적 공개가 가능한데, 이 키워드를 export라 한다.

그리고 공개된 모듈의 자산을 사용하는 모듈 사용자는 자신의 스코프 내로 불러들여 재사용할 수 있는데, 이 키워드를 import라 한다.

정리하면 모듈은 애플리케이션과 분리되어 개별적으로 존재 가능하며 재사용성을 향상시켜 개발 효율성과 유지보수성을 높일 수 있다.

자바스크립트에서 모듈

자바스크립트는 script 태그를 사용하여 외부의 자바스크립트 파일을 로드할 수 있지만 독립적인 파일 스코프를 갖지 않는다.

따라서, 파일 분리를 진행하고 script 태그로 로드해도 결국 하나의 자바스크립트 파일에 있는 것처럼 작동하는데 이는 전역 변수의 충돌 등 다양한 문제를 야기한다.

자바스크립트를 브라우저 환경 외에도 사용하고자 하는 움직임이 생기면서 모듈 시스템의 필요성이 더 높아졌고, 이런 상황에 제안된 것이 CommonJSAMD(Asynchronous Module Definition)이다.

Node.js는 CommonJS를 채택 후 독자적인 진화를 거쳐 완전히 동일한 사양의 CommonJS는 아니지만 기본적으로 CommonJS를 따르고 있다. 즉, ECMAScript 표준 사양은 아니다.

CommonJS

  • module.exports로 모듈 정의, require로 모듈 사용
  • 동기적으로 동작
  • 서버사이드 언어로 사용하기 위해 등장
  • 완전히 동일하지는 않지만 Node.js에서 사용
// A.js

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

const a = 10;
const b = 5;
const moduleSum = require('A.js');

moduleSum(a,b); // 15

정리하면, module.exports로 모듈을 내보내고 require 키워드로 모듈을 가져온다고 볼 수 있겠다.

module.exports와 exports

module.exportsexports가 있는데 공식 문서에 의하면 exports는 단순히 module.exports를 참조할 뿐이라고 되어있다.

즉, module.exprotsexports같은 객체를 바라보고 있으며, exportsmodule.exports의 shortcut이다.

내보내기 할 것들의 컨테이너로 활용할 경우 아래와 같이 사용하면 된다.

// A.js

module.exports.foo = function() { console.log('foo'); }
module.exports.name = 'Kim';

exports.age = 100;
// B.js

const imports = require('A.js');

console.log(imports); // {foo: ƒ, name: 'Kim', age: 100}

constructor function으로 사용할 경우 아래와 같이 사용하면 된다.

// A.js

module.exports = function() { console.log('foo'); }
// B.js

const foo = require('A.js');

foo(); // foo

다만, 주의할 점은 다음과 같은 코드는 불가하다.

exports = { name : 'Kim' } 
exports = function() { console.log('foo'); }

위 코드는 module.exports에 key, value로 값을 넣는 것이 아닌 새로운 변수 exports에 값을 넣으라는 뜻이다. 따라서 module.exports의 값은 비어있게 되어 오류를 발생시킨다.

AMD

  • defined으로 모듈 정의, require로 모듈 사용
  • 비동기 상황에서 모듈을 사용하기 위해 등장
  • RequireJS에서 구현

전체적인 문법은 다음과 같다.

// myModule.js

// 첫 번째 인자 = 모듈의 의존성을 나타내는 배열
// 두 번째 인자 = 모듈의 실제 구현부
define(['A', 'B'], function(moduleA, moduleB) {
  console.log(moduleA, moduleB);
  
  // 모듈을 내보내는(정의하는) 곳
  return {
    newModuleA: moduleA,
    newModuleB: moduleB,
  }
});
// A.js

// 첫 번째 인자 = 사용하고자 하는 모듈
// 두 번째 인자 = 모듈 사용부
require(['myModule'], function (myModule) {
  console.log(myModule.newModuleA); // A
  console.log(myModule.newModuelB); // B
});

UMD

AMD와 CommonJS를 호환하기 위해 나온 모듈 시스템이다. 정해진 코드라기보단 하나의 디자인 패턴과 가깝다고 한다. AMD의 define과 CommonJS의 module.exprots의 차이를 활용하면 UMD를 만들 수 있다.

예시는 여기(제로초 블로그)서 확인해보자.

ESM

ES6에서 클라이언트 사이드 자바스크립트에서도 동작하는 모듈 기능을 추가했다. script 태그에 type="module" 어트리뷰트를 추가하면 로드된 자바스크립트 파일은 모듈로써 동작한다.

보통은 ESM임을 명확히 하기 위해 파일 확장자를 mjs로 사용할 것을 권장한다고 한다.

<script src="app.mjs" type="module"></script>

ESM은 파일 자체의 독자적인 모듈 스코프를 제공하기 때문에 모듈 내 var 키워드로 선언한 변수는 더 이상 전역 변수가 아니며 window 객체의 프로퍼티도 아니다.

export와 export default 키워드

모듈 내부에서 선언한 식별자를 외부에 공개하기 위해 export 키워드를 사용한다. 선언문 앞에 사용하며 변수, 함수, 클래스 등 모든 식별자를 export 할 수 있다.

// A.mjs

export const school = {
  Seoul : '서울대',
  Suwon : '수원대',
};

export const name = 'Kim';

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

하나의 값만 내보내고자 한다면 export default 키워드를 사용할 수 있다. export와 차이점이라면 식별자 없이 하나의 값export 한다는 점과 var, let, const 키워드는 사용할 수 없다는 점이다.

export default const name = 'Kim'; // Uncaught SyntaxError

import 키워드

사용하고자 하는 모듈을 자신의 모듈 스코프 내부로 로드할 때 사용하는 키워드이다. export식별자 이름으로 import 해야하며 주의할 점은, CommonJS와 다르게 파일 확장자를 생략할 수 없다.

import { school, name, sum } from './A.mjs';

export한 식별자 이름을 일일히 지정하지 않고 한 번에 import도 가능하다. 또한 import되는 식별자는 as 키워드 뒤에 지정한 이름의 객체에 프로퍼티로 할당된다.

import * as lib from './A.mjs';

console.log(lib.school); // { Seoul : '서울대', Suwon : '수원대' }
console.log(lib.name); // 'Kim'
console.log(lib.sum(1,2)); // 3

export default 키워드의 경우 임의의 이름으로 import 할 수 있으나 가독성을 위하여 export한 식별자의 이름으로 사용한다.

// Person.mjs
export default class Person {}

// App.mjs
import abc from './Person.mjs'; // abc => class Person
profile
woowacourse 5th FE

0개의 댓글