ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TypeScript 4.5 이상 버전에서 추가된 TSConfig 옵션 살펴보기
    Javascript 2024. 5. 12. 07:17

     

    tsconfig.json에는 많은 옵션이 존재합니다.

     

    최근 2년 동안 TSConfig에 새롭게 어떤 옵션들이 추가되었는지 궁금해졌습니다.

     

    TypeScript 4.5의 릴리스가 2021년 11월 즈음이었으므로 약 2년 전이니, TypeScript 4.5 이후에 추가된 TSConfig 옵션들을 조사해보았습니다.

    버전 4.5 이후 새로 추가된 옵션 목록

    옵션 추가된 버전
    preserveValueImports 4.5
    moduleSuffixes 4.7
    moduleDetection 4.7
    allowArbitraryExtensions 5.0
    allowImportingTsExtensions 5.0
    customConditions 5.0
    resolvePackageJsonExports 5.0
    resolvePackageJsonImports 5.0
    verbatimModuleSyntax 5.0

    preserveValueImports

    tsc에서 미사용으로 판단된 코드가 삭제되는 것을 방지할 수 있습니다.

     

    이 옵션은 5.0에서 verbatimModuleSyntax가 추가되면서 비권장되었습니다.

    사용법

    다음 코드에서는 eval 함수를 평가하여 Animal 클래스의 호출이 실행됩니다.

     

    하지만 tsc는 Animal이 미사용이라고 판단하여 import 문을 삭제합니다.

     

    그 결과 컴파일된 JavaScript 코드를 실행하면 오류가 발생합니다.

    import { Animal } from "./animal.js";
    eval("console.log(new Animal().isDangerous())");

     

    tsconfig.json에서 preserveValueImports를 true로 설정하면 미사용 import 코드 삭제를 제한할 수 있습니다.

    {
      "compilerOptions": {
        "preserveValueImports": true
      }
    }

    moduleSuffixes

    moduleSuffixes는 모듈 해결 시 파일 확장자를 어떻게 처리할지 정의하는 설정입니다.

     

    다른 환경(예: 브라우저와 Node.js)을 위해 다른 파일 확장자를 가진 모듈을 빌드할 때 이 옵션을 사용하면 환경에 맞는 파일 해결을 수행할 수 있습니다.

     

    예를 들어 프로젝트 내에서 다른 종류의 모듈(.browser.ts와 .node.ts 등)을 구분하여 관리한다면, moduleSuffixes를 통해 이러한 파일을 적절히 해결하고 코드를 정리할 수 있습니다.

    사용법

    배열에 모듈 해결 시 참조할 확장자를 지정합니다.

     

    이 예에서는 TypeScript가 모듈을 해결할 때 .browser.ts, .node.ts, 그리고 표준 .ts 확장자 순으로 검색합니다.

    {
      "compilerOptions": {
        "moduleSuffixes": [".browser", ".node", ""]
      }
    }

     

    위 설정에서 다음과 같이 읽어들일 경우 myModule.browser.ts, myModule.node.ts, myModule.ts 순으로 파일 존재 여부를 확인합니다.

    import { myFunction } from "./myModule";

    moduleDetection

    파일을 모듈로 해석할지 스크립트로 해석할지를 제어합니다.

     

    auto, legacy, force 세 가지 값이 있으며, 기본값은 auto입니다.

     

    TypeScript는 원래 import, export 문의 유무로 파일이 모듈인지 판단했지만, 4.7부터 그 동작이 변경되어 이 옵션이 새로 추가되었습니다.

    사용법

    auto를 지정하면 파일 내 import나 export 유무뿐만 아니라 다른 조건도 확인하여 파일이 모듈인지 판단합니다.

     

    module이 nodenext나 node16인 경우 package.json의 type 필드를 확인합니다.

     

    jsx가 react-jsx인 경우 파일이 jsx 파일인지 확인합니다.

     

    legacy는 TypeScript 4.6 이하와 동일한 동작을 합니다.

     

    파일 내에 import나 export 문이 있으면 그 파일을 모듈로 취급하고, 없으면 스크립트로 취급합니다.

     

    force는 타입 정의 파일을 제외한 모든 파일을 무조건 모듈로 취급합니다.

    {
      "compilerOptions": {
        "moduleDetection": "legacy"
      }
    }

    allowArbitraryExtensions

    이 옵션은 표준 JavaScript나 TypeScript 파일 확장자 외에 다른 확장자로 끝나는 import 경로를 처리하기 위한 것입니다.

     

    이 옵션을 활성화하면 TypeScript 컴파일러가 비표준 파일 확장자에 대한 선언 파일을 찾습니다.

     

    예를 들어 프로젝트에서 번들러를 사용해 CSS 파일을 TypeScript 파일로 직접 import하는 경우가 있다면, allowArbitraryExtensions 옵션을 활성화하면 TypeScript가 비표준 확장자 파일을 import할 때 에러를 발생시키지 않습니다.

     

    대신 {파일명}.d.{확장자}.ts 형식의 선언 파일을 찾습니다.

     

    이를 통해 비표준 파일에 대한 타입 선언이나 export를 작성(또는 생성)할 수 있으며, TypeScript에서 올바르게 이해하고 사용할 수 있게 됩니다.

    사용법

    예를 들어 button.css라는 CSS 파일이 있다면 button.d.css.ts라는 선언 파일을 만들어 CSS 파일의 타입 선언과 export를 수행할 수 있습니다.

     

    이렇게 하면 TypeScript가 CSS 파일의 import를 올바르게 해석하고 TypeScript 코드에서 사용할 수 있습니다.

     

    이 기능은 실행 시에 이러한 비표준 import를 처리할 수 있는 번들러나 다른 도구와 함께 사용하는 것을 전제로 합니다.

     

    실행 환경이나 번들러가 이런 import를 처리하도록 설정되어 있지 않다면, 이 옵션을 활성화하면 실행 시 오류가 발생할 수 있으므로 주의해야 합니다.

    /* button.css */
    .button {
      color: red;
    }
    // button.d.css.ts
    declare const css: {
      button: string;
    };
    export default css;
    // Button.tsx
    import styles from "./button.css";
    
    styles.button; // string
    {
      "compilerOptions": {
        "allowArbitraryExtensions": true
      }  
    }

    allowImportingTsExtensions

    이 옵션은 .ts, .mts, .tsx 같은 TypeScript 전용 확장자를 명시하여 파일을 import할 수 있게 합니다.

     

    기존에는 ./util.ts와 같이 ts 확장자로 파일을 import하면 JavaScript 런타임에서 경로 해석이 불가능해 오류가 발생했습니다.

     

    이 옵션을 활성화하면 다음 코드에서 오류가 발생하지 않습니다.

    // index.ts
    import { hello } from './util.ts';

    사용법

    이 옵션은 --noEmit 또는 --emitDeclarationOnly가 활성화되어 JavaScript 파일을 출력하지 않는 경우에만 사용할 수 있습니다.

     

    .ts 확장자의 import 경로를 그대로 JavaScript로 출력하면 실행 시 해석 불가능해 오류가 발생하기 때문입니다.

    {
      "compilerOptions": {
        "allowImportingTsExtensions": true,
        "noEmit": true
      }
    }

    customConditions

    TypeScript가 패키지의 exports나 imports 필드를 해석할 때 추가 조건을 설정할 수 있게 합니다.

    사용법

    customConditions에 여러 조건을 지정하면 package.json의 exports나 imports 필드에 지정된 조건이 있을 경우 그 조건에 맞는 파일이 import됩니다.

     

    다음 예제에서는 foo를 조건으로 지정했으므로 foo.mjs가 읽혀집니다.

     

    이 필드는 --module이 node16 또는 nodenext이고 --moduleResolution이 bundler인 경우에만 유효합니다.

    {
      "compilerOptions": {
        "target": "es2022", 
        "moduleResolution": "bundler",
        "customConditions": ["foo"]
      }
    }
    // package.json
    {
      // ...
      "exports": {
        "./bar": {
          "foo": "./foo.mjs",
          "node": "./bar.mjs",
          "import": "./bar.mjs", 
          "require": "./bar.js"
        }
      }
    }

    resolvePackageJsonExports

    node_modules 내 패키지에서 파일을 읽어올 때 TypeScript가 그 패키지의 package.json의 exports 필드를 참조할지 여부를 제어합니다.

     

    이 옵션이 활성화되어 있으면 TypeScript는 package.json의 exports 필드를 사용해 모듈 해석을 수행합니다.

     

    이 설정은 --moduleResolution이 node16, nodenext, bundler 중 하나인 경우 기본적으로 true입니다.

    exports 필드 정보

    package.json의 exports 필드를 통해 패키지 배포 측에서 플랫폼별로 읽어들일 파일을 제어할 수 있습니다.

    호환성 문제

    아래위 호환성 문제로, resolvePackageJsonExports가 true인 상태에서 exports 필드를 참조하면 패키지가 의도하지 않은 파일의 import가 차단되어 컴파일 오류가 발생할 수 있습니다.

     

    다음 예제에서는 패키지 측에서 exports 필드로 src/utility.js만 공개하고 있으므로, 다른 파일의 import가 차단됩니다.

    // 특별한 이유로 공개되지 않은 파일을 직접 읽어들이고 있음
    // exports 필드를 참조하면 파일 읽기가 차단될 수 있음
    import { xxx } from '@package/dist/private/bar';
    {
      "name": "package",
      "version": "1.0.0",
      "exports": {
        "./utility": "./src/utility.js"
      }
    }

    resolvePackageJsonImports

    package.json의 imports 필드를 TypeScript가 분석할지 여부를 제어합니다.

     

    이 옵션이 활성화되면 TypeScript는 파일을 읽어들일 때 package.json의 imports 필드를 참조합니다.

     

    이 설정은 --moduleResolution이 node16, nodenext, bundler 중 하나인 경우 기본적으로 true입니다.

    imports 필드 정보

    package.json의 imports 필드는 읽어들일 패키지에 임의의 이름을 매핑합니다.

     

    다음 예제에서는 #dep를 dep-node-native에 매핑하고 있습니다.

    import {} from '#dep';
    // package.json
    {
      "imports": {
        "#dep": {
          "node": "dep-node-native",
          "default": "./dep-polyfill.js"
        }
      },
      "dependencies": {
        "dep-node-native": "^1.0.0"
      }
    }

    verbatimModuleSyntax

    이 옵션은 모듈의 가져오기와 내보내기 처리를 단순화합니다.

     

    기본값은 false입니다.

     

    타입스크립트는 기본적으로 사용되지 않는 가져오기나 타입만 참조하는 가져오기는 출력 자바스크립트에서 제거합니다.

     

    이 동작을 제어하기 위해 importsNotUsedAsValues와 preserveValueImports 옵션이 도입되었습니다.

     

    하지만 이 옵션들을 조합해 사용하면 제어가 복잡해지고, 처리할 수 없는 모서리 케이스도 존재합니다.

     

    verbatimModuleSyntax를 활성화하면 이 제어 규칙을 더 단순화할 수 있습니다.

    사용 방법

    true로 설정하면 type 한정자의 가져오기는 모두 제거되고, 나머지 가져오기 문장은 제거되지 않는 동작을 수행할 수 있습니다.

    {
      "compilerOptions": {
        "verbatimModuleSyntax": true
      }
    }
    // index.ts
    // 가져오기 문장이 모두 제거됨
    import type { Util } from "./utils"; 
    
    // 'import { b } from "./utils";' 형태로 출력됨
    import { group, type Pick, type Filter } from "./utils";
    
    // 'import {} from "./utils";' 형태로 출력됨  
    import { type Map } from "./utils";

    마무리

    최근 2년 동안 추가된 TSConfig 옵션들을 살펴봤는데, 제가 실제로 사용해본 적 없는 옵션들이 많아 새롭게 배울 수 있었습니다.

     

    전반적인 경향을 돌아보면 모듈 해결 관련 옵션이 압도적으로 많았는데, ESM과 CJS 모듈 처리가 얼마나 까다로운지 실감할 수 있어 흥미로웠습니다.

Designed by Tistory.