sosukesuzuki.dev

2022年 sosukesuzuki 1人アドベントカレンダー

December, 08 2022

Prettier のサイズが大きすぎるからパッケージを分割してダイエットする計画

2022年の sosukesuzuki 1人アドベントカレンダー 8 日目です。

最近の Prettier 開発の計画です。まだ何もやってないけど。

Prettier のサイズは大きい

Prettier は npm パッケージのサイズが大きいことで有名です。

Prettier 2.8.1 のサイズ(11.6MB)

Prettier のパッケージサイズが大きい理由はビルド方法にあります。

Prettier では、依存しているライブラリをすべてリリース時にビルド(バンドル)して Prettier の npm パッケージに含めています。

このあたりは Prettier のビルドの珍しいところで、リポジトリのルートに存在する package.json を見ると大量のパッケージが dependenciesdevDependencies に含まれているのですが、実際に npm のレジストリに publish されるときの package.jsondependenciesdevDependencies には何も含まれていないのです(ビルドスクリプトの中で package.json を更新している)。

リポジトリのルートに存在する package.json の dependencies の一部(ビルド前)
ビルドして /dist に吐き出された package.json(dependencies と devDependencies が存在しない)

このアプローチのおかげで Prettier の動作はユーザーの node_modules の状態による影響をほとんど受けませんが、代わりにパッケージサイズが大きくなっています。

そして、Prettier の npm パッケージに含まれている外部ライブラリの中でもサイズがダントツで大きいのはパーサーです。Prettier がサポートしている言語の構文はまあまあ複雑でサイズが大きくなりがちです。それらがすべて Prettier の npm パッケージに含まれているので、当然 Prettier のサイズが膨らみます。

そこで、パーサーの提供の仕方を改善すれば Prettier の npm パッケージサイズが大きすぎるという問題を少しは改善できるのではないかと考えました。

JavaScript パーサーを Babel だけにする

ご存知の人もいるかと思いますが、実は現在の Prettier はいくつかの JavaScript パーサーを含んでいます。https://prettier.io/docs/en/options.html#parser--parser オプションのすべての値が列挙されていますが、その中でも JavaScript に関連するものは次のとおりです。

  • babel (Babel の JavaScript パーサー)
  • babel-flow (Babel の Flow パーサー)
  • babel-ts (Babel の TypeScript パーサー)
  • flow (Flow 本家のパーサー)
  • typescript (typescript-eslint のパーサー)
  • espree (ESLint のパーサー)
  • meriyah (速い JavaScript パーサー)
  • acorn (espree のもとになっているパーサー)

これらのパーサーをすべて使っているユーザーはどのくらいいるでしょうか。多分 1 人もいないんじゃないでしょうか。

そこで、多くのユーザーに本当に必要とされているパーサーのみを Prettier コアに残して、それ以外のパーサーは別のパッケージとして切り出そうと考えています。

具体的には babelbabel-flowbabel-ts のみを Prettier 本体に残そうと考えています。これらは全部 @babel/parser を使っているので、その分最終的にビルドされるファイルのサイズを減らすことができます。

flowtypescript は特にサイズが大きく、それぞれ 1MB を超えています。それらが 2 つずつ(CJS 版、ESM 版)含まれているので、それだけで 5MB 近くの削減になります。

他のパーサーは別のパッケージとして、たとえば @prettier/plugin-typescript@prettier/plugin-espree のような名前のパッケージとして提供しようと考えています。

このアプローチをとると、後から新しい JavaScript パーサーを追加するのも簡単になります(HermesSWC を追加することを検討しています)。

パッケージを切り分けてモノレポとして管理する

さて、このように Babel 系以外のパーサーを別のパッケージとして提供したいわけですが、そのまま別のリポジトリに切り出してしまうといくつか困ることがあります。

  • すべての JavaScript パーサーには一部共通する処理(生成した AST の後処理)があるので、それをパーサー間でいい感じに共有するのが面倒くさい
  • すべての JavaScript パーサーで共通のテストケースを使いたいので、それをパーサー間でいい感じに共有するのが面倒くさい
  • Prettier 本体とバージョンを同期させたいが、それが面倒くさい

ということで、切り出したパッケージを https://github.com/prettier/prettier の一つのリポジトリに含めて、モノレポとして管理することを考えています。

リファクタリングとしての側面

Prettier をいくつかのパッケージに分割し一つのリポジトリ内でモノレポとして管理するのは、パッケージサイズの削減だけではなくリファクタリングとしての側面もあります。

まず CLI 部分とコア部分を別のパッケージにしたいと考えています(@prettier/cli@prettier/core)。現在でもできるだけ CLI とコアが密結合にならないように気をつけていますが、気をつけるだけでは「実は密結合になっていたぜ」ということもよくあるので、パッケージレベルで分けることでちゃんと分離したいわけです。

また、Prettier のプリントアルゴリズムにおける中間表現である document に関連する部分も別のパッケージに分けたいと考えています(@prettier/doc)。そうすれば Prettier を汎用 Pretty Print ライブラリとして使えるようになるはずです。

ただ、そのままパッケージを分離すると、ユーザーはただ Prettier を使いたいだけなのに @prettier/cli@prettier/core@prettier/plugin-typescript をインストールしなければならない、という状態になってしまいます。それではユーザーフレンドリーでないので、Jest のように内部的に分かれているもののユーザーはそれを気にせずに使えるような形を目指しています。

Prettier をただコードフォーマッターとして使いたい人はただ prettier のみをインストールすれば良いし、Prettier をライブラリとして使いたい人は必要最小限のパッケージのみをインストールすれば良い、という状態が理想です。

いけるか?

このブログに書いたことはすべて計画で、これに関するコードは一行も書かれていないので今後どうなるかはわかりません。頑張りたいと思います。

https://github.com/prettier/prettier/issues/13912 で話しています。