虎の穴開発室ブログ

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

MENU

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

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

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

Deno 1.33

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

始めに

最初に、Deno 1.33 自体の話ではなく、Deno 2 に向け様々な動きがある中で、変わらないプロジェクトの目標について書かれています。

ポイントは次の 3 つです。

  • コーディングを楽にすること
    開発にすぐ取り組める環境を整備し生産性を高めること。この方針による LSP の強化。

  • クラス最高パフォーマンス
    速度と効率はユーザーにとって重要なもの。このリリースでは HTTP と WebSocket のパフォーマンスが向上。

  • 妥協の無いセキュリティ
    オプトインによる許可モデルが採用され、Deno に組み込まれています。これから数カ月以内にパーミッションシステムに新しい機能の導入が予定されています。

Deno 1.33 のリリース内容はこれらの目標に向けたものであり、次のマイナーリリースでもパフォーマンス向上などの目標とともに、Node/npm との互換性に重点を置くということでした。

ビルトイン KV データベース

Deno にビルトインされた データベースが登場しました。 deno 1.32 時点で、unstable な API として追加されていましたが、今回のリリースノートで紹介されています。

Deno KV は、ビルトインされたデータベースですので、外部のモジュールなどを導入せずに使用できます。 さらに、Deno deploy でも使用でき、地理的に複製されたグローバルなデータベースとして稼働すると紹介されています。 今すぐに使用できるような文面で記述されているのですが、こちらは現在申し込みが必要です。

興味があれば申し込んでみましょう。

dash.deno.com

私は、申し込みして招待が返って来るまで約 2 週間程度かかりました。

最後にサンプルを紹介します。 次のように使用できます。

const db = await Deno.openKv();

await db.set(["keys", "key1"], "string");
await db.set(["keys", "key2"], { a: 1, b: "string" });

const entries = db.list({ prefix: ["keys"] });

for await (const entry of entries) {
  console.log(entry);
}
// => { key: [ "keys", "key1" ], value: "string", versionstamp: "00000000000000010000" }
// => {
//      key: [ "keys", "key2" ],
//      value: { a: 1, b: "string" },
//      versionstamp: "00000000000000020000"
//    }

console.log(await db.get(["keys", "key1"]));
// -> { key: [ "keys", "key1" ], value: "string", versionstamp: "00000000000000010000" }

console.log(await db.get(["keys", "key2"]));
// => {
//      key: [ "keys", "key2" ],
//      value: { a: 1, b: "string" },
//      versionstamp: "00000000000000020000"
//    }

await db.set(["keys", "key1"], "string-update");
console.log(await db.get(["keys", "key1"]));
// => {
//      key: [ "keys", "key1" ],
//      value: "string-update",
//      versionstamp: "00000000000000030000"
//    }

await db.delete(["keys", "key1"]);
console.log(await db.get(["keys", "key1"]));
// => { key: [ "keys", "key1" ], value: null, versionstamp: null }

// .atomic .commit を使うことで、複数の操作を纏めることができる。
await db
  .atomic()
  .check({ key: ["keys", "key3"], versionstamp: null })
  .set(["keys", "key3"], 1)
  .commit();

console.log(await db.get(["keys", "key3"]));
// => { key: [ "keys", "key3" ], value: 1, versionstamp: "00000000000000090000" }

await db.close();

より詳細な仕様についてはドキュメントで確認できます。

deno.com

また、Deno KVについての詳しい解説や検証もされていますので、これらも参考になると思います。

qiita.com

deno.json の構造が、フラットなものになりました

deno.json の構造がフラットになり、読み書きが容易になりました。
lint、fmt、bench、test のセクションで、ネストした深いところに書いていた記述をセクションのトップレベルで書けるようになっています。

変更例をリリースノートの記載を引用し紹介します。

[新方式導入前]

{
  "lint": {
    "files": {
      "exclude": ["gen.ts"]
    }
  },
  "fmt": {
    "options": {
      "lineWidth": 80
    }
  }
}

[新方式導入後]

{
  "lint": {
    "exclude": ["gen.ts"]
  },
  "fmt": {
    "lineWidth": 80
  }
}
新方式導入前 新方式導入後
bench.files.include bench.include
bench.files.exclude bench.exclude
fmt.files.include fmt.include
fmt.files.exclude fmt.exclude
fmt.options.useTabs fmt.useTabs
fmt.options.lineWidth fmt.lineWidth
fmt.options.indentWidth fmt.indentWidth
fmt.options.singleQuote fmt.singleQuote
fmt.options.proseWrap fmt.proseWrap
fmt.options.semiColons fmt.semiColons
lint.files.include lint.include
lint.files.exclude lint.exclude
test.files.include test.include
test.files.exclude test.exclude

以前の方式に対しても互換性がありますが、将来的に古い構成は非推奨になるそうなので、積極的に対応していきたいですね。

動的インポートの権限チェックの更新

動的インポートする際に、これまでよりも権限を要求することが少なくなります。
動作比較してみます。

いくつかのサンプルを動かして確認。

[dynamic_import_1.ts]

await import("https://deno.land/std/version.ts");
$ deno -V
deno 1.32.3

