Deno が Node.js に依存しなくなった

Deno (ディノ) Advent Calendar 2020、25日目の記事です。今日は Deno が Node.js に依存しなくなった経緯の話をします。

# Node.js に依存しながら始まった Deno の開発

Deno は、プロジェクトが始まって以来いくつかの点で Node.js に依存して開発が進められてきました。おもに Node.js に依存していたのは以下の3種類のプログラムです。

 バンドラ parcel (のちに rollup に移行)
 フォーマッタ prettier
 リンタ tslint (のちに eslint に移行)

この中でバンドラが最も最初に Deno 製のツールにリプレースされ、その後フォーマッター、リンターの順でリプレースされていきました。リンターがリプレースされたのはつい最近 (2020年11月) のことです。この記事ではそれぞれの経緯・手法について紹介していきます。

# Rollup のリプレース

そもそもなぜバンドラーが必要だったかというと、Deno の内部 API をバンドルして1ファイルにまとめるために必要でした。バンドルした1ファイルを V8 に読み込ませて、その状態の V8 スナップショットを取り、スナップショットをバイナリとして埋め込んで、起動時にはそのスナップショットで V8 を起動することで、Deno のランタイムを初期化していました (V8スナップショットから起動するという部分は今も変わっていません)。

当初は Parcel でバンドルをしていましたが、Rollup の方がバンドル後のファイルサイズ・スナップショットサイズが小さいため、後に Rollup によるバンドルに置き換えられていました (2018年7月)。 ( https://github.com/denoland/deno/pull/368 , https://github.com/denoland/deno/pull/395 )

Rollup はバンドル結果そのものには問題ありませんでしたが、Rollup 自体の処理の遅さが問題視されており、2019年9月に次の PR がレポジトリに提出されて、Rollup の依存が無くなりました。

- https://github.com/denoland/deno/pull/2825
  - チェックイン直後の deno_typescript は以下で確認することが出来ます。 ( https://github.com/ry/deno/tree/f2c49416ec958729f55de6ada8be2d49f0ae70ec/deno_typescript )

上の PR は Rollup によるバンドルを独自ツールの deno_typescript で置き換えるという変更です。このツールは TS ファイルをつなげて1ファイルにするのではなく、TS ファイルを1つ1つ(AMD形式に)トランスパイルしながら、V8 エンジンに読み込ませていき、最終的な V8 の状態でスナップショットを取ることでバンドルしたのと同じ効果を得るというプログラムです。この deno_typescript 自体が deno_core に独自の Op を設定した(バンドルを目的とする)独自 JS ランタイムという形になっていて、まさに Deno らしい問題解決の仕方と言えます。

JS のバンドラというと一般的には、与えられたエントリポイントから import / require 文を辿っていって、全体を1つの大きなスクリプトファイルにするプログラムのことを指しますが、Deno の場合、バンドル結果を文字列で得る必要はなく、V8 の状態として作れればそれで良いため、上のような解決策を取ることが可能となりました。

この変更により、Deno のビルド (特に内部 API 変更時の差分ビルド) が大幅に高速化されました。

(なお、現在は deno_typescript も削除されていて、より原始的なファイルを名前順で concat するビルド方法に置き換わっています (この変更により、更に Deno 自体のビルドが高速化しました。[参考](https://github.com/denoland/deno/pull/6793)。

# prettier のリプレース

Deno は当初 prettier を2つの目的で使っていました。一つは deno fmt の実装として利用していました。もう一つは Deno リポジトリの中の JS / TS のフォーマッターとして利用していました。Node.js に依存していたのは後者の Deno 自体の formatter として使っていた方の prettier の利用です。

- https://github.com/denoland/deno/pull/6682

上の PR で、Deno 本体の JS / TS のフォーマッティングが  prettier から dprint に移行しました(2020年7月)。[dprint](https://github.com/dprint/dprint) は、大まかに prettier と互換な、Rust 製の JS、TS、markdown、etc のフォーマッターです。dprint 自体は必ずしも deno のためのフォーマッターという訳ではなく、ある程度独立したプロジェクトの形をとっています。メインの開発者の David Sherret [@dsherret](https://github.com/dsherret) さんも、もともとは Deno のコントリビュータだったわけではなく、Deno とは独立したコンテキストでこのツールを開発していたようです。現在は deno fmt コマンドは内部実装は dprint になっているため、deno fmt 周りの実装は David さんが担当しています。

上の変更によって、prettier の依存が無くなり、Deno レポジトリ全体のフォーマットが3倍以上速くなりました。

# ESLint のリプレース

Deno は Deno の JS / TS 実装のリントに ESLint を利用していました。2020年11月に以下の PR で ESLint が dlint に置き換えられました。

- https://github.com/denoland/deno/pull/8176
- https://github.com/denoland/deno/pull/8232

dlint ([deno_lint](https://github.com/denoland/deno_lint)) は `deno lint` コマンドの実装として 2020年4月から開発が始められた、大まかに ESLint 互換の Rust 製のリンターです。この変更により、Deno 本体のリントが、非常に速くなりました。ESLint と deno_lint のベンチマーク比較は Advent Calendar 6日目の[まぐろさんの記事](https://zenn.dev/magurotuna/articles/66618f26475702) でも読むことが出来ます。

ESLint 依存が無くなったことにより、Deno レポジトリ内で Node.js を使っていた箇所が完全に無くなりました。**Deno を開発するために Node.js をインストールする必要が無くなりました。**

# おまけ、Python 2 のリプレース

Node.js 以外で Deno の中で好ましくない依存関係として Python 2 のスクリプト類がありました。

Deno はビルド関係のスクリプトをすべて Python 2 で書いていました。これは、V8 のビルドにもともと Python 2 が必要であること、また、クロスプラットフォームで安定して動くスクリプト言語として Python が安定してるということの2点がおもな理由でした。

最近になって、Deno 1.x 系が出たこともあり、Deno 自体が安定してきたため、開発用スクリプトも Deno で書こうというコンセンサスが取れたことにより、開発に必要なスクリプト類がすべて Deno のスクリプトで書かれるようになりました (2020年11月)。

- https://github.com/denoland/deno/pull/8247

この変更でレポジトリ内から Python のソースコードが無くなり、Deno レポジトリ内は、Rust / TS / JS のみで書かれるようになりました。

当初は上記 Python 以外にも C++ や GN などの言語もレポジトリ内で開発されており、C++/GN/Python/JS/Rust の5言語用のそれぞれ違うリンター/フォーマッターを起動してからコミットするというかなり複雑な開発環境でしたが、この変更によって遂に、本来使いたかった言語のみで開発出来る体制が整ったと言えます。

# まとめ

今日は Deno が Node.js への依存を外して行った経緯を紹介しました。