Skip to Content
WebTypeScriptTypeScript 실무16. tsconfig project references 구성

이번 편의 결과물: 루트에서 tsc -b 한 번으로 shared-typesweb 순서로 빌드되고, 의도적으로 순환 참조를 만들어 오류를 확인합니다. · 다루는 개념: composite: true, tsconfig.jsonreferences 배열, tsc -b 증분 빌드, 순환 참조 오류

15편에서 apps/web@bookshelf/shared-typesworkspace: 프로토콜로 참조하게 됐습니다. pnpm install이 만든 심볼릭 링크 덕분에 실행은 잘 되지만, 지금 두 패키지의 tsconfig.json은 서로를 전혀 모릅니다. 그래서 편집기가 shared-types의 타입을 매번 소스부터 다시 읽고, 두 패키지를 한 번에 타입 검사하려면 각각 따로 명령을 실행해야 합니다. 이 편에서 compositereferences로 두 패키지의 빌드 의존 관계를 명시하고, tsc -b로 순서를 자동 관리합니다.

이 편에서 만드는 파일

bookshelf-ts/ ├── tsconfig.json (+, 모노레포 루트 솔루션 파일) ├── package.json (~, typescript devDependency 추가, typecheck 스크립트를 tsc -b로 교체) ├── packages/shared-types/ │ └── tsconfig.json (~, composite: true 추가) └── apps/web/ └── tsconfig.app.json (~, shared-types에 대한 references 추가)

개념 정리

composite: 다른 프로젝트가 참조할 수 있게 만드는 표시

composite: true를 켠 프로젝트는 “내 타입 정보를 다른 프로젝트가 가져다 쓸 수 있다”고 선언하는 것입니다. 이 옵션은 declaration(.d.ts 생성)이 켜져 있을 것을 요구하고, 소스 파일이 전부 includefiles에 잡혀 있을 것을 요구합니다. 반대로, 남을 참조하기만 하고 자신은 아무도 참조하지 않는 프로젝트(apps/web)는 composite가 필요 없습니다.

references 배열

tsconfig.json이 다른 프로젝트의 산출물에 의존한다는 것을 references로 표시합니다.

{ "references": [{ "path": "../../packages/shared-types" }] }

path는 다른 프로젝트의 tsconfig.json이 있는 폴더(또는 파일 자체)를 가리킵니다. 이 배열이 있으면 tsc가 “이 프로젝트를 검사하기 전에 저 프로젝트부터 최신 상태로 만들어야 한다”는 것을 압니다.

tsc -b: 순서를 아는 빌드

일반 tsc는 지정된 프로젝트 하나만 검사합니다. tsc -b(빌드 모드)는 references를 따라가며 의존하는 프로젝트부터 먼저 빌드하고, 이미 최신 상태인 프로젝트는 건너뜁니다.

tsc -b # 현재 폴더의 tsconfig.json 기준 tsc -b --verbose # 어떤 프로젝트를 빌드·스킵했는지 출력 tsc -b --force # 전부 오래된 것으로 간주하고 다시 빌드

순환 참조는 빌드 자체를 막는다

A가 B를 참조하고 B가 다시 A를 참조하면, “어느 쪽을 먼저 빌드해야 하는지” 결정할 수 없습니다. tsc -b는 이런 순환을 감지하면 아무것도 빌드하지 않고 즉시 오류를 냅니다.

실습

1. shared-types를 참조 가능하게 표시

// bookshelf-ts/packages/shared-types/tsconfig.json { "compilerOptions": { "target": "ES2023", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "skipLibCheck": true, "composite": true, "declaration": true, "outDir": "./dist" }, "include": ["src"] }

composite: true를 추가했습니다. 이미 declaration: trueinclude가 있었으므로 요구 조건은 그대로 충족됩니다.

2. apps/web에서 shared-types를 참조로 명시

// bookshelf-ts/apps/web/tsconfig.app.json { "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "target": "es2023", "lib": ["ES2023", "DOM"], "module": "esnext", "types": ["vite/client"], "allowArbitraryExtensions": true, "skipLibCheck": true, "moduleResolution": "bundler", "allowImportingTsExtensions": true, "verbatimModuleSyntax": true, "moduleDetection": "force", "noEmit": true, "jsx": "react-jsx", "noUnusedLocals": true, "noUnusedParameters": true, "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true }, "include": ["src"], "references": [{ "path": "../../packages/shared-types" }] }

apps/webshared-types의 산출물을 가져다 쓰는 쪽이라 composite가 필요 없습니다. noEmit: true는 그대로 둡니다. 실제 번들링은 여전히 vite가 담당하고, tsc -b는 타입 검사와 빌드 순서 관리만 맡습니다.

3. 모노레포 루트 솔루션 파일 작성

