📚

TSKaigi 2024 Prettierの未来を考える スピーカーノート

2024/05/11に公開

TSKaigi 2024 で話した「Prettier の未来を考える」という発表のスピーカーノートです。スライドは


こんにちは、今日は「Prettierの未来を考える」というタイトルでお話させていただきたいと思います。

鈴木 颯介と言います。ユビー株式会社でプロダクト開発エンジニアとして働きながら、筑波大学でパソコンの勉強をしています。オープンソースソフトウェアが好きで、今日お話するPrettierのメンテナーをしたり、トランスパイラのBabelのコミッターをしたりしています。最近はWebKitのJSエンジンにたくさんパッチを投げたりしています。

私が働いているユビーは、TSKaigiのGold Sponsorをさせてもらっています。ブースがあります。ユビーのグッズの他に、Prettierのステッカーも配布しておりますので、興味がある方はぜひお立ち寄りください。

まず、Prettierについてあまり知らないという方もいらっしゃると思いますので、Prettierについて簡単に紹介させてください。Prettierは、JavaScriptやTypeScript、HTMLやCSSなどの言語をサポートしたコードフォーマッタです。最大の特徴は、設定項目が少ないことと、書き手によってフォーマット結果がブレることが少ない、いわゆる Opinionated なコードフォーマッターであることです。

そんなん知ってるよ、という人もいるかもしれないですけど、ちょっとだけ具体的な例を紹介します。左のコードがPrettierを適用する前、右のコードがPrettierを適用したあとです。こういう変換を自動でやってくれるということです。

コーディングスタイルに関するスタンスは、個人の経験や価値観に大きく左右されがちです。コードレビューの際に「私はこのスタイルが好きだ」とか「いいや、そのスタイルでは経験上こういう問題がある」とか、そういうプロダクトを開発するにあたってあまり本質的ではない議論をして、余計に時間を使ってしまったことが、誰しも一度はあるでしょう。Prettierはそういうコーディングスタイルに関する議論や争いを失くすことを目的としています。

GoやRustなどの一部のプログラミング言語では、コンパイラを提供している組織がPrettierと同じようなOpinionatedなコードフォーマッタを提供しています。

Prettierは、James Longによって2017年に開発され、オープンソースソフトウェアとして公開されました。

2017年と言えばすでにReactが普及していましたが、James Longは当時Reactを書くときにJSXを使っていなかったそうです。彼が使っていたEmacs上で動作するJSX用の良いコードフォーマッタが存在せず、自分でインデントやスペースを整形する必要があったからだそうです。当時の彼は、Facebookが開発したRescriptというML系言語も使っていました。Rescriptにはrefmtというコードフォーマッタが付属しています。refmtは、今のPrettierと似た体験を提供するコードフォーマッタで、彼はこれを気に入ったそうです。そして、このrefmtにインスピレーションを受けて、Prettierを開発しました。

その後、FacebookのエンジニアであるVjeuxがFacebookでの仕事として、フルタイムでPrettierの開発に取り組み、品質を向上させました。そして、現在ではJavaScriptのエコシステムの中でのデファクトスタンダードのような立場を確立していると言えるでしょう。

James LongとVjeuxの二人が開発から離れたあとも、ボランティアのPrettierチームによって活発なメンテナンスが行われてきました。

ちなみに、私がPrettierの開発に参加したのは2019年ですが、その時点で今と同じ言語をサポートしており、今の使用感とさほど違いはありませんでした。私はPrettierを最も長くメンテナンスしているメンテナー二人のうちの一人ではありますが、今のPrettierを作り上げたメンバーではない、ということですね。

2017年にPrettierが誕生してから7年ほどが経過しました。長期にわたってたくさんのユーザーに使ってもらったことによって、いくつかの問題が顕在化してきました。その中でも特に重要なのは、使い勝手が悪いところと、実行速度が遅いところです。

まずは、使い勝手の悪さについてです。実際のところ、Prettierを単体で使う場合には、使い勝手はそれほど悪くないはずです。というか、特に設定することもないので、使いやすいと思っています。

