【Deno】「やっぱnpmをサポートするわ」 → 10日後「サポートしたわ」

[Deno](https://deno.land/)は、[Node](https://nodejs.org/)の作者がNodeの反省を生かして作り上げたランタイムシステムです。
Nodeより高速・高性能で使い方も容易です。

が、いまいち流行ってない理由の大きなひとつが[npm](https://www.npmjs.com/)が使えないことです。
Denoユーザの多くが[古臭くて非標準的で奇怪なパッケージ管理から開放されたことに歓喜している](https://deno.com/blog/changes#compatibility-with-node-and-npm)そうですが、それはそれとして過去の遺産が使えないので新たなライブラリを探さなければならず手間がかかります。

またDenoは[様々なレジストリが使えることが利点](https://qiita.com/access3151fq/items/455c34ff0892b96e74ff#%E4%B8%AD%E5%A4%AE%E9%9B%86%E6%A8%A9%E7%9A%84%E3%81%AA%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%83%AC%E3%82%B8%E3%82%B9%E3%83%88%E3%83%AA)とされていますが、個人的にはこれ欠点だと思っています。
探し回るのなんて面倒だから一か所にまとめてくれよ。

そんなわけで2022/08/15に[やっぱnpm対応するわ](https://deno.com/blog/changes)という公式発表がなされました。

と思ったらわずか10日後の2022/08/25に[対応した](https://deno.com/blog/v1.25)って発表されました。
はえーよ。

ということで以下該当[リリースノート](https://deno.com/blog/v1.25)の紹介です。
npm以外にも様々な更新が行われています。


# [Deno 1.25 Release Notes](https://deno.com/blog/v1.25)


[Deno1.25](https://github.com/denoland/deno/releases/tag/v1.25.0)がリリースされ、以下のような新機能や変更が追加されました。

・deno init subcommand
・Experimental npm support
・New HTTP server API
・Improvements to startup time
・FFI API improvements

既にインストール済みの場合は、以下のコマンドでアップグレードできます。

```bash
deno upgrade
```

初めてであれば以下のコマンドでインストールしましょう。

```bash
# MacOS・Linux
curl -fsSL https://deno.land/x/install/install.sh | sh

# Windows
iwr https://deno.land/x/install/install.ps1 -useb | iex
```

より詳細は[こちら](https://github.com/denoland/deno_install)。

## deno init subcommand

Denoで新しいプロジェクトを始めるのは、信じられないほど簡単です。
たったひとつのファイルを用意するだけです。
設定ファイルもマニフェストもビルドスクリプトも必要ありません。

他のエコシステムから移動してきたユーザは、このシンプルさに戸惑うかもしれません。
彼らはしばしば、正しいプロジェクト構造を作るための足掛かりとなるツールを探しはじめます。
ということで本リリースでは、基本的なDenoプロジェクトの準備ができる`deno init`コマンドを追加しました。

```bash
$ 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`にはそのテストの書き方の例が書かれています。

また、引数を指定することで、指定のディレクトリでプロジェクトを初期化することもできます。

```bash
$ deno init my_deno_project
✅ Project initialized
Run these commands to get started
  cd my_deno_project
  deno run main.ts
  deno test
```

この機能へのご意見は、[Issue](https://github.com/denoland/deno/issues/12781)で聞かせてください。

## Experimental npm support

本リリースでは、npmの実験的なサポートが追加されました。
この機能はまだ開発中であることに注意してください。
入ったばかりの機能であるため、うまく動作しないシナリオが見つかる可能性があります。
不具合が見つかったら[Issue](https://github.com/denoland/deno/issues)で報告してください。
今後のリリースにおいて、互換性とユーザエクスペリエンスを向上させていく予定です。

この動作は、例で見せるのが一番わかりやすいと思います。

```js
// 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を取り込むには、以下の指定子を使います。

```js
npm:<package-name>[@<version-requirement>][/<sub-path>]
```

その後、以下のコマンドを打つだけでexpressサーバが起動します。

```bash
$ 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を直接実行することができます。

```bash
npm:<package-name>[@<version-requirement>][/<binary-name>]
```

こちらは実行例です。

```text
$ 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サーバを導入します。
[ベンチマーク](https://deno.land/benchmarks)では、Nodeと比較して4倍以上、そしてDenoの既存サーバより3倍高速です。
それどころかRustの標準的HTTPサーバである[Hyper](https://hyper.rs/)よりも20%高速です。

新しい`Deno.serve()`は、これまであった`std/http`の`serve()`をそのまま置き換えることができるので、`std/http`ユーザは非常になじみやすいと思います。

基本的な使い方はこれだけです。

```js
Deno.serve(() => new Response("Hello, world!"));
```

詳細なドキュメントは[doc.deno.land](https://doc.deno.land/deno/unstable/~/Deno.serve)から見ることができます。

以下の例では、リクエストに対してReactコンポーネントが動的レンダリングした結果を返します。

```js:ssr.jsx
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](https://github.com/denoland/deno/issues)で報告してください。

新しいHTTPサーバのパフォーマンスについては、より詳しい記事をブログにアップする予定なので、ご期待ください。


## Improvements to startup time

Denoの起動時には、リモートモジュールの依存関係を解析して必要なファイルのキャッシュを行っています。
この解析は、大きめのファイルでは大きな時間がかかるため、Deno1.25ではファイルごとにバックグラウンドでキャッシュを行うようにしました。
この変更により、Deno1.25では起動時間がかなり改善されたことを体感できると思います。

たとえば`ts_morph`はTypeScriptコンパイラに依存していますが、このJavaScriptファイルは10MBもあります。

```js
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](https://deno.land/manual@v1.25.0/runtime/ffi_api)に新機能を追加し、パフォーマンスを向上させました。

### Uint8Array & 64-bit numbers in Fast FFI calls

Deno1.2.4において[FFIコールの改善](https://deno.com/blog/v1.24#improved-ffi-call-performance)を行い、劇的なパフォーマンス向上を得ましたが、この最適化は数値型にのみ対応したものでした。
今回のリリースにおいては、これを`u64`・`i64`・`pointer`・`buffer`型にも対応しました。
これによって、多くのFFIモジュールがネイティブ並みのパフォーマンスを発揮できるようになりました。

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/86f2c48c-73db-7bd5-d9fc-059a5f245bfd.png)


### New buffer type

かつて、`pointer`型はパラメータとして`TypedArray`を渡すことができました。

本リリースでは、`TypedArray`のパフォーマンスを最適化するために`buffer`型を導入しました。


```js
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`を受け付けなくなりました。

```js:以前
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
```

```js:以後
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メソッドを追加しました。

```js
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](https://bun.sh/)の登場が契機になったんじゃないかなと勝手に思っています。

BunはNodeよりもDenoよりも高速で、そしてnpmに対応しているのが特徴です。
元々は[個人開発](https://qiita.com/shadowTanaka/items/5fb99819629dcaab3e05#comment-e639c7d6131b167358bc)であり、いつ開発が止まるかわからないという状態で導入に踏み込むのはなかなか難しかったのですが、その後[700万ドルの資金調達に成功して会社建てた](https://twitter.com/oven_sh/status/1562248114773565440)らしいので今後の開発が安定しそうな雰囲気です。
そんなわけで新興のBunに足下を掬われないようnpmをサポートすることにしたあたりではないかと思っているのですが真相はどうなのでしょうね。

ということで、今後はDenoでもnpmを使えるようになります。
npmがネックで移行できなかったプロジェクトも、今後はDenoが選択肢に入ってくるかもしれませんね。