- 컴파일러(TSC)가 모듈을 해석하는 방법
- 빌드시스템(Webpack, Gulp)이 모듈을 해석하는 방법
- 모듈이 실제로 런타임에 응용프로그램으로 로드(<script />태그, SystemJs 등)되는 방법
-
모듈 시스템을 지원하지 않음 (~1995)
-
단점
- 전역 네임스페이스에 정의 → 확장이 어려움
- 사용할 수 있는 변수명 고갈 → 같은 변수명으로 인한 충돌 발생
- 외부에서 어떤 기능을 사용할 수 있고, 어떤 부분은 사용하면 안되는지 구별 어려움
-
해결방안
-
객체 이용
// 객체 이용 window.memberModule = { memberList: [], getMemberList() { return this.memberList }, setMemberList(member: Member) { this.memberList.push(member); } } memberModule.setMemberList({ name: 'soo', age: 30 }); console.log(memberModule.memberList); // [{name: 'soo', age: 30}]
-
즉시 실행 함수 사용
const memberMoudle = (function () { const memberList = []; return { getMemberList: () => { return memberList; }, setMemberList: (member) => { memberList.push(member); }, }; })(); memberMoudle.setMemberList({ name: 'soo', age: 30 }); console.log(memberMoudle.getMemberList()); // [{name: 'soo', age: 30}] memberMoudle.setMemberList({ name: 'jin', age: 30 }); console.log(memberMoudle.getMemberList()); // [{name: 'soo', age: 30}, {name: 'jin', age: 30}]
-
-
문제점
- 자바스크립트 코드 → 브라우저 렌더링의 블로킹 요소: 즉, 응용 프로그램이 커질수록 첫 렌더링 속도 느려짐
-
-
페이지를 한번에 로드하는 대신, 필요한 파일만 동적으로 로드하는 방식 채택(2004~2009)
- 특징: 첫 페이지가 로딩된 다음 자바스크립트를 게으르게(그리고 비동기로) 로딩하는 모듈 로더 제공
- 비동기적으로 모듈을 로딩한다는 뜻
- 모듈은 잘 캡슐화되어야 한다.
- 모듈 간의 의존성은 명시적이다.
- 모든 모듈은 앱 내에서 고유 식별자를 가져야 한다.
-
NodeJS의 개발(2009)
- 특징: 모듈 시스템을 플랫폼 자체에 추가
-
Browserify(2011)의 출시:
require('modules') in the browser-
특징: 프론트엔드 엔지니어도 CommonJS 사용 가능
export const test = () => { return 'hi'; }
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); // defineProperty(obj: 정의할 object명, prop: ojbect 프로퍼티 , descriptor) // console.log(exports.__esModule) // output: true exports.test = void 0; // void 연산자로 undefined를 반환한다 (undefined 값을 주기 위해 사용) const test = () => return 'hi'; }; exports.test = test;
-
CommonJS 모듈 방식의 문제점
- require 호출은 반드시 동기 방식이어야 한다
- CommonJS 모듈 해석 알고리즘이 웹에 적합하지 않다
- 정적 분석이 불가능하다 → ?? 아마도 Object.defineProperty 메서드를 통해 동적으로 프로퍼티가 추가되니까 런타임 때 분석이 가능하므로 정적 분석이 불가능하다고 한게 아닐까 싶음
-
-
ES2015 개정판 출시
- 특징: 정적 분석이 가능한 새로운 표준 import/export 소개
- 문제점
- 표준을 지원하지 않는 자바스크립트 런타임도 있었으므로 환경에 맞는 타입으로 컴파일 필요
-
타입스크립트의 탄생
- 특징: 모듈의 코드를 소비하고 익스포트할 수 있는 여러 방식 추가
- 전역 선언, 표준 ES2015의 import와 export, 과거 호환성을 제공하는 CommonJS 모듈의 import 등
- 특징: 모듈의 코드를 소비하고 익스포트할 수 있는 여러 방식 추가
-
정적 임포트 vs 동적 임포트
정적 임포트 동적 임포트 장점 안정성 - 코드를 분할하여 병렬로 로딩할 수 있다. (응용 프로그램 최적화 가능) -
필요할 때만 코드를 로딩할 수 있음 | | 단점 | 코드의 양이 많아질수록 첫 렌더링 시간이 점점 길어진다 | 안정성의 이슈 |
-
정적 임포트
import user from './user'; export function userModule() { }
-
동적 임포트
// test.ts export function foo() { alert('foo'); } export function boo() { alert('boo'); } // app.ts let {foo, boo} = await import('./test.ts'); foo(); boo(); --------------- import(경로).then(obj => {...}).catch(err => {...})
-
-
모듈 모드 vs 스크립트 모드
- import나 export를 포함하느냐를 기준으로 모드 결정
- 모듈 모드: import, export 포함 → 권장
- 스크립트 모드: import, export 불포함
- import나 export를 포함하느냐를 기준으로 모드 결정
-
namespace 키워드 → 캡슐화 수단으로 권장하지 않음 (WHY?)
-
특징
- 이름이 있어야 한다
- 함수, 변수, 타입, 인터페이스, 다른 네임스페이스 익스포트 가능
- namespace 블록 안의 모든 코드는 명시적으로 익스포트하지 않는 한 외부에서 볼 수 없다
- 언제사용? 모듈이 커져서 여러 서브 모듈로 쪼개야 하는 상황을 때
-
왜? 모든 파일에서 Network.HTTP.get, Network.TCP.listenOn 등의 형태로 호출 가능 → 파일이 다르더라도 하나의 Network 네임스페이스에 속하도록 가능
// HTTP.ts namespace Network { export namespace HTTP { export function get<T>(url: string): Promise<T> { return new Promise((resvole, reject) => { }); } } } // UDP.ts namespace Network { export namespace UDP { export function send(url: string, packets): Promise<void> { return new Promise((resolve, reject) => { }); } } } // app.ts **Network**.HTTP.get<string[]>('http://url.com/dogs'); **Network**.UDP.send('http://url.com/cats', 'buffer');
- 어떻게 다른 파일에서 사용 가능?
- Network가 전역으로 이미 정의되어 있는 경우, 타입스크립트는 Network 변수를 새로 생성하는 것이 아니라, 기존의 변수를 확장하기 때문에 → 즉 그렇기 때문에 일반 모듈을 사용하는 것이 더 좋다
- 어떻게 다른 파일에서 사용 가능?
-
-
컴파일 코드 예시
namespace Flowers { export function give(count: number) { return count + 'flowers' } }
"use strict"; var Flowers; (function (Flowers) { function give(count) { return count + 'flowers'; // 클로저 } Flowers.give = give; })(Flowers || (Flowers = {}));
-
값과 타입 합치기(6.3.4 컴패니언 객체 패턴) → 같은 이름을 공유하는 객체와 타입을 쌍으로 묶는 패턴 / 타입과 값은 별도의 네임스페이스를 갖는다
type Currency = { unit: 'EUR' | 'GBP' | 'JPY' | 'USD' } let Currency = { DEFAULT: 'USD', from(value: number, unit = Currency.DEFAULT): Currency { return {unit, value} } }
-
여러 네임스페이스를 하나로 합치기
-
여러 인터페이스를 하나로 합치기
interface User { name: string } interface User { age: number } let a: User = { name: 'soo', age: 30 }