しかし現実的には、PrettierはESLintなどの別のツールと組み合わせて使われます。Prettierはコードフォーマッタであり、リンターではありません。コーディングスタイルについては勝手に修正してくれますが、それ以上の問題については全く関与しません。たとえば、一度も再代入していない変数に対して const ではなく let を使っているなどの問題を指摘するのは、Prettierの責務ではありません。従って、そのような問題を自動的に検知するためには、別のツールを使う必要があります。今のところ、ESLintがもっともよく使われるツールだと思います。

PrettierとESLintを組み合わせて使うときの設定がめんどくさく使い勝手が悪い、という声をよく聞きます。ESLintはリンターですが、Prettierのようなコードフォーマッタとして機能するようなルールも持っています。最近になってそれらのスタイル系のルールはESLintのコアからは取り除かれたのですが、以前はESLintの推奨ルールセットを使うとそれらのスタイル系のルールが有効になってしまい、それがPrettierによるフォーマットと衝突する、という問題がよく起こっていました。

この問題を回避するためには、ESLintやPrettierのユーザーがeslint-config-prettierやeslint-plugin-prettierなどをインストールして設定を記述する必要があります。この設定は、慣れてしまえば難しいものではないのですが、ただESLintとPrettierを組み合わせて使いたいだけの新しいユーザーからしたら使い勝手が悪いものでした。

さらに、これはPrettierの問題ではありませんが、ESLintはTypeScriptをサポートしていません。なのでESLintとPrettierとTypeScriptを使いたいときには、最低限 eslint prettier @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-prettier typescript などのパッケージをインストールして使う必要があります。これはまあ普通にめんどくさいですよね。

次に実行速度が遅いことです。これは、どのようにPrettierを使うかによって、具体的な問題や原因が異なります。

まず、VSCodeなどのテキストエディタの拡張で、保存時にフォーマットを実行するというユースケースを考えてみましょう。この場合、ほとんどの場合パフォーマンスは問題にならないでしょう。しかし、あまりにファイルサイズが大きい場合、体感してわかるくらいPrettierの実行に時間がかかります。これはかなりストレスフルです。

次に、CIやローカルのCLIからプロジェクト全体に対してPrettierを実行するというユースケースを考えましょう。これはかなり遅いです。必要以上に遅いです。ユビーのコードベースに対してPrettierを実行したときにもかなりストレスを感じました。

このように、現在のPrettierにはいくつかの問題があります。ここ数年で、これらの問題を解決するツールが開発されて、有名になってきています。

まずは、このあたりの問題が解決されている例としてDenoを考えてみましょう。DenoはNode.jsの作者であるライアン・ダールが、自身のNode.jsでの経験と失敗を基にして開発したJavaScriptランタイムです。Node.jsとは違ってデフォルトでTypeScriptを実行できることや、ファイルアクセスやネットワークアクセスのために明示的に権限を与える必要があること、ECMAScript Modulesをベースにしたモジュールシステムであるなど、Node.jsの微妙なところが改善されたランタイムになっています。また、deno lintやdeno fmtというコマンドを叩くだけで設定なしでもリンターやフォーマッタを実行できるという特徴があります。

deno lintコマンドではdeno_lintというRustで記述された独自のlinterが実行され、deno fmtではdprintというDenoチームのメンバーが開発したこれまたRustで記述されたフォーマッタが実行されます。つまり、Denoを使っている人たちは先ほど説明したPrettierの問題に全く遭遇しないということになります。

個人的には、これは本当に素晴らしい体験だと思います。私は最近簡単なCLIツールを作るときはDenoを使うことがありますが、全く設定なしでTypeScriptとlinterとformatterが使えるようになっているというのは、ものすごく楽だと感じます。私はlinterとかformatterに比較的詳しい方だと思いますが、それでもそう思うのだから、あまり詳しくない人からしたらなおさらでしょう。もちろんDenoはlinterやformatterの問題を解決するためだけに作られたものではありませんが、それらの問題の解決の仕方としてはかなりスマートです。

問題はNode.jsを使い続ける場合で、その場合の良いツールとしてはBiomeが今アツいです。Biomeは誕生の経緯が少し複雑です。BiomeはRomeという別のツールのコミュニティフォークとして誕生しました。Romeは、BabelやYarnの作者として知られているセバスチャン・マッケンジーによって開発されました。

