🧊

PrettierのExperimental CLIについて

Prettier 3.6: Experimental fast CLI and new OXC and Hermes plugins! · Prettier https://prettier.io/blog/2025/06/23/3.6.0#cli

これ。

今まではprettier@nextで使えてたけど、この度バージョン3.6からlatestでも利用可能になった。

Experimentalとはいえ

これ自体は息の長い取り組みで、その詳細は2023年の時点で公開されてる。

Prettier’s CLI: A Performance Deep Dive · Prettier https://prettier.io/blog/2023/11/30/cli-deep-dive

なかなか劇的な改善なのではないだろうか。

この2年でどう変わったかも知りたかったけど、その詳細は見つけられなかった。まあ悪くはならんか。

というわけで、よき頃合いならばコードを読もう!というわけで。

prettier側のエントリーポイント

まずは本体側で、現状は--experimental-cliというオプションか、PRETTIER_EXPERIMENTAL_CLIという環境変数で有効化できる。

https://github.com/prettier/prettier/blob/b4cf9395ac338c6a73cf1efee8b19bbbc42bca0d/bin/prettier.cjs#L19-L28

実体はdynamicImport("../src/experimental-cli/index.js")と、丸投げしてるだけ。

var dynamicImport = new Function("module", "return import(module)");

dynamicImport()をわざわざ定義してるのは、.cjsではimport()ができないからか。

src/experimental-cli/index.jsは、単にexport * from "@prettier/cli/bin";というわけで、本体リポジトリはここまで。

prettier-cli側のエントリーポイント

https://github.com/prettier/prettier-cli/blob/4b1a66a1b8d7091ba53a37b4762b7863eaf09585/src/bin.ts#L325

エントリーはここの__promise = runBin()という関数。

このファイルでやってることは、

あらゆる場面の遅延ロードにパフォーマンスへのこだわりを感じる・・・!

index/run(), index/runGlobs()

https://github.com/prettier/prettier-cli/blob/4b1a66a1b8d7091ba53a37b4762b7863eaf09585/src/index.ts#L29

標準入力からフォーマット対象を決定するかどうかの分岐のみ。runGlobs()runStdin()かどっちか。

runGlobs()を見ていく。

https://github.com/prettier/prettier-cli/blob/4b1a66a1b8d7091ba53a37b4762b7863eaf09585/src/index.ts#L61

  • getTargetPaths()
    • ファイルの一覧を取得
    • ファイルパスだけでなく、ディレクトリまで集めてるのがポイント
    • このあたりは冒頭の解説ブログにも書かれてたやつ
  • .editorconfigと、.gitignore.prettierignoreのフィルタリング
    • Knownで存在するもののみ、ファイルアクセスする
  • このあたりがチューニングしてるポイントで、たくさんの自作ライブラリも駆使されてる
  • 設定ファイルをまとめてハッシュしてキャッシュの準備
  • makePrettier()
  • prettier_serial.jsに実際のフォーマット処理へのマッピングがある
  • fileTsPathsTargetsというファイルパス(拡張子)ごとに、オプションやプラグインを用意して、フォーマット処理を呼んでいく
  • あとは処理結果をまとめてコンソールに出して終わり

以上。

ファイルをかき集めてからフォーマット処理に回すという単純な内容だが、その細部にこだわりを感じる・・・。

1つ気になったのは、UndefinedParserErrorというフォーマットエラーの扱い。

これはつまり、たとえば.astroファイルみたいなプラグイン前提の拡張子であっても、

  • とりあえずはパスとして回収して
  • フォーマッタに投げて
  • プラグインが有効になってればパースできるが、それ以外はエラー

という処理の流れになってるけど、そもそもプラグインをパースした時点で拡張子の選別はできる気もしていて、なんでそうなってないのだろうか。

まあ最初に判断して捨てるほうが、不要なパスを抱えない分だけメモリ消費は少なくなるけど、プラグインの詳細を確認する手間を後回しにしたかった的な感じ?

感想

めちゃめちゃシンプルで読みやすかった。

そもそもファイルもこれだけしかないし。

src/
├── bin.ts
├── cache.ts
├── config_editorconfig.ts
├── config_ignore.ts
├── config_prettier.ts
├── constants.ts
├── index.ts
├── known.ts
├── logger.ts
├── logger_transports.ts
├── prettier.ts
├── prettier_cached.ts
├── prettier_lazy.ts
├── prettier_parallel.ts
├── prettier_serial.ts
├── types.ts
└── utils.ts

行数でいうと、.editorconfig.ignore系の設定や、フォーマットオプションの関係解決がメインと言ってもいいくらいだった。

というか、やはりこの設定ファイルまわりは永遠の鬼門らしく、現状たってるIssueもほとんどがそれ関係に見える・・・。

https://github.com/prettier/prettier-cli/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen

.prettierrcのフォーマットだけでも、JSONにYAMLにTOMLにJSにTSに・・・大変やもんな・・・。その上overrideとかまでできるし。

ちなみに、このCLIのリライトを担当してた@fabiospampinato氏は、たくさんのモジュールをnpmで公開してる人で、ここでもそれが多分に取り入れられてる。

というか、dependenciesのほとんどが、氏の自作モジュールだったりする。

npmgraph - Dependencies for @prettier/cli https://npmgraph.js.org/?q=%40prettier%2Fcli#zoom=h