// bookshelf-ts/tsconfig.json { "files": [], "references": [ { "path": "packages/shared-types" }, { "path": "apps/web" } ] }
// bookshelf-ts/package.json (일부) { "devDependencies": { "typescript": "7.0.2" }, "scripts": { "dev": "pnpm --filter @bookshelf/web dev", "build": "pnpm -r build", "test": "pnpm -r test", "lint": "pnpm -r lint", "typecheck": "tsc -b" } }

루트 typecheck 스크립트가 각 패키지의 tsc -b를 따로 실행하던 pnpm -r typecheck 대신, 루트 tsconfig.json을 기준으로 전체를 한 번에 빌드하는 tsc -b로 바뀌었습니다.

4. 설치와 실행

pnpm install pnpm typecheck

5. 순환 참조 오류 직접 만들어보기

shared-typesapps/web을 참조하도록 잘못 추가해봅니다.

// bookshelf-ts/packages/shared-types/tsconfig.json (임시로 추가) { "compilerOptions": { "composite": true, "declaration": true, "outDir": "./dist" }, "include": ["src"], "references": [{ "path": "../../apps/web/tsconfig.app.json" }] }

이제 shared-typesapps/web을, apps/web은 다시 shared-types를 참조합니다.

pnpm typecheck
Project references may not form a circular graph. Cycle detected: packages/shared-types/tsconfig.json, apps/web/tsconfig.app.json

원인을 확인했으면 방금 추가한 references 항목을 지워 원래 상태로 되돌립니다. shared-types는 도메인 타입만 담는 패키지라 애초에 apps/web을 알 이유가 없습니다.

확인

  • 순환 참조를 만들기 전, pnpm typecheck를 실행하면 packages/shared-types/dist/book.js·book.d.ts가 생기고 오류 없이 끝납니다.
  • 같은 명령을 한 번 더 실행하면 아무것도 다시 빌드하지 않고 즉시 끝납니다(이미 최신 상태이기 때문).
  • packages/shared-types/src/book.ts를 한 글자 고치고 다시 실행하면 그 프로젝트만 다시 빌드됩니다.
  • 순환 참조를 임시로 추가한 상태에서 실행하면 빌드가 진행되지 않고 순환을 알리는 오류만 출력됩니다.
  • 오류 원인이 된 references 항목을 지우면 다시 정상적으로 빌드됩니다.

직접 해보기

  1. pnpm typecheck -- --verbose(또는 루트에서 tsc -b --verbose)를 실행해, 어떤 프로젝트를 빌드했고 어떤 프로젝트를 건너뛰었는지 로그로 확인해보세요.
  2. packages/shared-types/dist 폴더를 삭제한 뒤 다시 pnpm typecheck를 실행해, 산출물이 없을 때는 최신 여부와 상관없이 다시 빌드되는지 확인해보세요.

정답 보기

--verbose 실행 결과에는 대략 다음과 같은 줄이 섞여 나옵니다.

Project 'packages/shared-types/tsconfig.json' is out of date because output file 'packages/shared-types/dist/book.js' does not exist Building project 'packages/shared-types/tsconfig.json'... Project 'apps/web/tsconfig.app.json' is up to date because newest input 'apps/web/src/...' is older than output '...tsbuildinfo'

dist 폴더를 지우면 산출물 자체가 없어져 tsc -b가 “출력 파일이 없다”는 이유로 무조건 다시 빌드합니다. 소스가 안 바뀌었어도 산출물이 사라지면 최신 상태로 보지 않습니다.

자주 하는 실수

증상원인고치는 법
shared-types를 고쳐도 apps/web에서 타입이 안 바뀜apps/web/tsconfig.app.jsonreferences를 추가하지 않음참조 대상 경로를 정확히 추가하고 pnpm typecheck를 다시 실행한다
composite를 켰는데 오류가 남declaration이 꺼져 있거나 소스가 include에 안 잡힘composite를 쓰는 프로젝트는 declaration: true와 정확한 include가 함께 필요하다
순환 참조 오류가 나는데 원인을 못 찾음참조 방향을 반대로 착각함(누가 누구를 쓰는지)실제로 코드를 가져다 쓰는 쪽에서 쓰이는 쪽으로만 references를 건다. shared-types는 누구도 참조하면 안 된다

확인 문제

문제 14지선다
composite: true가 요구하는 조건은
문제 24지선다
apps/web/tsconfig.app.json에 composite를 켜지 않아도 되는 이유는
문제 34지선다
tsc -b를 한 번 실행한 뒤 아무것도 바꾸지 않고 다시 실행하면
문제 44지선다
shared-types의 tsconfig.json에 apps/web에 대한 references를 추가하면 안 되는 이유는

참고 자료

Last updated on