Typescript Compiler API で tsconfig.json を指定する

備忘録。TypeScript の Compiler API を特定の tsconfig.json の設定で使う方法について。

TypeScript のバージョンは 4.1.3。

いつ tsconfig.json の設定が必要か?

例えば tsconfig.jsonpaths を設定している時。tsconfig.json で以下の設定をしているとする:

{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@/*": ["./src/*"]
    }

この設定によって import のパス指定で src/ へのエイリアスとして @/ と書くことができる。

src/point.ts:

export class Point {
  readonly x: number
  readonly y: number

  constructor(x: number, y: number) {
    this.x = x
    this.y = y
  }
}

src/main.ts:

import { Point } from "@/point"

function dist(p: Point, q: Point): number {
  return Math.sqrt(Math.pow(p.x - q.x, 2) + Math.pow(p.y - q.y, 2))
}

const p = new Point(1, 2)
const q = new Point(4, 6)
console.log(dist(p, q))

この src/main.ts を Compiler API でパース・解析する時、上記の tsconfig.json の情報が無いと import を解決することができない。

.compilerOptions を Compiler API に渡す方法

createProgram の第2引数にはコンパイラオプション(CompilerOptions 型)を渡すことができる。これは tsconfig.json 内の .compilerOptions と概ね一致している。

ただし tsconfig.json はコメント付き JSON なので、単に JSON.parse で読み込んでも大抵はエラーとなってしまう。readConfigFile を使うとコメント付きの tsconfig.json をパースすることができる。readConfigFile は第2引数としてファイル読み込みを行うコールバック()を受け取るので、fs.readFileSync など適宜適切な関数を渡せばよい。

import * as ts from "typescript"
import { readFileSync } from "fs"

const readFile: (path: string) => string | undefined = (path) => readFileSync(path, "utf-8")
const tsconfig: { config?: any, errors?: ts.Diagnostic; } = ts.readConfigFile("tsconfig.json", readFile)

if (tsconfig.errors !== undefined) {
  throw new Error("cannot load tsconfig")
}

正常に読み込めた場合、戻り値オブジェクトの .configtsconfig.json の中身が入っている。.compilerOptions の内容を CompilerAPI が解釈できる形式に変換するために、convertCompilerOptionsFromJson を使う。

const { options: ts.CompilerOptions, errors: ts.Diagnostic[] } = ts.convertCompilerOptionsFromJson(tsconfig.config.compilerOptions, ".")
if (errors.length !== 0) {
  throw new Error("cannot convert compilerOptions in tsconfig.json")
}

手に入れた CompilerOptions オブジェクトを createProgram に渡すことで、paths の設定などを使って TypeScript のコードをパース・解析することができる。

const program: ts.Program = ts.createProgram(["src/main.ts"], compilerOptions)