denoを触ってみる

[deno](https://github.com/denoland/deno)はnodejsに代わる次世代javascript/typescriptランタイムです。
[つい最近v1.0.0がリリースされた](https://twitter.com/deno_land/status/1260701643311583234)ので触ってみたのですが、めちゃくちゃいい感じだったので感想をまとめます。

denoってそもそも何？というのは
https://qiita.com/so99ynoodles/items/c3ba2a528052827e3b3c
などがわかりやすいです。

(なお、サラッと触ってみただけなので間違いがある可能性があります。)

# インストール

https://github.com/denoland/deno_install

mac使いなので

```sh
brew install deno
```

で入りました。
直接installする場合は

```sh
curl -fsSL https://deno.land/x/install/install.sh | sh
```

で良さそうです。

# チュートリアル
https://deno.land/manual/getting_started/first_steps
公式ドキュメントが死ぬほど読みやすい・・・

### HelloWorld

最初のスクリプトは以下のURLに置いてありました。
当然のように拡張子は.tsです。

```sh
$ curl https://deno.land/std/examples/welcome.ts
console.log("Welcome to Deno 🦕");
```

実行してみます

```sh
$ deno run https://deno.land/std/examples/welcome.ts
Welcome to Deno 🦕
```

web上のtypescriptがそのまま実行されている・・・すごい！

### httpリクエスト
denoはnodejsよりブラウザ互換なAPIを提供しているようです。
`fetch()` が使えます。

```typescript:https//deno.land/std/examples/curl.ts                                         
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
const url_ = Deno.args[0];
const res = await fetch(url_);

// TODO(ry) Re-enable streaming in this example.
// Originally we did: await Deno.copy(res.body, Deno.stdout);
// But maybe more JS-y would be: res.pipeTo(Deno.stdout);

const body = new Uint8Array(await res.arrayBuffer());
await Deno.stdout.write(body);
```

地味にトップレベルでawaitが使われていて感動。

これはさっきと同じコマンドでは実行できません。denoは明示的に権限を与えないとネットワークアクセスができないからです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116979/18a81290-bc38-f42e-a8b0-9d02e92a6049.png)

エラーメッセージも明瞭ですね。 `--allow-net` を付けて再実行します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116979/e9819566-b967-876f-9b31-ad2eaf8260a8.png)

ちなみに初回実行時は `Download https://deno.land/std/examples/curl.ts` などが標準エラー出力に出てくるようです。
2回目以降の実行ではキャッシュされているので出てきません。

また `--allow-net` オプションはドメインを指定しないと任意のドメインを許可するようです。

### ファイル読み込み

```typescript:https//deno.land/std/examples/cat.ts
for (let i = 0; i < Deno.args.length; i++) {
  let filename = Deno.args[i];
  let file = await Deno.open(filename);
  await Deno.copy(file, Deno.stdout);
  file.close();
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116979/d217dc68-7419-cc81-90d0-e1b3c735bb7e.png)
(大事な部分はカット)
IOも明示的な許可が必要です。 `--allow-read` でディレクトリ単位に制限した読み込み許可を与えます。

### TCPサーバ

```ts:https//deno.land/std/examples/echo_server.ts
const listener = Deno.listen({ port: 8080 });
console.log("listening on 0.0.0.0:8080");
for await (const conn of listener) {
  Deno.copy(conn, conn);
}
```
[for await](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/for-await...of)は非同期反復子をイテレートするjavascriptの構文ですね。(普段あまり使う機会ないので急に出てきてビックリした)

`deno run --allow-net https://deno.land/std/examples/echo_server.ts` ネットワークへのフルアクセスを付けて実行できます。

# CLI
`deno` コマンドは [リリース](https://github.com/denoland/deno/releases/tag/v1.0.0) を見るとシングルバイナリ(!)のCLIとして提供されています。
`deno run` 以外のサブコマンドをいくつか触ってみます

#### repl
`deno repl` でREPLが起動します。サブコマンド無指定の `deno` でも同じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116979/afed461f-79f2-eeb9-4d9a-2e1270244f67.png)

