虎の穴開発室ブログ

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

MENU

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

皆さんこんにちは。おっくんです。

去る 2022 年 2 月 17 日に Deno 1.19 がリリースされました。 今回も、リリースノートを参考に 変更事項の気になるところを紹介します。

Deno 1.19

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

deno vendor が追加されました

deno vendor コマンドが、本リリースで追加されました。 このコマンドは、ソースコードが参照しているモジュール群をローカルの vendor ディレクトリに展開します。 実際の例を元に紹介します。

次の sample.ts を用意します。

[sample.ts]

import { serve } from "https://deno.land/std@0.128.0/http/server.ts";

console.log("http://localhost:8000/");
serve((req) => new Response("Hello World\n"), { port: 8080 });

こちらを、deno run --allow-net=0.0.0.0:8080 sample.ts で実行すると、次のようにコンソールに出力されます。

# deno run app.ts
Download https://deno.land/std@0.128.0/http/server.ts
Download https://deno.land/std@0.128.0/async/mod.ts
Download https://deno.land/std@0.128.0/async/deadline.ts
Download https://deno.land/std@0.128.0/async/debounce.ts
Download https://deno.land/std@0.128.0/async/deferred.ts
Download https://deno.land/std@0.128.0/async/delay.ts
Download https://deno.land/std@0.128.0/async/mux_async_iterator.ts
Download https://deno.land/std@0.128.0/async/pool.ts
Download https://deno.land/std@0.128.0/async/tee.ts
Check file:///usr/src/app/app.ts
http://localhost:8000/

Download https://deno.land/~~ と記載されています。 この時のダウンロード先は、deno info を実行することで確認できます。

$ deno info
DENO_DIR location: "/deno-dir/"
Remote modules cache: "/deno-dir/deps"
Emitted modules cache: "/deno-dir/gen"
Language server registries cache: "/deno-dir/registries"
Origin storage: "/deno-dir/location_data"

Remote modules cache: "/deno-dir/deps" とある通り、/deno-dir/deps 以下のディレクトリにダウンロードされて、キャッシュされていることがわかります。 確認します。

