🦕

Deno 1.10.1 がリリースされたので新機能や変更点の紹介

2021/05/12に公開

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

日本時間の今日(2021 年 5 月 12 日)に Deno の v1.10.0 v1.10.1 がリリースされました。
※ 1.10.0 がリリースされる予定でしたが、1.10.0 のリリース後すぐにバグが発覚したため、revert 対応がなされた 1.10.1 がリリースされました。

https://deno.com/blog/v1.10

詳細なリリース内容は上記のリリースノートにまとまっていますが、ざっと紹介していきたいと思います。

  1. deno test コマンドの改良
  2. Worker.postMessage が「構造化複製アルゴリズム」をサポート
  3. Web Storage API サポート
  4. Markdown ファイルへのフォーマッタ適用を抑制する deno-fmt-ignore-file ディレクティブ
  5. 共有Wasmメモリの有効化をサポート
  6. リモート import map が使えるように
  7. plugin API のアップデート
  8. Deno CLI の機能を使う際に必要だった --unstable フラグが削除

1. deno test コマンドの改良

Deno は組み込みのテストランナーを備えています。このテストランナーに大幅な改修が施されました。

テストの並列実行ができるようになった

これまで Deno のテストランナーは直列で実行されていましたが、v1.10 からは並列実行ができるようになりました。
--jobs というオプションで並列数を指定できます。特に何も指定しなかった場合は、これまで通り直列で実行されます。

個々のテストにパーミッションを設定できるようになった

Deno はファイルへの読み書き、ネットワークアクセス、などに対して明示的な「パーミッション」を与える必要があるというモデルを採用しています[1]が、v1.10 からは個々のテストケースに対してパーミッションを指定することができるようになりました。

以下の例を見てみます。

test_permissions.ts
Deno.test({
  name: "write only",
  permissions: { write: true, read: false },
  async fn() {
    await Deno.writeTextFile("./foo.txt", "I can write!");
    console.log(await Deno.readTextFile("./foo.txt"));
  },
});

この例では、ファイルの読み込みと書き込みを行っていますが、permissionsread: false となっています。つまり、このテストは read パーミッションが無いという理由で失敗することが期待されます。
実行してみましょう。

read パーミッションの不足で失敗
$ deno test --allow-read --allow-write --unstable test_permissions.ts
Check file:///Users/ry/src/deno/test_permissions.ts
running 1 test from file:///Users/ry/src/deno/test_permissions.ts
test write only ... FAILED (5ms)

failures:

