TypeScriptでオプショナルオブジェクトを埋めるときsatisfiesを使うとグッド

satisfies の使いどころをメモする。たまにしか使わないから忘れがちだけど思い出すと便利なときがたまにある。

satisfiesとは

他者のスライドだがこれがわかりやすいと思う。

使いどころ

例えばライブラリを作っていて、configの定義がありデフォルトのオプションもあって、ユーザーはオプションを上書きできるというシーンを想定する。

そうすると、configの型は厳密で、ユーザー側の型はすべてオプションオブジェクトになる。

type Config = {
  foo: number;
  bar: string;
  baz: string[];
};

type OptionalConfig = Partial<Config>;

2つ例を書く。どちらもエディタの補完は効くが、変数の結果の型が違う。前者は Partial<Config> になるが、後者はConfig型をベースに非オプショナルな中身のみになる。

const myWidenConfig: OptionalConfig = {
  bar: "baaar",
  baz: ["b", "a", "z"],
};

これはTSコンパイラではこのように解釈される。当たり前にPartialのままで、実体の変数ではfooプロパティは定義していないし型でも未定義のままになる。

satisfies を使うとこう書くことができる。

const mySatisConfig = {
  bar: "baaar",
  baz: ["b", "a", "z"],
} satisfies OptionalConfig;

これはTSコンパイラではこのように解釈される。Config型をベースに定義したものだけが入っていてundefinedではなくなっており、fooに関する情報はない。

なので、例えばオプショナルではないConfigのプロパティを期待するところにこうやって気持ちよく挿せる。

どちらもランタイムでは非undefinedだが、pleaseBar1のほうは型エラーになる。一方pleaseBar2のほうはsatisfiesを介しているので補完が効いた上で非undefinedを表現できている。

手元で理解するには

プレイグラウンドを見るのがよいと思う

pnpmで意図しないpeer dependenciesがあり問題が起こるときの対処

※この情報はpnpm@9時点での情報です

tl;dr

  • pnpm i --resolution-only
  • pnpm i --fix-lockfile

peer dependenciesで問題が起こったら、どちらかを実行する。


pnpmを使っているプロジェクトで、node_modulesの中に不必要なモジュール(optional peer dependencies)がずっと入っていたりする問題がある。

このことで普通はそんなに問題は起こらないが、稀に問題を引き起こすことがある。

昔入ってしまったpeer dependenciesがnode_modules内に存在することで

こういったことが起こる。

  • import.meta.resolve でモジュールがあると誤検知してしまう
    • 入れた覚えも入っている前提でもないのに、たしかにいる!
  • TSが型解決するときにpeerのバージョンを先に見つけて古い型を使うことでエラーになる

少なくとも自分が観測した事例だと以下のようなことがあった。

  • storybookをセットアップすると less sass sugarcss などが入っている
  • viteのプラグインを入れると rollup@2 rollup@3 など過去のバージョンが入っている
    • プロジェクト環境では rollup@4 を期待するが、TSがrollup@3を見に行きプラグインが型エラーになる

ちなみに

前提としてこの問題はpnpmの hoistauto-install-peers をenableにしていると発生するはず。

両者とも.npmrcで設定しない限りデフォルトではtrue。

peer dependenciesを整理整頓する

pnpmは通常の pnpm install では一度ロックファイルに書き込まれたpeer dependenciesのバージョンは更新しない(これはpnpmではfull resolutionという概念らしい)。そしてpnpm-lock.yamlに書かれている以上は、そのバージョンのそのモジュールがインストールされてしまう。

トレードオフとして、pnpm-lock.yamlに書き込まれた「昔入れたモジュールに付随していたpeer dependenciesが更新されていない」ということが発生する。

というわけで、full resolutionする

以下のどちらかをすればよい。そしてロックファイルが更新されたあとにもう一度 pnpm iを実行して node_modules を更新する。

  • pnpm i --resolution-only
  • pnpm i --fix-lockfile

これでlockファイルがメンテされ、このエントリで言っている問題を回避できるようになる(可能性がある)。

※なおpnpmのソースコードはこのあたり