# 実行結果は一部省略
$ tree /deno-dir/deps
/deno-dir/deps
`-- https
    `-- deno.land
        |-- 36787f82612f5ba8672f136ec0b2ea965f132c9befbf1675e62363ae08caa974
        |-- 36787f82612f5ba8672f136ec0b2ea965f132c9befbf1675e62363ae08caa974.metadata.json
        |-- 4c6be3d215f27a63ef23312109ee64fd8464c0f78fa8c95ca4636f77e9eb694b
        |-- d81b6a4c078ed0c3b11068d078a003e15fc11d4d0c52923a4e09e4e5bc6aa98e.metadata.json
        |-- e69a1acdd75c91705c4d0c85a7d92d5458b8db838db0266acaf0f715e99a5ff9
        `-- e69a1acdd75c91705c4d0c85a7d92d5458b8db838db0266acaf0f715e99a5ff9.metadata.json

このようなキャッシュ以外のところでの、新しいモジュールの管理方法が、deno vendor です。

deno vendor を実行してみます。

$ deno vendor sample.ts
Download https://deno.land/std@0.128.0/http/server.ts
Download https://deno.land/std@0.128.0/async/mod.ts
Download https://deno.land/std@0.128.0/async/deadline.ts
Download https://deno.land/std@0.128.0/async/debounce.ts
Download https://deno.land/std@0.128.0/async/deferred.ts
Download https://deno.land/std@0.128.0/async/delay.ts
Download https://deno.land/std@0.128.0/async/mux_async_iterator.ts
Download https://deno.land/std@0.128.0/async/pool.ts
Download https://deno.land/std@0.128.0/async/tee.ts
Vendored 9 modules into vendor/ directory.

To use vendored modules, specify the `--import-map` flag when invoking deno subcommands:
  deno run -A --import-map vendor/import_map.json sample.ts

$ tree
.
|-- sample.ts
|-- docker-compose.yml
|-- dockerfile
`-- vendor
    |-- deno.land
    |   `-- std@0.128.0
    |       |-- async
    |       |   |-- deadline.ts
    |       |   |-- debounce.ts
    |       |   |-- deferred.ts
    |       |   |-- delay.ts
    |       |   |-- mod.ts
    |       |   |-- mux_async_iterator.ts
    |       |   |-- pool.ts
    |       |   `-- tee.ts
    |       `-- http
    |           `-- server.ts
    `-- import_map.json

現在のディレクトリに vendor ディレクトリが作成され、参照しているモジュール群が展開されました。

このローカルのモジュール群を参照するには、deno vendor の実行結果に書いているように、 --import-map vendor/import_map.json をつけて次のように実行します。

deno run --allow-net=0.0.0.0:8080 --import-map vendor/import_map.json sample.ts

deno vendor の目的は、大きく二つあります。 1 つ目は、外部モジュールを本体のリポジトリの管理下に置くこと。このことで外部モジュールも本体と同じ git で管理できます。 2 つ目は、デバッグ時に外部モジュールへの一時的なデバッグコードの挿入が容易にできるようになることです。

個人的には、2 つ目の恩恵が大きいと感じます。 外部モジュール関連の確認の際に、一時的にデバッグコードを仕込みたいということがあります。 この時は、リポジトリを(ソースと関係ないものも含めて)まとめてクローンして、デバックコードを挿入。 クローンしたディレクトリを参照するように自身コードを直すというという流れになると思います。 deno vendorを使うと、ソースだけを取得し、--import-map vendor/import_map.json をつけて実行するだけで良くなります。

パーミッションの許可プロンプトが標準動作になりました。

Deno では、パーミッションの管理が厳格にされていますが、結果多くのコマンドラインフラグを要求する傾向にあります。 Deno 1.9 では--prompt を実行時に渡すことで、プロンプトで権限を与えるオプションが公開されていました。 Deno 1.19 からは、この動作が標準になりました。 先に出している sample.ts の動作で確認します。

[sample.ts の実行結果]

# プロンプトを表示
$ deno run sample.ts
http://localhost:8000/
⚠️  ️Deno requests net access to "0.0.0.0:8080". Run again with --allow-net to bypass this prompt.
   Allow? [y/n (y = yes allow, n = no deny)]  y

# プロンプトを表示しない場合には、例外発生
$ deno run --no-prompt sample.ts
http://localhost:8000/
error: Uncaught (in promise) PermissionDenied: Requires net access to "0.0.0.0:8080", run again with the --allow-net flag
    const listener = Deno.listen({
                          ^
    at Object.opSync (deno:core/01_core.js:168:12)
    at opListen (deno:ext/net/01_net.js:40:17)
    at Object.listen (deno:ext/net/01_net.js:299:17)
    at Server.listenAndServe (https://deno.land/std@0.128.0/http/server.ts:181:27)
    at serve (https://deno.land/std@0.128.0/http/server.ts:557:23)
    at file:///usr/src/app/sample.ts:4:1

ネイティブ Web ストリームとしてファイル、ネットワークソケット、標準入力が使えるようになりました

Deno.FsFileDeno.conn に、ReadableStreamreadableWritableStreamwritable が追加されました。 このことで、Web ストリームを扱ういくつかの API と併せて使いやすくなったそうです。

サンプルとして、いくつかの使用例をリリースノートで示してものを参考に紹介します。

[file_001.ts]

const file = await Deno.create("./example.html");
const response = await fetch("https://example.com");
const body = await response.body;
if (!body) Deno.exit();

body.pipeTo(file.writable);

(具体的な URL は記載できませんが、書き換えて試すとわかりやすいと思います。)
コチラを実行すると、fetch で取得した結果をファイルに書き出すことができます。 これは、Deno.create により作られた WritableStream に、Response.body が持つ ReadableStream をつないで読み取りから書き出しまで Web ストリームで完結させた形です。

[stream_002.ts]

const resp = await fetch("http://0.0.0.0:8080", {
  method: "POST",
  body: Deno.stdin.readable,
});
console.log("Upload succeded?", resp.ok);

コチラは、標準入力から与えられたストリームを入力として、fetch の body プロパティに引き渡し、ストリーミングアップロードを実現したものです。 実行方法は、次のように紹介されています。 cat file.txt | deno run --allow-net=example.com stream_002.ts

[stream_003.ts]

import { serve } from "https://deno.land/std@0.128.0/http/server.ts";

serve(
  async (request) => {
    if (request.method === "POST") {
      // 受け取ったファイルのストリーム処理で書き込み
      const path = await Deno.makeTempFile();
      console.log(`output: ${path}`); // 書き出し先
      const file = await Deno.create(path);
      const body = await request.body;
      if (!body) return new Response("Not Save file");

      await body.pipeTo(file.writable);
      return new Response(`Saved file to ${path}`);
    } else {
      // ディスクからページを読み込みストリームで配信
      const path = "./example.html";
      const file = await Deno.open(path);
      return new Response(file.readable, {
        headers: { "content-type": "text/html" },
      });
    }
  },
  { port: 8080 }
);

stream_003.ts は、WEB のリクエストからストリームを作成しストリームで書き込む処理と、ディスクからストリームで読み込んだファイルを、クライアントにストリームで返す処理が実装されています。

リリースノートに説明は無いのですが、 stream_003.ts をサーバーに、stream_002.st をクライアントとして動作し、 内容の受け渡しができます。 動作は以下の通りです。

[サーバー側コンソール]

$  deno run --allow-net=0.0.0.0:8080 stream_003.ts
Check file:///usr/src/app/file/stream_003.ts

# ここでクライアント側コンソールを実行
output: /tmp/5323ea3c

^C # コンソールを停止
$ cat /tmp/5323ea3c
UPLOAD TEXT

[クライアント側コンソール]

$ cat upload.txt
UPLOAD TEXT

$ cat upload.txt | deno run --allow-net=0.0.0.0:8080 stream_002.ts
Upload succeded? true

CompressionStream と DecompressionStream API が追加されました

先述の Web ストリームの対応と同じくして CompressionStream と DecompressionStream API が追加されました。 これらは、MDN に個別に解説があるので、ご覧ください。

それぞれ、ストリームの圧縮と解凍を行う API です。 .gz ファイルを解凍する例は次のように示されています。

[decomp.ts]

const input = await Deno.open("./comp.txt.tar.gz");
const output = await Deno.create("./decomp.txt");
await input.readable
  .pipeThrough(new DecompressionStream("gzip"))
  .pipeTo(output.writable);

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

$ echo PLAIN FILE > plain.txt
$ gzip plain.txt -c > comp.txt.gz
$ deno run --allow-write --allow-read decomp.ts
Check file:///usr/src/app/comp/decomp.ts

$ cat decomp.txt
PLAIN FILE

console.log が循環参照を表示するようになった

console.log がログの対象となったオブジェクトの循環参照についてより分かりやすく表示するようになりました。 Deno 1.18 と比較します。

// Deno 1.18
> const x = { a: {}, b: {}, foo: { deno: "land", bar: 1 } };
> x.a.x = x;
> x.b.y = x;
> console.log(x);
{ a: { x: [Circular] }, b: { y: [Circular] }, foo: { deno: "land", bar: 1 } }

> x.b.x = x.a;
> console.log(x.b.x);
{
  x: {
    a: [Circular],
    b: { y: [Circular], x: [Circular] },
    foo: { deno: "land", bar: 1 }
  }
}

// Deno 1.19
> const x = { a: {}, b: {}, foo: { deno: "land", bar: 1 } };
> x.a.x = x;
> x.b.y = x;
> console.log(x);
<ref *1> { a: { x: [Circular *1] }, b: { y: [Circular *1] }, foo: { deno: "land", bar: 1 } }

> x.b.x = x.a;
> console.log(x.b.x);
<ref *1> {
  x: <ref *2> {
    a: [Circular *1],
    b: { y: [Circular *2], x: [Circular *1] },
    foo: { deno: "land", bar: 1 }
  }
}

Deno 1.19 では、循環の対象になっているオブジェクト を<ref *1> のように示すようになりました。 循環の対象が複数ある場合、より顕著に Deno 1.19 の表示の方がわかりやすくなります。

signal listener API が安定化

Deno 1.16 で登場した signal listener API が、安定化しました。 ただし、Windows では現在も使用できないそうですので、ご注意下さい。

リリースノートに記載のサンプルは次のようになっています。

const listener = () => {
  console.log("Got SIGTERM!");
};

// Starts listening for SIGTERM
Deno.addSignalListener("SIGTERM", listener);

// Stops listening for SIGTERM
Deno.removeSignalListener("SIGTERM", listener);

Unix ソケットを使用して、HTTP 接続ができるようになりました

Deno では Unix ソケット介した TCP 接続をこれまで使用できませんでした。 Deno 1.19 から、HTTP も Unix ソケットで提供できるようになります。 使用には、--unstable の付与が実行時に必要になります。

リリースノートに記載のサンプルは次のようになっています。

import { serveListener } from "https://deno.land/std/http/server.ts";

const listener = Deno.listen({ transport: "unix", path: "/path/to/socket" });

serveListener(listener, (req) => {
  return new Response("Hello World");
});

HTTP の受け口として NginxのようなWebサーバを使うような構成を取っていた時、 これまでは WEBサーバーから アプリケーションが使用している ポートへの転送だけでした。 これからは Unix ソケットも使用してWEBサーバーと連携できるようになります。

unstable な API Deno.Conn.setNoDelay() と Deno.Conn.setKeepAlive() が追加されました。

TCP接続を行う Deno.Conn に2つのAPIが増えました。

  • Deno.Conn.setNoDelay() : Nagle アルゴリズムの設定
  • Deno.Conn.setKeepAlive() : キープアライブの設定

使用には、--unstable の付与が実行時に必要になります。

unstable な Deno.getUid APIが追加されました

Deno 1.19 では、deno プロセスを実行しているユーザーのuidを取得できるようになりました。 リリースノート記載のサンプルは次のようになっています。

const interfaces = Deno.getUid();
console.log(interfaces);

実行には、--unstable に加え --allow-env も必要になります。実行すると次のようになります。

$ whoami
root

$ id root
uid=0(root) gid=0(root) groups=0(root)

$ deno run --unstable --allow-env get_uid.ts
0

unstable な Deno.networkInterfaces APIが追加されました

Deno 1.19 では、実行環境のネットワークインターフェース取得できるようになりました。 リリースノート記載のサンプルは次のようになっています。

const interfaces = Deno.networkInterfaces();
console.log(interfaces);

Deno.getUid 同様に実行には、--unstable に加え --allow-env も必要になります。実行すると次のようになります。

$ deno run --unstable --allow-env get_network.ts
Check file:///usr/src/app/network/get_network.ts
[
  {
    family: "IPv4",
    name: "lo",
    address: "127.0.0.1",
    netmask: "255.0.0.0",
    scopeid: null,
    cidr: "127.0.0.1/8",
    mac: "00:00:00:00:00:00"
  },
  ...
]

--allow-env を設定することで環境変数すべてへの参照を許可したくないので、--allow-env=hogehoge のような設定ができないか確認しましたが、API ドキュメント には、詳細の記載がありませんでした。 改めてstable になった時に確認したいと思います。

V8 9.9 へ更新されました

Deno に同梱される V8 のバージョンが、9.9 にアップデートされました。 これにより、Intlオブジェクトに機能が追加されています。

MDNに、Intlについて詳細な解説があります。

ロケールの拡張機能

言語に対応したカレンダーが取得できるようになっています。

> const japanLocale = new Intl.Locale("ja-JP");
> japanLocale.calendars
[ "gregory", "japanese" ]

識別子の列挙

V8 が対応する言語と、通貨のリストを取得できるようになりました。

> Intl.supportedValuesOf("calendar");
[
  "buddhist",      "chinese",
  "coptic",        "dangi",
  "ethioaa",       "ethiopic",
  "gregory",       "hebrew",
  "indian",        "islamic",
  "islamic-civil", "islamic-rgsa",
  "islamic-tbla",  "islamic-umalqura",
  "iso8601",       "japanese",
  "persian",       "roc"
]

> Intl.supportedValuesOf("currency");
[
  "AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD",
  "AWG", "AZN", "BAM", "BBD", "BDT", "BGN", "BHD", "BIF",
  "BMD", "BND", "BOB", "BRL", "BSD", "BTN", "BWP", "BYN",
  "BZD", "CAD", "CDF", "CHF", "CLP", "CNY", "COP", "CRC",
  "CUC", "CUP", "CVE", "CZK", "DJF", "DKK", "DOP", "DZD",
  "EGP", "ERN", "ETB", "EUR", "FJD", "FKP", "GBP", "GEL",
  "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", "HKD", "HNL",
  "HRK", "HTG", "HUF", "IDR", "ILS", "INR", "IQD", "IRR",
  "ISK", "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF",
  "KPW", "KRW", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR",
  "LRD", "LSL", "LYD", "MAD", "MDL", "MGA", "MKD", "MMK",
  "MNT", "MOP", "MRU", "MUR", "MVR", "MWK", "MXN", "MYR",
  "MZN", "NAD", "NGN", "NIO",
  ... 58 more items
]

その他

  • Deno.test のサニタイザーエラーの記述が見やすくなりました
  • Deno.File は、Deno.FsFile に名前が変更されました
  • deno compile が、より確実に動作するようになりました
  • ファイルウォッチャーの再起動時に 画面のクリアを無効にするオプション --no-clear-screen が追加されました
  • deno coverage に --output フラグが追加されました
  • LSP が改善しました

まとめ

今回のリリースでは、deno vendor が特に興味惹かれるものだったと感じます。 また、以前個人的にUnix ソケットで HTTP が使用できないのか確認して、当時できないことを確認していたこともあり、嬉しさがあるものでした。

ファイルアクセスもWebAPIとの共通の仕様でアクセスできるようになり、嬉しい点が多いです。 ストリーム処理について学んでみるきっかけができたと感じるので、近いうちに勉強してみたいと思いました。

明日は、 Deno 1.20のリリースノート追っかけ記事を公開予定です。

P.S.

採用情報

■募集職種
yumenosora.co.jp