write only
PermissionDenied: Requires read access to "./foo.txt", run again with the --allow-read flag
    at deno:core/core.js:86:46
    at unwrapOpResult (deno:core/core.js:106:13)
    at async open (deno:runtime/js/40_files.js:46:17)
    at async Object.readTextFile (deno:runtime/js/40_read_file.js:40:18)
    at async fn (file:///Users/ry/src/deno/test_permissions.ts:6:17)
    at async asyncOpSanitizer (deno:runtime/js/40_testing.js:21:9)
    at async resourceSanitizer (deno:runtime/js/40_testing.js:58:7)
    at async exitSanitizer (deno:runtime/js/40_testing.js:85:9)
    at async runTest (deno:runtime/js/40_testing.js:199:7)
    at async Object.runTests (deno:runtime/js/40_testing.js:244:7)

failures:

	write only

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (37ms)

期待通り read パーミッションが無いことにより失敗しています。

ここで、テストケースごとに指定するパーミッションは、プロセスに対して与えるパーミッションを超えることができない ということに注意してください。
つまり、上の例では、テストの実行コマンドとして deno test --allow-read --allow-write というように readwrite のパーミッションを付与しています。このような場合に、テストケースに渡す permissionsnet: true というような指定はできない、ということです。
テストケースに渡す permissions が省略された場合には、起動コマンドに渡されたパーミッションがそのまま使われます。

この機能は Unstable としての提供です。使用するためには --unstable フラグが必要です。

テストランナーの出力が改善された

テスト実行時の出力で、そのモジュールがいくつのテストケースを含んでいるのかと、そのテストがどのファイルに書かれたものなのかが表示されるようになりました。

running 4 tests from file:///dev/deno/cli/tests/unit/tty_test.ts
test consoleSizeFile ... ok (11ms)
test consoleSizeError ... ok (4ms)
test isatty ... ok (4ms)
test isattyError ... ok (3ms)
running 6 tests from file:///dev/deno/cli/tests/unit/rename_test.ts
test renameSyncSuccess ... ok (17ms)
test renameSyncReadPerm ... ok (5ms)
test renameSyncWritePerm ... ok (6ms)
test renameSuccess ... ok (13ms)
test renameSyncErrorsUnix ... ok (34ms)
test renameSyncErrorsWin ... ignored (1ms)
...

(これまでは running 4 tests from ... の行がなかった)

ドキュメント内の型チェックができるようになった

コード内のドキュメントにコード例を書いたものの、その後コードを更新するときにドキュメントの更新を忘れてしまった、ということがよくあります。
そのようなドキュメントとコードの乖離を防ぐため、ドキュメント内に書いたコードブロックの型チェックを行うことができるようになりました。

test_docs.ts
/**
 * ```
 * import { example } from "./test_docs.ts";
 *
 * console.assert(example() == 42);
 * ```
 */
export function example(): string {
  return "example";
}

この例では、example 関数は string を返すような実装になっているのに対し、ドキュメント内では number を返すことになっていて、実装とドキュメントの乖離が生まれてしまっています。
以下のように --doc をつけることで、型チェックを行ってくれて、実装とドキュメントの乖離に気づくことができます。[2]

$ deno test --doc https://deno.com/v1.10/test_docs.ts

Check file:///dev/test_docs.ts:2-7
error: TS2367 [ERROR]: This condition will always return 'false' since the types 'string' and 'number' have no overlap.
console.assert(example() == 42);
               ~~~~~~~~~~~~~~~
    at file:///dev/test_docs.ts:2-7.ts:3:16

将来的には、ドキュメント内のコードブロックで型チェックのみならず通常のテストと同等のことが行えるようになる予定です。Rust みたいですね。

--watch に対応した

Deno が提供しているツールのうちいくつかは --watch フラグをつけることでファイルの変更を監視してくれるようになります。例えば deno run --watch foo.ts とすると foo.ts の変更を検知して自動で再実行をしてくれるようになります。

これまで --watch はテストランナーには対応していなかったのですが、v1.10 からはテストでも --watch が使えるようになりました。

2. Worker.postMessage が「構造化複製アルゴリズム」をサポート

Deno は 1.0 リリース時点から Web Worker をサポートしており、別スレッドでスクリプトを実行することができましたが、ワーカースレッドとメインスレッドの間でメッセージをやり取りする際の方式に問題があり、送受信できるデータに制限がありました。
v1.10 からは 構造化複製アルゴリズム が使われるようになり、この問題は解決しました。

例えば、以下のように循環した構造を持つオブジェクトをワーカーに送ると、以前はエラーになっていました:

const obj = { hello: "world" };
obj.self = obj;

const worker = new Worker(
  "data:application/javascript,self.onmessage = (e) => self.postMessage(e.data);",
  { type: "module" },
);

worker.onmessage = (e) => {
  console.log("Received event:", e.data);
};
worker.postMessage(obj);

v1.10 では問題なく動きます。

3. Web Storage API サポート

Web Storage API が使えるようになりました。localStoragesessionStorage を、ブラウザで使うのと同じような使用感で使うことができます。

localStorage は最大 5 MB のデータを保存できて、プロセスを再起動してもデータは残り続けます。一方、sessionStorage に保存したデータは、そのプロセスが終了するまでの間だけ生存し続けます。

Web Storage API を利用するにあたって、パーミッションの付与は必要ありません。
また、データの保存の際、ブラウザ上だとサイトのオリジン単位で分けられた空間に保存されます(例えば https://example.com と http​://example.com だと別のストレージに保存される扱いになる)が、Deno でこの「オリジン」を指定するために、起動時に --location オプションで値を渡す必要があります。

kv.ts
const key = Deno.args[0];

if (key === undefined) {
  // コマンドライン引数が渡されなかったら、
  // 今 localStorage に保存されているエントリの数を表示
  console.log(localStorage.length);
} else {
  const value = Deno.args[1];

  if (value === undefined) {
    // コマンドライン引数が1つだけ指定されていたら、
    // それをキーとして値を取得し、表示する
    console.log(localStorage.getItem(key));
  } else {
    // コマンドライン引数が2つ指定されたら、
    // それらをキー・バリューの組み合わせとして保存する
    localStorage.setItem(key, value);
    console.log(`${key}: ${value} is saved`);
  }
}
実行例
$ deno run --location https://example.com ./kv.ts
0

$ deno run --location https://example.com ./kv.ts foo bar
foo: bar is saved

$ deno run --location https://example.com ./kv.ts foo
bar

$ deno run --location https://example.com ./kv.ts
1

$ deno run ./kv.ts # `--location` を渡さないとエラー
error: Uncaught ReferenceError: Access to "location", run again with --location <href>.
  console.log(localStorage.length);
              ^
    at get (deno:extensions/web/12_location.js:365:17)
    at createStorage (deno:extensions/webstorage/01_webstorage.js:91:28)
    at localStorage (deno:extensions/webstorage/01_webstorage.js:178:24)
    at file:///tmp/hostname.ts:6:15

4. Markdown ファイルへのフォーマッタ適用を抑制する deno-fmt-ignore-file ディレクティブ

Deno は組み込みのフォーマッタ deno fmt コマンドを備えています。

  • JavaScript
  • TypeScript
  • JSON
  • Markdown

に対応したフォーマッタです。

基本的にはフォーマットを機械的に適用してしまうのが望ましいですが、一部ファイルにフォーマットを適用したくない、という場合もあるかと思います。
そのようなときには、ファイルの先頭に // deno-fmt-ignore-file というディレクティブコメントをつけることで、deno fmt の適用対象外とすることができます。
しかしこのディレクティブは今まで JavaScript / TypeScript でしか使えませんでした。

v1.10 からは Markdown ファイルでもこのディレクティブが使えるようになりました。
<!-- deno-fmt-ignore-file --> をファイルの先頭に付ければ OK です。

5. 共有Wasmメモリの有効化をサポート

共有Wasmメモリを使うことができるようになりました。これは Chrome と Firefox ではすでにデフォルトで有効になっている機能で、ついに Deno でも利用可能となった形です。

以下のように sharedtrue にして WebAssembly.Memory コンストラクタに渡すことで、共有Wasmメモリが生成され、これをデータストアとした SharedArrayBuffer が使えるようになります。

const memory = new WebAssembly.Memory({
  initial: 1,
  maximum: 10,
  shared: true,
});
console.assert(memory.buffer instanceof SharedArrayBuffer);

Wasm のスレッドサポートはまだ Deno では利用できませんが、優先度高く対応していく予定です。
Wasm スレッドについては以下の記事が参考になります。

https://developers.google.com/web/updates/2018/10/wasm-threads

6. リモート import maps が使えるように

Deno は v1.8 から import maps に対応しましたが、今回のバージョンからは "リモート import maps" が使えるようになりました。
つまり、import map のファイルがローカルのファイルシステムに存在している必要はなくなったということです。
以下のようにHTTP越しで import map ファイルを指定できます。

$ deno install --import-map=https://example.com/import_map.json -n example https://example.com/mod.ts

7. plugin API のアップデート

Deno は Rust で書かれたプラグインを読み込むという機能をもっています。ネイティブプラグインと呼ばれていますが、かなりの不安定機能で、セキュリティ的な懸念もあることから、いったん削除することも議論されていました。

そんな中ではありますが、1.9.0 の目玉アップデートだった serde_v8 を利用し、ネイティブプラグインがリファクタリングされました。
このリファクタリングによって、ネイティブプラグインが提供する Op をサードパーティコードなしで呼び出せるようになりました。[3]
また、Rustのオブジェクトが保存されている ResourceTable という場所にプラグインからアクセスができるようにもなりました。

新しいネイティブプラグインの実装例は こちら(test_plugin) にあります。

8. Deno CLI の機能を使う際に必要だった --unstable フラグが削除

Deno ではいくつかの機能は Unstable として提供されていて、将来的にその機能が削除されたり、別の名前に変わったり、インターフェースが変わったりする可能性がありますよということが示されています。
そのような機能を使う際には実行コマンドに --unstable フラグをつける必要があります。

例えば、1.10.1 現在では Deno.hostname というAPIは Unstable として提供されているため、以下のようなスクリプトを

hostname.ts
console.log(Deno.hostname());

--unstable なしで実行しようとすると、エラーになります。

--unstable なしで実行してエラーになる様子
$ deno run hostname.ts
Check file:///tmp/hostname.ts
error: TS2339 [ERROR]: Property 'hostname' does not exist on type 'typeof Deno'. 'Deno.hostname' is an unstable API. Did you forget to run with the '--unstable' flag?
console.log(Deno.hostname());
                 ~~~~~~~~
    at file:///tmp/hostname.ts:1:18

また、このようにスクリプトの実行時に必要な --unstable の他にも、Deno CLI に備わっているツールを実行するときにも --unstable が必要なことがありました。
例えば Deno に組み込まれている TypeScript / JavaScript リンターの deno_lint を使うときには、

1.10 より前
$ deno lint --unstable foo.ts

というようにする必要がありました。

1.10 からは、この「CLIツールを起動するときに必要だった --unstable」が不要になりました。

1.10 からはこれでOK
$ deno lint foo.ts

Unstable な API を含んだスクリプトを実行する際には依然として --unstable が必要です。

おわり

以上、1.10.1 の紹介でした。
次のマイナーバージョン 1.11.0 のリリースは 2021 年 6 月 8 日の予定です。

過去のリリース情報

1.9.0

https://zenn.dev/magurotuna/articles/deno-release-note-1-9-0

1.8.0

https://zenn.dev/magurotuna/articles/deno-release-note-1-8-0

1.7.0

https://zenn.dev/magurotuna/articles/55575eb16ae422

1.6.0

https://zenn.dev/magurotuna/articles/020f6b103937ed

脚注
  1. 詳細は https://deno.land/manual/getting_started/permissions#permissions を参照 ↩︎

  2. 手元で試してみたのですが、動かないですね……🤔 ↩︎

  3. ネイティブプラグインについてほとんどドキュメントがなく、また筆者の前提知識もないので、間違った訳になっている可能性があります。 ↩︎

Discussion