🥑

Namespace Import に補完を効かせる TypeScript プラグインを作った

2021/09/10に公開

Namespace Import に補完を効かせる TypeScript Language Service Plugin[1]typescript-plugin-namespace-import を作りました。

そのモチベーションと使い方を紹介します。

TL;DR

  • 普通のオブジェクトを名前空間として用いると Tree Shaking が効かずバンドルサイズが膨らむ
  • Namespace Import を用いれば Tree Shaking が効く
  • しかし Namespace Import は Default Export と同様に補完が効かない
  • TypeScript Language Service Plugin でファイル名を用いた補完を効かせていいとこ取り!

モチベーション

大規模なコードベースでは、全ての関数を無邪気に Named Export していると補完に全て出てきて開発体験が悪くなります。また、ドメイン駆動設計のエッセンスを取り入れていると、関数をドメイン領域でまとめたいこともあると思います。

そんなとき、一番シンプルなのは通常のオブジェクトをネームスペースとして用いることです。

someLogics.ts
export const someLogics = {
  multiply() { ... },
  divide() { ... },
}
main.ts
import { someLogics } from "./someLogics.ts"
someLogics.multiply()

someLogics は単なる Named Export なので補完が効き、開発体験の面では非常に良いです。しかし、オブジェクトを丸ごとインポートするため、Tree Shaking が効かずバンドルサイズが膨らんでしまいます。上記コードの場合は、someLogics.divide が実際には使われていないのにも関わらずバンドルに含まれてしまいます。

この問題は Namespace Import (Star Import とも呼ぶ) で解決します。

someLogics.ts
export function multiply() { ... }
export function divide() { ... }
main.ts
import * as someLogics from "./someLogics.ts"
someLogics.multiply()

これで someLogics.divide はバンドルに含まれなくなります。(この挙動を示す Rollup Playground を用意したので興味がある方は見てみてください)

しかし、Named Export しているのは multiplydivide になったため、someLogics には補完が効かなくなり、開発体験は落ちてしまいました。

そこで、someLogics.ts のようなファイル名で補完を効かせて自動で Namespace Import を挿入できるようにすることで、開発体験の回復を狙うのが typescript-plugin-namespace-import です。

VSCode Plugin などでも実装できますが、TypeScript Language Service Plugin として実装することによって TypeScript に対応する他のエディタでも動かすことができます。

インストール

まずパッケージをインストールします。

npm install -D typesript-plugin-namespace-import
# yarn add -D typescript-plugin-namespace-import

そして tsconfig.json でプラグインを追加します。

tsconfig.json
{
  "compilerOptions": {
    ...
    "plugins": [
      {
        "name": "typescript-plugin-namespace-import",
        "options": {
          "paths": ["src/logics"]
        }
      }
    ]
  }
}

全てのファイルが Namespace Import の候補になっては困りますから、paths オプションを用意しています。ここで補完候補にするディレクトリのパスを tsconfig.json があるディレクトリからの相対パスで指定してください。

動作

以下のような src/logics/someLogics.ts があるとします。

src/logics/someLogics.ts
export function multiply(a: number, b: number) { ... }
export function divide(a: number, b: number) { ... }

このとき、プロジェクト内の他のファイルから someLogics という名前で補完候補を表示することができます。

この候補を選択すると、Namespace Import が挿入されて関数を呼び出すことができます。

また、これだけでは「大量の Named Export で補完候補がたくさん出てくる」という問題は解決しません。そこで、ignoreNamedExport オプションを用意しています。これを有効にすることで、Namespace Import の補完候補になるファイルからの Named Export を補完から除外することができます。

まとめ

typescript-plugin-namespace-import を紹介しました。僕が所属している Ubie ではすでにプロダクションのコードベースで利用できているので、ぜひ使ってみてください。

また、ここでは新たなツールを導入せざるを得ませんでしたが、現在 Stage 1 の Module Fragments[2] が仕様に入れば、 ES2020 で追加された export-ns-from と組み合わせてシンプルに書くこともできそうです。楽しみですね。

module logics {
  export function multiply(a: number, b: number) { ... }
  export function divide(a: number, b: number) { ... }
}

export * as someLogics from logics;
脚注
  1. TypeScript Language Service Plugin については、少し古いですがこの記事がわかりやすいです。 ↩︎

  2. Module Fragments についてはこの記事 がくわしいです。 ↩︎

Discussion