타입스크립트 모듈에 대해서 알아보자

정민교·2023년 8월 11일
0

typescript

목록 보기
1/17

자바스크립트는 굉장히 오랜 시간동안 다양한 모듈 시스템을 지원해왔다.

하지만 최근에는 ES6의 ES 모듈 시스템으로 통합되고 있다. ES 모듈 시스템은 많이 알고있는 import/export 문법을 통한 모듈 노출 및 로드를 말한다.

ES 모듈 시스템은 2015년 ECMAScript에 소개되면서 2020년에 들어 대부분의 브라우저(IE는 미안하지만 빠져줘) JS 런타임이 지원하게 되었다.

📒자바스크립트 모듈 정의

타입스크립트에서는 top-level에 import 혹은 export를 포함하고 있는 파일을 모듈로 간주한다.

자바스크립트 명세서에 따르면, 기본적으로 자바스크립트에서는 import, export 구문이 없거나 top-level await이 없다면 모듈로 보지 않고 하나의 스크립트로 간주한다. 스크립트 파일 내에서 선언된 변수와 타입은 global scope에 포함되어 공유된다.

어찌되었든 모듈은 각자의 스코프를 가지고 실행되기 때문에, 모듈 안에서 선언된 변수, 함수, 클래스 등은 외부 모듈에서 참조할 수 없다.

참조하기 위해서는 export를 사용해서 명시적으로 노출시켜야 다른 모듈에서 참조할 수 있으며, 이러한 노출된 변수, 함수, 클래스 등을 다른 모듈에서 사용하기 위해서는 import를 사용해야 한다.

예를 들어, 이 파일에서 노출하고 싶은 변수, 함수 등은 없지만 이 파일을 모듈로 만들고 싶으면

export {};

이렇게 export 문을 추가해주면 된다.

MDN: JavaScript Modules

📒타입스크립트의 모듈

타입스크립트에서 module-based code를 작성하기 위해서는 세 가지를 고려해야 한다.

  • Syntax: import와 export를 위해 어떤 구문(문법)을 사용할 것인지
  • Module Resolution: 모듈 이름(혹은 경로)와 디스크 상의 파일 간의 관계가 무엇인지
  • Module Output Target: 내가 생성한 자바스크립트 모듈이 어떤 형식을 하고 있어야 하는지

모듈에서 어떤 식별자를 어떻게 노출시키고 다른 모듈에서는 어떻게 import 할 수 있는지 알아봅시다. CommonJS 모듈 시스템에 대해선 별도로 언급하지 않습니다.

✔️ES Module Syntax

📌export default

main export 선언은 export default로 가능하다.

// @filename: hello.ts
export default function helloWorld() {
  console.log("Hello, world!");
}

그 후에 import는 아래와 같이 한다.

import helloWorld from "./hello.js";
helloWorld();

📌export

다음과 같이 변수 혹은 함수 등을 하나씩 export할 수 있다.

// @filename: maths.ts
export var pi = 3.14;
export let squareTwo = 1.41;
export const phi = 1.61;
 
export class RandomNumberGenerator {}
 
export function absolute(num: number) {
  if (num < 0) return num * -1;
  return num;
}

아래와 같은 방식으로 import 한다.

import { pi, phi, absolute } from "./maths.js";
 
console.log(pi);
const absPhi = absolute(phi);

아래와 같이 노출된 변수 혹은 함수 등을 별칭으로 지정하여 import도 가능하다.

import { pi as π } from "./maths.js";
 
console.log(π);

아니면 모든 노출된 식별자들을 하나의 namespace에 담아서 import 할 수도 있다.

// @filename: app.ts
import * as math from "./maths.js";
 
console.log(math.pi);
const positivePhi = math.absolute(math.phi);
          

섞어서 사용도 가능

// @filename: maths.ts
export const pi = 3.14;
export default class RandomNumberGenerator {}
 
// @filename: app.ts
import RandomNumberGenerator, { pi as π } from "./maths.js";
 
RandomNumberGenerator;
//(alias) class RandomNumberGenerator
//import RandomNumberGenerator
 
console.log(π);
//(alias) const π: 3.14
//import π

어떠한 식별자도 포함하지 않고 그냥 파일(모듈)을 import 할 수도 있다.

// @filename: app.ts
import "./maths.ts";
 
