|
| 1 | +<div align="center"> |
| 2 | + <img width="180" src="./media/icon.svg" alt="TypeScript Remove (tsr) logo" /> |
| 3 | +</div> |
| 4 | +<div align="center"> |
| 5 | + <a href="https://badge.fury.io/js/tsr"><img alt="npm version" src="https://badge.fury.io/js/tsr.svg" /></a> |
| 6 | + <a href="https://packagephobia.com/result?p=tsr"><img alt="install size" src="https://packagephobia.com/badge?p=tsr" /></a> |
| 7 | + <a href="https://github.com/tsrorg/tsr/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/tsrorg/tsr/actions/workflows/ci.yml/badge.svg?branch=main" /></a> |
| 8 | +</div> |
| 9 | + |
| 10 | +# tsr |
| 11 | + |
| 12 | +TypeScript Remove (tsr)은 TypeScript 프로젝트에서 사용되지 않는 코드를 제거하는 유틸리티입니다. 이는 소스 파일에 대한 트리 쉐이킹과 유사합니다. |
| 13 | + |
| 14 | +[English](./README.md) | 한국어 |
| 15 | + |
| 16 | +[Migrating from v0.x (ts-remove-unused)](./doc/migration.md) |
| 17 | + |
| 18 | +## 기능 |
| 19 | + |
| 20 | +### 🕵️ 사용되지 않는 코드 찾기 |
| 21 | + |
| 22 | +tsr는 번들러에서 트리 쉐이킹이 구현되는 방식처럼 TypeScript 프로젝트를 정적으로 분석합니다. tsr를 실행하면 TypeScript 프로젝트에서 사용되지 않는 export와 파일(모듈)의 목록을 얻을 수 있습니다. CI 파이프라인에서 tsr를 사용하여 추가되는 사용되지 않는 코드를 감지할 수 있습니다. |
| 23 | + |
| 24 | +### 🧹 사용되지 않는 코드 자동 제거 |
| 25 | + |
| 26 | +tsr는 자동 코드 제거를 위해 만들어졌습니다. tsr은 사용되지 않는 선언에서 export 키워드를 제거할 뿐만 아니라, 파일 내에서 사용되지 않는 선언은 전체 선언을 제거합니다. tsr은 또한 선언을 제거한 후 필요 없어지는 임포트와 다른 로컬 선언도 제거합니다. [tsr이 파일을 어떻게 편집하는지에 대한 예제를 확인해보세요.](#examples) |
| 27 | + |
| 28 | +### 📦 즉시 사용 가능 |
| 29 | + |
| 30 | +tsr은 TypeScript 컴파일러를 사용하여 프로젝트의 파일을 감지하고 임포트를 해결합니다. 유일한 요구 사항은 유효한 `tsconfig.json`입니다. tsr을 실행하기 위해 다른 설정 파일을 설정할 필요가 없습니다. 진입점 파일을 지정하고 몇 초 안에 tsr을 사용하기 시작하세요. |
| 31 | + |
| 32 | +## 설치 |
| 33 | + |
| 34 | +```bash |
| 35 | +npm i tsr |
| 36 | +``` |
| 37 | + |
| 38 | +TypeScript는 피어 종속성(peer dependency)입니다. |
| 39 | + |
| 40 | +## 빠른 시작 |
| 41 | + |
| 42 | +1. **🔍 `tsconfig.json` 확인** – `include` 및 `exclude`가 철저하게 구성되어 tsr이 사용하지 않는 코드를 올바르게 감지할 수 있도록 하세요. |
| 43 | + |
| 44 | +2. **🔍 진입점 파일 확인** – 진입점 파일이 없으면 모든 파일이 불필요합니다. 일반적으로 `src/main.ts`와 같은 파일이나 `src/pages/*`와 같은 파일 그룹이 될 수 있습니다. |
| 45 | + |
| 46 | +3. **🚀 실행** – 진입점과 일치하는 정규 표현식 패턴을 전달하세요. `--write`를 사용하여 파일을 제자리에서 변경합니다. |
| 47 | + |
| 48 | +```bash |
| 49 | +npx tsr 'src/main\.ts$' |
| 50 | +``` |
| 51 | + |
| 52 | +## 사용법 |
| 53 | + |
| 54 | +### CLI |
| 55 | + |
| 56 | +<!-- prettier-ignore-start --> |
| 57 | + |
| 58 | +``` |
| 59 | +
|
| 60 | +사용법: |
| 61 | + tsr [options] [...entrypoints] |
| 62 | +
|
| 63 | +옵션: |
| 64 | + -p, --project <file> tsconfig.json 파일의 경로 |
| 65 | + -w, --write 변경 사항을 즉시 반영 |
| 66 | + -r, --recursive 프로젝트가 정리될 때까지 파일을 재귀적으로 검사 |
| 67 | + --include-d-ts .d.ts 파일에서 사용되지 않는 코드 확인 |
| 68 | + -h, --help 이 메시지 표시 |
| 69 | + -v, --version 버전 번호 표시 |
| 70 | +
|
| 71 | +예시: |
| 72 | + # 프로젝트의 진입점이 src/main.ts인 경우 사용하지 않는 코드를 검사 |
| 73 | + tsr 'src/main\.ts$' |
| 74 | +
|
| 75 | + # 변경 사항을 즉시 적용 |
| 76 | + tsr --write 'src/main\.ts$' |
| 77 | +
|
| 78 | + # 커스텀 tsconfig.json을 사용하는 프로젝트의 경우 사용하지 않는 코드를 검사 |
| 79 | + tsr --project tsconfig.app.json 'src/main\.ts$' |
| 80 | +
|
| 81 | + # src/pages에 여러 개의 진입점이 있는 프로젝트의 경우 사용하지 않는 코드를 검사 |
| 82 | + tsr 'src/pages/.*\.ts$' |
| 83 | +
|
| 84 | +``` |
| 85 | +<!-- prettier-ignore-end --> |
| 86 | + |
| 87 | +#### `-p`, `--project` |
| 88 | + |
| 89 | +코드베이스를 분석하는 데 사용되는 `tsconfig.json`을 지정합니다. 기본값은 프로젝트 루트에 있는 `tsconfig.json`입니다. |
| 90 | + |
| 91 | +```bash |
| 92 | +npx tsr --project tsconfig.client.json 'src/main\.ts$' |
| 93 | +``` |
| 94 | + |
| 95 | +#### `-w`, `--write` |
| 96 | + |
| 97 | +수정 가능한 변경사항을 해당 위치에 바로 적용합니다. |
| 98 | + |
| 99 | +> [!경고] |
| 100 | +> 이 작업은 코드를 삭제할 수 있습니다. Git으로 관리되는 환경에서 사용하는 것을 강력히 권장합니다. |
| 101 | +
|
| 102 | +#### `-r`, `--recursive` |
| 103 | + |
| 104 | +기본적으로 CLI는 모든 파일을 한 번만 처리합니다. |
| 105 | +프로젝트 내 다른 파일의 수정으로 인해 사용되지 않게 된 코드는 이 방식으로는 감지되지 않을 수 있습니다. |
| 106 | +이 옵션을 활성화하면, tsr은 한 파일의 변경으로 영향을 받을 수 있는 다른 파일들도 재귀적으로 탐색합니다. |
| 107 | + |
| 108 | +이 방식은 시간이 더 오래 걸리지만, 한 번의 실행으로 전체 프로젝트를 수정하고자 할 때 유용합니다. |
| 109 | + |
| 110 | +#### `--include-d-ts` |
| 111 | + |
| 112 | +기본적으로 `.d.ts`파일에서 export된 타입은 감지되지 않습니다. |
| 113 | +`.d.ts`파일의 타입도 포함하고 싶다면 `--include-d-ts`옵션을 사용하세요. |
| 114 | + |
| 115 | +### JavaScript API |
| 116 | + |
| 117 | +또는 JavaScript API를 사용하여 tsr을 실행할 수도 있습니다. |
| 118 | + |
| 119 | +```typescript |
| 120 | +import { tsr } from 'tsr'; |
| 121 | + |
| 122 | +await tsr({ |
| 123 | + entrypoints: [/main\.ts/], |
| 124 | + mode: 'check', |
| 125 | +}).catch(() => { |
| 126 | + process.exitCode = 1; |
| 127 | +}); |
| 128 | +``` |
| 129 | + |
| 130 | +프로젝트 경로 및 커스텀 `tsconfig.json`파일을 수동으로 지정할 수 있습니다. |
| 131 | + |
| 132 | +```typescript |
| 133 | +await tsr({ |
| 134 | + entrypoints: [/main\.ts/], |
| 135 | + mode: 'check', |
| 136 | + configFile: 'tsconfig.sample.json', |
| 137 | + projectRoot: '/path/to/project', |
| 138 | +}); |
| 139 | +``` |
| 140 | + |
| 141 | +사용 가능한 모든 옵션을 확인하려면 `import('tsr').Config` 타입을 참고하세요. |
| 142 | + |
| 143 | +## 제외하기 |
| 144 | + |
| 145 | +`export` 선언에 `// tsr-skip` 주석을 추가하면, 해당 항목은 제거 대상에서 제외됩니다. |
| 146 | + |
| 147 | +```ts |
| 148 | +// tsr-skip |
| 149 | +export const hello = 'world'; |
| 150 | +``` |
| 151 | + |
| 152 | +## 테스트 파일 |
| 153 | + |
| 154 | +테스트를 위한 별도의 `tsconfig`를 [Project References(프로젝트 참조)](https://www.typescriptlang.org/docs/handbook/project-references.html) 방식으로 분리해두셨다면 아주 좋습니다! |
| 155 | +tsr은 테스트 목적만으로 존재하는 export나 파일을 제거합니다. |
| 156 | + |
| 157 | +만약 구현 코드와 테스트 파일을 모두 포함하는 `tsconfig.json`을 CLI에 전달하면, |
| 158 | +기본적으로 진입점(entry point) 파일에서 참조되지 않는 테스트 파일은 제거될 수 있습니다. |
| 159 | + |
| 160 | +임시 방편으로는 테스트 파일과 일치하는 패턴을 인자로 전달하여 삭제를 피할 수 있지만, |
| 161 | +가장 권장되는 방법은 Project References를 사용하여 TypeScript 설정 자체를 더 엄격하고 견고하게 만드는 것입니다 |
| 162 | +(이 라이브러리뿐만 아니라 전체 개발환경을 위해서도요.) |
| 163 | + |
| 164 | +```bash |
| 165 | +npx tsr -w 'src/main\.ts$' ## tsconfig에 따라 테스트 파일이 삭제될 수 있습니다. |
| 166 | +npx tsr -w 'src/main\.ts$' '.*\.test\.ts$' ## 테스트 파일을 진입점으로 지정하면 삭제를 피할 수 있습니다. |
| 167 | +``` |
| 168 | + |
| 169 | +## 비교 |
| 170 | + |
| 171 | +### TypeScript |
| 172 | + |
| 173 | +`compilerOptions.noUnusedLocals` 옵션을 활성화하면, **읽히지 않는 선언(declaration)** 에 대해 경고가 표시됩니다. |
| 174 | + |
| 175 | +```typescript |
| 176 | +// 'a'는 선언되었지만, 값이 읽히지 않았습니다. |
| 177 | +const a = 'a'; |
| 178 | +``` |
| 179 | + |
| 180 | +하지만 이 값을 `export`할 경우, 프로젝트 내에서 실제로 사용되지 않더라도 에러가 발생하지 않습니다. |
| 181 | +tsr의 목적은 프로젝트 전역에서의 사용 여부를 고려하여, 사용되지 않는 코드를 감지하고 수정하는 것입니다. |
| 182 | + |
| 183 | +### ESLint |
| 184 | + |
| 185 | +ESLint는 사용되지 않는 import를 감지할 수 있으며, `eslint-plugin-unused-imports` 같은 플러그인을 통해 자동 수정도 가능합니다. |
| 186 | + |
| 187 | +```typescript |
| 188 | +// 'foo'는 정의되었지만 사용되지 않았습니다. |
| 189 | +import { foo } from './foo'; |
| 190 | +``` |
| 191 | + |
| 192 | +하지만, 사용되지 않는 export는 감지할 수 없습니다. |
| 193 | +ESLint는 기본적으로 파일 단위로 동작하도록 설계되어 있어, 프로젝트 전체 범위의 사용 여부를 기반으로 분석하는 기능은 제공되지 않습니다. |
| 194 | + |
| 195 | +```typescript |
| 196 | +// 이 export가 프로젝트 내에서 실제로 사용되는지를 감지하는 규칙은 도입되기 어렵습니다. |
| 197 | +export const a = 'a'; |
| 198 | +``` |
| 199 | + |
| 200 | +tsr의 주요 목표는 사용되지 않는 export를 제거하거, 관련 모듈 자체를 삭제하는 것입니다. |
| 201 | +또한 export 제거의 결과로 발생하는 불필요한 import도 함께 제거합니다. |
| 202 | + |
| 203 | +### Knip |
| 204 | + |
| 205 | +Knip은 저장소 내의 사용되지 않는 코드(심지어 의존성까지)를 감지하는 것을 목표로 하는 포괄적인 라이브러리입니다. |
| 206 | +물론 Knip과 tsr 간에는 몇 가지 뚜렷한 차이가 존재하며, 예를 들어 tsr은 TypeScript 프로젝트 전용이라는 점이 대표적입니다. |
| 207 | + |
| 208 | +아래는 두 라이브러리 간의 주요 차이점입니다. |
| 209 | + |
| 210 | +#### 자동 수정을 위한 설계 |
| 211 | + |
| 212 | +tsr은 처음부터 자동 코드 수정을 위해 설계된 라이브러리입니다. |
| 213 | +Knip도 현재 자동 수정 기능을 제공하지만, 그 기능에는 일부 제약이 있습니다. |
| 214 | +예를 들어 다음 코드를 고려해봅시다. |
| 215 | + |
| 216 | +```typescript |
| 217 | +export const a = 'a'; |
| 218 | + |
| 219 | +export const f = () => a2; |
| 220 | + |
| 221 | +const a2 = 'a2'; |
| 222 | +``` |
| 223 | + |
| 224 | +위 코드에서 `f()`가 프로젝트 내에서 사용되지 않는 경우, |
| 225 | + |
| 226 | +- Knip은 `export` 키워드만 제거합니다. |
| 227 | +- tsr은 `f()`선언 자체를 제거하며, `a2`도 함께 삭제합니다. |
| 228 | + |
| 229 | +#### 설정 없이 바로 사용 가능 (Zero Configuration) |
| 230 | + |
| 231 | +Knip은 사용자가 별도의 설정 파일을 제공해야 합니다. 이는 유연성을 제공하지만, 자신의 프로젝트에 맞게 Knip을 정확히 설정하는 것이 다소 복잡할 수 있습니다. |
| 232 | +반면, tsr은 `tsconfig.json` 만 있으면 추가 설정 없이 바로 동작합니다. 즉, 저장소에 TypeScript 설정이 이미 되어 있다면 tsr을 곧바로 사용할 수 있습니다. |
| 233 | + |
| 234 | +#### 더 명확한 동작 방식 |
| 235 | + |
| 236 | +Knip은 프로젝트 구조에 대해 몇 가지 가정을 하며, 자체 모듈 해석 방식(custom module resolution)을 사용합니다. |
| 237 | +이런 설계는 특정 상황에서는 유용할 수 있고 TypeScript가 지원하지 않는 파일 타입도 처리 가능하게 하지만, 때로는 예측하기 어려운 결과를 초래할 수 있습니다. |
| 238 | + |
| 239 | +반면, tsr은 TypeScript 기반의 모듈 해석만을 사용하며, `tsconfig.json` 을 통해 모든 동작을 명확히 제어할 수 있도록 설계되어 있습니다. |
| 240 | +즉, 당신의 프로젝트가 TypeScript의 타입 검사를 통과하면 tsr도 잘 작동하며, 타입 검사(tsc)가 실패하면 tsr도 정확한 결과를 낼 수 없습니다. |
| 241 | + |
| 242 | +#### 최소한의 설계 |
| 243 | + |
| 244 | +tsr은 단일 목적을 위한 최소한의 설계(minimal design)를 추구합니다. |
| 245 | +설치 용량도 현저히 작고, 실행 시 `@types/node` 에 의존하지 않는 런타임 기반으로 작동합니다. |
| 246 | + |
| 247 | +| tsr | Knip | |
| 248 | +| ---- | ------ | |
| 249 | +| 98kB | 5.86MB | |
| 250 | + |
| 251 | +#### 더 나은 성능 |
| 252 | + |
| 253 | +벤치마크 결과에 따르면, tsr은 Knip보다 약 2.14배 더 빠릅니다🚀 |
| 254 | +(자세한 내용은 `benchmark/vue_core.sh` 파일 참고) |
| 255 | + |
| 256 | +<img width="400" src="./media/comparison.png" alt="benchmark of tsr and Knip" /> |
| 257 | + |
| 258 | +#### 재귀 편집 기능 |
| 259 | + |
| 260 | +tsr은 `--recursive` 옵션을 통해, |
| 261 | +한 번의 실행으로 사용되지 않는 코드를 모두 제거할 때까지 파일을 반복적으로 편집합니다. |
| 262 | + |
| 263 | +#### 주요 차이점 |
| 264 | + |
| 265 | +| 기능 항목 | tsr | Knip | |
| 266 | +| -------------------- | ------------------------------ | -------------------------------------- | |
| 267 | +| **자동 코드 수정** | ✅ 포괄적인 자동 편집 지원 | 제한적 | |
| 268 | +| **설정 필요 없음** | ✅ `tsconfig.json` 만으로 작동 | 정확한 결과를 위해 별도 설정 파일 필요 | |
| 269 | +| **예측 가능한 동작** | ✅ TypeScript 기반의 로직 | 프로젝트 구조에 대한 가정이 존재 | |
| 270 | +| **설치 용량** | ✅ 98kB, 최소한의 의존성 | 5.86MB, `@types/node` 필요 | |
| 271 | +| **성능** | ✅ 2.14배 더 빠름 | | |
| 272 | +| **재귀 편집 기능** | ✅ `--recursive` 옵션 지원 | | |
| 273 | + |
| 274 | +## 예시 |
| 275 | + |
| 276 | +tsr이 사용되지 않는 코드를 발견했을 때, 파일을 어떻게 수정하는지에 대한 예시입니다. |
| 277 | + |
| 278 | +<!-- prettier-ignore-start --> |
| 279 | + |
| 280 | +프로젝트 내에서 `a2`가 사용되지 않을 경우: |
| 281 | + |
| 282 | +```diff |
| 283 | +--- src/a.ts |
| 284 | ++++ src/a.ts |
| 285 | +@@ -1,3 +1 @@ |
| 286 | + export const a = 'a'; |
| 287 | +- |
| 288 | +-export const a2 = 'a2'; |
| 289 | +``` |
| 290 | + |
| 291 | +프로젝트 내에서 `b`는 사용되지 않지만 `f()`는 사용될 경우: |
| 292 | + |
| 293 | +```diff |
| 294 | +--- src/b.ts |
| 295 | ++++ src/b.ts |
| 296 | +@@ -1,5 +1,5 @@ |
| 297 | +-export const b = 'b'; |
| 298 | ++const b = 'b'; |
| 299 | + |
| 300 | + export function f() { |
| 301 | + return b; |
| 302 | + } |
| 303 | +``` |
| 304 | + |
| 305 | +프로젝트 내에서 `f()`가 사용되지 않고, 이를 삭제하면 해당 `import`문도 불필요해지는 경우: |
| 306 | + |
| 307 | +```diff |
| 308 | +--- src/c.ts |
| 309 | ++++ src/c.ts |
| 310 | +@@ -1,7 +1 @@ |
| 311 | +-import { cwd } from "node:process"; |
| 312 | +- |
| 313 | + export const c = 'c'; |
| 314 | +- |
| 315 | +-export function f() { |
| 316 | +- return cwd(); |
| 317 | +-} |
| 318 | +``` |
| 319 | + |
| 320 | +프로젝트 내에서 `f()`와 `exported`가 사용되지 않고, `f()`를 삭제함으로써 `exported`와 `local`또한 불필요해지는 경우: |
| 321 | + |
| 322 | +```diff |
| 323 | +--- src/d.ts |
| 324 | ++++ src/d.ts |
| 325 | +@@ -1,8 +1 @@ |
| 326 | +-export const exported = "exported"; |
| 327 | +-const local = "local"; |
| 328 | +- |
| 329 | + export const d = "d"; |
| 330 | +- |
| 331 | +-export function f() { |
| 332 | +- return { exported, local }; |
| 333 | +-} |
| 334 | + |
| 335 | +``` |
| 336 | + |
| 337 | +<!-- prettier-ignore-end --> |
| 338 | + |
| 339 | +## 기여하기 |
| 340 | + |
| 341 | +기여는 언제나 환영입니다! |
| 342 | + |
| 343 | +## 작성자 |
| 344 | + |
| 345 | +Kazushi Konosu (https://github.com/kazushisan) |
| 346 | + |
| 347 | +## License |
| 348 | + |
| 349 | +``` |
| 350 | +Copyright (C) 2023 LINE Corp. |
| 351 | +
|
| 352 | +이 파일은 Apache License, Version 2.0(이하 "License") 하에 License가 부여됩니다. |
| 353 | +License에 따라 사용하지 않으면 안 됩니다. |
| 354 | +License 사본은 다음에서 확인하실 수 있습니다: |
| 355 | +
|
| 356 | +http://www.apache.org/licenses/LICENSE-2.0 |
| 357 | +
|
| 358 | +적용 법률 또는 서면 동의에 따라 요구되지 않는 한, |
| 359 | +이 소프트웨어는 License에 따라 "(AS IS)" 제공되며, |
| 360 | +명시적이든 묵시적이든 어떠한 보증이나 조건도 포함하지 않습니다. |
| 361 | +License에 따른 권한 및 제한 사항에 대한 자세한 내용은 해당 License를 참조하십시오. |
| 362 | +``` |
0 commit comments