...
타입스크립트 모듈 시스템
모듈은 독립 가능한 기능의 단위이다.
프로그램은 여러 모듈로 구성되어 있고 모듈을 결합해 하나의 프로그램을 만든다.
모듈을 사용하면 다음과 같은 장점이 있다.
- 유지 보수의 용이성 : 중복 코드의 최소화
- 전역 스코프 오염을 방지 : 이름 공간이 파일 단위로 제한되어 전역 이름 공간을 침범하지 않음
- 재사용성 향상 : 모듈을 다른 프로젝트에 공유하여 재사용 가능
이처럼 모듈은 전역 변수와 구분되는 파일 자체 유효 범위(Local Scope)를 가진다.
이는 모듈 내 선언된 변수, 함수, 클래스 등을 명시적으로 내보내지 않는 이상 모듈 외부에서 접근할 수 없음을 의미한다.
예를들어 타입스크립트 프로젝트에 다음과 같이 index.ts , test/test.ts 두 파일이 있다고 하자.
그리고 이 소스 파일에 똑같은 변수명을 선언해보자.
분명 별개의 파일이라 이름이 겹쳐도 상관없을 거라 생각했지만, 타입스크립트에서는 파일 자체가 (심지어 파일 경로가 달라도) 전역 스코프로 취급됨을 볼 수 있다.
따라서 각 파일에 전역 변수명이 공유되는 현상을 막기위해 모듈이라는 개념을 사용하는 것이다.
파일에 export {} 를 적어 소스를 모듈화 해주면 빨간줄이 사라지는걸 볼 수 있다.
우리가 자바스크립트(Node.js) 에서 모듈을 require 하여 사용했던 것 처럼, 타입스크립트에서도 모듈을 불러와 사용할 수 있다.
다만, 타입스크립트는 ES6+의 Modules 개념을 그대로 차용해서 노드(commonjs)와 모듈을 불러오는 문법이 다르다.
타입스크립트에서 모듈화를 하고 모듈을 불러올때는 export 와 import 키워드를 사용한다.
정리하자면, 모듈(module)은 소스 파일에 import / export 키워드가 있으면 취급이 되며, 외부에서 직접적으로 모듈을 import 해주어야 그 모듈의 데이터를 사용할 수 있게 되는 것이다.
타입스크립트의 모듈 문법과 ES6 자바스크립트의 모듈 문법은 둘이 같은 구문을 이용한다.
따라서 ES6의 모듈 문법인 import / export 에 대해서 사용법을 학습해보고, 그리고 CommonJS의 모듈 문법인 require / exports 와의 차이점이 어떤점이 있는지 다음 포스팅에서 확인해보자.
[ ES6 와 CommonJS 의 export(내보내기) 차이점 ]
CommonJS와 AMD라는 모듈 시스템에서는exports객체라는 개념이 존재한다.exports는 모듈로부터 내보내지는 데이터들을 담고 있는 하나의 객체이다. 우리는 노드 프로젝트를 진행할때module.exports =라는 모듈 내보내기 구문을 자주 써왔을 것이다.
사실 ES6의export default구문이, CommonJS의module.exports구문 동작을 대체하기 위해 등장한 것이지만, 사실 이 둘은 호환성이 좋지 않다.
그래서 타입스크립트 에서는 CommonJS와 AMD에서exports객체 동작을 유사하게 따라할 수 있도록export =문법을 따로 지원한다.
다만,export =문법으로 객체를 내보낸 모듈의 경우 반드시import module = require("module")문법으로만 불러와야 한다는 특징이 있다. (뭔가 ES6와 CommonJS의 짬뽕 같다..)
단, tsconfig.json의 module 속성값이 esnext이면 이 문법을 사용할 수 없다며 에러가 발생하니 유의하자.
내부 모듈 과 외부 모듈
타입스크립트에서의 모듈은 크게 두 가지로 Internal Module(내부 모듈)과 External Module(외부 모듈) 두가지의 모듈이 존재한다.
- 내부모듈 : 네임스페이스를 의미
- 외부모듈 : export 로 선언되어 외부로 공개된 모듈
외부 모듈 (Internal Module)
간단하게 정리하자면 export로 선언한 모듈을 외부 모듈이라고 한다.
즉, 우리가 일반적으로 말하고 인식하던 모듈은 모두 외부 모듈을 가리키는 것이다.
외부 모듈의 특징을 살펴보면, 외부 모듈의 이름 공간은 파일 내부로 제한되는 특징이 있다.
예를 들어 .ts 파일의 top-level(아무것으로도 감싸지지 않은 최상위 레벨)에 export가 존재하면 해당 ts파일을 모듈 파일로 생각한다.
그러나 파일의 top-level에 아무런 import나 export가 존재하지 않는다면 TS는 파일을 모듈이 아닌 스크립트 파일로 생각하고 이는 일반적인 JS파일과 같이 파일 내에 생성된 변수는 window, global과 같은 전역 스코프에 영향을 미치게 된다.
즉, 이 포스팅에서 처음에 예시를 들었던 .ts 파일단의 전역 변수명 충돌 현상은 바로 전역 스코프의 이름 공간을 공유해서 생긴 현상인 것이다
즉, ts파일의 top-level에 export나 import가 있으면 모듈 파일(외부 모듈)이고 export나 import가 존재하지 않는다면 그것은 모듈파일이 아니며 파일 내의 변수에 대한 독립성을 지켜주지 않는고 말할 수 있다.
내부 모듈 (External Module)
내부 모듈인 네임스페이스는 타입스크립트만의 특유한 모듈 방법으로써 전역 이름 공간과 분리된 네임스페이스 단위의 이름공간을 뜻한다.
즉, 클래스나 인터페이스, 각종 함수 같은 내용을 한 파일에서 그룹화하여 관리 할 수 있게 해주는 개념이다.
어렵게 이해할 필요없이, 모듈이란 자기만의 독립적인 스코프를 가지고 있는 것이라고 말할 수 있는데, 이를 하나의 파일 내부 에서 구현할수 있다는 것으로 이해하면 된다.
// 내부에서 선언한 DOM 이라는 모듈 (네임스페이스)
namespace Dom {
// 외부에서 접근 불가
const variable = 123;
// 외부에서 접근 가능하도록 export
export function add(arg1: number, arg2: number): number {
return arg1 + arg2;
}
// export 하지 않은 네임스페이스 내부에 정의된 함수는 외부에서 접근 불가
function subtract(arg1: number, arg2: number): number {
return arg1 - arg2;
}
}
Dom.add(1,2); // 3
타입스크립트 네임스페이스
위에서 잠시 살펴봤듯이 네임스페이스란, 내부 모듈을 칭한다.
이렇게 명칭이 혼용되는 이유는 TypeScript 1.5버전 이전에는 내부 모듈(Internal modules) 이라고 불렸기 때문이다.
프로젝트 규모가 커지면 파일 단위로 모듈을 분할해야 하는데, 이때 네임스페이스를 이용하면 여러 파일에 걸쳐 하나의 네임스페이스의 이름 공간을 공유할 수 있다.
타입스크립트는 네임스페이스를 이용해 논리적 그룹화를 제공한다.
논리적 그룹화는 네임스페이스의 이름만 같다면 컴파일 시에 각 파일들을 마치 하나의 논리적 영역 파일로 묶어 컴파일 해준다.
사실 namespace와 module은 키워드는 다르지만, 사실상 역할과 기능상 차이가 없다.
실제로 컴파일 후에도 동일하게 작동하는 코드를 반환한다.
module은 하나하나 import 해야되지만, 네임스페이스는 보통 여러 파일에 걸쳐 하나의 이름 공간을 공유한다는 차이점만 있을 뿐이다.
간단하게 네임스페이스 실전 코드를 통해 사용법을 익혀보자.
다음과 같은 프로젝트 디렉토리가 있다.
A.ts 와 B.ts 는 네임스페이스를 정의하는 파일이고, app.ts 는 네임스페이스의 메소드를 가져와 사용하는 파일이다.
여기서 보지 못한 새로운 문법이 나오는데, 바로 네임스페이스 임포트 /// <reference path="<file.ts>" /> 구문을 사용해 app.ts 파일에서 네임스페이스 파일들을 불러오도록 설정한 것이다. (사실상 import 문과 다를바 없다)
여기서 조심할 점은 비록 파일이 다르더라도, 프로젝트 내에서 같은 네임스페이스 내에서는 이름을 중복해 클래스, 함수, 변수 등을 선언하면 오류가 난다.
왜냐하면 같은 네임스페이스에서는 같은 논리적 스코프를 가지기에 동시에 같은 변수명을 선언하면 중복이 되기 때문이다.
반대로 네임스페이스가 다르다면 변수 이름이 같아도 이름 충돌이 없다.
이렇게 파일이 준비가 되었으면 컴파일을 해주는데, 여기서 반드시 --outFile 옵션을 통해 출력 js 파일을 위치를 지정해주어야 한다. (안그러면 네임스페이스가 합쳐지지 않음)
> tsc ./namespace/app.ts --outFile dist/app.js
위 컴파일된 자바스크립트 결과문에서 볼 수 있듯이, 내부 모듈(네임스페이스)은 JS ES6로 컴파일 될 때 즉시 실행 함수로 변환이 된다는 점을 알수가 있다.
자바스크립트의 즉시 실행 함수(IIFE) 내부는 전역 스코프와 분리된 이름 공간이다.
기본적으로 네임스페이스 외부에선 네임스페이스 내부를 볼 수 없는데, 이러한 동작은 IIFE 함수 내에서 지역 변수를 선언하며 각종 코드를 실행시키는 방식을 통해 구현한 것이다.
Namespace 의 단점
위에서 네임스페이스 사용법을 봤듯이 약간 번거롭다..
그리고 사실상 문법 생김새만 다르지 import / export 모듈 시스템과 큰 차이가 있지 않아 보인다.
더군다나 제대로 컴파일 옵션을 지정하지 않으면, 의존성들이 불러오지 않더라도 오류 메세지를 발생시키지 않고 정상적으로 Javascript 로 Transpile 하는 경우가 생길수도 있다.
그렇다면 Namespace 와 Modules 중에 어떤것을 선택해서 선택해야 될까?
이점은 Typescript 팀에서 명확히 가이드 라인을 제시해주었는데, 공식문서에 따르면 모던 코드들에 대해 ES Module 을 사용할 것을 권장하고 있다.
사실 주변 타입스크립트 개발자에게도 물어봐도 요즘 타입스크립트 개발 환경에선 Namespace 활용 빈도가 현격히 줄어들고 있는 형편이다.
그래도 ES6 module 등장전에 어떻게 타입스크립트 개발 환경에서 코드 재사용을 이루었는지 이해하고, 레거시한 코드를 손봐야 할때 이해를 돕기 위해 알아보는 정도는 나쁘지 않다고 생각해본다.
# 참고자료
https://lts0606.tistory.com/25
https://typescript-kr.github.io/pages/namespaces-and-modules.html
https://devowen.com/243
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.