みどりのさるのエンジニア

TypeScriptのskipLibCheckを理解する

2021年01月19日

tsconfig.json には skipLibCheck という設定オプションがあります。
このオプションの挙動やどんな時に設定するかについてまとめました。

skipLibCheckの挙動

このオプションのデフォルト値は false で true を設定することで *.d.ts ファイルに対する型チェックをスキップすることができます。

次のようなサーバーのコードを考えてみます。

import express from "express";

const app = express();

app.post("/uses/sign_in", (req: express.Request, res: express.Response) => {
    if (req.tokens.includes('abc')) {
        res.sendStatus(200)
    } else {
        res.sendStatus(403);
    }
});

app.listen(3000, () => {
    console.log("Listening on port 3000");
});

tsconfig.json では skipLibCheck は定義しておらず、デフォルトの false が設定されている状態です。

{
    "compilerOptions": {
        "esModuleInterop": true,
        "typeRoots": [
            "node_modules/@types",
            "types"
        ]
    }
}

req.tokens は次のような型定義ファイルを作成して、 express.Request の型定義を拡張して新たにプロパティを定義しています。この時、 tokens: Array とジェネリクスを指定すべき箇所を誤って指定しているため、型定義エラーが発生しています。

// types/express/index.d.ts
/// <reference types="express" />

// @see: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/method-override/index.d.ts
declare namespace Express {
    export interface Request {
        // Interface Array<T>
        // Generic type 'Array<T>' requires 1 type argument(s).ts(2314)
        tokens: Array;
    }
}

この状態で、 tsconfig.json にて skipLibCheck: true を設定します。

{
    "compilerOptions": {
        "skipLibCheck": true,
        // (省略)
    }
}

*.d.ts ファイルに対する型チェックがスキップされるのでエラーが消え、正しくない型定義ファイルが存在してもコンパイルエラーが出なくなってしまいました。

declare namespace Express {
    export interface Request {
        tokens: Array;
    }
}

この状態では req.tokensany として解釈されます。

// (省略)
app.post("/uses/sign_in", (req: express.Request, res: express.Response) => {
    // (property) Express.Request.tokens: any
    if (req.tokens.includes('abc')) {
        res.sendStatus(200)
    } else {
        res.sendStatus(403);
    }
});

skipLibCheckのメリット

挙動を見る限りでは skipLibCheck: true を設定することで、型チェックの精度が下がるデメリットがありましたが skipLibCheck: true を設定するメリットは何なのでしょうか?

アプリケーションが中規模、大規模になり依存するパッケージが増えた時に、それに比例して型定義ファイルが増えていきます。 tsc のデフォルトの挙動では、TypeScriptをコンパイルするときに @types/express などのライブラリの *.d.ts に対しても型チェックを実施するため、コンパイル時間が増加します。 skipLibCheck: true を設定することで、型チェックの精度を犠牲にする代わりにコンパイル時間を削減できます。

# skipLibkCheck: false
$ yarn tsc --extendedDiagnostics -p ./
Files:                          88
Check time:                  1.47s
Total time:                  2.51s
✨  Done in 2.85s.

# skipLibCheck: true
$ yarn tsc --extendedDiagnostics -p ./
Files:                         88
Check time:                 0.18s
Total time:                 1.17s
✨  Done in 1.49s.

他のシナリオとしては、複数のパッケージが、それぞれが異なるバージョンの型定義ファイルに依存している(別々のパッケージ @types/react@^16@types/react@^17 に依存しているなど)場合に、同じ型定義ファイルが複数 node_modules にインストールされる場合があります。この時に error TS2300: Duplicate Identifier という型定義の重複エラーが発生してコンパイルに失敗します。

解決策としては、パッケージのバージョンを上げるなどして依存関係を整理して重複を無くす方法があります。しかし、それが難しい場合は skipLibCheck: true を指定することで型定義ファイルの型チェックをスキップすることで、エラーを回避することができます。

さいごに

skipLibCheck は true or false のそれぞれにメリット・デメリットがあるため、プロジェクトで何を優先するかを決めて設定の有無を決めると良いでしょう。