Denoは、Nodeの作者がNodeの反省を生かして作り上げたランタイムシステムです。
Nodeより高速・高性能で使い方も容易です。
が、いまいち流行ってない理由の大きなひとつがnpmが使えないことです。
Denoユーザの多くが古臭くて非標準的で奇怪なパッケージ管理から開放されたことに歓喜しているそうですが、それはそれとして過去の遺産が使えないので新たなライブラリを探さなければならず手間がかかります。
またDenoは様々なレジストリが使えることが利点とされていますが、個人的にはこれ欠点だと思っています。
探し回るのなんて面倒だから一か所にまとめてくれよ。
そんなわけで2022/08/15にやっぱnpm対応するわという公式発表がなされました。
と思ったらわずか10日後の2022/08/25に対応したって発表されました。
はえーよ。
ということで以下該当リリースノートの紹介です。
npm以外にも様々な更新が行われています。
Deno 1.25 Release Notes
Deno1.25がリリースされ、以下のような新機能や変更が追加されました。
・deno init subcommand
・Experimental npm support
・New HTTP server API
・Improvements to startup time
・FFI API improvements
既にインストール済みの場合は、以下のコマンドでアップグレードできます。
deno upgrade
初めてであれば以下のコマンドでインストールしましょう。
# MacOS・Linux
curl -fsSL https://deno.land/x/install/install.sh | sh
# Windows
iwr https://deno.land/x/install/install.ps1 -useb | iex
より詳細はこちら。
deno init subcommand
Denoで新しいプロジェクトを始めるのは、信じられないほど簡単です。
たったひとつのファイルを用意するだけです。
設定ファイルもマニフェストもビルドスクリプトも必要ありません。
他のエコシステムから移動してきたユーザは、このシンプルさに戸惑うかもしれません。
彼らはしばしば、正しいプロジェクト構造を作るための足掛かりとなるツールを探しはじめます。
ということで本リリースでは、基本的なDenoプロジェクトの準備ができるdeno init
コマンドを追加しました。
$ deno init
✅ Project initialized
Run these commands to get started
deno run main.ts
deno test
$ deno run main.ts
Add 2 + 3 = 5
$ deno test
Check file:///dev/main_test.ts
running 1 test from main_test.ts
addTest ... ok (6ms)
ok | 1 passed | 0 failed (29ms)
このコマンドを打つと2つのファイルmain.ts
とmain_test.ts
が生成されます。
main.ts
にはDenoプログラムの書き方の基本的な例が、そしてmain_test.ts
にはそのテストの書き方の例が書かれています。
また、引数を指定することで、指定のディレクトリでプロジェクトを初期化することもできます。
$ deno init my_deno_project
✅ Project initialized
Run these commands to get started
cd my_deno_project
deno run main.ts
deno test
この機能へのご意見は、Issueで聞かせてください。
Experimental npm support
本リリースでは、npmの実験的なサポートが追加されました。
この機能はまだ開発中であることに注意してください。
入ったばかりの機能であるため、うまく動作しないシナリオが見つかる可能性があります。
不具合が見つかったらIssueで報告してください。
今後のリリースにおいて、互換性とユーザエクスペリエンスを向上させていく予定です。
この動作は、例で見せるのが一番わかりやすいと思います。
// main.ts
import express from "npm:express";
const app = express();
app.get("/", function (req, res) {
res.send("Hello World");
});
app.listen(3000);
console.log("listening on http://localhost:3000/");
npmを取り込むには、以下の指定子を使います。
npm:<package-name>[@<version-requirement>][/<sub-path>]
その後、以下のコマンドを打つだけでexpressサーバが起動します。
$ deno run --unstable -A main.ts
listening on http://localhost:3000/
npm install
コマンドを打つ必要はなく、さらにnode_modules
ディレクトリが作られることもありません。
また、これらのパッケージにはDenoと同じパーミッションが適用されます。
現時点では不要なパーミッションが要求されることがありますが、将来的にはネットワークのパーミッションだけになる予定です。
この指定子は現在、deno run
・deno test
・deno bench
でのみ動作します。
型指定はサポートされておらず、またLanguage Server、deno vendor
・deno info
・deno install
には対応していません。
また、コマンドラインからも以下の形式でnpmを直接実行することができます。
npm:<package-name>[@<version-requirement>][/<binary-name>]
こちらは実行例です。
$ deno run --unstable --allow-env --allow-read npm:cowsay@1.5.0 Hello there!
______________
< Hello there! >
--------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
$ deno run --unstable --allow-env --allow-read npm:cowsay@1.5.0/cowthink What to eat?
______________
( What to eat? )
--------------
o ^__^
o (oo)\_______
(__)\ )\/\
||----w |
|| ||
このnpmパッケージは現在env
とread
のパーミッションが必要ですが、将来的には不要になります。
今後は、deno install
およびlockfileのサポートを追加します。
この機能はまだ実験的なものであるため、有効にするには--unstable
オプションを必要とします。
New HTTP server API
Deno1.25では、最高のパフォーマンスを提供するために構築された、新たな実験的HTTPサーバを導入します。
ベンチマークでは、Nodeと比較して4倍以上、そしてDenoの既存サーバより3倍高速です。
それどころかRustの標準的HTTPサーバであるHyperよりも20%高速です。
新しいDeno.serve()
は、これまであったstd/http
のserve()
をそのまま置き換えることができるので、std/http
ユーザは非常になじみやすいと思います。
基本的な使い方はこれだけです。
Deno.serve(() => new Response("Hello, world!"));
詳細なドキュメントはdoc.deno.landから見ることができます。
以下の例では、リクエストに対してReactコンポーネントが動的レンダリングした結果を返します。
import * as React from "npm:react";
import { renderToReadableStream } from "npm:react-dom/server";
const App = () => (
<html>
<body>
<h1>Hello World</h1>
</body>
</html>
);
const options = {
headers: {
"Content-Type": "text/html",
},
};
Deno.serve(
{ port: 4500 },
async () => new Response(await renderToReadableStream(<App />), options),
);
このサーバは、いまのところHTTP/1.1
のみをサポートしています。
今後はHTTP/2
のシームレスなサポートを予定しています。
またレスポンスボディの圧縮など、既存サーバにある他の機能の実装も予定されています。
この新しいサーバは、まだ本番運用に使用するべきではありませんが、ぜひ試しに遊んでベンチマークを取ってみてください。
この機能はまだ実験的なものであるため、有効にするには--unstable
オプションを必要とします。
バグを発見したらIssueで報告してください。
新しいHTTPサーバのパフォーマンスについては、より詳しい記事をブログにアップする予定なので、ご期待ください。
Improvements to startup time
Denoの起動時には、リモートモジュールの依存関係を解析して必要なファイルのキャッシュを行っています。
この解析は、大きめのファイルでは大きな時間がかかるため、Deno1.25ではファイルごとにバックグラウンドでキャッシュを行うようにしました。
この変更により、Deno1.25では起動時間がかなり改善されたことを体感できると思います。
たとえばts_morph
はTypeScriptコンパイラに依存していますが、このJavaScriptファイルは10MBもあります。
import { Project } from "https://deno.land/x/ts_morph@15.1.0/mod.ts";
console.log(Project);
1.25より前のバージョンでは、このコードを実行するたびに1080msほどの時間がかかっていました。
最新バージョンでは、2回目以降はわずか225msの時間で済みます。
さらにメモリの使用量に関する改善も行われました。
計測するとメモリ使用量が大きく削減されていることがわかるでしょう。
FFI API improvements
まだunstableですが、Foreign Function Interfaceに新機能を追加し、パフォーマンスを向上させました。
Uint8Array & 64-bit numbers in Fast FFI calls
Deno1.2.4においてFFIコールの改善を行い、劇的なパフォーマンス向上を得ましたが、この最適化は数値型にのみ対応したものでした。
今回のリリースにおいては、これをu64
・i64
・pointer
・buffer
型にも対応しました。
これによって、多くのFFIモジュールがネイティブ並みのパフォーマンスを発揮できるようになりました。
New buffer type
かつて、pointer
型はパラメータとしてTypedArray
を渡すことができました。
本リリースでは、TypedArray
のパフォーマンスを最適化するためにbuffer
型を導入しました。
const { symbols: { hash } } = Deno.dlopen("libtest.so", {
hash: {
parameters: ["buffer", "u32"],
result: "u32",
},
});
const u8 = new Uint8Array([1, 2, 3]);
hash(u8, u8.byteLength);
buffer
型の返り値はpointer
型と同じです。
新機能の使用を推進するため、pointer
型はTypedArray
を受け付けなくなりました。
cpu: Apple M1
runtime: deno 1.24.0 (aarch64-apple-darwin)
file:///deno/test_ffi/tests/bench.js
hash() 180.77 ns/iter (176.22 ns … 195.53 ns) 181.3 ns 193.97 ns 195.35 ns
cpu: Apple M1
runtime: deno 1.25.0 (aarch64-apple-darwin)
file:///deno/test_ffi/tests/bench.js
hash() 56.01 ns/iter (55.43 ns … 62.43 ns) 56.03 ns 60.02 ns 61.33 ns
Updates to Deno.UnsafePointerView API
Deno.UnsafePointerView
インターフェイスに3つのstaticメソッドを追加しました。
const ptr = symbols.get_hello();
// Cの文字列からJSの文字列にする
const string = Deno.UnsafePointerView.getCString(ptr);
// ゼロコピーでArrayBufferからpointerに
const arrayBuffer = Deno.UnsafePointerView.getArrayBuffer(ptr);
// コピー
const copy = new Uint8Array(32);
Deno.UnsafePointerView.copyInto(ptr, copy);
感想
3倍ってなんだよ赤い奴かよ。
npmの話題に隠れてしまっていますが、性能の改善も相当なものですね。
これまでずっと、npmにはそのうち対応するよと言いつつ延々放置されていたのですが、いきなりえらい勢いで対応されました。
この急な変節と対応は、おそらくBunの登場が契機になったんじゃないかなと勝手に思っています。
BunはNodeよりもDenoよりも高速で、そしてnpmに対応しているのが特徴です。
元々は個人開発であり、いつ開発が止まるかわからないという状態で導入に踏み込むのはなかなか難しかったのですが、その後700万ドルの資金調達に成功して会社建てたらしいので今後の開発が安定しそうな雰囲気です。
そんなわけで新興のBunに足下を掬われないようnpmをサポートすることにしたあたりではないかと思っているのですが真相はどうなのでしょうね。
ということで、今後はDenoでもnpmを使えるようになります。
npmがネックで移行できなかったプロジェクトも、今後はDenoが選択肢に入ってくるかもしれませんね。