TypeScript

TypeScript는 monorepo에서 훌륭한 도구로, 팀이 JavaScript 코드에 안전하게 타입을 추가할 수 있습니다. 설정에 약간의 복잡성이 있지만, 이 가이드는 대부분의 사용 사례에 대한 TypeScript 설정의 중요한 부분을 안내합니다.

이 가이드는 최신 버전의 TypeScript를 사용한다고 가정하며 해당 버전에서만 사용할 수 있는 일부 기능을 사용합니다. 해당 버전의 기능을 사용할 수 없는 경우 이 페이지의 지침을 조정해야 할 수 있습니다.

tsconfig.json 공유

TypeScript 구성에 일관성을 구축하여 전체 저장소가 훌륭한 기본값을 사용할 수 있고 동료 개발자가 Workspace에서 코드를 작성할 때 무엇을 기대할 수 있는지 알 수 있기를 원합니다.

TypeScript의 tsconfig.json은 TypeScript 컴파일러에 대한 구성을 설정하며 워크스페이스 전체에서 구성을 공유하는 데 사용할 extends를 제공합니다.

이 가이드는 예제로 create-turbo를 사용합니다.

Terminal
pnpm dlx create-turbo@latest

기본 tsconfig 파일 사용

packages/typescript-config 내부에는 다양한 패키지에서 TypeScript를 구성하려는 여러 가지 방법을 나타내는 몇 개의 json 파일이 있습니다. base.json 파일은 워크스페이스의 다른 모든 tsconfig.json에 의해 확장되며 다음과 같습니다:

./packages/typescript-config/base.json
{
  "compilerOptions": {
    "esModuleInterop": true,
    "skipLibCheck": true,
    "target": "es2022",
    "allowJs": true,
    "resolveJsonModule": true,
    "moduleDetection": "force",
    "isolatedModules": true,
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "module": "NodeNext"
  }
}

tsconfig options reference

나머지 패키지 만들기

이 패키지의 다른 tsconfig 파일은 extends 키를 사용하여 기본 구성으로 시작하고 Next.js(nextjs.json) 및 React 라이브러리(react-library.json)와 같은 특정 유형의 프로젝트에 맞게 사용자 지정합니다.

package.json 내부에서 Workspace의 나머지 부분에서 참조할 수 있도록 패키지 이름을 지정합니다:

packages/typescript-config/package.json
{
  "name": "@repo/typescript-config"
}

TypeScript 패키지 빌드

구성 패키지 사용

먼저, @repo/typescript-config 패키지를 패키지에 설치합니다:

./apps/web/package.json
{
  "devDependencies": {
     "@repo/typescript-config": "workspace:*",
     "typescript": "latest",
  }
}

그런 다음 @repo/typescript-config 패키지에서 패키지에 대한 tsconfig.json을 확장합니다. 이 예제에서 web 패키지는 Next.js 애플리케이션입니다:

