내가 보려고 정리한 tsconfig(2)

milkboy2564·2023년 11월 15일
3
post-thumbnail

지난 게시물 에 이어 tsconfig의 compilerOptions에 대해서 추가적으로 살펴보도록 하겠습니다.

Modules

allowArbitraryExtensions

allowArbitraryExtensions 속성은 TypeScript가 .ts, .tsx 와 같은 표준 TypeScript 확장자와 일치하지 않는 파일의 컴파일 유무를 결정합니다.

기본적으로 TypeScript는 allowJs 옵션을 사용할 때 .ts, .tsx, .d.ts 와 같은 확장자를 가진 파일과 .js 와 같은 특정 모듈의 확장자만 인식합니다.

allowArbitraryExtensions를 true로 설정하면 TypeScript는 일반적으로 인식되지 않는 다른 파일 확장명을 고려하여 해당 확장명을 TypeScript 프로젝트의 일부로 허용합니다.

예를 들어, 해당 속성을 true로 설정하면 custom.milkboy 와 같은 파일 혹은 다른 확장자를 프로젝트의 일부로 허용합니다.

일반적으로 이러한 허용은 일관성이 없이 사용되거나 TypeScript 구문이나 규칙을 준수하지 않는 코드가 포함된 경우 복잡성과 잠재적인 문제가 발생할 수 있으므로 사용하지 않는 것이 좋습니다.

true : .ts 또는 .tsx가 아닌 허용되지 않은 확장자를 사용하면 TypeScript 파일로 처리
false (default) : .ts 또는 .tsx가 아닌 허용되지 않은 확장자를 사용하면 TypeScript 파일로 처리하지 않고 오류 발생

allowImportingTsExtensions

allowImportingTsExtensions 속성은 .ts 파일을 모듈로 불러올 수 있는지를 결정합니다.
이 속성은 --noEmit 또는 --emitDeclarationOnly가 활성화된 경우에만 허용되는데, 이는 이러한 가져오기 경로가 JavaScript 출력 파일에서 런타임에 확인되지 않기 때문입니다.

true : .ts, .mts, .tsx와 같은 TypeScript 전용 확장자를 통해 모듈로서 불러오는 것을 허용
false (default) : .ts, .mts, .tsx와 같은 TypeScript 전용 확장자를 통해 모듈로서 불러오는 것을 허용하지 않음

allowUmdGlobalAccess

allowUmdGlobalAccess 속성은 UMD(Universal Module Definition) 전역 변수에 대한 내부 모듈의 액세스를 허용할지 여부를 결정합니다.

UMD는 웹 브라우저, Node.js, 데스크톱 애플리케이션 등 다양한 환경에서 사용할 수 있도록 설계된 모듈 형식입니다. UMD 모듈은 일반적으로 window 전역 변수에 정의된 전역 변수로 내보냅니다.

allowUmdGlobalAccess 속성을 true로 설정하면 내부 모듈에서 UMD 전역 변수에 직접 액세스할 수 있습니다.

해당 속성을 사용할 때는 코드의 가독성과 유지 관리성을 위해 신중하게 고려하여 사용해야 합니다.

true : 전역 변수에 대한 내부 모듈의 액세스를 허용
false (default) : 전역 변수에 대한 내부 모듈의 액세스를 허용하지 않음

baseUrl

baseUrl 속성은 상대 경로로 가져오는 모듈의 기본 URL을 지정합니다.

// before
import { a } from "./src/app";

const hello = () => a();

// after 
import { a } from "app";

const hello = () => a();

baseUrl 속성을 사용하면 상대 경로로 가져오는 모듈의 경로를 간결하게 표현할 수 있습니다. 또한, 프로젝트의 구조를 변경하더라도 import 문의 경로를 변경할 필요가 없습니다.

baseUrl 속성은 기본적으로 빈 문자열로 설정됩니다. 즉, 상대 경로는 현재 디렉터리에서 해석됩니다.

❗️
baseUrl 속성은 브라우저에서 AMD 모듈과 함께 사용하도록 설계되었으며 다른 모듈 시스템 상에서는 권장되지 않는다고 합니다.

