ReactNextCentral

모듈

Published on
타입스크립트에서 모듈을 다루는 방법을 설명하는 가이드로, 자바스크립트 모듈의 정의, 비모듈, ES 모듈 구문, CommonJS 구문, 모듈 상호 운용성, 모듈 해석 및 출력 옵션, 그리고 타입스크립트 네임스페이스에 대해 다룹니다.
Table of Contents

모듈

자바스크립트는 코드 모듈화를 다루는 다양한 방법을 오랫동안 가지고 있었습니다. 2012년부터 존재해온 타입스크립트는 이러한 형식들 중 많은 것을 지원하도록 구현되었지만, 시간이 지남에 따라 커뮤니티와 자바스크립트 사양은 ES 모듈(또는 ES6 모듈)이라고 불리는 형식으로 수렴하게 되었습니다. 이는 import/export 구문으로 알려져 있을 것입니다.

ES 모듈은 2015년 자바스크립트 사양에 추가되었으며, 2020년까지 대부분의 웹 브라우저와 자바스크립트 런타임에서 널리 지원되었습니다.

집중을 위해, 이 핸드북은 ES 모듈과 그 인기 있는 선행자인 CommonJS의 module.exports = 구문을 다룰 것이며, 다른 모듈 패턴에 대한 정보는 모듈 아래 참조 섹션에서 찾아볼 수 있습니다.

자바스크립트 모듈 정의하기

타입스크립트에서는 ECMAScript 2015에서와 마찬가지로 최상위 수준에서 importexport를 포함하는 모든 파일을 모듈로 간주합니다.

반대로, 최상위 수준에서 importexport 선언이 없는 파일은 스크립트로 처리되어 그 내용이 전역 스코프(따라서 모듈에도)에서 사용 가능합니다.

모듈은 전역 스코프가 아닌 자체 스코프 내에서 실행됩니다. 이는 모듈에서 선언된 변수, 함수, 클래스 등이 모듈 밖에서는 보이지 않는다는 것을 의미합니다. 이들을 명시적으로 export 형태 중 하나를 사용하여 내보내지 않는 한 말이죠. 반대로, 다른 모듈에서 export된 변수, 함수, 클래스, 인터페이스 등을 사용하려면 import 형태 중 하나를 사용하여 가져와야 합니다.

비모듈

시작하기 전에, 타입스크립트가 무엇을 모듈로 간주하는지 이해하는 것이 중요합니다. 자바스크립트 사양은 import 선언, export, 또는 최상위 await이 없는 모든 자바스크립트 파일을 스크립트로 간주하고 모듈이 아니라고 선언합니다.

스크립트 파일 내에서 변수와 타입은 공유 전역 스코프에 선언되며, 여러 입력 파일을 하나의 출력 파일로 결합하기 위해 컴파일러 옵션 outFile을 사용하거나, 여러 <script> 태그를 HTML에 로드하여 이 파일들을 사용할 것으로 가정합니다(올바른 순서로!).

현재 importexport가 없지만 모듈로 처리되길 원하는 파일이 있다면, 다음 줄을 추가하세요:

export {};

이는 아무것도 내보내지 않는 모듈로 파일을 변경합니다. 이 구문은 모듈 타겟에 관계없이 작동합니다.

타입스크립트에서의 모듈

추가 읽을 거리:

타입스크립트에서 모듈 기반 코드를 작성할 때 고려해야 할 세 가지 주요 사항은 다음과 같습니다.

  • 구문: 무엇을 import하고 export하기 위해 어떤 구문을 사용하고 싶은가?
  • 모듈 해석: 모듈 이름(또는 경로)과 디스크 상의 파일 간에는 어떤 관계가 있는가?
  • 모듈 출력 대상: 생성된 자바스크립트 모듈은 어떤 모습이어야 하는가?

ES 모듈 구문

파일은 export default를 통해 주요 내보내기를 선언할 수 있습니다.

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

이는 다음과 같이 가져와집니다.

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

default를 생략하고 변수와 함수를 여러 개 내보내기도 할 수 있습니다.

// @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 {old as new}와 같은 형식을 사용해 가져온 것을 이름 바꾸기 할 수 있습니다.

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

위의 구문을 하나의 import로 혼합하여 매치할 수 있습니다.

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

* as name을 사용하여 내보낸 모든 객체를 단일 네임스페이스에 넣을 수 있습니다.

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

현재 모듈에 변수를 포함하지 않고 파일을 가져오려면 import "./file"을 사용합니다.

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

이 경우, import는 아무 작업도 하지 않습니다. 그러나 maths.ts의 모든 코드가 평가되어 다른 객체에 영향을 미치는 부작용을 일으킬 수 있습니다.

타입스크립트 특화 ES 모듈 구문

타입은 자바스크립트 값과 같은 구문을 사용하여 내보내고 가져올 수 있습니다.

// @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

타입만 가져오는 가져오기 구문입니다.

// @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();
인라인 타입 가져오기

타입스크립트 4.5는 가져온 참조가 타입임을 나타내기 위해 개별 가져오기에 type을 접두사로 붙일 수 있도록 허용합니다.

// @filename: app.ts
import { createCatName, type Cat, type Dog } from "./animal.js";

export type Animals = Cat | Dog;
const name = createCatName();

이 기능들을 함께 사용하면 Babel, swc 또는 esbuild와 같은 타입스크립트 변환기가 아닌 도구가 안전하게 제거할 수 있는 가져오기를 알 수 있습니다.

ES 모듈 구문과 CommonJS 동작

