Skip to content

Instantly share code, notes, and snippets.

@azu
Last active April 1, 2024 10:23
Show Gist options
  • Star 145 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save azu/56a0411d69e2fc333d545bfe57933d07 to your computer and use it in GitHub Desktop.
Save azu/56a0411d69e2fc333d545bfe57933d07 to your computer and use it in GitHub Desktop.
TypeScriptの設定の良し悪し

tsconfig.json の設定についてのメモ書きです。

Node.jsのバージョンごとの設定

target は 変換後のコードのECMAScriptバージョンを指定する たとえば、Node.js 14はES2020をサポートしている。そのため、Node.js 14向けのコード(サーバなど)ならtarget: "ES2020"を指定することで、余計なTranspileが省かれててコードサイズや実行時間が最適化される。

Node.jsのバージョンとベースとなるtargetの設定は次のパッケージを参照するのが良い。

または、次のページにNode.jsとtsconfig.jsonのtargetの対応が記載されている。

機能、構文ごとの対応を調べたい場合は次のページを参照する。

Front向けのコードならサポート対象のブラウザがサポートしているECMAScriptのバージョンに合わせる。

📝 誤解しやすいがmoduleは、モジュールをCommonJS, ES Module形式で出力するかなどモジュールの構文のみについてのオプションなので、他のECMAScript構文とは関係ない。webpackやRollupなどの中でTypeScriptを扱っているなら、module: "ESNext" にしておくことでTree Shakingなどの最適化ができるという仕組み。

targetmoduleを両方 "ESNext" にする状況は普通はないので、そういった設定は避ける。

使う

tsc --init で生成された tsconfig.json のデフォルトONのオプションは使う。

{
  "compilerOptions": {
    "target": "es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
    "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
    "strict": true,                           /* Enable all strict type-checking options. */
    "esModuleInterop": true,                  /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    "skipLibCheck": true,                     /* Skip type checking of declaration files. */
    "forceConsistentCasingInFileNames": true  /* Disallow inconsistently-cased references to the same file. */
  }
}

targetとmoduleは環境によって違うので、tsconfig/basesを参照する。

strict modeは有効化しないとTypeScriptの恩恵が受けにくい。 後から、strict: trueにするのは、JavaScriptをTypeScriptにするよりもはるかに大変なので、最初からstrict: trueにしたほうが良い。

CommonJSなモジュールをimportするときにinteropするオプション。

macOSでファイル名が大文字小文字の違いを無視してimportに成功するけど、他のOSでは失敗する現象をコンパイラのレベルで阻止する。ONにしておくのが無難

mochaとjestの型が衝突してコンパイルエラーが起きるのを避けたりするときに使えるオプション。 ライブラリの型の不一致を無視するためのオプションなので、個人的にはオフで困らないならオフの方が健全にみえる。

オフの場合は、yarnのresolusionsなどパッケージ管理ツール側で解決できる。 npmには同等のForce Resolutionsするものがないので、このオプションが必要になったりする状況がある。(acceptDependenciesはどうもforce resolusions的には使えない感じがする)

使わない

alias的に使えるが、pathをファイルパスのショートカットとして使うのはツール解析を難しくするので使わない。

{
  "compilerOptions": {
    "baseUrl": "src",
    "paths": {
        "app/*": ["app/*"],
        "config/*": ["app/_config/*"],
        "environment/*": ["environments/*"],
        "shared/*": ["app/_shared/*"],
        "helpers/*": ["helpers/*"],
        "tests/*": ["tests/*"]
    },
}

このように設定すると import config from "config/path" と書くと、configが示すものが一意に決まらなくなる。 config というモジュールの可能性と paths の設定での app/_config/path へのaliasなのかは実際にパスを解決してみないと判別できない。 ツールで解析できなくなることが多いため、ショートカットのために使うのは基本的に避ける。 (リファクタリングの際に問題となっているケースをよくみます)

Next.jsやNuxt.jsのようにディレクトリ名に強い規約がないケースにおいて、 相対パスを避けるために paths を使うと、逆に{読む、書く}手間を増やしてしまうことがあるため避ける。

解決方法:

  • 普通に相対パスで書く。現代的なエディタなら補完が効くので、絶対パスにしたいかどうかは見た目の問題であることがほとんどです
  • パッケージとして切り出してProject Referencesを使う
  • ディレクトリ構造の設計を見直す(関心事でまとめる、テストとアプリケーションを同じディレクトリ以下で扱うなど)

📝 特殊なパターンとして、frontとserver間で型を共有したい場合(どちらもprivateパッケージ)に、型だけのパッケージを作ることを避けるために、pathsで特定のパスだけを参照できるようにするという妥協案で使うことがある。型だけのパッケージはメンテナンスコストがそこまで良いわけではないので、抜け穴的な使い方になるけど現実的なコストとのトレードオフとして理解して使う。この場合も、ハマりやすいのでやっぱりできるだけ paths の数は少ない状態を維持する。

ちなみに、ライブラリとして公開するパッケージの場合は型だけのパッケージを作った方が参照がきれいになるので、そこにコストをかけたほうが総合的には良い場合が多い。pathsみたいなハックは循環参照とかが生まれやすいので、Project Referencesを使って正攻法で解決するコストを払う。

pathsをパッケージ分離の代替として使うと破綻します。(lerna/npm/yarn workspaceやProject Referencesあたりが適切です) この paths は型の参照だけならコンパイルでコケるのでまだいいですが、 スコープを超えた実装の共有を paths でやると N x M の複雑さを持ち込んでしまうので避けたほうがいい。

その他

BabelやDenoとの相互運用性があがる。 構文的に一部制限が増えるが、いろいろなツールがisolatedModulesを要求してくることがあるので、最初からONしておいたほうが無難かもしれない。 TyepScriptサポート が tscじゃなくてbabelを使っているケースがある。

noFallthroughCasesInSwitchなどのno*系オプション。

使ってない変数などもコンパイルエラーにできたり、出力コードには影響ないけどLintに近いチェックができる。 個人的にはONにしているけど、使ってない変数が残ってたらコンパイルエラーとかは開発中は少し騒がしい場合もあるので、人の好みによる。 strictオプションに含まれているわけじゃないので、オプショナルなオプション。

    /* Additional Checks */
    /* Report errors on unused locals. */
    "noUnusedLocals": true,
    /* Report errors on unused parameters. */
    "noUnusedParameters": true,
    /* Report error when not all code paths in function return a value. */
    "noImplicitReturns": true,
    /* Report errors for fallthrough cases in switch statement. */
    "noFallthroughCasesInSwitch": true,
    /* Include 'undefined' in index signature results */
    "noUncheckedIndexedAccess": true,
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment