虎の穴開発室ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

Deno 1.10 へのアップデートと変更事項まとめ

皆さんこんにちは、アレクサのボイスをマツモトにしたいおっくんです。

去る 2021 年 5 月 11 日に Deno 1.10 がリリースされました。 今回も、リリース内容の中から気になったものをピックアップして、紹介したいと思います。

実行環境

  • macOS Catalina 10.15.7

アップデートのやり方

今回は Deno 1.9.2 から Deno 1.10.1 へのアップデートを行います。 (既に 2021 年 5 月 12 日 に Deno 1.10.1 にアップデートされていました。)

アップデートする Deno を導入した時のコマンドは以下の通りです。

curl -fsSL https://deno.land/x/install/install.sh | sh

アップデートは、以下のコマンドで実施しました。

$ deno upgrade

# バージョン指定する場合は次のコマンドで実行します。

# deno upgrade --version 1.10.1

ネットワーク環境もあるでしょうが、完了するまでに 2 分ほどで終わりました。

方法については、こちらに記載があります。

https://deno.land/manual/getting_started/installation

Deno 1.10

Deno 1.10 での変更事項をDeno 1.10 リリースノートを元に確認します

deno test の改善

Deno 1.10 では、組み込みテストランナーのdeno test に大規模な修正が入りました。

テストの並列実行

--jobs [数字]のフラグを与えることで、テスト実行時に使用するスレッド数を指定できるようになりました。 デフォルトでは、すべてのテストは 1 スレッドで直列に実行されます。

例えば、以下のテスト対象のコードがあるとします。

const textConvert = (src: String, mode: 1 | 2) => {
  if (mode === 1) return src.toLowerCase();
  return src.toUpperCase();
};

export { textConvert };

こちらを以下の二つのテストを定義したテストコードで検証します。 (並列実行されていることを確認したいのでわざと停止時間を仕込んでいます。)

[test_1.ts]

import { textConvert } from './module_1.ts'

import {
   assertEquals,
 } from "https://deno.land/std@0.65.0/testing/asserts.ts";

const sleep = (msec:number)=>{
 const start = (new Date()).getTime()
 while((new Date()).getTime() - start < msec);
}

Deno.test("lower case test",()=>{
 sleep(5000)
 const result = textConvert("TestString", 1)
 assertEquals(result,"teststring")
})

Deno.test("upper case test",()=>{
 sleep(5000)
 const result = textConvert("TestString", 2)
 assertEquals(result,"TESTSTRING")
})

次のコマンドで実行します。

$ deno test test_1.ts --jobs 2
running 2 tests from file:///[ディレクトリ]/test_1.ts
test lower case test ... ok (5003ms)
test upper case test ... ok (5001ms)

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (10034ms)

並列実行してくれません。 5 秒停止させたテストの 2 件を 10 秒かかって処理しました。 書いてあることと違う気がします。

以下のように、テスト用のコードを二つに分割します。

[test_1_lower.ts]

import { textConvert } from "./module_1.ts";

import { assertEquals } from "https://deno.land/std@0.65.0/testing/asserts.ts";

const sleep = (msec: number) => {
  const start = new Date().getTime();
  while (new Date().getTime() - start < msec);
};

Deno.test("lower case test", () => {
  sleep(5000);
  const result = textConvert("TestString", 1);
  assertEquals(result, "teststring");
});

[test_1_upper.ts]

import { textConvert } from "./module_1.ts";

import { assertEquals } from "https://deno.land/std@0.65.0/testing/asserts.ts";

const sleep = (msec: number) => {
  const start = new Date().getTime();
  while (new Date().getTime() - start < msec);
};

Deno.test("upper case test", () => {
  sleep(5000);
  const result = textConvert("TestString", 2);
  assertEquals(result, "TESTSTRING");
});

この準備をしたら改めて、次のコマンドで実行します。

$ deno test test_1_lower.ts test_1_upper.ts --jobs 2
Check file:///[ディレクトリ]/test_1_lower.ts
Check file:///[ディレクトリ]/test_1_upper.ts
running 1 test from file:///[ディレクトリ]/test_1_lower.ts
running 1 test from file:///[ディレクトリ]/test_1_upper.ts
test upper case test ... ok (5004ms)
test lower case test ... ok (5004ms)

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (5034ms)

2 件のテストが並列に実行され 5 秒程度で終了できました。 --jobsのフラグを有効に使うには、テストコードの分割が必要でした。

テスト用のパーミッションの指定

Deno はリソースを管理するパーミッションの設定が豊富にあり詳細に権限を設定できます。 テストコード中に個別のテストでのパーミッションを記述することができるようになりました。 Deno 1.9.2 と Deno 1.10.1 で実行してみます。

Deno 1.9.2 の場合

[test_2.ts]

Deno.test("write",
  async fn() {
    await Deno.writeTextFile("./foo.txt", "Write!");
    console.log(await Deno.readTextFile("./foo.txt"));
  },
});

実行次のようになります。

deno test --allow-write --allow-read test_2.ts
# もしくは
deno test --allow-write=foo.txt --allow-read=foo.txt test_2.ts

オプションで与えたパーミッションが、直接テストに使用されるパーミッションになっています。

Deno 1.10.1 の場合

以下のようにテストコードを修正します。

[test_2_deno_1_10.ts]

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

実行は次のようになります。

$ deno test --allow-read --allow-write --unstable test_2_deno_1_10.ts
running 1 test from file:///[ディレクトリ]/test_2_deno_1_10.ts
test write ... FAILED (3ms)

failures:

write
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:///[ディレクトリ]/test_2_deno_1_10.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

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

実行時に与えたパーミッションを、テストコードの記述で狭めることができました。 逆に、実行時に与えていないパーミッションを、テストコードで許可はできません。

実行時に与えた、パーミッションをテストコードで狭めるパターンをリリースノートでは紹介されています。 これらのパーミッションは、あり/なし だけでなくリストで与えることが可能です。 テストコードでも、パーミッションのリスト指定が可能です。 リストでパーミッションを制限するテストコードの実装は次のようになります。

[test_2_deno_1_10_2.ts]

Deno.test({
  name: "write",
  permissions: { write: true, read: ["./foo.txt"] },
  async fn() {
    await Deno.writeTextFile("./foo.txt", "Write!");
    console.log(await Deno.readTextFile("./foo.txt"));
  },
});

同じようにパーミッションを与えて、実行してみます。

$ deno test --allow-read --allow-write --unstable test_2_deno_1_10_2.ts
Check file:///[ディレクトリ]/test_2_deno_1_10_2.ts
running 1 test from file:///[ディレクトリ]/test_2_deno_1_10_2.ts
test write ...Write!
 ok (7ms)

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

テストコードでのパーミッションもリストで与えることができました。 この機能は、--unstable をつけることが必要です。

テストランナーの出力の向上

Deno 1.10 から deno test を実行したときに、モジュールごとのテスト数やテストの記述されたファイルをプロンプトで出してくれるようになりました。 先に用意した test_1.ts test_1_lower.ts の二つを Deno 1.9.2 と Deno 1.10.1 で実行してみます。

Deno 1.9.2 の場合

$ deno test test_1.ts test_1_lower.ts
Check file:///[ディレクトリ]/$deno$test.ts
running 3 tests
test lower case test ... ok (5001ms)
test upper case test ... ok (5001ms)
test lower case test ... ok (5002ms)

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (15006ms)

Deno 1.10.1 の場合

$ deno test test_1.ts test_1_lower.ts
running 2 tests from file:///[ディレクトリ]/test_1.ts
test lower case test ... ok (5004ms)
test upper case test ... ok (5000ms)
running 1 test from file:///[ディレクトリ]/test_1_lower.ts
test lower case test ... ok (5002ms)

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (15062ms)

Deno 1.9.2 の場合、テストの記述されたファイルがどれだったのかテストケースの名称から探す必要がありました。 Deno 1.10 からは、どのファイルに書いているテストなのか、ファイルにいくつテストが書いてあるのかを一目でわかるようになりました。

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

すべてのプロジェクトで、ドキュメントを最新の状態に保つことは大事です。 例えば、API を修正をした後にその API のドキュメントが修正されずに古いままだったなんていうシチュエーションに立ち会うことがあります。

こういった状況を防ぐために、ドキュメントになっているコメントに書かれているコードの型チェックが効くようになりました。

リリースノートに示されているように、コメントが付いたコードを用意します。

[test_3.ts]

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

次のように実行します。

$ deno test --doc test_3.ts
Check file:///[ディレクトリ]/test_3.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:///[ディレクトリ]/test_3.ts:2-7.ts:3:16

# console.assert(example() == 42);
# を
# console.assert(example() == '42');
# のように型チェックに合うようにサンプルコードを修正

$ deno test --doc test_3.ts
Check file:///[ディレクトリ]/test_3.ts:2-7
running 0 tests from file:///[ディレクトリ]/test_3.ts

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (23ms)

よくありそうなシチュエーションに即した機能、素晴らしいです。 願わくは、マークダウンに書かれたコードブロックも型チェックしてくれるとうれしいですね。

テストの実行時にファイルの変更を監視する

テストの終了後に、プロセスを継続させて関連するテストケースを再実行するオプション --watch が追加されました。 --watch を付与することによる監視対象は、Deno が自動的に検出します。

先に出している test_1.ts に関連した変更を監視するときは、以下のように実行してあげます。

$ deno test test_1.ts --watch

test_1.ts は、module_1.ts をインポートしています。 test_1.tsmodule_1.ts を変更しても、再度テストが実行されます。

Mac では、ファイルの変更を監視できたのですが、Docker コンテナ上では上手く効いていませんでした。

テストさえちゃんと用意しておけば、リファクタリング時の強力な武器になると思います。

Web StorageAPI をサポート

Deno が Web StorageAPI を使えるようになりました。 Web StrageAPI は、localStoragesessionStrage で構成されています。

https://developer.mozilla.org/ja/docs/Web/API/Web_Storage_API

localStorage は、ファイルシステムに直接アクセスすることなく、少量のデータを永続的に保存できます。 ブラウザでの利用と同じように動作し、同じオリジンを指定していればプロセス再起動後もデータを使用できます。 データはオリジンにより、キー設定され、Deno では、実行時に --location オプションを与えることで設定できます。

sessionStrage は、プロセスの実行期間中のみ使用が可能です。 localStoragesessionStrage の利用にあたっどちらもパーミッションを必要としません。

以下のようなサンプルコードを用意します。

[web_strage.ts]

const key = 'key'
const value = "value"

// localStorage
console.log(`key:${localStorage.getItem(key)}`)

localStorage.setItem(key, value);

console.log(`key:${localStorage.getItem(key)}`)

// sessionStorage
console.log(`sessionStorage:key:${sessionStorage.getItem(key)}`)

sessionStorage.setItem(key, value);

console.log(`sessionStorage:key:${sessionStorage.getItem(key)}`)

以下のように実行します。

$ deno run --location http://example.com web_strage.ts
localStorage:key:null
localStorage:key:_value
sessionStorage:key:null
sessionStorage:key:_value

# 2 回目からは、localStrage データが保存されています。
$ deno run --location http://example.com web_strage.ts
localStorage:key:_value
localStorage:key:_value
sessionStorage:key:null
sessionStorage:key:_value

# オリジンが変わると、データは保存されていません。
$ deno run --location http://example_2.com web_strage.ts
localStorage:key:null
localStorage:key:_value
sessionStorage:key:null
sessionStorage:key:_value

localStoragesessionStorage の動作確認ができました。 --location のオプションは、--location http://a のような適当な渡し方でも動作します。

リリースノートに記載は見当たらないのですが、以下のようにsessionStorageは、--location を付与せずに動作できました。

[session_strage.ts]

const key = 'key'
const value = "value"

// sessionStorage のみ
console.log(`sessionStorage:key:${sessionStorage.getItem(key)}`)

sessionStorage.setItem(key, value);

console.log(`sessionStorage:key:${sessionStorage.getItem(key)}`)
$ deno run session_strage.ts
sessionStorage:key:null
sessionStorage:key:_value

永続化せずにプロセス実行中の一時的なストレージとして使うなら、 sessionStorage でほとんどの要件は満たせそうです。

Markdown ファイルの deno-fmt-ignore-file ディレクティブをサポートする

deno fmt は、マークダウンや、TyupeScript などのファイルをフォーマットできるツールです。 ファイルフォーマットをスキップする deno-fmt-ignore-file ディレクティブが以前からありましたが、マークダウンに対して機能していませんでした Deno 1.10 からマークダウンもサポートされます。

ファイルの先頭で、<!-- deno-fmt-ignore-file --> と記述します。 記述することで、フォーマットがスキップされます。 コンソールには、スキップされたことがわかるような表示は出ません。

リモートインポートマップをサポートする

Deno 1.8 から import maps に対応しました。 今回 Deno 1.10 から、 Deno の各種モジュールのように、HTTP 経由でロードできるようになります。

リリースノートでは、実行例は以下のように示されています。

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

更新されたプラグイン API

Deno 1.10 よりプラグインインターフェイスが更新されました。 Deno 1.9 にてリリースされた serde_v8 のランタイムとネイティブプラグイン間のインターフェイスを使用できるようになりました。 サードパーティのコードを使用せずに、ネイティブプラグインが提供する ops を呼び出せるようになりました。 (ops:JavaScriptの高レベル関数をサポートするRustで実装されたの低レベル関数群) さらに、プラグインは、Rust オブジェクトをランタイムインフラストラクチャに保管している ResourceTable にアクセスできるようになりました。

プラグインシステムは、実験的機能であるため --unstable が必要です。

プラグインのサンプルコードが紹介されています。

https://github.com/denoland/deno/blob/0d319161bc19a520df653bc0c8386f14a68efbdb/test_plugin/src/lib.rs

不安定なCLI 機能を使うためのフラグが削除されます

今回のリリースで Deno の CLI 機能の deno compile deno lint について、 --unstable フラグを付与せずに実行できるようになりました。 (CLI 機能の一部には、まだ不安定とみなされているものがあるそうです。そういったものは、ヘルプコメントに UNSTABLE: と記載が入ります。)

今後の--unstable フラグが管理するのは、不安定なランタイム API の実行について許可するためだけに使用されます。 不安定なランタイム API とは、JavaScriptAPI だそうです。

そのほか

  • Worker.postMessage 構造化クローンアルゴリズムをサポート
  • 共有 WASM メモリをサポートを有効にする

Deno 1.10 のリリースでは、Deno test と Web StorageAPI が強く興味惹かれるものでした。 Web StorageAPI は、アプリケーションの開発時の小規模なデータ保管先として、ファイルや外部データベースに加えて、新たな選択肢が増えたことになります。 特に sessionStrage は、パーミッションやオリジンの要求が無く最小限のオプションで起動できるのが、とても好みです。

また次のリリースも追いかけます。

P.S.

■ 直近のイベント情報

とらのあなエンジニア&マーケター採用説明会+リモート懇親会【地方勤務も可能!】
を5月26日 (水) 19:30から開催します。YouTubeでのライブ配信となりますので、お気軽にご参加ください!
yumenosora.connpass.com

【オンライン】オタクが最新技術を追うLTイベント#24【初心者歓迎】【テーマフリー】
を5月28日 (金) 19:30から開催します。こちらは発表者も募集中ですので、LT初心者という方でもぜひご応募ください!
yumenosora.connpass.com

■ 採用情報

とらのあなでは、オタクなエンジニアを募集しています。
yumenosora.co.jp

カジュアル面談も随時開催中です。お申し込みはこちら!
yumenosora.connpass.com

■ ToraLab.fmスタートしました!

メンバーによるPodcastを配信中!是非スキマ時間に聞いて頂けると嬉しいです。
anchor.fm

■ Twitterもフォローしてくださいね!

ツイッターでも随時情報発信をしています。
twitter.com