repl内では `import` はできないっぽい？

#### bundle
`deno bundle`はimportを辿って依存スクリプトを全てシングルファイルにバンドルしてくれます。
[lodash](https://github.com/lodash/lodash)はpureなjavascriptだからdenoで実行できると聞いたので、これを使って試してみました

```ts:hello.ts
import isString from "https://raw.githubusercontent.com/lodash/lodash/master/isString.js";
console.log(isString("abc")); // true
```

```sh

$ deno run hello.ts # denoでは実行可能
true
$ node run hello.ts # 当然nodeでは実行できない(importできないから)
internal/modules/cjs/loader.js:1023
  throw err;
  ^

Error: Cannot find module '/Users/moajo/deno/run'
...(略)

$ deno bundle hello.ts > bundle.js # bundle.jsにバンドル結果を吐く
Bundling "file:///Users/moajo/deno/hello.ts"
$ node bundle.js # nodeでも実行可能に！
true
```

#### install
denoで書いたスクリプトを呼び出すshell scriptを~/.deno/bin以下に置いてくれるコマンドです
せっかくなので依存があっても大丈夫かも検証します(例によってlodash)

```lt10.ts
// 引数は10より小さいときにtrue,そうじゃないときにfalseを返すコマンド
import lt from "https://raw.githubusercontent.com/lodash/lodash/master/lt.js";
console.log(lt(parseInt(Deno.args[0]), 10));

```
特に役に立たないコマンドですがinstallしてみます。

```sh
$ deno install lt10 lt10.ts
Compile file:///Users/moajo/deno/lt10.ts
Download https://raw.githubusercontent.com/lodash/lodash/master/lt.js
✅ Successfully installed lt10
/Users/moajo/.deno/bin/lt10
$ lt10 9       
true
$ lt10 10
false
```
ちなみに`deno install` したときにPATHが通ってないとちゃんと指摘してくれます。優しい。

```sh
ℹ️  Add /Users/moajo/.deno/bin to PATH
    export PATH="/Users/moajo/.deno/bin:$PATH"
```
実行可能ファイルの中身はこんな感じでした

```sh
$ cat `which lt10`
#!/bin/sh
# generated by deno install
deno "run" "file:///Users/moajo/deno/lt10.ts" "$@"
```

シンプルですね

# typescriptサポート
https://deno.land/manual/getting_started/typescript

denoは標準でtypescriptをサポートしているので、特に理由がなければ基本的にtypescriptを使ったほうが良さそうです。
一方で、型定義のないjavascriptコードを参照するためのサポートもちゃんと用意されています

[compiler-hint](https://deno.land/manual/getting_started/typescript#compiler-hint)をコメントで付けることで型定義を読み込んでくれます。型定義も当然URLで指定できるようです。

```javascript
// @deno-types="./foo.d.ts"
import * as foo from "./foo.js";
```

tsconfigはデフォルトで[strictな](https://deno.land/manual/getting_started/typescript#custom-typescript-compiler-options)オプションが指定されるようですが、`-c`オプションで指定することもできます。

```
deno run -c tsconfig.json mod.ts
```

個人的には[X-TypeScript-Typesカスタムヘッダー](https://deno.land/manual/getting_started/typescript#x-typescript-types-custom-header)で型定義を指定できる機能がめちゃくちゃかっこよくて好きです。そのうちこれに対応したホスティングサービスも出てくるんでしょうか？

# 最後に
さらっと触ってみただけですが、UXが最高すぎて完全に惚れました。流行ってくれ！
ドキュメンテーションもわかりやすいですし、機能的にもめちゃくちゃ使いやすいです。
仕様上npmパッケージとの互換性がないのが唯一にして最大のつらみポイントですが、今後の発展に期待です。

# リンク
[日本のユーザコミュニティのscrapbox？](https://scrapbox.io/deno-ja/)
[slackもある](https://scrapbox.io/deno-ja/Slack%E3%81%AE%E5%8F%82%E5%8A%A0%E6%96%B9%E6%B3%95)


# 参考
https://deno.land/manual