Deno入門[インストール、環境構築、ファイル実行、標準ライブラリ]

この記事は筆者の[ソロ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/panda) 14日目の記事です。

前回までKotlinとテスト中心に記事を書いてきましたが今回からJavascriptランタイムであるDenoに入門してみたのでその備忘録です。

[Deno入門[インストール、環境構築、ファイル実行、標準ライブラリ]](https://qiita.com/JY8752/items/9cd6c05394bf955bdd75) <- 今ここ
[Deno入門[CLIコマンド、モジュール、環境変数、Webフレームワーク]](https://qiita.com/JY8752/items/a00866917626a8719ebb)
[Deno入門[npmモジュールの使用]](https://qiita.com/JY8752/items/d177e176a7544b693a34)
[Deno入門[prisma + oakで作るAPI]](https://qiita.com/JY8752/items/198762a69d463ac90fab)
[Deno入門[テスト編]](https://qiita.com/JY8752/items/e7e52aae6a0a3eb69572)

# Denoとは
Denoの情報はまだ少ないにしろ大変わかりやすくまとめていただいている神記事があるので詳しくはそちらを

https://qiita.com/azukiazusa/items/8238c0c68ed525377883

https://zenn.dev/uki00a/books/effective-deno/viewer/what-is-deno

公式はこちら
https://deno.land/

# なぜ書くのか
普段はサーバーサイドエンジニアとしてKotlinを書いていますが、世の中のトレンド的なものを考えるとフロントエンドを書かないにしてもTypeScriptのキャッチアップをしておかないといろんなものから置いていかれそうという焦燥感と恐竜が可愛いという理由と新しいものは触っておきたいというミーハー心からDenoを使ってみようと思っていたのですがなかなか時間が取れず、Advent Calenderを利用して2022年滑り込み入門することにしました。

基本的に公式ドキュメントのサンプルなどを実際に動かしていきたいと思います。

# Getting Started
## install
とりあえずインストール
```
brew install deno

deno --version
deno 1.28.1 (release, aarch64-apple-darwin)
v8 10.9.194.1
typescript 4.8.3
```

好きなエディタで環境整えてねと公式に書いてあって親切。VSCode, vim, JetBrainsなどがありましたが、筆者はVSCodeの環境を整えます。

公式から拡張プラグインが提供されているのでインストール
https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno

デフォルトではdenoのプラグインは有効になっていないのでワークスペース単位で有効設定にすることを推奨しているようです。

VSCodeでコマンドパレットを開き、```Deno: Initialize Workspace Configuration```を選択し、設定を有効にするとプロジェクトのルートディレクトリに.vscode/setting.jsonが作成される。

```json
{
  "deno.enable": true,
  "deno.unstable": true
}
```

## Hello World
以下のファイルを作成しHello Worldしてみる。
```ts:first_steps.ts
console.log("Welcome to Deno!");
```

実行
```
% deno run first_steps.ts 
Welcome to Deno!
```

DenoはURLからスクリプトを実行することもできるらしい。
```
% deno run https://deno.land/std@0.103.0/examples/welcome.ts
Welcome to Deno!
```

## fetch APIでHTTPリクエスト
標準APIであるfetch APIを使用しHTTPリクエストをしてみます。first_steps.tsに以下を追加します。

```ts:first_steps.ts
const url = Deno.args[0];
const res = await fetch(url);

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

実行時引数でURLを指定し、fetchし取得したレスポンスを標準出力に書き出すだけのコードです。これを実行すると以下のようになります。

```
% deno run first_steps.ts https://example.com 
Welcome to Deno!
✅ Granted net access to "example.com".
<!doctype html>
<html>
<head>
    <title>Example Domain</title>

    <meta charset="utf-8" />
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style type="text/css">
    body {
        background-color: #f0f0f2;
        margin: 0;
        padding: 0;
        font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
        
    }
    div {
        width: 600px;
        margin: 5em auto;
        padding: 2em;
        background-color: #fdfdff;
        border-radius: 0.5em;
        box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
    }
    a:link, a:visited {
        color: #38488f;
        text-decoration: none;
    }
    @media (max-width: 700px) {
        div {
            margin: 0 auto;
            width: auto;
        }
    }
    </style>    
</head>

<body>
<div>
    <h1>Example Domain</h1>
    <p>This domain is for use in illustrative examples in documents. You may use this
    domain in literature without prior coordination or asking for permission.</p>
    <p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>
```

上記のように実行するとリクエストは成功するのですが以下のようなエラー表示が出ます。これはDenoは標準でセキュアな作りとなっておりネットワークアクセスに対してpermissionを許可する必要があるからです。
```
⚠️  ┌ Deno requests net access to "example.com".
   ├ Requested by `fetch()` API
   ├ Run again with --allow-net to bypass this prompt.
   └ Allow? [y/n] (y = yes, allow; n = no, deny) > 
```

ネットワークアクセスを許可して実行するには以下のように実行します。
```
deno run --allow-net=example.com first_steps.ts https://example.com
```

## ファイル読み込み
DenoはWebからでないAPIも提供している。例えば、ファイルシステムのAPIはWeb標準の形がないため、Denoは独自のAPIを提供しています。以下のコードはファイルを読みこみ、その内容を標準出力にコピーして出力するコードです。

```ts:reading_file.ts
import { copy } from "https://deno.land/std@0.166.0/streams/conversion.ts";
const filenames = Deno.args;
for (const filename of filenames) {
  const file = await Deno.open(filename);
  await copy(file, Deno.stdout);
  file.close();
}
```

実行にはAPIリクエスト同様ファイル読み込みの権限が必要です。
```
% deno run --allow-read reading_file.ts hello.txt hi.txt
Hello!!
Deno!!
Hi!!
Deno!!%
```

## HTTPサーバーの構築
以下のように標準ライブラリからserver関数をimportしサーバーを立ち上げることができます。

```ts:http_server.ts
import { serve } from "https://deno.land/std@0.157.0/http/server.ts";

const port = 8080;

const handler = async (request: Request): Promise<Response> => {
  const resp = await fetch("https://api.github.com/users/denoland", {
    // The init object here has an headers object containing a
    // header that indicates what type of response we accept.
    // We're not specifying the method field since by default
    // fetch makes a GET request.
    headers: {
      accept: "application/json",
    },
  });
  return new Response(resp.body, {
    status: resp.status,
    headers: {
      "content-type": "application/json",
    },
  });
};

console.log("Listening on http://localhost:8000");
serve(handler);
```

以下のようにサーバーを起動
```
% deno run --allow-net --allow-write http_server.ts
Listening on http://localhost:8000
```

アクセスしてみる
```
% curl http://localhost:8000 | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1222    0  1222    0     0   4484      0 --:--:-- --:--:-- --:--:--  4628
{
  "login": "denoland",
  "id": 42048915,
  "node_id": "MDEyOk9yZ2FuaXphdGlvbjQyMDQ4OTE1",
  "avatar_url": "https://avatars.githubusercontent.com/u/42048915?v=4",
  "gravatar_id": "",
  "url": "https://api.github.com/users/denoland",
  "html_url": "https://github.com/denoland",
  "followers_url": "https://api.github.com/users/denoland/followers",
  "following_url": "https://api.github.com/users/denoland/following{/other_user}",
  "gists_url": "https://api.github.com/users/denoland/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/denoland/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/denoland/subscriptions",
  "organizations_url": "https://api.github.com/users/denoland/orgs",
  "repos_url": "https://api.github.com/users/denoland/repos",
  "events_url": "https://api.github.com/users/denoland/events{/privacy}",
  "received_events_url": "https://api.github.com/users/denoland/received_events",
  "type": "Organization",
  "site_admin": false,
  "name": "Deno",
  "company": null,
  "blog": "https://deno.land",
  "location": null,
  "email": "support@deno.com",
  "hireable": null,
  "bio": null,
  "twitter_username": "deno_land",
  "public_repos": 80,
  "public_gists": 0,
  "followers": 1683,
  "following": 0,
  "created_at": "2018-08-02T22:47:41Z",
  "updated_at": "2022-10-05T16:30:46Z"
}
```

# まとめ
- Denoのインストールについて紹介しました
- DenoのVSCodeの開発環境の準備について紹介しました
- Denoの標準ライブラリやfetchAPIの使用について紹介しました

DenoをインストールするだけでTypeScriptがすぐ書けるのがめちゃくちゃいいなと思いました。とりあえず今回は以上です！