AMD, CommonJS, ESM 등 자바스크립트 모듈 시스템에 관해서는 별도의 포스팅으로 정리해보도록 하겠습니다.

customConditions

customConditions 속성은 package.jsonexports 또는 imports 필드에서 TypeScript가 추가적으로 해석해야 하는 목록을 받습니다. 이러한 조건은 리졸버가 기본적으로 사용하는 기존 조건에 추가됩니다.

예를 들어, tsconfig.json 파일이 아래와 같이 구성되어 있다고 가정할 때,

{
  "compilerOptions": {
    "target": "es2022",
    "moduleResolution": "bundler",
    "customConditions": ["my-condition"]
  }
}

TypeScript는 package.jsonimports 또는 exports가 참조될 때마다 my-condition이라고 불리는 customConditions를 고려하게 됩니다.

따라서 다음과 같이 package.json이 구성되어있다고 한다면

{
  // ...
  "exports": {
    ".": {
      "my-condition": "./foo.mjs",
      "node": "./bar.mjs",
      "import": "./baz.mjs",
      "require": "./biz.mjs"
    }
  }
}

TypeScript는 foo.mjs 에 해당하는 파일을 찾으려고 시도하게 됩니다.

customConditions 속성은 moduleResolution 속성 값이 nodenext, bundler, node16 인 경우에만 유효합니다.

module

module 속성은 프로그램의 모듈 시스템을 설정합니다. 일반적으로 최신 node.js 프로젝트의 경우 "nodenext"를 요구합니다.

module 속성을 변경하면 moduleResolution 속성에도 영향을 미치게 됩니다.

아래와 같은 간단한 코드가 있고 이 코드가 각각의 모듈 시스템 하에서 어떻게 컴파일이 완료가 되는지 실제로 컴파일을 하면서 결과물을 살펴보고 각 모듈 시스템에 대해 간단하게 설명하도록 하겠습니다.

export const addNumber = (a: number, b: number): number => a + b;

모듈이란?

개발하는 애플리케이션의 크기가 커지면 언젠간 파일을 여러 개로 분리해야 하는 시점이 옵니다. 이때 분리된 파일 각각을 '모듈(module)'이라고 부르는데, 모듈은 대개 클래스 하나 혹은 특정한 목적을 가진 복수의 함수로 구성된 라이브러리 하나로 구성됩니다.

시간이 흐르면서 JavaScript 파일이 커지고 기능이 복잡해지면서 특정한 기능을 가진 코드를 모듈로 분리하여 언제든지 불러올 수 있게끔 만들기 위한 다시 말해, 더욱 범용적인 목적으로 사용하기 위한 시도들이 이어지게 됩니다. 이러한 시도를 통해 CommonJS, AMD, UMD와 같은 모듈 시스템이 등장하게 됩니다.

1. CommonJS

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.addNumber = void 0;
const addNumber = (a, b) => a + b;
exports.addNumber = addNumber;

