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という環境変数で有効化できる。
実体は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側のエントリーポイント
エントリーはここの__promise = runBin()という関数。
このファイルでやってることは、
- CLI引数のパース
- CLI自体は
specialist - プラグインをロードしてオプションやパーサーの登録
- プラグインはJSで書かれてる前提なので、実装を
import()してる - https://github.com/prettier/prettier-cli/blob/4b1a66a1b8d7091ba53a37b4762b7863eaf09585/src/utils.ts#L113-L117
- プラグインはJSで書かれてる前提なので、実装を
- パースできたら
import("./index.js")してrun()を実行
あらゆる場面の遅延ロードにパフォーマンスへのこだわりを感じる・・・!
index/run(), index/runGlobs()
標準入力からフォーマット対象を決定するかどうかの分岐のみ。runGlobs()かrunStdin()かどっちか。
runGlobs()を見ていく。
getTargetPaths()- ファイルの一覧を取得
- ファイルパスだけでなく、ディレクトリまで集めてるのがポイント
- このあたりは冒頭の解説ブログにも書かれてたやつ
.editorconfigと、.gitignoreと.prettierignoreのフィルタリングKnownで存在するもののみ、ファイルアクセスする
- このあたりがチューニングしてるポイントで、たくさんの自作ライブラリも駆使されてる
- 設定ファイルをまとめてハッシュしてキャッシュの準備
makePrettier()- https://github.com/prettier/prettier-cli/blob/4b1a66a1b8d7091ba53a37b4762b7863eaf09585/src/prettier.ts#L5
- オプションに応じて
prettierのフォーマット処理の準備 prettier_parallel.jsなら、worktankによってWorker threadsで実行される(デフォルト)prettier_lazy.jsなら、非同期ロードされるだけ
prettier_serial.jsに実際のフォーマット処理へのマッピングがある- https://github.com/prettier/prettier-cli/blob/4b1a66a1b8d7091ba53a37b4762b7863eaf09585/src/prettier_serial.ts#L30
format()で、お馴染みのprettier.formatWithCursor()を呼んでる- これを利用した
check()やwrite()といった亜種があるだけ
fileTsPathsTargetsというファイルパス(拡張子)ごとに、オプションやプラグインを用意して、フォーマット処理を呼んでいく- ここは
Promise.allSettled()でまとめて呼んでた - https://github.com/prettier/prettier-cli/blob/4b1a66a1b8d7091ba53a37b4762b7863eaf09585/src/index.ts#L123
- ここは
- あとは処理結果をまとめてコンソールに出して終わり
以上。
ファイルをかき集めてからフォーマット処理に回すという単純な内容だが、その細部にこだわりを感じる・・・。
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