$ deno run dynamic_import_1.ts
✅ Granted net access to "deno.land".
$ deno -V
deno 1.33.0

$ deno run dynamic_import_1.ts
# => パーミッションの確認プロンプトが出ない。

今回、大きく変わったのがこのパターンです。 静的解析できる動的インポートは、パーミッションの確認が要求されないようになっています。 静的できるものはモジュールグラフの一部になっているので、deno info で確認できます。

$ deno info dynamic_import_1.ts
local: /usr/src/app/1_33/dynamic_import_1.ts
emit: /deno-dir/gen/file/usr/src/app/1_33/dynamic_import_1.ts.js
type: TypeScript
dependencies: 1 unique
size: 422B

file:///usr/src/app/1_33/dynamic_import_1.ts (51B)
└── https://deno.land/std@0.185.0/version.ts (371B)

引き続き、静的に解析できない動的インポートは確認要求を受けるので次のサンプルを用意して確認します。

[dynamic_import_2.ts]

await import(`https://deno.land/std/version.ts`);

[dynamic_import_3.ts]

await import("" + "https://deno.land/std/version.ts");

[dynamic_import_4.ts]

const STD_VERSION = "0.185.0"
await import(`https://deno.land/std@${STD_VERSION}/version.ts`);

[dynamic_import_5.ts]

const someVariable = "./const.ts";
await import(someVariable);

[const.ts]

export const greet = 'Hello, Deno!';

動作させると次の様になります。

$ deno run dynamic_import_2.ts
✅ Granted net access to "deno.land".

$ deno run dynamic_import_3.ts
✅ Granted net access to "deno.land".

$ deno run dynamic_import_4.ts
✅ Granted net access to "deno.land".

$ deno run dynamic_import_5.ts
✅ Granted read access to "/usr/src/app/1_33/const.ts".

$ deno info dynamic_import_2.ts
local: /usr/src/app/1_33/dynamic_import_2.ts
emit: /deno-dir/gen/file/usr/src/app/1_33/dynamic_import_2.ts.js
type: TypeScript
dependencies: 0 unique
size: 55B

file:///usr/src/app/1_33/dynamic_import_2.ts (55B)

# => https://deno.land/std@0.185.0/version.ts (371B) が出てこない

それぞれ静的に解決しないものは、パーミッションの確認プロンプトが出ることを確認できます。 リリースノートでは静的に解析できない例が示されており、それらを参考に容易をしました。 静的解析できそうに見えますが、dynamic_import_2.ts の例のように、

await import(`https://deno.land/std/version.ts`);

は、静的解析できずにチェック対象になります。リリースノートでも文字列リテラルを使用しないでほしい旨が載っています。

CLI の改善

deno bench に --no-run フラグが追加

deno bench に --no-run フラグが追加され、実行せずにベンチファイルがキャッシュされます。

deno task に unset コマンドが追加

deno task で使用できるクロスプラットフォームシェルにunsetコマンドが追加されました。 これにより、環境変数を削除できるようになりました。

以下の deno.json を用意して、確認します。

[deno.json]

{
  "tasks": {
    "do_export": "export VAR=1 && deno eval 'console.log(Deno.env.has(\"VAR\"))'",
    "check": "deno eval 'console.log(Deno.env.has(\"VAR\"))'",
    "do_unset": "unset VAR && deno eval 'console.log(Deno.env.has(\"VAR\"))'"
  }
}

動作させると次のようになります。

$ deno task do_export
Task do_export export VAR=1 && deno eval 'console.log(Deno.env.has("VAR"))'
true

$ deno task check
Task check deno eval 'console.log(Deno.env.has("VAR"))'
false

$ deno task do_unset
Task do_unset unset VAR && deno eval 'console.log(Deno.env.has("VAR"))'
false

deno task do_export を実行してから、deno task check をしたときに維持されているかと思っていたのですが、deno の提供するクロスプラットフォームの環境変数だったというのが確認できます。

改めて、以下の deno.json を作成して確認します。

[deno.json]

{
  "tasks": {
    "do_export": "export VAR=1 && deno eval 'console.log(Deno.env.has(\"VAR\"))'",
    "do_unset": "unset VAR && deno eval 'console.log(Deno.env.has(\"VAR\"))'",
    "export_to_unset": "deno task do_export && deno task do_unset",
    "export_to_unset_simple": "export VAR=1 && unset VAR && deno eval 'console.log(Deno.env.has(\"VAR\"))'"
  }
}

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

$ deno task do_export
Task do_export export VAR=1 && deno eval 'console.log(Deno.env.has("VAR"))'
true

$ deno task export_to_unset_simple
Task export_to_unset_simple export VAR=1 && unset VAR && deno eval 'console.log(Deno.env.has("VAR"))'
false

$ deno task export_to_unset
Task export_to_unset deno task do_export && deno task do_unset
Task do_export export VAR=1 && deno eval 'console.log(Deno.env.has("VAR"))'
true
Task do_unset unset VAR && deno eval 'console.log(Deno.env.has("VAR"))'
false

環境変数が削除されているのが確認できます。

Deno API の変更