CommonJS(http://www.commonjs.org/) 는 JavaScript를 브라우저에서뿐만 아니라, 서버사이드 애플리케이션이나 데스크톱 애플리케이션에서도 사용하려고 조직한 자발적 워킹 그룹입니다.

2005년 Ajax가 부상하면서 덩달아 JavaScript의 연산과 중요성이 올라갔고 자연스레 더욱 빠른 JavaScript 엔진이 필요하게 됐습니다. 이러한 흐름에서 2008년 Google에서 V8 JavaScript 엔진을 공개하였습니다.

V8 엔진은 기존 JavaScript 엔진보다 월등히 빨랐고, 브라우저 이외의 환경에서도 충분히 쓰일만한 성능을 가지고 있었습니다.

V8 엔진의 등장으로 본격적으로 서버사이드 JavaScript 진영에도 새로운 바람이 불기 시작했고 사람들은 이 서버사이드 JavaScript가 성공하기 위해 기술적인 맥락 보다 표준화된 명세를 수립하고 이를 지켜나가는 활동이 필요하다고 생각하였습니다. CommonJS 그룹은 이렇게 처음 시작하게 됐습니다.

CommonJS는 기본적으로 서버 환경 즉, Node.js 환경에서 JavaScript를 활용할 때 표준을 만들기 위해 나타난 그룹입니다. 그룹을 처음 제안한 Kevin Dangoor가 제기한 서버사이드 JavaScript의 주요 쟁점은 다음과 같습니다.

  • 서로 호환되는 표준 라이브러리가 없다.
  • 데이터베이스에 연결할 수 있는 표준 인터페이스가 없다.
  • 다른 모듈을 삽입하는 표준적인 방법이 없다.
  • 코드를 패키징해서 배포하고 설치하는 방법이 필요하다.
  • 의존성 문제까지 해결하는 공통 패키지 모듈 저장소가 필요하다.

위의 문제는 모듈화로 귀결이 되고 CommonJS는 이 모듈을 정의하는 방법과 사용하는 방법을 정의한 명세가 됐습니다.

CommonJS에서 모듈화는 아래의 세 부분으로 이루어집니다.

  • 스코프(Scope): 모든 모듈은 자신만의 독립적인 실행 영역이 있어야 한다.
  • 정의(Definition): 모듈 정의는 exports 객체를 이용한다.
  • 사용(Usage): 모듈 사용은 require 함수를 이용한다.

각각의 모듈은 자신만의 실행 영역을 가지고 있어야 하는데, 만약 그렇지 않다면 전역 변수가 파일 사이에서 공유되는 문제가 발생하게 됩니다.

그리고 두 모듈(파일) 사이에 정보 교환이 필요하다면, exports라는 전역객체를 통해 공유하게 됩니다.

위 코드의 exports.addNumber = addNumber;와 같이exports 전역 객체의 메서드로 addNumber 함수를 넣어주면 외부에서 해당 모듈(파일)에 접근할 수 있게 됩니다.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const app_1 = require("./src/app");
const add = () => (0, app_1.addNumber)(6, 7);
add();

참고 : https://d2.naver.com/helloworld/12864

2. AMD

define(["require", "exports"], function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.addNumber = void 0;
    const addNumber = (a, b) => a + b;
    exports.addNumber = addNumber;
});

가장 오래된 모듈 시스템 중 하나로 require.js라는 라이브러리를 통해 처음 개발되었습니다.

AMD(Asynchronous Module Definition)의 가장 큰 특징은 CommonJS와는 다르게 비동기적으로 모듈을 가져온다는 점입니다.

참고 : https://d2.naver.com/helloworld/591319

3. 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"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.addNumber = void 0;
    const addNumber = (a, b) => a + b;
    exports.addNumber = addNumber;
});

AMD와 CommonJS 두 진영으로 모듈 시스템이 분리가되면서 서로 호환이 되지 않는 문제가 발생했는데 이를 해결하기 위해 나온 것이 UMD입니다.

AMD는 define 메서드를 사용하고 CommonJS는 module.exports를 사용하기 때문에 위의 코드에서 보면 두 방식 모두 대응하고 있는 것을 볼 수 있습니다.

4. System

System.register([], function (exports_1, context_1) {
    "use strict";
    var addNumber;
    var __moduleName = context_1 && context_1.id;
    return {
        setters: [],
        execute: function () {
            exports_1("addNumber", addNumber = (a, b) => a + b);
        }
    };
});

SystemJS는 모듈을 정의하는 것이 아니라 CommonJS, ESM, AMD 등의 모듈 시스템을 로드해주는 모듈 로더입니다.

브라우저에서 네이티브 ES 모듈의 프로덕션 워크플로(예: rollup code splitting build)를 위해 작성된 코드를 System.register 모듈 형식으로 트랜스파일하여 네이티브 모듈을 지원하지 않는 구형 브라우저에서 작동할 수 있는 워크플로를 제공하며, 최상위 대기, 동적 가져오기, 순환 참조 및 라이브 바인딩, import.meta.url, 모듈 유형, 가져오기 맵, 무결성 및 콘텐츠 보안 정책을 지원하면서 IE11까지 구형 브라우저에서 호환되는 기능을 제공합니다.

브라우저에 호환이 된다고 하더라도 트랜스파일링을 하지 않으면 실행 시킬 수 없는 경우가 있는데, 이러한 경우에 systemjs-babel을 통해 해결한다고 합니다.