타입스크립트는 CommonJS와 AMD의 require에 직접 대응하는 ES 모듈 구문을 제공합니다. ES 모듈을 사용한 가져오기는 대부분의 경우 그 환경에서의 require와 같지만, 이 구문은 타입스크립트 파일이 CommonJS 출력과 1대1로 일치하도록 보장합니다.

import fs = require("fs");
const code = fs.readFileSync("hello.ts", "utf8");

이 구문에 대해 더 자세히 알고 싶다면 모듈 참조 페이지에서 확인할 수 있습니다.

CommonJS 구문

CommonJS는 npm에서 제공되는 대부분의 모듈이 사용하는 형식입니다. 위의 ES 모듈 구문을 사용하여 작성하더라도 CommonJS 구문이 어떻게 작동하는지 간략히 이해하는 것이 디버깅을 더 쉽게 할 수 있게 해줍니다.

내보내기

전역 변수인 moduleexports 속성을 설정하여 식별자를 내보냅니다.

function absolute(num: number) {
  if (num < 0) return num * -1;
  return num;
}

module.exports = {
  pi: 3.14,
  squareTwo: 1.41,
  phi: 1.61,
  absolute,
};

그런 다음 이 파일들은 require 구문을 통해 가져올 수 있습니다.

const maths = require("./maths");
maths.pi;

또는 자바스크립트의 구조 분해 할당 기능을 사용하여 조금 단순화할 수 있습니다.

const { squareTwo } = require("./maths");
squareTwo;

CommonJS와 ES 모듈 상호 운용성

기본 가져오기와 모듈 네임스페이스 객체 가져오기 사이의 차이점에 관한 CommonJS와 ES 모듈 간의 기능 불일치가 있습니다. 타입스크립트에는 두 가지 다른 제약 조건 사이의 마찰을 줄이기 위한 컴파일러 플래그인 esModuleInterop이 있습니다.

타입스크립트의 모듈 해석 옵션

모듈 해석은 가져오기 또는 요구 구문에서 문자열을 가져와 해당 문자열이 어떤 파일을 가리키는지 결정하는 과정입니다.

타입스크립트에는 Classic과 Node라는 두 가지 해석 전략이 포함되어 있습니다. Classic은 컴파일러 옵션 모듈이 commonjs가 아닐 때 기본값으로, 하위 호환성을 위해 포함되어 있습니다. Node 전략은 .ts 및 .d.ts에 대한 추가 검사와 함께 CommonJS 모드에서 Node.js가 작동하는 방식을 복제합니다.

타입스크립트 내에서 모듈 전략에 영향을 주는 여러 TSConfig 플래그가 있습니다. moduleResolution, baseUrl, paths, rootDirs.

이 전략들이 어떻게 작동하는지 전체 세부 정보는 모듈 해석 참조 페이지를 참조하세요.

타입스크립트의 모듈 출력 옵션

발행된 자바스크립트 결과에 영향을 주는 두 가지 옵션이 있습니다.

  • target은 어떤 JS 기능이 이전 자바스크립트 런타임에서 실행될 수 있도록 다운레벨링(변환)되고 어떤 기능이 그대로 유지될지 결정합니다.
  • module은 모듈 간에 서로 어떻게 상호 작용하는지 결정하는 코드를 정의합니다.

사용할 target은 타입스크립트 코드가 실행될 것으로 예상되는 자바스크립트 런타임에서 사용 가능한 기능에 의해 결정됩니다. 이는 지원하는 가장 오래된 웹 브라우저, 실행할 것으로 예상되는 Node.js의 최저 버전 또는 Electron과 같은 런타임에서 오는 고유한 제약사항일 수 있습니다.

모듈 간의 모든 통신은 모듈 로더를 통해 이루어지며, 컴파일러 옵션 module은 사용될 모듈 로더를 결정합니다. 런타임에서 모듈 로더는 실행하기 전에 모듈의 모든 의존성을 찾아 실행하는 책임이 있습니다.

예를 들어, ES 모듈 문법을 사용하는 타입스크립트 파일과 모듈에 대한 몇 가지 다른 옵션을 보여주는 예시입니다.

import { valueOfPi } from "./constants.js";

export const twoPi = valueOfPi * 2;

ES2020

import { valueOfPi } from "./constants.js";
export const twoPi = valueOfPi * 2;

CommonJS

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.twoPi = void 0;
const constants_js_1 = require("./constants.js");
exports.twoPi = constants_js_1.valueOfPi * 2;

UMD

(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "./constants.js"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.twoPi = void 0;
    const constants_js_1 = require("./constants.js");
    exports.twoPi = constants_js_1.valueOfPi * 2;
});

ES2020은 실질적으로 원본 index.ts와 같습니다.

사용 가능한 모든 옵션과 해당 자바스크립트 코드의 모습은 TSConfig 참조 문서의 module에서 확인할 수 있습니다.

타입스크립트 네임스페이스

타입스크립트는 ES 모듈 표준 이전에 나온 자체 모듈 형식인 네임스페이스를 가지고 있습니다. 이 문법은 복잡한 정의 파일을 만드는 데 유용한 많은 기능을 제공하며, DefinitelyTyped에서 여전히 활발히 사용되고 있습니다. 네임스페이스는 폐기되지 않았지만, 네임스페이스의 대부분 기능은 ES 모듈에 존재하며 자바스크립트의 방향과 일치하도록 ES 모듈 사용을 권장합니다. 네임스페이스에 대해 더 자세히 알아보려면 네임스페이스 참조 페이지를 참조하세요.