console.log("3.14");

이러면 아무 식별자도 import 하지 않지만 maths.ts의 모든 코드가 평가되기 때문에 다른 객체에 영향을 미치는 side-effect가 발생할 수 있다.

✔️TypeScript Specific ES Module Syntax

모든 타입들은 JS의 문법처럼 export, import 될 수 있다.

// @filename: animal.ts
export type Cat = { breed: string; yearOfBirth: number };
 
export interface Dog {
  breeds: string[];
  yearOfBirth: number;
}
 
// @filename: app.ts
import { Cat, Dog } from "./animal.js";
type Animals = Cat | Dog;

📌import type

import type을 사용하면 타입만 import 할 수 있다.

// @filename: animal.ts
export type Cat = { breed: string; yearOfBirth: number };
export type Dog = { breeds: string[]; yearOfBirth: number };
export const createCatName = () => "fluffy";
 
// @filename: valid.ts
import type { Cat, Dog } from "./animal.js";
export type Animals = Cat | Dog;
 
// @filename: app.ts
import type { createCatName } from "./animal.js";
const name = createCatName();

여기서 createCatName은 import type으로 import 되었기 때문에 값처럼 사용이 불가능하다.

📌inline type imports

// @filename: app.ts
import { createCatName, type Cat, type Dog } from "./animal.js";
 
export type Animals = Cat | Dog;
const name = createCatName();

타입스크립트 4.5부터 이렇게 type을 앞에 붙여 한 번에 타입과 값을 import 할 수 있다.

CommonJS and ES Modules interop
There is a mis-match in features between CommonJS and ES Modules regarding the distinction between a default import and a module namespace object import. TypeScript has a compiler flag to reduce the friction between the two different sets of constraints with esModuleInterop.

✔️CommonJS와 ES 모듈 간 상호 운용성

CommonJS와 ES 모듈 사이에는 default import와 module namespace object import에 대한 차이점과 관련하여 기능이 일치하지 않는다..

TypeScript는 esModuleInterop이라는 컴파일러 플래그를 사용하여 두 가지 다른 제약 조건 세트 간의 마찰을 줄인다.

typescript 공식 문서: esModuleInterop

✔️TypeScript’s Module Resolution Options

모듈 해석은 import나 require 문에서 문자열을 가져와 해당 문자열이 어떤 파일을 가리키는지 결정하는 과정이다.

TypeScript에는 ClassicNode 두 가지 해석 전략이 포함되어 있다.

컴파일러 옵션의 module이 commonjs가 아닌 경우 기본값인 Classic은 하위 호환성을 위해 포함되어 있습니다.

TypeScript 내에서 모듈 전략에 영향을 주는 많은 TSConfig 플래그는 다음과 같다.

  • moduleResolution
  • baseUrl
  • paths
  • rootDirs.

typescript 공식 문서: moduleResolution

✔️TypeScript’s Module Output Options

생성되는 JS 결과물에 대해 영향을 미치는 두 가지 옵션이 존재한다.

  • target: 어떤 JS 기능이 downlevel 되거나 유지될지 결정하는 옵션이다.(쉽게 말하면 컴파일된 JS 파일이 JS 버전으로 만들어지냐를 의미한다.)
  • module: 쉽게 말해 어떤 모듈 시스템을 사용할 것인지를 결정하는 옵션이다.

어떤 target을 사용할지는 ts 코드를 실행할 js runtime을 기준으로 결정하면 된다.

모든 모듈 간 통신은 모듈 로더를 통해 이루어지며 module 컴파일러 옵션은 어떤 모듈 시스템을 사용할지 결정한다.

런타임에서 모듈 로더는 모듈을 실행하기 전에 해당 모듈의 모든 종속성을 찾아서 실행하는 역할을 한다.

✔️TypeScript namespaces

ES 모듈이 표준 이전에 만들어진 namespaces라는 TS 모듈이 있다. 여러 유용한 기능을 가지고 있고 DefinitelyTyped에서도 활발히 쓰이고 있다.

namespaces의 대부분의 기능은 ES 모듈에도 존재하기 때문에 아직 deprecated되지 않았지만 자바스크립트의 방향성을 따라가는 것이 좋을 것 같다.

profile
백엔드 개발자

0개의 댓글