모듈
모듈은 독립 가능한 기능의 단위이다. 모듈을 사용하면
1) 유지보수가 쉬워진다. 자주 사용하는 공통 기능을 정의해 사용하면 중복 코드가 적어져 유지 보수가 쉬워진다.
2) 전역 스코프 오염을 방지한다. 전역 스코프 내부에서 함수나 변수 이름을 중복해 선언할 수 없으므로 이를 파일 내부에 한정해 모듈로 선언하면 전역 공간을 침범하지 않는다.
3) 재사용성이 향상된다. 모듈은 프로젝트에 자주 사용되는 기능을 공통으로 뺴놓을 수 있게 된다.
내부 모듈과 외부 모듈
내무 모듈은 네임스페이스를 의미하고, 외부 모듈은 export 라고 선언해 외부로 공개된 모듈을 의미한다.
네임 스페이스는 여러 파일에 걸쳐 하나의 이름 공간을 공유하지만, 외부 모듈은 모듈 파일마다 이름 공간이 정해진다.
- 내부모듈
내부 모듈은 네임스페이스는 전역 이름 공간과 분리된 네임스페이스 단위의 이름공간이다.
따라서 같은 네임스페이스내 이름공간에서는 파일 B가 파일 A에 선언된 모듈을 참조(reference)할 수 있다.
파일이 다르더라도 같은 네임 스페이스 내에서는 이름을 중복해 클래스,함수,변수 등을 선언 할 수 없다.
- 외부모듈
export로 선언한 모듈을 외부 모듈이라고 한다. export 키워드로 외부 모듈로 선언할 수 있는 대상은 변수, 함수, 클래스 , 네임스페이스 등이 가능하다.
export로 외부 모듈로 선언하면 이름이 같아도 충돌이 일어나지 않는다.
1 | //ts1.ts |
타입스크립트는 module 옵션을 통해 다른 모듈 형식으로 변환할 수 있게 지원한다. tsc의 –module 옵션을 통해 특정 모듈 형식으로 변환할 수 있다.
1 | tsc --module <모듈형식> <변환할 파일명> |
module 옵션에서 사용할 수 있는 모듈형식은 다음과 같다.
- commonjs
- amd
- system
- umd
- es2015 또는 es6
모듈 예제를 컴파일 할 때 주의사항
Node.js 버전이 8.5.0 이상이면 –experimental-modules 플래그를 통해 es 모듈에 대한 컴파일을 지원한다.
1 | node --experimental-modules load-default.mjs |
이 때 자바스크립트 확장자가 .mjs 여야한다.
반대로 8.5.0 미만이면 es2015를 지원하지 않으므로 다운 레벨 컴파일이 필요하다.
네임스페이스
네임스페이스는 하나의 독립된 이름 공간을 만들고, 여러 파일에 걸처 하나의 이름 공간을 공유하게 한다.
namespace 키워드를 이용해 선언한다. module 키워드로도 같은 역할과 기능을 한다.
1 | namespace Hello {} |
namespace와 module의 선언과 컴파일 결과 확인
1 | namespace Hello { |
1 | var Hello; |
namespace와 module은 컴파일 결과가 완전히 동일하다. 내부 모듈은 자바스크립트(es6)로 컴파일 될 때 즉시 실행함수로 변환된다.
한 파일에 여러 네임스페이스 선언
네임스페이스는 보통 여러 파일에 걸처 하나의 이름 공간을 공유하는데, 특정 파일에만 네임 스페이스를 선언하거나, 하나의 파일에 여러 네임스페이스를 함께 써도 가능하다. 한 파일에서 여러 네임스페이스를 써서 서로의 이름 공간에 접근하려면 모듈을 export로 선언해야한다.
1 | // 네임스페이스 간에는 모듈을 서로 호출하고 주고 받을 수 있다. |
자바스크립트는 코드가 순차적으로 실행되므로 상위의 있는 네임스페이스에서 하위에 선언된 네임스페이스로 접근할 수 없어야 한다.
그런데, 컴파일 결과를 보면 실행 순서와 관계없이 자유롭게 호출할 수 있는데 이는 컴파일시 네임 스페이스 이름이 var 변수로 선언됬기 때문이다.
1 | // 네임스페이스 간에는 모듈을 서로 호출하고 주고 받을 수 있다. |
참조 경로 추가
1 | namespace Car { |
car1.ts 파일에는 export로 auto변수와 ICar 인터페이스가 선언되어있음.
1 | namespace Car { |
car2.ts에서 Taxi 클래스는 car1.js의 ICar 인터페이스를 참조한다. 같은 네임스페이스(Car) 안에서 선언되어 있으므로
tsc로 프로젝트 단위로 컴파일 하면 이상없이 컴파일된다. 그러나 특정 파일 (car2.ts)만 컴파일 하면 문제가 발생한다.
따라서 파일 상단에 명시적으로 참조 경로(reference path)를 선언해줘야한다.
1 | /// <reference path="car1.ts" /> |
참조 경로는 트리플 슬래시를 이용해 작성한다. 참조 경로를 작성한 car2.ts를 컴파일하면 참조 경로로 연결된 car1.ts도 함께 컴파일되어
car1.js, car2.js 두 파일이 생성된다.
그 다음 컴파일된 car2.js를 실행하면 다음과 같다.
1 | node car2.js |
undefined가 출력되는 이유는 컴파일 후 js 파일에서 참조 경로가 주석처럼 인식되어 제 기능을 할 수 없기 때문이다.
올바른 결과를 컴파일 하려면 두 파일을 합쳐서 컴파일 해아한다.
1 | tsc --out out.js car2.ts |
위 명렁어를 입력하면 car1.js 파일의 결과를 합쳐 out.js라는 파일을 생성한다. 컴파일 결과가 한 파일에 존재하므로 참조 경로나 require 함수가 없더라도 정상적으로 결과가 출력된다.
1 | var Car; |
네임스페이스 모듈
네임스페이스는 export를 이용해 모듈로 선언할 수 있다.
import문을 이용해 js로 컴파일 된 뒤에도 명시적으로 모듈 호출(imort문 , require 함수) 등을 할 수 있다.
먼저 export 키워드를 이용해 Car 네임스페이스를 모듈로 선언한다.
1 | export namespace Car { |
그 다음 네임스페이스 모듈을 호출하는 import문을 다음과 같이 작성한다.
1 | import * as ns from "./car1"; |
그 뒤 car2.ts 파일을 컴파일 한다.
1 | tsc car2.ts |
tsc로 컴파일시 –module 옵션을 설정하지 않았으므로 기본 설정인 CommonJs 모듈 형식으로 변환된다.
네임스페이스 이름 확장
네임스페이스 이름은 알파벳 대소문자를 사용한다.
1 | namespace Animal{} |
네임 스페이스 이름은 예외로 점(.)을 허용한다. 점을 이용하면 네임스페이스 간의 이름 계층을 만든다.
1 | namespace Animal.Land{} |
Animal과 Animal.Land 네임스페이스는 서로 다른 이름 공간이다.
따라서 Animal.Land 네임스페이스에서 Animal의 모듈 함수를 호출할 수 있다.
또한, 한 파일내에 여러 네임스페이스를 선언할 때 논리적인 이름 순서 상 상위 이름은 앞에 선언되어야한다.
그러나 선언 순서를 바꾼다고 해서 문제가 생기지는 않는다.
1 | namespace Animal{ |
위와 같이 선언된 네임스페이스를 import 할때는 최상위 이름만을 이용해야한다.
1 | import { Animal } from "./animal"; |
모듈의 이해와 사용
타임스크립트는 ES2015 모듈 선언과 호출과 관련한 스타일을 지원한다.
모듈 선언과 모듈 임포트
export나 import 제한자를 통해 모듈을 선언하고 호출할 수 있다. 이러한 방식을 명명된 내보내기(named exports)라고 한다.
외부로 노출된 모듈은 import를 통해 가져올 수 있다.
함수나 인터페이스와 같은 단위별로 노출하려면 개별 노출 형식을 사용한다.
1 | export interface ICard {} |
export로 노출된 ICar 인터페이스와 saveInfo 함수는 다음과 같은 방식으로 임포트 할 수 있다.
1 | import { ICard,saveInfo} from './export' |
여러 모듈 export 하기
함수나 인터페이스 뿐만 아니라 배열과 변수 등도 모듈로 선언할 수 있다.
1 | let ver = "1.0"; |
일일이 export 붙이기 힘드므로 다음과 같은 형태로 여러 변수를 함꼐 export 할 수 있다.
export된 함수는 다음과 같이 import 해 사용할 수 있다.
1 | import { ver, author, extensions, display } from "./variable"; |
만약, 모둘로 선언할 대상이 인터페이스를 사용하는 함수 일때는 함수가 인터페이스와 의존관계에 있으므로 함께 선언해주어야한다.
1 | interface ICar {} |
모듈을 재 노출해 사용하기
- 가져온 모든 모듈 재노출
한 파일에서 재노출할 모듈이 많아 구체적인 이름을 열거하기 불편할 때 export * from 을 이용한다.
(*) 은 모든 모듈을 의미한다.
1 | export * from "./module1"; |
외부 모듈 *에 대한 별칭이 없으므로 임포트시 as 키워드를 이용해 별칭을 추가해야한다.
1 | import * as m from "./module1"; |
디폴트 모듈의 이해와 사용법
디폴트 모듈 선언
타입스크립트 1.5에서는 import-equals문과 export-equals 문 대신 다른 형태로 모듈을 선언하거나 임포트 할 수 있다.
export-equals 할당은 default 키워드를 이용한 방식으려 변경되었다.
1 | export default { |
위와 같이 default로 선언된 모듈은 파일마다 하나씩만 선언되어야 한다.
다음과 같은 방식으로도 선언 가능하다.
1 | //기본 |
import-equals 문은 require() 대신 import 형식으로 가져올 수 있다.
1 | import Validator from " ./validator"; |
디폴트 모듈과 명명된 모듈을 함께 가져오기
디폴트 모듈은 default 키워드를 이용해 선언한다.
익명의 객체 리터럴을 디폴트 모듈로 선언하고 임포트하는 과정은 다음과 같다.
1 | export default{ |
익명의 객체 리터럴을 디폴트 모듈로 선언할 경우 외부에 export 할 때 이름이 없으므로 임의로 지정해 주어야한다.
1 | import profile from "./default"; |
또한 default 키워드는 한 번만 사용해야 한다. 유일한 식별자로서만 인식되기 때문이다. 두 번 사용하면 컴파일 에러가 발생한다.
default 모듈을 import 할때는 {}에 선언하면 안돼고 {}가 없이 선언해야한다.
1 | import {default} from "./default"; (x) |
따라서 디폴트 모듈과 일반 모듈을 함꼐 import 할때는 다음과 같이 사용해야한다.
1 | import profile from "./default"; // 한 개의 디폴트 모듈 임포트시 |
이처럼 디폴트 모듈과 일반 모듈을 함께 임포트 할 때는 디폴트 모듈은 이름만 선언하고 일반 모듈은 {} 내부에 선언해야한다.
디폴트 모듈로 타입과 모듈을 함께 노출하기
1 | interface HelloMessage { |
interface 명과 함수 명을 HelloMessage로 일치시키고 해당이름으로 export 한 뒤 ,
export된 디폴트 모듈을 임포트하면 같은 이름을 이용해 함수 또는 인터페이스 타입으로서 사용할 수 있다.
1 | import hello from "./export"; // 디폴트 모듈은 임포트할 때 이름 변경이 가능하다. |
임포트한 hello 모듈은 hello(‘hello’)와 같이 함수로 사용될 수 있고, : hello와 같이 인터페이스 타입으로 사용될 수도 있다.
모듈 시스템
모듈 로더와 모듈 형식
모듈 로더(module loader)는 모듈 파일에 선언된 모듈을 실행할 수 있다.
모듈 형식은 모듈 정의에 관한 표준 명세에 해당한다.
모듈 로더마다 지원하는 명세가 다소 다른데 최근 모듈 로더는 대체로 CommonJs와 AMD 모듈 형식을 기본으로 지원한다.
- Commonjs
- amd
- system
- umd
- es2015
이들 중 타입스크립트는 es2015 모듈 형식을 기본으로 사용한다.
모듈 형식에 맞춰 컴파일하기
타입스크립트는 es2015 모듈을 이용해 하위 표준으로 컴파일 할 수 있다.
1 | // 프로젝트 기반으로 target을 ES3로 지정해 컴파일하기 |
–module에 옵션에 사용할 수 있는 모듈형식의 설정값은 다음과 같다.
- none
- commonjs
- amd
- system
- umd
- es6 또는 es2015
설정값중 none은 es2015 모듈 형식과 commonjs 모듈 형식을 사용하지 않을 떄 설정한다.
만약 –module 옵션 사용 시 모듈 형식을 지정하지 않으면 target 값에 따라 다르게 기본값이 정해지게 된다.
–moodule 옵션에 관한 부가적 설명
1) 명령어로 모듈을 컴파일 할 때 –out 옵션을 사용하면 단일 파일로 생성할 수 있다.
1 | tsc --out <출력할 js파일명> <변환할 ts 파일명> --module <모듈형식> |
–out 옵션과 –module 옵션을 사용할 떄 허용하는 설정값은 amd,system 2가지 이다. 이 외에는 컴파일 에러가 발생한다.
2) 여러 파일에 걸쳐 모듈이 나눠져 있는 대상을 컴파일 할 때
1 | tsc <파일1이름>.ts <파일2이름>.ts --module amd | system |
3) 소스맵 필요시
1 | tsc <파일이름>.ts <파일이름>.ts - sourcemap--out <파일이름>.js |
특정 모듈 형식을 실행하기 위한 준비
1) 모듈 로더를 구동하기 위한 HTTP 서버 준비
SystemJs 모듈 로더를 실제 웹 브라우저 환경에서 실행할 수 있도록 HTTP 서버를 준비한다. Node.js 서버를 준비한다.
편의를 고려해 express 프레임 워크를 이용한다.
1 | npm install express --save |
설치가 끝나면 사용할 http 서버 코드 작성
1 | var express = require("express"); |
작성후에 node로 서버를 실행한다.
1 | node server.js |
http://localhost:3000 로 서버를 시작할 수 있다.
2) 모듈 로더에서 사용할 a.ts와 b.ts 파일 준비
1 | export function unique(arr) { |
1 | import { unique } from "./a"; |
a와 b는 특정 모듈 형식으로 변환해 SystemJs 에서 호출해 사용할 예정이다.
각종 모듈 형식에 대한 소개
ES2015 모듈 형식
타입스크립트는 ES2015 모듈 형식을 기본으로 채택해 모듈을 선언하고 모듈을 호출한다.
b.ts 파일을 es2015 형식으로 컴파일 하는 방법은 이렇다.
1 | tsc b.ts --outDir src-es6 --module es2015 --target es2015 |
위 명렁어는 b.ts 파일을 컴파일 할 떄 src-es6 디렉터리에 컴파일 결과(a.ts , b.ts)가 저장 될수 있도록 –outDir 옵션을 사용했다.
또한 –module 옵션을 이용해 es2015 모듈 형식으로 컴파일 되도록 설정했다. 이로인해 a.ts와 b.ts 파일이 es2015 모듈 형식으로 컴파일된다.
es2015는 Node.js 에서 곧바로 실행할 수 없다.
1 | cd src-es6 |
그러므로 es2015 모듈 형식을 node.js에서 확인하려면 앞서 언급했던 것처럼 –experimental-modules 플래그를 이용해야하고, 확장지를 .mjs로 변경해야하는 불편함이 발생한다. 그러므로 es6 형식은 모듈로더인 SystemJs 를 이용해 실행한다.
1) 먼저 SystemJs 모듈 로더를 설치한다.
1 | npm install systemjs --save-dev |
2) 그 다음 traceur 컴파일러를 로컬에 설치한다.
1 | npm install traceur --save |
3) 그 다음 systemJs 모듈 로더를 구동하기 위한 html 페이지를 작성한다.
1 | <html> |
CommonJs 모듈 형식
CommonJs는 node.js에서 지원하는 모듈 형식이고 대부분의 모듈 로더에서 지원한다.
commonJs는 export로 노출한 모듈을 require 함수를 이용해 호출 할 수 있다.
예를 들어,
1 | exports.add = function (a, b) { |
위 모듈 파일을 호출해 사용하려면 require()함수를 이용한다.
1 | var add = require("calc").add; |
그런데, calc.js를 calc.ts로 옮겨오면 에러가 발생한다. 정상적으로 CommonJs를 타입스크립트에 인식시키려면 다음과 같이 해아한다.
1) @types/node 설치
1 | npm install @type/node --save-dev |
2) tsconfig.json 파일의 type 속성에 node 추가
1 | { |
3) calc.js를 calc.ts로 옮기고 ts 파일 컴파일
1 | tsc calc.ts --outDir src-cjs --module commonjs --target es5 |
AMD 모듈 형식
AMD(Asynchronous Module Definition)은 비동기 모듈 호출 방식으로 웹 사이트의 성능을 개선하기 위한 목적으로 나왔다.
웹 사이트에서 AMD 모듈을 호출하면 모듈 파일을 비동기로 가져와서 호출한다.
AMD 모듈을 정의할 떄는 define() 함수를 이용한다.
1 | define(id?,dependencies?,factory); |
id는 모듈 아이디를 의미하며 생략 가능하다. dependencies는 모듈 id의 배열을 의미하며 의존하고 있는 모듈의 id목록을 배열로 전달한다.
factory는 익명 함수로 모듈의 구현 코드가 위치한다.
1 | // id를 생략하고 두번째 매개변수에 의존 모듈로 jquery를 사용할 경우 |
amd로 컴파일 하려면 다음과 같다.
1 | tsc calc.ts --outDir src-amd --module amd --target es5 |
UMD 모듈 형식
umd(Universial Module Definition) 모듈 형식은 클라이언트, 서버에서 보편적으로 작동하며, RequireJs등 모듈 로더에서 지원한다.
umd는 CommonJs 형식과 AMD 형식 모두 고려하기 때문에 호환성을 갖춘 모듈로 정의할 수 있지만 모듈 코드 양이 많아진다는 단점이 있다.
UMD 모듈패턴은 익명함수 매개변수인 factory로 모듈을 정의한 뒤 익명 함수를 전달하는 형태로 사용한다.
1 | (function(factory){ // 모듈을 factory 매개변수로 받음 |
umd로 컴파일 하려면 다음과 같다.
1 | tsc calc.ts --outDir src-umd --module umd --target es5 |
SystemJs 모듈 형식
SystemJs 모듈 형식은 es를 호출할 때 브라우저나 node.js에서 비동기 형태로 es 모듈을 호출하는 모듈 로더이다.
SystemJs 모듈 형식은 지원하는 모듈 로더가 SystemJs에 한정된다.
컴파일 하는 방법은 다음과 같다.
1 | tsc b.ts --outDir src-system --module system --target es5 |