Romeの目的はフロントエンドツールチェインの大統一でした。現代的なフロントエンド開発を行うためには、いくつものツールを使いこなす必要があります。まずBabelやSWCなどのトランスパイラ、webpackやRollupのようなモジュールバンドラ、Terserのようなミニファイア、ESLintのようなlinter、Prettierのようなフォーマッタ、Jestのようなテストランナー、などなど。最近ではNext.jsなどの大規模なメタフレームワークによってこのあたりの複雑さの一部は吸収されて、直接意識することは減ったかもしれませんが、今でも内部的にはこれらのツールが別々に動いていることがほとんどです。

このような状況は、いくつものツールのセッティングを行うプログラマにとっても負担ですし、計算資源の観点でも無駄が多くなります。それぞれのツールが、パスのどこかでは同じようなことをするものの、その結果を共有しないためです。たとえば、モジュールバンドラーもトランスパイラもlinterもフォーマッターもJavaScriptのコードをパースしますけど、パースした成果物であるASTは共有されず、同じコードを別のツールが何度もパースをすることになります。

このような問題を解決するために、Romeは生まれました。Romeの作者であるセバスチャンはそのための会社を立ち上げ開発を進めていきましたが、志半ばで会社は解散されることとなりました。詳細に関しては事実かどうかわからないので述べられませんが、まあスタートアップなので色々ありますよね、というやつです。

会社としてのRomeは解散しましたが、その時点でlinterとfomatterとしては、ある程度動くものが完成していました。そこで、Romeのメンバーだったematipicoが、Romeをフォークし、Biomeプロジェクトとして立ち上げました。
Biomeが、フロントエンドツールチェインの大統一という大きな夢を諦めたのかどうかはわかりませんが、少なくとも現在はlinterとフォーマッタのみの開発に注力しているようです。

Biomeの最大の特徴は、linterとformatterが両方搭載されていること、そして極めて高速に実行できる点です。これだけで、Prettierの大きな問題を2つとも解決しています。Biomeを使い始めるには、以下のコマンドを叩くだけです!

npm install --save-dev --save-exact @biomejs/biome
npx @biomejs/biome init

結構いろいろなところで「Biomeについてどう思う?」という質問をいただくのでこの機会に明言しておくと、僕はBiomeのことが好きです。普通に素晴らしいプロジェクトだと思って応援しています。

僕がBiomeのことを好きなのはそれはそれとして、Prettierのメンテナーとしては、これらのツールが存在する上で、未来のPrettierがどのようにあるべきかを考えなくてはいけません。長くなりましたが、ここからが本題です。

同じような用途に使われるソフトウェアを比較するときに、各機能を行とした表がよく使われます。こういう表は、自分のソフトウェアを宣伝するために使うときは恣意的になってしまって微妙なんですが、ソフトウェアの性質を本当に比較したいときは便利な道具です。ここではPrettierとBiomeを比較する表を作ってみたいと思います。

この各行を見ていきましょう。

まずはアルゴリズム。これはBiomeとPrettierで全く同じです。使っている中間表現も同じで、高い互換性があります。JavaScriptに関しては97%の互換性があります。以前からかなり似た挙動をしていたのですが、Prettier Challengeによってさらに互換性が高められました。Prettier Challengeというのは、Prettierが企画したキャンペーンで、Prettierと互換性があるRust製のJavaScriptのフォーマッターを開発した組織に$20,000を寄付する、というものです。BiomeはこのキャンペーンのためにPrettierとの互換性を高め、結果として賞金を手に入れました。

次は単一ファイルをフォーマットするときのパフォーマンスです。Biomeは高速で、Prettierは困るほど遅くはありませんが、Biomeと比べて速くはありません。PrettierはBiomeに追いつきたいと思っていますが、正直なところこれは難しいでしょう。単一ファイルに対するPrettierの実行は十分に最適化されているとは言い難いですが、劇的に速くするのは難しいでしょう。Prettierがファイルをフォーマットするときにかかる時間の大体半分くらいはパーサーであり、パーサーは外部のライブラリを使っています。プリントの処理を十分に速くできたと仮定しても、倍速くするのは難しく、その程度では到底Biomeには近づけません。

次は複数ファイルをフォーマットするときのパフォーマンスです。Biomeは高速です。Prettierは、一般的なWeb系企業のコードベースでストレスを感じる程度には遅いです。PrettierがBiomeに追いつくのは難しいかもしれませんが、Prettierを劇的に改善することは可能であると考えています。Prettierの悪いところは、この場合は良いところでもあるんですが、これまで誰も速くしようとしてこなかったことです。つまり、パフォーマンス改善の余地が大きく残されている、ということです。PrettierのCLIによる実行が遅いのはCPUインテンシブな理由ではなく、IOインテンシブな理由です。Fabioというエンジニアがその観点に着目しPrettierのCLIを完全にゼロから書き直す実験を進めています。今のところ、Babelのリポジトリに対する実行結果が10倍以上速くなったという結果が出ています。この新しいCLIは次のメジャーバージョンであるv4に搭載される予定です。しかし、10倍速くなったとはいえBiomeにはまだまだ追いつくことはできません。

次は何かしらのlinterとのインテグレーションです。Biomeは自分でlinterを持っていますので、これはeasy。Prettierは、どんなlinterとインテグレーションするにしても、やや面倒です。これはBiomeの最も良い点の一つですよね。ツイッターとかでBiomeユーザーの声を聞いていると、体感ですが、パフォーマンスよりも設定が簡単なことの方が評価されている印象があります。Prettierがこの体験に近づくためには、ラッパーを用意するとか、設定するためのスキャフォールディングコマンドを用意するとか、そういう感じになりそうですね。

次はESLintとのインテグレーションです。これはどちらも同程度には面倒です。個人的にはこれはかなり重要だと思っています。ESLintは極めて柔軟に設定できるところがウリのlinterです。もしESLintとPrettierを使っている状況からBiomeに移行しようとしたら、フォーマッタの部分をBiomeに移行するのは簡単だと思いますが、linterの部分は難しいでしょう。特にESLintを使いこなしている場合は、BiomeのlinterでESLintと同じ挙動を再現するのはおそらく不可能です。私の所属しているユビー、Prettier、Babelではいずれも自分たちのコードベースのためにカスタムルールを作って運用しています。そういうことがカジュアルにできるのがESLintの良さです。特に、そのコードベースにあまり詳しくないメンバーがよくコードを書く環境では独自のルールというのは良く機能します。また、ESLintのコミュニティがメンテナンスしている各種プラグインに含まれるルールも、Biome Linterには存在しないものがあります。そのため、現実的にPrettierを剥がすことができてもESLintは剥がせないというケースが存在し、そのためにBiome FormatterとESLintを併用する場合、結局設定がめんどくさいため、嬉しさは半減するでしょう。Biomeがこれについて何か計画しているのかは知らないのですが、もし何かできるとしても、ラッパーを作るとかスキャフォールディングを工夫するとか、そういう感じになるのではないかと思っています。

次は現在サポートしている言語です。BiomeはTypeScript、JavaScript、JSX、TSX、JSON、JSONCをサポートしています。一方でPrettierはそれに加えてFlow、HTML、Vue、Angular、Handlebars、CSS、Less、SCSS、SaSS、GraphQL、Markdown、YAMLをデフォルトでサポートしている他、RubyやAstro、Svelteのためのよくメンテナンスされたプラグインがあります。今のところはPrettierの方がサポートしている言語が多いです。Prettierはすでに世の中に存在しているパーサーライブラリを使って新しい言語をサポートするので、比較的簡単に新しい言語をサポートできます。たとえばJavaScriptのパーサーとしてはBabelを使っていますし、TypeScriptのパーサーとしてはtypescript-eslintと同じものを使っています。一方でBiomeは自分たちでパーサーを書いてそれをメンテナンスしています。これは、依存をできるだけなくしたいという思想があるのと、Red Green Tree をサポートするパーサーを使いたいという理由からだと思われます。新しい言語をサポートするのが遅くなってしまっているのは、そのためなのではないかと想像しています。

次はプラグインです。Biomeは現在どのような形でプラグインを実現するのか計画中のようです。GritQLというDSLを使ったプラグインシステムを構築しようとメンテナーは計画していて、一部の実装が進んでいるようです。PrettierはJavaScriptでプラグインを書くことができるようになっています。Prettierのプラグインは、本来は別の言語を新しくサポートするための仕組みなのですが、prettier-plugin-tailwindcssのように、すでにサポートされている言語のフォーマットの挙動を変える目的でも使われています。この使い方は実は結構トリッキーなので、Prettierをメンテする立場としては微妙な気持ちにはなるのですが、実際には良く使われているようです。Biomeがもしパフォーマンスを低下させることなく、Prettierと同じようなTypeScriptで書けるプラグインシステムを実現したならそれは本当にすごいことです。どのような形になるか気になっています。これは、Biomeのエコシステムがこの先どうなるかを決定付ける重要な意思決定になるでしょう。

最後にそれぞれのソフトウェアが記述されている言語です。BiomeはRust、PrettierはJavaScriptで記述されています。これは良く議論されることではありますが、Rustの方が一般に速いプログラムを書きやすいでしょう。一方で、動的なプログラミング言語であるJavaScriptは柔軟性が高く、プラグインシステムなどを構築するのは比較的容易です。これらの言語の特徴が、わかりやすくBiomeとPrettierの特徴として現れているように見えますね。

RustとJavaScriptの違いとして、最終的に配布される形式に違いがあります。たとえば、Biomeはnpmで配布されていますが、最終的に降ってくるのはバイナリです。一方でPrettierはJavaScriptファイルが降ってきます。これは大きな違いなのではないかと思っていたのですが、実際は大した違いではないかもしれません。というのも、PrettierはNode.jsごとバンドルしてしまえばバイナリにできるし、BiomeはWebAssemblyにコンパイルすればNode.jsさえあれば動く状態にできるからです。両方とも、今のところはそういう形式をサポートしていませんが、やればできる、という感じではあります。

どのような形式で配布されていると良いかというのは状況によります。JavaScriptで配布されていれば、Node.jsさえマシンにインストールされていれば、OSやCPUの違いに関わらず実行できます。逆に言えばNode.jsを必要とします。バイナリで配布されている場合は、Node.jsがなくても実行できますが、自分の環境にあったバイナリをインストールする必要があります。

さて、ここまでの話から、今後もPrettierが提供できる価値と、注力するべきポイントを改めて整理してみましょう。

まず、今後もPrettierが提供できる価値は、サポート対象の言語の多さです。Prettierは、すでにJavaScriptのエコシステムの他のツールで使われている品質の高いパーサーを流用できます。これによって、Prettierチームが大きなメンテナンスコストを払うことなく、いくつもの言語を安定してサポートし続けることができています。

また、JavaScriptで書かれているがゆえの柔軟なプラグインシステムもPrettierの強みでしょう。現在は、トリッキーな方法を使わなければ、既存の言語のフォーマットを変更することはできませんが、これを公式にサポートするなど、発展の余地もあります。

反対に、注力するべきポイントは手軽さとパフォーマンスです。手軽さに関しては、主にESLintとのインテグレーションを強化することになるでしょう。今のESLintは、スタイル系のルールが推奨ルールセットに含まれなくなったため、eslint-config-prettierが不要であることが多いので、実際にはもう結構手軽なのですが、このあたりのガイドをもっとわかりやすくする必要があるでしょう。

もっとも困難な問題はやはりパフォーマンスです。正直に言って、Biomeに匹敵するパフォーマンスを実現するのはかなり困難なことだと思います。それでもPrettierとしてできることをする必要があります。まずは、Fabioが作ってくれた新しいPrettierのCLIを搭載した新しいメジャーバージョンv4をリリースするところから始まるでしょう。そして、我々のコードベースをRustやGoに書き換えることはおそらくないでしょう。やるとしてもそれはまた別のプロジェクトとして、ですね。

今日は、Prettierの誕生から強力な競合Biomeとの比較、そして今後のPrettierについてお話しました。これまでPrettierには強い競争相手が存在せず、自分たちに磨きをかける努力を怠っていました。Biomeという競争相手が登場したことによって、お互いに強くなりあって、結果としてTypeScript/JavaScriptのコードフォーマッタの品質が改善され、ユーザーの開発体験が改善するでしょう。

話は以上です。ありがとうございました。

スライドにはほとんど情報がありませんので、あとでスピーカーノートを公開します。

Discussion