Deno の組み込み機能、ソースコードの変更を検知してオートリロードする "File Watcher" の紹介

4 min read読了の目安(約3800字

deno illust
Copyright (c) 2018-2020 the Deno authors. MIT License.

この記事は Deno Advent Calendar 2020 13日目の記事です。

12日目は -> (あとで埋める)
14日目は -> (あとで埋める)

Deno は組み込みでいろいろ便利機能が入っている

以前の記事 でも簡単に紹介したのですが、Deno には組み込みで以下のような機能が入っています。

  • テストランナー(Deno.test という組み込み関数を使って定義されたコードをテストする)
  • ベンチマーク測定
  • バンドラ(依存関係を解決して、単一の .js ファイルを出力する)
  • ドキュメンテーション(ファイルを解釈して、JSDoc を抽出して表示。この出力をベースに、サードパーティのライブラリであってもこのようなドキュメントサイトが生成される
  • REPL
  • フォーマッタ (prettier のようなもの)
  • リンター(ESLint のようなもの)

今回はこれらの機能(の一部)に対して付与できるオプションである --watch を紹介します。

--watch の紹介

--watch を付けることで、ファイルの内容が更新されたときに自動で再読み込みしてくれるようになります。Node.js でいうところの nodemon のような機能です。

現時点(Node v1.6.0)では以下のコマンドが --watch に対応しています。

  • run
  • bundle
  • fmt

run --watch のデモ

例として run --watch してみた場合にどうなるのか見てみましょう。
まず実行するスクリプトを用意します。自動更新があって嬉しいケースで試してみるのが良いと思うので、APIサーバーの開発という想定でいきます。

my-server.ts
import { Application } from "https://deno.land/x/oak/mod.ts";

const app = new Application();

app.use((ctx) => {
  ctx.response.body = "Hello World!";
});

console.log("server is ready!");

await app.listen({ port: 7777 });

https://github.com/oakserver/oak#application-middleware-and-context より[1]

7777番ポートで待ち受けて、あらゆるリクエストに対して Hello World! を返すだけのシンプルなサーバーです。このファイルを用意したら、以下のようにすることで --watch オプション付きでサーバーを起動させることができます。

$ deno run --watch --unstable --allow-net my-server.ts

起動後、curl などで7777番ポートに対してリクエストを送ると、Hello World! が返ってくることが確認できます。
ここでサーバープログラムを書き換えてみましょう。

my-server.ts
import { Application } from "https://deno.land/x/oak/mod.ts";

const app = new Application();

app.use((ctx) => {
-  ctx.response.body = "Hello World!";
+  ctx.response.body = "Hello Deno!";
});

console.log("server is ready!");

await app.listen({ port: 7777 });

このように書き換えて保存してみると、立ち上げているサーバープロセスがファイル変更を検知して、自動で再起動されます。
再起動後に curl でもう一度7777番ポートを叩いてみると、今後は Hello Deno! に変わっていることが確認できます。

以上の流れをGIFアニメーションにしてみました。

sample animation of deno watcher

bundle, fmt と一緒に --watch を使うとどうなるか

bundle --watch とすると、ファイルの変更があるたびにバンドルを再実行し、常に最新の .js ファイルを出力します。

同様に fmt --watch では、ファイルの変更があるたびにフォーマッタを実行します。常にコードがフォーマットされた状態で保たれます。(VSCode などのエディタがこの機能をもっていることが多いと思いますが……)
fmt --watch --check とすることもできます。こうすると、ファイルの変更があるたびに、フォーマットされたキレイなコードになっているかどうかを チェック します。チェックするだけなので、汚いコードの場合にも警告が出るのみです。

--watch をつけたときの監視対象ファイルはどのように決定されるか

Deno は Node.js と違って、プロジェクトのルートディレクトリ、といった概念は存在しません。package.json が無いので、package.json があるディレクトリがルートディレクトリで、それ以下にある TypeScript / JavaScript のファイルを Watcher の監視対象にする……みたいな扱いができない、ということです。

ではどうやって監視対象ファイルを決めているかというと、ファイル間の依存関係を解決した結果のうち、ローカルにあるファイルをすべて監視対象としています。
例えば、deno run --watch --unstable main.ts というように起動させたとします。そして、main.ts にある import から順に依存関係を見ていった結果が、以下のようになったとします。

main.ts
├── a.ts
├── src/b.ts
│    ├── https://example.com/c.ts
│    └── src/foo/d.ts
└── https://example.com/e.ts
      └── https://example.com/f.js

この依存関係のうち、ローカルにあるものは

  • main.ts
  • a.ts
  • src/b.ts
  • src/foo/d.ts

の4ファイルです。したがって、これらのファイルを監視対象とします。

ファイルが新規作成された場合は、その新規作成ファイルに到達するような依存関係が追加されれば、監視対象に加えられます。例えば、src/new.ts を作ったすると、作った段階では Watcher の監視対象には加わっていません。
a.tsimport { someFunction } from "./src/new.ts"; を追加して保存したとします。このタイミングで、src/new.ts に到達する依存関係ができあがったので、監視対象に加わります。

おわり

Deno の組み込み便利機能の1つである Watcher を紹介しました。
まだ --unstable フラグが必要だったり、対応しているコマンドが少ない(例えば deno lint --watch はまだ出来ない)といった問題はありますが、それでも Deno をインストールするだけですぐに Watcher が使えるというのは強力だと思うので、ぜひ使ってみてください。

Watcher に関して、こういう機能が欲しい、この挙動が気に食わない、などありましたらぜひご連絡ください!(できる限り実装します)

脚注
  1. MIT License https://github.com/oakserver/oak/blob/main/LICENSE ↩︎