🧙‍♂️

ディレクトリ単位でTypeScriptの自動補完を制御する

2024/07/10に公開

前提条件

  • TypeScriptを使用している
  • package.jsonは1つのみ
    • 今回対象とするディレクトリのルートにpackage.jsonがあり、それより子のディレクトリにはpackage.jsonがない
    • モノレポではなくモノリスのイメージ。モノレポであれば恐らくworkspaceを切る方が何かと素直になりそう

試した環境

やりたいこと

  • ディレクトリ毎に依存方向を決めている場合等に、依存してはいけないディレクトリの関数や変数が自動補完の対象となるのを防ぎたい

今回検証するリポジトリの構成

ルートディレクトリにpackage.jsontsconfig.jsonがある(ここは通常通り)
srcの直下にはconstants, utils, featuresというディレクトリがある

featuresutilsconstantsという依存方向になっている

  • featuresutilsconstantsをインポートできる
  • utilsconstantsをインポートできる
  • constantssrc配下の他のディレクトリはインポートできない
src/
    constants/
    utils/
    features/
package.json
tsconfig.json

ディレクトリ毎に自動補完を制御したい場合は、tsconfigのexcludeを利用して制御する

1. ルートディレクトリのtsconfigは通常通り記述する

ルートディレクトリに置くtsconfig.jsonは通条通り各種設定を記載する

// includeとexclude以外は今回の主旨と関係ない

{
  "compilerOptions": {
    "composite": true,
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "moduleDetection": "force",
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

2. 自動補完を制御したいディレクトリにtsconfigを追加し、excludeのみ上書きする

  • featuresは全てのディレクトリからインポートが可能なので、自動補完の制御は不要
  • constantsutilsディレクトリは自動補完を制御したい

この場合はconstantsutilsの直下にtsconfigを追加する

// src/constants
{
  "extends": "../../tsconfig.json",
  "exclude": ["node_modules", "../utils", "../features"]
}

// src/utils
{
  "extends": "../../tsconfig.json",
  "exclude": ["node_modules", "../features"]
}

基本的にはルートディレクトリのtsconfig.jsonをそのまま継承し、excludeに依存したくないディレクトリを追加する
継承先で記述した項目は、継承元とマージされるのではなく上書きされるので、ルートでexclude指定していたnode_modulesはサブディレクトリに追加するtsconfig.jsonにも記載する

https://www.typescriptlang.org/ja/tsconfig/#extends

constantsとutilsディレクトリで何が起きているか

  • 現在のディレクトリからルートに向かってtsconfigを探しに行くので、constantsutilsではそれぞれのディレクトリに置いたtsconfig.jsonの設定が適用される
  • excludeはinclude設定の結果を変更する
    • ルートディレクトリでは全てのts,tsxを対象としているが、constantsではutilsfeaturesを除外、 utilsではfeaturesを除外していることになる

3. tsconfigをサブディレクトリに配置した後の挙動を確認

1. featuresではconstantsとutilsのモジュールが自動補完に出てくるのを確認

constantsの変数が自動補完に出てくる
constants
featuresの変数が自動補完に出てくる
features

2. utilsではconstantsのモジュールは自動補完に出てくるが、featuresのモジュールは自動補完に出てこない

constantsの変数が自動補完に出てくる
constants

featuresの変数が自動補完に出てこない
features

3. constantsではutilsとfeaturesのモジュールが自動補完に出てこない

utilsの変数が自動補完に出てこない
utils

featuresの変数が自動補完に出てこない
features

constantsの変数は自動補完に出てくる(一応確認)
constants

バッチリ期待取り自動補完が効いている

(番外編1)ディレクトリ関係なく自動補完を制御したい場合は、settings.jsonのautoImportFileExcludePatternsを使用する

vscodeに限った話だが、settings.jsonに追加するだけで自動補完の対象外にすることが出来る。補完によく出てくるが全く使わないモジュール、ライブラリを直接使わずにラップした関数を使ってもらいたい場合などに有効

{
  "typescript.preferences.autoImportFileExcludePatterns": [
    "**/node_modules/@testing-library/react"
  ]
}

こちらはTypeScript4.8で登場した機能
https://devblogs.microsoft.com/typescript/announcing-typescript-4-8-rc/

(番外編2)明確な禁止はeslint-plugin-importのno-restricted-pathsで制御する

tsconfigで変数を書いている際の自動補完の制御はできて便利だが、import文を書いている時は補完が効いてしまうし、vscode上でエラーも表示されない
import文の補完

今回は割愛するが、eslint-plugin-importrestricted-pathsを使えば、imoprt側でリントエラーを出すことが出来る

兄弟ディレクトリで依存関係をもたせたくない場合などより細かいルールが設定できる

参考
https://zenn.dev/sqer/articles/35d56d9850efb2

終わりに

eslint-plugin-imoprt-accessdefaultImportability: "package"で設定し、ディレクトリ外からのアクセスを原則不可にすれば、変数名を狭いスコープで考えることが出来て良いのではないかと思って導入したところです。
そうすると今度は同じ変数名が自動補完にたくさん出てくる可能性が上がるのではないかと思い、何か良い方法はないかと考えたところ、この方法に辿り着きました。

ワークスペースを切ってしっかりとした依存関係を作るのが正攻法だと思いますし、この方法を取り入れるデメリットもあるかもしれませんが、ビルドやデプロイのことは特に考える必要なく簡単に導入でき、簡単に剥がすことが出来るのも魅力だと思っています。

検証で使用したコード

https://github.com/tatsuya-asami/ts-restrict-paths

コミューン株式会社

Discussion