皆さんこんにちは。おっくんです。
去る 2023 年 3 月 23 日に Deno 1.32 がリリースされました。 今回も、リリースノートを参考に 変更事項の気になるところを紹介します。
Deno 1.32
Deno 1.32 での変更事項をDeno 1.32 リリースノートを元に確認します。
Node.js の互換性が強化されました
今回のリリースでも引き続き、Node.js との互換性の強化が入りました。
パッケージインストールの制限
リリースノートでは、package.json に記述があり、ベア指定子を使用した読み込みが、コードで使用されていた場合にパッケージがインストールされると読めたのですが、 Deno 1.31 と Deno 1.32 の間で比較を試みたところ、具体的な差異が出る部分が確認できませんでした。
遅延してのエラー表示
package.json に記述されたパッケージのインストールの際に、依存関係の解析にエラーが起きることがあります。このエラーが遅延して出力されるようになり、中断することが最小限になったとのことです。
package.json の自動検出を制御するフラグ --no-config --no-npm が追加
掲題の通り、--no-config または、--no-npm を使用することで、package.json を自動検出しなくなります。
Deno 1.32 のリリースノートとして記載があるものの、このフラグ自体は、Deno 1.31 では、既に deno run -h
でヘルプを見ると出てきます。
実際に使ってみると次のようになります。
$ deno -V deno 1.32.0 $ cat index.mjs import express from "express"; const app = express(); app.get("/", function (req, res) { res.send("Hello World"); }); console.log("start server") app.listen(8000); $ deno run -A --no-npm index.mjs error: Relative import path "express" not prefixed with / or ./ or ../ at file:///usr/src/app/index.mjs:1:21 $ deno run -A --no-config index.mjs error: Relative import path "express" not prefixed with / or ./ or ../ at file:///usr/src/app/index.mjs:1:21 $ deno run -A index.mjs start server
環境変数 DENO_NO_PACKAGE_JSON が追加
互換性の強化という項目ではありますが、こちらも package.json の読み込みを制御する環境変数です。
DENO_NO_PACKAGE_JSON=1
を設定することで、次のように動作します。
$ DENO_NO_PACKAGE_JSON=0 deno run -A index.mjs start server $ DENO_NO_PACKAGE_JSON=1 deno run -A index.mjs error: Relative import path "express" not prefixed with / or ./ or ../ at file:///usr/src/app/index.mjs:1:21
Node.js 環境から移設してきたものではない package.json を使う意図の無いプロジェクトでは、積極的に使用していくのがよいのではないでしょうか。
暗号化のサポート範囲拡張
node:crypto に createCipheriv と createDecipheriv の 2 つの API が導入されました。
詳細なそれぞれの仕様は、Node.js のドキュメントがわかりやすいです。
deno compile が、Web Worker と 動的インポートをサポート
Deno 1.31 で対応された動的インポートが強化され、Webワーカーも同一バイナリに取り込めるようになりました。 Deno 1.32 では、静的に解決できないインポートにも対応します。
比較し、確認します。
$ deno -V deno 1.31.0 $ cat main.js const dynamicModule = await import("./dynamic_module.ts"); console.log(dynamicModule.hello()); $ cat dynamic_module.ts export function hello(): string { return "hello world!"; } $ deno compile -o compiled_main main.ts $ ./compiled_main hello world!
Deno 1.31 のリリースノートを確認した際に、記載されていたポイントは「静的解析可能」という部分でした。
const dynamicModule = await import("./dynamic_module.ts");
のように、このソースは、インポートの対象が文字列で指定された、静的なものです。
では、静的解析できないものを確認します。
$ deno -V deno 1.31.0 $ cat main.js const dynamicModule = await import(import.meta.resolve("./dynamic_module.ts")); // <= 書き換えた console.log(dynamicModule.hello()); $ cat dynamic_module.ts export function hello(): string { return "hello world!"; } $ deno compile -o compiled_main main.ts Check file:///usr/src/app/main.ts Compile file:///usr/src/app/main.ts Emit compiled_main $ ./compiled_main error: Uncaught (in promise) TypeError: Module not found const dynamicModule = await import(import.meta.resolve("./dynamic_module.ts")); ^ at async file:///usr/src/app/main.ts:1:23
import.meta.resolve("./dynamic_module.ts")
のように、静的解析できないものを与えると、上記のようにエラーになります。
実行環境を Deno 1.32 に切り替えてリトライしてみます。
$ deno -V deno 1.32.0 $ cat main.js const dynamicModule = await import(import.meta.resolve("./dynamic_module.ts")); console.log(dynamicModule.hello()); $ cat dynamic_module.ts export function hello(): string { return "hello world!"; } # 静的解析できないものを、バイナリに含めるには、--include フラグを使う $ deno compile -o compiled_main --include dynamic_module.js main.js Check file:///usr/src/app/main.ts Compile file:///usr/src/app/main.ts Emit compiled_main $ ./compiled_main hello world!
静的に解析できない、(import.meta.resolve("./dynamic_module.ts")
をインポートし動作させることができました。
これと同様のことを、WebWorker のワーカー側のソースをバイナリに含めるときにも使用できます。 リリースノートにならって実装し動作確認します。
$ deno -V deno 1.32.0 $ cat main.ts const worker1 = new Worker(import.meta.resolve("./worker1.js"), { type: "module", }); worker1.postMessage("hello from main!"); worker1.onmessage = (e) => { console.log("main received", e.data); worker1.terminate(); }; const worker2 = new Worker(import.meta.resolve("./worker2.js"), { type: "module", }); worker2.postMessage("hello from main!"); worker2.onmessage = (e) => { console.log("main received", e.data); worker2.terminate(); }; $ cat worker1.js self.onmessage = (e) => { console.log("worker1 received:", e.data); self.postMessage("hello from worker1!"); }; $ cat worker2.js self.onmessage = (e) => { console.log("worker2 received:", e.data); self.postMessage("hello from worker2!"); }; # 失敗パターン --include を使わない $ deno compile -o worker_example main.ts Compile file:///usr/src/app/main.ts Emit worker_example # 実行時にエラーを起こす $./worker_example error: Uncaught (in worker "") Module not found error: Uncaught (in worker "") Module not found error: Uncaught (in promise) Error: Unhandled error in child worker. at Worker.#pollControl (ext:runtime/11_workers.js:157:19) # 成功パターン # --include は、複数個指定できる $ deno compile --include worker1.js --include worker2.js -o worker_example main.ts Compile file:///usr/src/app/main.ts Emit worker_example # 実行できる $ ./worker_example worker1 received: hello from main! main received hello from worker1! worker2 received: hello from main! main received hello from worker2!
Web Worker の動作も確認できました。
ここで疑問に1つの疑問が浮かぶかもしれません。
ワーカーのソースコードを静的解析可能な形で呼び出したらどうなるのか?です。
具体的には、次のようにワーカーを呼び出せれば、--include は不要なのではないか?ということです。
const worker1 = new Worker("./worker1.js", { type: "module", });
実際に、この状態で deno compile したものを実行すると、次のようにエラーになります。
$ ./worker_example error: Uncaught URIError: invalid URL: relative URL without a base: relative URL without a base at createWorker (ext:runtime/11_workers.js:39:14) at new Worker (ext:runtime/11_workers.js:108:16) at file:///usr/src/app/main.ts:1:17
URL として解釈できないというエラーになります。 Deno で、Web Worker を使用したマルチスレッドアプリケーションをバイナリ化したいとき、--includeは必須になります。
拡張子の無いファイルを deno run で実行できるようになりました
--ext フラグを使用することで、拡張子の無いファイルを実行できるようになりました。 ヘルプで、設定可能な値を確認できます。
$ deno run -h # 省略 --ext <ext> Set content type of the supplied file [possible values: ts, tsx, js, jsx]
こちらを使用し、動作確認してみます。
# 一旦 --ext を使用しないパターンで確認 $ cat ./no_ext_script #!/usr/bin/env -S deno run console.log("Hello Deno") # これでも 動作する。 $ ./no_ext_script Hello Deno # 関数をTypeScriptの構文で記述 $ cat ./no_ext_script #!/usr/bin/env -S deno run console.log("Hello Deno") function add(a: number, b:number):number{ return a + b } console.log(add(1, 2)) # 今回は失敗する => Typescript として解釈できていない様子が確認できる $ ./no_ext_script error: The module's source code could not be parsed: Expected ',', got ':' at file:///usr/src/app/no_ext_script:5:15 function add(a: number, b:number):number{
拡張子が無い場合に、JavaScript として解釈ができているようです。 TypeScriptの構文で、関数を記述したらエラーになりました。 --ext=ts オプションを設定して、Typescript として解釈させてみます。
# --ext オプションを設定 $ cat ./no_ext_script #!/usr/bin/env -S deno run --ext=ts console.log("Hello Deno") function add(a: number, b:number):number{ return a + b } console.log(add(1, 2)) # 動作します。 $ ./no_ext_script Hello Deno 3
この機能が何を目的としているのかですが、拡張子の無いスクリプトを作成したいという issue が出ていました。
2020年5月に立ち上がり、こちらを起点としたpull requestがいくつも立っています。
この確認では、--ext を指定していない時 JavaScript として解釈している様だと先に書きましたが、こちらは修正され TypeScript として解釈されるようになるようです。(本記事の公開タイミングでは既にマージ済みかもしれません。)
Deno API の変更
Deno.FileInfo.dev が、Windows環境 にも対応しました
Deno.FileInfo.dev が、Windows環境上でも定義されました Deno.FileInfo.dev のインターフェイス定義はこれまで null | number でしたが、これを機に number に変わっています。
次のように使用します。
$ Deno 1.32.1 exit using ctrl+d, ctrl+c, or close() REPL is running with all permissions allowed. To specify permissions, run `deno repl` with allow flags. > const fileInfo = await Deno.stat("./no_ext_script"); undefined > fileInfo.dev 3701651949
この値は、ファイルを含むデバイスのIDになっています。
インターフェースの差分は、次のドキュメントで確認できます。
Deno 1.31.3 時点
Deno 1.32.0 時点
「Linux/Mac OS only.」の記述が、Deno 1.32 では消えているのがわかります。
unstable な API の追加
次の2つのunstableなAPIが追加されました。
- Deno.DatagramConn.joinMulticastV4
- Deno.DatagramConn.joinMulticastV6
マルチキャストに使用するAPI群です。
Web API の変更
URLSearchParams.size のサポートを追加
whatwg による提案で策定された URLSearchParams.size() が実装されました。
提案の issue はこちらです。
簡単に動作確認します。
// deno 1.31 で確認 $ deno Deno 1.31.0 exit using ctrl+d, ctrl+c, or close() REPL is running with all permissions allowed. To specify permissions, run `deno repl` with allow flags. > const url = new URL('https://example.com?foo=1&bar=2'); undefined > const params = new URLSearchParams(url.search); undefined > params.size undefined > Array.from(params).length 2 >
Deno 1.31 では、.size は存在しておらず、Array.from()を使用することで個数を確認できるようになっています。
// Deno 1.32 で確認 $ deno Deno 1.32.1 exit using ctrl+d, ctrl+c, or close() REPL is running with all permissions allowed. To specify permissions, run `deno repl` with allow flags. > const url = new URL('https://example.com?foo=1&bar=2'); undefined > const params = new URLSearchParams(url.search); undefined > params.size 2 >
.size を使用すると、シンプルな記述ができます。
Web GPU API は削除されました
Web GPU は unstable なAPIとしてサポートをしていましたが、
Web GPU を使わないユーザーも deno バイナリサイズの肥大化と起動時間が長くなるというコストを背負う状況になっていました。
このリリースでは、Web GPU API は削除になります。
引き続き、起動時間に影響しない実装を調査するそうです。
標準ライブラリの変更
std/ 以下で提供されている標準モジュールのディレクトリ構造が大きく変わりました。
std/encoding/以下の6モジュールが std/ に移動されます。
Typescript 5.0 を同梱しています
Deno 1.32 に同梱されるTypeScriptは、最新の安定版 TypeScript 5.0 になりました。 これによる型チェックのパフォーマンスが向上しました。
注意事項として、 ES decorator はまだサポートされておらず、将来的なリリースで有効化される見込みです。
V8 11.2 を導入
Deno に同梱されている V8 のバージョンが 11.2 に変わりました。 いくつかの新しい機能を使えます。
この中で、リサイズ可能な ArrayBuffer が導入されていますが、こちら脆弱性の指摘がされています。 対象は、Deno 1.32.0 だけですので、Deno 1.32.1 以上を必ず使用しましょう。
脆弱性の指摘はこちら
リリースノート記事に書いていない機能
Deno 1.32.0 で、リリースノート記事では紹介にはありませんが、興味深い unstable なAPIが追加されました。
key-value ストアがランタイムに導入されました。
pull request
ドキュメント
pull requestとドキュメントを参考に、こちらを使ってみます。
[use_store_1.ts]
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 } await db.close();
このように動作します。
データの保存先を確認したところ、/deno-dir/location_data/[ハッシュ値]/kv.sqlite3
に保管されていました。
カレントディレクトリを変更して実行したところ、別のハッシュ値でパスが切られ保存されていました。
任意のファイルで永続化することもできます。 この場合には、--allow-write --allow-read の付与が必要です。
[use_store_2.ts]
const db = await Deno.openKv("store"); console.log(await db.get(["key1"])) await db.set(["key1"], "string") await db.close();
こちらを実行すると次のように動作します。
$ ls use_store_1.ts use_store_2.ts # 1回目 $ deno run --unstable --allow-read --allow-write use_store_2.ts { key: [ "key1" ], value: null, versionstamp: null } # store が増えている $ ls store use_store_1.ts use_store_2.ts # 2回目 => 1回目で書き込んだ内容が取得できる $ deno run --unstable --allow-read --allow-write use_store_2.ts { key: [ "key1" ], value: "string", versionstamp: "00000000000000010000" } # store を削除 $ rm store # store を削除したので、永続化した情報は無い $ deno run --unstable --allow-read --allow-write use_store_2.ts { key: [ "key1" ], value: null, versionstamp: null }
Deno.openKv に渡した名称でファイルが作成され、登録した内容が永続化できていることが確認できます。
また、Deno.openKv(":memory:")
のように呼び出すと、メモリ上にデータベースが構築されるので永続化されません。
少し中を探索するため、ソースを見ると Cache Web API 同様に SQLite の機能が使われているようです。
sqlite3 クライアントを使用し、中を覗いてみます。
$ sqlite3 store SQLite version 3.34.1 2021-01-20 14:10:07 Enter ".help" for usage hints. sqlite> .tables data_version migration_state queue_running kv queue sqlite> .schema kv CREATE TABLE kv ( k blob primary key, v blob not null, v_encoding integer not null, version integer not null ) without rowid; sqlite> select * fromkv; Error: near "fromkv": syntax error sqlite> select * from kv; key1|�" string|1|1
一部文字化けを起こしていますが、key1
をキーに、値 "string"
を持っていることが確認できます。
localStorage に加え、Deno でデータ永続化が可能なAPIが1つ増えたことになります。
本記事を書いている時ちょうど、さらに詳細に解説されている記事が公開されていましたので、そちらも紹介させていただきます。
まとめ
今回のリリースでは、--include の導入で Web Worker も含めてバンドルできるようになったこと、key-value ストアの導入の2点が特に気になるものでした。
特に key-value ストアは先に書いたとおり、データの永続化が可能なAPI として、localStorage との使い分けの点で少々悩ましい点を感じます。 ただ、localStorage ブラウザ互換のAPIであること、永続化されたときのデータ実体の保存先に差異がでるので、そのあたりで用途が分かれるのではないかと感じます。 今回は、リリースノート記事でも言及が無いものでしたので、stable になった時には、意図しているところについて言及があればうれしいと思います。
Deno 2.0 のマイルストーンが、少しずつ埋まり始めています。
deno.json の構成のフラット化の提案もされているものの、破壊的変更のバッチがついていないので不安にも思いましたが、pull requestを見ると旧構成もサポートが継続するようで安心しました。
いつ頃すべてクローズになり(過去のリリースでは100%完了でなくてもリリースされていることもある)、リリースになるのか見えてはいませんが首を長くして待ちましょう。
次回のリリースも追いかけます。
P.S.
採用
虎の穴では一緒に働く仲間を募集中です!
この記事を読んで、興味を持っていただけた方はぜひ弊社の採用情報をご覧下さい。
yumenosora.co.jp