하지만 런타임에 모듈을 트랜스파일링 하는 작업은 리소스가 많이 드는 작업이고 이러한 작업은 빌드 타임에 수행하는 것이 성능 상에도 더 낫기 때문에 일반적으로는 사용하지 않는다고 합니다.

5. ESM

export const addNumber = (a, b) => a + b;
import { addNumber } from "./src/app";
const add = () => addNumber(6, 7);
add();

ESM은 ECMA 재단에서 공식으로 정의한 표준 모듈 시스템입니다.

ESM은 CommonJS나 AMD에서 사용하는 require, define 과 같은 함수를 사용하는 것이 아니라 import, export 를 통해 모듈을 내보내고 불러올 수 있습니다.

기존 CommonJS나 AMD와 비교했을 때 변경되는 사항(EX. this가 window를 바라보지 않음.)이 많았기 때문에 현재까지도 많은 라이브러리들이 ESM으로 변경을 하지 못하는 상황이 이어져오고 있습니다.

그렇기 때문에 자바스크립트 환경에서 ESM을 사용하려면 package.json 안에 별도로 "type": "module" 이라는 속성을 추가해줘야 하는 별도의 설정이 필요하게 됐습니다.

ESM은 비교적 최근(2015년)에 나왔기 때문에 보다 안전하게 사용하기 위해서는 번들러와 트랜스파일러를 사용하여 런타임이 해석할 수 있게 변환하는 과정이 필요한데 이 작업이 모듈을 사용하는 환경에 따라서 번거로운 작업이 될 수 있습니다.

하지만 ESM은 각각의 export 키워드를 사용해 값을 따로 내보낼 수 있고(트리 쉐이킹), 이로 인해 상세한 의존 관계를 쉽게 파악할 수 있는 장점이 있습니다.

moduleResolution

moduleResolution 속성은 간단하게 말해 모듈을 어떻게 방식으로 해석할지 결정하는 속성입니다.

1. classic

TypeScript의 가장 오래된 모듈 해석 전략으로, 모듈이 CommonJS, Node16 또는 nodenext가 아닌 다른 것으로 설정된 경우 기본값이 됩니다. 다양한 RequireJS 구성에 대해 대응하기 위한 최선의 해결 방법을 제공하기 위해 만들어졌습니다. 새 프로젝트(또는 RequireJS 또는 다른 AMD 모듈 로더를 사용하지 않는 이전 프로젝트)에는 사용해서는 안 되며, TypeScript 6.0에서 더 이상 사용되지 않을 예정입니다.

2. node10

이전에는 node라고 알려졌지만 모듈이 CommonJS로 설정된 경우 기본값입니다.

node_modules에서 패키지를 조회하고, 디렉토리 index.js 파일을 로드하고, 상대 모듈 지정자에서 .js 확장자를 생략하는 것을 지원합니다.

하지만 Node.js v12에서는 ESM에 대해 다른 module resolution 규칙이 도입되었기 때문에 최신 버전의 Node.js에서는 사용해서는 안됩니다.

3. node16, nodenext

Node.js v12 이상은 ESM과 CJS를 모두 지원하며, 각 모듈은 자체 알고리즘을 사용하여 모듈을 해석합니다.

node16과 nodenext는 현재 동일하지만 향후 Node.js가 모듈 시스템을 크게 변경하는 경우 node16은 동결되고 새로운 동작을 반영하도록 nodenext가 업데이트됩니다.

모듈 형식

  • .mts/.mjs/.d.mts 파일은 항상 ESM으로 해석
  • .cts/.cjs/.d.cts 파일은 항상 CommonJS로 해석
  • 가장 가까운 package.json 파일에 "type: "module"이 포함된 경우 .ts/.tsx/.js/.jsx/.d.ts 파일은 ESM으로 해석되고 그렇지 않으면 CommonJS로 해석됩니다.

4. bundler

"moduleResolution": "bundler"는 코드가 번들된 파일로 제공되는 경우 TypeScript에게 코드가 다른 번들러(Ex.Webpack, ESBuild, SWC 등)에 의해 번들로 제공되므로 import를 통해 규칙을 완화하도록 지시합니다.

해당 속성은 모듈을 es2015 이상으로 설정해야 합니다.

참고 - https://www.typescriptlang.org/docs/handbook/modules/theory.html#module-resolution

moduleSuffixes

moduleSuffixes 속성은 모듈을 확인할 때 검색할 파일 이름 접미사의 기본 목록을 재정의할 수 있는 방법을 제공합니다.

{
  "compilerOptions": {
    "moduleSuffixes": [".ios", ".native", ""]
  }
}

위와 같이 설정이 되어 있고 아래와 같이 import를 수행한다면

import * as foo from "./foo";

TypeScript는 ./foo.ios.ts, ./foo.native.ts, 마지막으로 ./foo.ts에 해당하는 파일을 찾습니다.

moduleSuffixes 속성의 값으로 들어간 ""./foo.ts 를 조회하기 위해 필요합니다.

이 기능은 각 플랫폼에서 모듈 suffix가 다른 별도의 tsconfig.json을 사용할 수 있는 React Native 프로젝트에 유용할 수 있습니다.

noResolve

기본적으로 TypeScript 컴파일러는 tsconfig의 includefiles 필드에 포함되지 않는 파일이라고 하더라도 모든 모듈을 컴파일하려고 시도합니다.

이때 noResolve 옵션을 true로 설정하게 되면 이러한 암시적인 컴파일을 막을 수 있습니다.

true : 명시적으로 작성된 모듈만 해석
false (default) : 암시적으로 모든 모듈을 해석

paths

paths 속성은 baseUrl 속성을 기준으로 상대 위치로 가져로기를 다시 매핑하는 항목을 설정합니다.

 "baseUrl": "./src",
  "paths": {
    "@/components/*": ["components/*"],
    "@/modules/*": ["modules/*"],
    "@/api/*": ["api/*"],
      ...
  },

resolveJsonModule

resolveJsonModule 속성은 확장자가 .json인 모듈의 import를 허용하는 설정입니다.

이 속성은 json의 타입을 자동으로 설정해줘서 따로 변환 작업없이 타입스크립트에서 json 파일을 사용할 수 있도록 해줍니다.

resolvePackageJsonExports

resolvePackageJsonExports 속성은 는 TypeScript가 node_modules의 패키지에서 읽을 경우 package.json 파일의 내보내기 필드를 참조하도록 한다.

이 옵션은 moduleResolution 속성 값이 node16, nodenextbundler 옵션에서 기본값이 true로 설정된다.

rootDir, rootDirs

관련 자료를 찾아보다가 이해하기 너무 좋은 설명이 있어 링크 첨부합니다.

rootDir 참고👍

rootDirs 참고👍

types, typeRoots

typeRoots 속성은 @types 디렉토리의 기본 경로를 변경할 수 있는 속성입니다.

tsconfig는 기본적으로 node_modules 폴더를 제외하지만, 라이브러리의 타입을 정의해놓는 @types 폴더는 컴파일에 자동으로 경로에 포함됩니다. 그런데 만약 이 @types의 기본 경로를 변경하고 싶다면 아래와 같이 지정할 수 있습니다.

"compilerOptions": {
	"typeRoots": ["./my-tslib"], // 컴파일 목록에 자동으로 포함시킬 패키지들의 기준 디렉토리
	// .d.ts 파일을 읽을 폴더를 node_modules/@types에서 node_modules/my-tslib로 변경

	"types": ["node", "jest", "express"], // typeRoots 디렉토리 내에 존재하는 패키지들 중 어떤 패키지들을 컴파일 목록에 포함시킬지
	// 만약 types가 지정되어 있지 않다면 typeRoots에 지정된 경로의 패키지들은 전부 컴파일 목록에 자동으로 포함
}
출처: https://inpa.tistory.com/entry/TS-📘-타입스크립트-tsconfigjson-설정하기-총정리#language_and_environment_옵션 [Inpa Dev 👨‍💻:티스토리]

위와 같이 typeRoots 속성을 작성을 하게 되면./my-tslib 아래의 모든 패키지가 포함되며 ./node_modules/@types 의 패키지는 포함되지 않습니다.

여기서 작성하는 모든 경로는 tsconfig.json에 상대적입니다.

참고

profile
FE 개발자

0개의 댓글