./apps/web/tsconfig.json
{
  "extends": "@repo/typescript-config/nextjs.json",
  "compilerOptions": {
    "outDir": "dist"
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}

패키지에 대한 진입점 만들기

먼저, dist 디렉터리가 있도록 코드가 tsc로 컴파일되었는지 확인합니다. build 스크립트와 dev 스크립트가 필요합니다:

./packages/ui/package.json
{
  "scripts": {
    "dev": "tsc --watch",
    "build": "tsc"
  }
}

그런 다음, 다른 패키지가 컴파일된 코드를 사용할 수 있도록 package.json에 패키지의 진입점을 설정합니다:

./packages/ui/package.json
{
  "exports": {
    "./*": {
      "types": "./src/*.ts",
      "default": "./dist/*.js"
    }
  }
}

이런 방식으로 exports를 설정하면 여러 장점이 있습니다:

  • types 필드를 사용하면 tsserversrc의 코드를 코드 타입의 신뢰할 수 있는 소스로 사용할 수 있습니다. 편집기는 항상 코드의 최신 인터페이스를 최신 상태로 유지합니다.
  • 위험한 배럴 파일을 만들지 않고도 패키지에 새 진입점을 빠르게 추가할 수 있습니다.
  • 편집기에서 패키지 경계를 넘어서는 import에 대한 자동 가져오기 제안을 받을 수 있습니다.

패키지를 게시하는 경우, 컴파일된 코드만 npm에 게시되므로 types에서 소스 코드에 대한 참조를 사용할 수 없습니다. 선언 파일과 소스 맵을 생성하고 참조해야 합니다.

코드베이스 린팅

TypeScript를 린터로 사용하려면 Turborepo의 캐싱과 병렬화를 사용하여 워크스페이스 전체의 타입을 빠르게 검사할 수 있습니다.

먼저, 타입을 검사하려는 패키지에 check-types 스크립트를 추가합니다:

./apps/web/package.json
{
  "scripts": {
    "check-types": "tsc --noEmit"
  }
}

그런 다음, turbo.jsoncheck-types 작업을 생성합니다. 작업 구성 가이드에 따라 Transit Node를 사용하여 다른 패키지의 소스 코드 변경 사항을 준수하면서 작업을 병렬로 실행할 수 있습니다:

Turborepo logo
./turbo.json
{
  "tasks": {
    "topo": {
      "dependsOn": ["^topo"]
    },
    "check-types": {
      "dependsOn": ["topo"]
    }
  }
}

그런 다음, turbo check-types를 사용하여 작업을 실행합니다.

모범 사례

패키지 컴파일에 tsc 사용

Internal Packages의 경우, 가능한 한 TypeScript 라이브러리를 컴파일할 때 tsc를 사용하는 것을 권장합니다. 번들러를 사용할 수는 있지만 필수는 아니며 빌드 프로세스에 추가적인 복잡성을 더합니다. 또한, 라이브러리를 번들링하면 애플리케이션의 번들러에 도달하기 전에 코드가 변형될 수 있어 디버깅하기 어려운 문제가 발생할 수 있습니다.

패키지 경계를 넘어 정의로 이동 활성화

"정의로 이동"은 클릭이나 단축키로 심볼(변수나 함수 등)의 원래 선언이나 정의로 빠르게 이동할 수 있는 편집기 기능입니다. TypeScript가 올바르게 구성되면 Internal Packages 간에 쉽게 이동할 수 있습니다.

Just-in-Time 패키지

Just-in-Time 패키지에서 내보낸 항목은 자동으로 원본 TypeScript 소스 코드로 이동합니다. 정의로 이동 기능이 예상대로 작동합니다.

컴파일된 패키지

컴파일된 패키지에서 내보낸 항목의 정의로 이동 기능이 작동하려면 declarationdeclarationMap 구성을 사용해야 합니다. 패키지에 이 두 구성을 활성화한 후 tsc로 패키지를 컴파일하고 출력 디렉터리를 열면 선언 파일과 소스 맵을 찾을 수 있습니다.

button.js
button.d.ts
button.d.ts.map

이 두 파일이 준비되면 편집기가 원본 소스 코드로 이동합니다.

TypeScript 컴파일러 paths 대신 Node.js 서브패스 가져오기 사용

TypeScript 컴파일러의 paths 옵션을 사용하여 패키지에서 절대 가져오기를 만들 수 있지만, 이러한 경로는 Just-in-Time 패키지를 사용할 때 컴파일 실패를 일으킬 수 있습니다. TypeScript 5.4부터 더 견고한 솔루션을 위해 Node.js 서브패스 가져오기를 대신 사용할 수 있습니다.

Just-in-Time 패키지

Just-in-Time 패키지에서는 dist와 같은 빌드 출력이 생성되지 않으므로 imports가 패키지의 소스 코드를 대상으로 해야 합니다.

./packages/ui/package.json
{
  "imports": {
    "#*": "./src/*"
  }
}

컴파일된 패키지

컴파일된 패키지에서 imports는 패키지의 빌드된 출력을 대상으로 합니다.

./packages/ui/package.json
{
  "imports": {
    "#*": "./dist/*"
  }
}

프로젝트 루트에 tsconfig.json 파일이 필요하지 않을 가능성이 높습니다

저장소 구조화 가이드에서 언급했듯이, 도구의 각 패키지를 자체 단위로 취급하려고 합니다. 이는 각 패키지가 프로젝트 루트의 tsconfig.json을 참조하는 대신 사용할 자체 tsconfig.json을 가져야 한다는 것을 의미합니다. 이 방법을 따르면 Turborepo가 타입 검사 작업을 캐시하기가 더 쉬워지고 구성이 단순해집니다.

Workspace 루트에 tsconfig.json을 두고자 할 수 있는 유일한 경우는 패키지에 없는 TypeScript 파일에 대한 구성을 설정하는 것입니다. 예를 들어, 루트에서 실행해야 하는 TypeScript로 작성된 스크립트가 있는 경우 해당 파일에 대한 tsconfig.json이 필요할 수 있습니다.

그러나 Workspace 루트의 변경 사항은 모든 작업이 캐시를 놓치게 하므로 이 방법도 권장되지 않습니다. 대신 해당 스크립트를 저장소의 다른 디렉터리로 이동하세요.

TypeScript 프로젝트 참조가 필요하지 않을 가능성이 높습니다

TypeScript 프로젝트 참조는 워크스페이스에 또 다른 구성 지점과 캐싱 계층을 추가하므로 사용을 권장하지 않습니다. 이 두 가지는 이점이 거의 없으면서 저장소에 문제를 일으킬 수 있으므로 Turborepo를 사용할 때는 피하는 것이 좋습니다.

제한 사항

편집기가 패키지의 TypeScript 버전을 사용하지 않습니다

tsserver는 코드 편집기에서 다른 패키지에 대해 서로 다른 TypeScript 버전을 사용할 수 없습니다. 대신 특정 버전을 발견하여 모든 곳에서 사용합니다.

이로 인해 편집기에 표시되는 린팅 오류와 타입을 검사하기 위해 tsc 스크립트를 실행할 때의 오류가 다를 수 있습니다. 이것이 문제가 되는 경우 TypeScript 종속성을 동일한 버전으로 유지하는 것을 고려하세요.