Deno.run APIが非推奨になりました

Deno.Command API が安定化されたことにより、Deno.run APIが非推奨になりました。 Deno 2 では、削除されるので、Deno.Command に移行することが推奨されます。

Deno.serve API の変更

Deno.serve API は、安定化に向けて、APIのオーバーロードが削除になりました。 削除されたオーバーロードは、Deno.serve(handler: Deno.ServeHandler, options: Deno.ServeOptions) です。

残るオーバーロードの中からいずれかを選択し、書き換えることが推奨されます。

Deno.serve API は、来月には、安定化が予定されており、Deno.serveHttpよりも使われるAPIになることが見込まれます。

std ライブラリの変更

std/encoding モジュールへの破壊的な変更

Deno 1.32 リリース時の記事で、案内されていた通り std/encoding 以下にある6つのモジュールが、最上位に移動しました。
非推奨の移動前のモジュールは完全に削除されており、移動後の最上位のパスに切り替えていない場合には、変更する必要があります。

また、使用しているモジュールが移動前のパスからモジュールを呼び出している場合、自身のソースコードを直しても対応できない可能性があります。 その場合、次のインポートマップを与えることで対応できる可能性があるものとして紹介されています。

{
  "imports": {
    "https://deno.land/std/encoding/yaml.ts": "https://deno.land/std@0.179.0/encoding/yaml.ts"
  }
}

fs.exists が安定化

std/fs の exists と existsSync は、std@0.111.0で非推奨になっていましたが、このリリースより非推奨が取り消されました。

std/csv モジュールの更新

CsvStringifyStream API が追加になりました。こちらのAPIは、ストリーミング入力をcsv行のストリームに変換します。 リリースノートに記載された以下のサンプルを引用し、動かしてみます。

[output_csv.ts]

import { CsvStringifyStream } from "https://deno.land/std@0.185.0/csv/mod.ts";
import { readableStreamFromIterable } from "https://deno.land/std@0.185.0/streams/mod.ts";

const file = await Deno.open("data.csv", { create: true, write: true });
const readable = readableStreamFromIterable([
  { id: 1, name: "one" },
  { id: 2, name: "two" },
  { id: 3, name: "three" },
]);

await readable
  .pipeThrough(new CsvStringifyStream({ columns: ["id", "name"] }))
  .pipeThrough(new TextEncoderStream())
  .pipeTo(file.writable);

動かすと次の様になります。

$ deno run --allow-write=. output_csv.ts

$ cat data.csv
id,name
1,one
2,two
3,three

こちらに加えて、CsvStream が、CsvParseStream に改称されました。こちらも動かしてみます。

[input_csv.ts]

import { CsvParseStream } from "https://deno.land/std@0.185.0/csv/csv_parse_stream.ts";

const file = await Deno.open("./data.csv");

const linesStream = file.readable
  .pipeThrough(new TextDecoderStream())
  .pipeThrough(new CsvParseStream({ skipFirstRow: true }));

for await (const line of linesStream) {
  console.log(line);
}

動作させると次の通りです。

$ deno run --allow-read=. input_csv.ts
{ id: "1", name: "one" }
{ id: "2", name: "two" }
{ id: "3", name: "three" }

ドキュメントにある通り、CSVでエンコードされたストリームを読むAPIです。

その他

  • Node/npm との互換性の改善

    • node:crypto、node:http、node:vm モジュールが大きく改善
      • node:crypto API のほとんどをポリフィル
      • node:http と node:vm を変更
    • npm パッケージのキャッシュ処理が改善
  • HTTP サーバーと、WebSocket のクライアント/サーバーのパフォーマンス向上

  • LSP のドキュメントが、初期化時にプリロードされるようになりました。
  • Deno 1.33 には、V8 11.4 が同梱されます。

今回のリリースとリリースノート記事では、Deno 2に向けてプロジェクトが何を目指すのかを端的に示すことから始まっていたのが非常に印象的でした。

4月 14 日からベルリンで行われていた Node Congress 2023 では、Ryan Dahl が Deno 2 や Deno KV などについて話すセッションが持たれていました。

内容はこちら。

www.youtube.com

ここでは、Deno 2 は今夏にリリースになることが語られています。 また、Deno KV についてはかなりの時間を割いて話しているので、Denoと Deno Deployにとってのキラーソリューションになる(する)ことが伺われます。

この虎の穴開発室ブログで、「Deno で掲示板サイトを作ろう! with upstash & supabase」という連載を進めています。 この連載ではセッションストレージとして、ローカル開発では Redis、Deno Deploy では、Upstash Redis を使うための切り替えとそのための抽象化をしています。 key-value ストアとして切り替えを行うことで、これらの環境構築的な実装から、より生産的なアプリケーションそのものに取り組みする時間を増やせるなら、冒頭に記述されている開発を楽にするという目的にも合致すると感じます。

夏に向けてマイナーアップデートが続きますが、引き続き追いかけていきます。

採用情報

虎の穴ラボでは一緒に働く仲間を募集中です!
この記事を読んで、興味を持っていただけた方はぜひ弊社の採用情報をご覧下さい。
yumenosora.co.jp