Denoとはなにか - 実際につかってみる

# はじめに
`Deno`というものが面白そうだったので、これを書きたいと思います。

# Denoとはなにか
![deno.png](https://firebasestorage.googleapis.com/v0/b/app-blog-1ef41.appspot.com/o/articles%2FF2z5J7aePghFMh2hqfVK%2Fc5f6e76283924635826976de979b2243.png?alt=media&token=9dd400da-b863-4435-b486-58bc8fc259e3)

↑かわいい

[Deno(ディーノ)](https://deno.land/)という名前について、聞いたことがありますでしょうか。私も最近まで知りませんでしたが、実はv1.0がリリースされたのが2020/5/13とごく最近のことです。開発自体は2年前から行われておりましたが、結構新しめの技術です。
その証拠(?)に[Denoでググる](https://www.google.com/search?q=deno&sxsrf=ALeKk0123LZ8I95_5DE5Mjms6B7ZSrcAPw:1589727721603&source=lnms&tbm=isch&sa=X&ved=2ahUKEwiBw4qhlbvpAhWO-mEKHchCDxYQ_AUoBHoECBUQBg&biw=1440&bih=717)と担々麺ばっかりでてきます。(2019/5/18現在)

![スクリーンショット 20200518 0.02.53.png](https://firebasestorage.googleapis.com/v0/b/app-blog-1ef41.appspot.com/o/articles%2FF2z5J7aePghFMh2hqfVK%2F0d8f32bffa9a2ca42846152a503a9f6d.png?alt=media&token=6a3ce549-6d1c-4a39-9bf5-5edce64982b1)

## 結局Denoってなんなの？
`Deno`は、`Node.js`の製作者であるRyan Dahlによって作られた、新しいJS/TSランタイムです。すっごい雑に説明すると、`Node.js`のイケてなかったところを改良したものが`Deno`になります。`Deno`って文字を並べ替えると`Node`になりますね。

```js
const deno = 'node'.split('').sort().join('')
```

## Denoがつくられた背景
`Deno`はJSConf EU 2018でのRyan Dahlによる講演「Node.jsに関する10の反省点」において発表されました。

[10 Things I Regret About Node.js - Ryan Dahl - JSConf EU](https://www.youtube.com/watch?v=M3BM9TB-8yA&t=1319s)
[Node.js における設計ミス By Ryan Dahl](https://yosuke-furukawa.hatenablog.com/entry/2018/06/07/080335)

Ryan Dahlは講演の中で、自身が開発した`Node.js`における10個の後悔している点について言及しました。それらの設計ミスに基づいて開発されたのが、`Deno`です。

# 実際にDenoを使ってみる🦕
なにはともあれ、実際に`Deno`を使ってみて`Node.js`との違いについて見ていきましょう。

## インストール
まずは[インストール](https://deno.land/#installation)をします。私はMacを使っているので、`Homebrew`を使用してインストールしました。

```sh
$ brew install deno
```

以下のコマンドで正しくインストールされたか確認してみましょう。

```sh
$ deno -V
deno 1.0.0
```

## Denoを実行する
`Deno`を簡単に実行してみるために、公式のサンプルコードを利用してみます。

```sh
$ deno run https://deno.land/std/examples/welcome.ts
Download https://deno.land/std/examples/welcome.ts
Warning Implicitly using master branch https://deno.land/std/examples/welcome.ts
Compile https://deno.land/std/examples/welcome.ts
Welcome to Deno 🦕
```

恐竜さんが出てきました。可愛いですね🦕

2回目以降の動作は、初回と変わってきます。

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

`Download`や`Compile`などの行がなくなっていますね。
今回のように、リモートのURLを実行した場合にはローカルにキャッシュされ2回目以降は素早く実行することができます。
これは後ほど出てくる`import`でパッケージを読み込むときと同じです。

### 変更点1　TypeScriptをそのままサポート
先程実行したプログラム自体はとても簡単なものでしたが、早速`Node.js`との変更点が含まれています。
先程実行したプログラム`https://deno.land/std/examples/welcome.ts`の拡張子を見ると、`TypeScript`のコードであることがわかります。
従来では、`TypeScript`を実行するには、`npm`でインストールして、ルート配下に`tsconfig.json`を設置して、コンパイルして...といった作業が必要でした。
しかし、`Deno`ならそのような設定はすべて必要ありません。デフォルトで`TypeScript`をサポートしているので、そのまま実行することができます。

## サンプルコードを見てみる
簡単なプログラムだけではつまらないので、次はローカルにサーバーを立ててみます。以下のコードは公式サイトからのコピペで持ってきました。
8000ポートでサーバを立てて、Hello Worldと表示させます。

```js:server.js
import { serve } from "https://deno.land/std@0.89.0/http/server.ts";
const s = serve({ port: 8000 });
console.log("http://localhost:8000/");
for await (const req of s) {
  req.respond({ body: "Hello World\n" });
}
```



次に、下記のコードが`Node.js`で書いた同じようなコードです。

```js
const http = require('http');

const server = http.createServer((req, res) => {
  res.end('Hello World');
});

server.listen(8000)
```

この2つのコードを比べていきましょう。

### 変更点2 npmがない

と、先にコードの方を提出しましたが、その前にやることがありましたね。
まずは`npm init`で`package.json`を作ってそれから...えっ`Deno`では必要ないって?

そうなんです、`Deno`はインストールさえ済ませれば先に見たようにそのままコードを実行することができます。
驚くなかれ、そもそも`Deno`には**npmがありません**。
`npm`がないということは当然`node_modules`や`package.json`なんてものも存在しません。

`node_modules`ってかなり巨大なファイルでしたし、こいつがなくなるだけでフォルダ構成が結構スッキリしてくるんじゃないでしょうか。
`node_modules`と`package.json`の採用は、`Node.js`の設計ミスとしても上げられていた点です。

では、`npm`を使用しないとなれば、どのようにして外部モジュールを使用するのでしょうか。
その答えは、サンプルコードにもあるように、`import`にURLを渡します。

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

これは、`Deno`で外部モジュールを使用する唯一の方法です。もう`npm install`は必要としません。
ダウンロードは実行時に行われ、結果はキャッシュされます。


### 変更点3 requireがなくなった

関連して、今まで`Node.js`で利用されてきた`require`が廃止されました。

Node.js

```js
const http = require('http');
```

Deno

```js

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

[CommonJS](https://ja.wikipedia.org/wiki/CommonJS)の代わりに`ES Module`(`import`と`export`)をデフォルトのモジュールとシステムとして使用します。

### 変更点4 トップレベルのawait

あ！サンプルコードのこの部分、間違ってますよ！ほら！

```js
for await (const req of s) {
  req.respond({ body: "Hello World\n" });
}
```

`await`は`async`関数の中でしか動作できないんですよー、ほら`VSCode`だって怒ってる。

![スクリーンショット 20200518 1.40.50.png](https://firebasestorage.googleapis.com/v0/b/app-blog-1ef41.appspot.com/o/articles%2FF2z5J7aePghFMh2hqfVK%2Fb9f9d424a89a2b8ec279e2ee04aca1b0.png?alt=media&token=2d87f1d9-ba5c-4961-95e2-14893eb07993)

...え、これができるようになった？

そうです、もう`await`使いたさにわざわざ`async`関数で囲む必要はありません。やったね。

このトップレベルawaitに関してはDenoでの特徴ではなく、ECMAScriptの仕様と言うほうが正しいですね。

https://github.com/tc39/proposal-top-level-await

（@YoshiTheChinchillaさんご指摘ありがとうございます！）

## 実行してみる
コードベースでの変更点はここまでにして、実際にコードを実行してローカルサーバを立ち上げてみましょう。
`deno run`で実行できます。

```sh
$ deno run server.js
Download https://deno.land/std@0.50.0/http/server.ts
Compile https://deno.land/std@0.50.0/http/server.ts
Download https://deno.land/std@0.50.0/encoding/utf8.ts
Download https://deno.land/std@0.50.0/io/bufio.ts
Download https://deno.land/std@0.50.0/testing/asserts.ts
Download https://deno.land/std@0.50.0/async/mod.ts
Download https://deno.land/std@0.50.0/http/_io.ts
Download https://deno.land/std@0.50.0/io/util.ts
Download https://deno.land/std@0.50.0/path/mod.ts
Download https://deno.land/std@0.50.0/path/win32.ts
Download https://deno.land/std@0.50.0/path/posix.ts
Download https://deno.land/std@0.50.0/path/common.ts
Download https://deno.land/std@0.50.0/path/separator.ts
Download https://deno.land/std@0.50.0/path/interface.ts
Download https://deno.land/std@0.50.0/path/glob.ts
Download https://deno.land/std@0.50.0/path/_constants.ts
Download https://deno.land/std@0.50.0/path/_util.ts
Download https://deno.land/std@0.50.0/fmt/colors.ts
Download https://deno.land/std@0.50.0/testing/diff.ts
Download https://deno.land/std@0.50.0/path/_globrex.ts
Download https://deno.land/std@0.50.0/async/deferred.ts
Download https://deno.land/std@0.50.0/async/delay.ts
Download https://deno.land/std@0.50.0/async/mux_async_iterator.ts
Download https://deno.land/std@0.50.0/textproto/mod.ts
Download https://deno.land/std@0.50.0/http/http_status.ts
Download https://deno.land/std@0.50.0/bytes/mod.ts
error: Uncaught PermissionDenied: network access to "0.0.0.0:8000", run again with the --allow-net flag
    at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11)
    at Object.sendSync ($deno$/ops/dispatch_json.ts:72:10)
    at Object.listen ($deno$/ops/net.ts:51:10)
    at listen ($deno$/net.ts:152:22)
    at serve (https://deno.land/std@0.50.0/http/server.ts:261:20)
    at file:///Users/asaiippei/deno-test/server.js:2:11
```

外部モジュールをインストールするとことまでは良かったのですが、エラーが発生してしまいました。

### 変更点5 セキュリティルールの変更

なぜエラーが発生したのでしょうか。
実は、`Deno`デフォルトでセキュアであり、明示的に有効にしない限り、ファイル、ネットワーク、環境変数等にアクセスすることができません。

ネットワークを許可するには`--allow-net`フラグを付与して実行する必要があります。
もう一度やってみましょう。

```sh
$ deno run --allow-net  server.js
http://localhost:8000/
```

無事成功しましたね。
`http://localhost:8000/`にアクセスすると次のように表示されているはずです！
![スクリーンショット 20200518 1.56.19.png](https://firebasestorage.googleapis.com/v0/b/app-blog-1ef41.appspot.com/o/articles%2FF2z5J7aePghFMh2hqfVK%2F30318c8d890e66997ffe751265148cca.png?alt=media&token=8c70892b-43c0-4f8c-9af8-3c53f4df4a40)

### 変更点5 Web APIの実装によるブラウザとの互換性の向上
[Web API](https://developer.mozilla.org/ja/docs/Learn/JavaScript/Client-side_web_APIs)はWebブラウザに組み込まれている機能です。ブラウザやコンピュータの環境の情報を取得し、これを使って役に立つややこしい事を行えるようにするものです。
代表的なものでいえば、`DOM`,`Canvas API`,`Storage`,`fetch API`などがあります。

これらのAPIは日常的に使用していて、JavaScriptを覚えたての頃はJavaScript標準のモノだと思いこんでしまうほどですが、`Node.js`ではこれらを使用することはできません。理由は簡単で、これらのAPIはブラウザ(Google Chrome, Firefox)で利用できるものであり、`Node.js`はブラウザではないからです。

#### Node.jsの場合

例えば、次のように`Node.js`で`Fetch API`を利用しようとすると失敗します。

```js
fetch('https://pokeapi.co/api/v2/pokemon/')
  .then(res => res.json())
  .then(data => console.log(data) )
```

```bash
$ node node-fetch.js
node-fetch.js:2
fetch('https://pokeapi.co/api/v2/pokemon/1')
^

ReferenceError: fetch is not defined
    at Object.<anonymous> (/Users/asaiippei/deno-test/node-fetch.js:2:1)
    at Module._compile (internal/modules/cjs/loader.js:955:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:991:10)
    at Module.load (internal/modules/cjs/loader.js:811:32)
    at Function.Module._load (internal/modules/cjs/loader.js:723:14)
    at Function.Module.runMain (internal/modules/cjs/loader.js:1043:10)
    at internal/main/run_main_module.js:17:11
```

`fetch`が定義されていないと怒られていますね。
これを解決するには、外部モジュールである`node-fetch`をインストールする必要があります。

```bash
npm i node-fetch
```

```js
// これを追加
const fetch = require('node-fetch')

fetch('https://pokeapi.co/api/v2/pokemon/')
  .then(res => res.json())
  .then(data => console.log(data) )
```

```bash
$ node node-fetch.js

{
  count: 964,
  next: 'https://pokeapi.co/api/v2/pokemon/?offset=20&limit=20',
  previous: null,
  results: [
    { name: 'bulbasaur', url: 'https://pokeapi.co/api/v2/pokemon/1/' },
    { name: 'ivysaur', url: 'https://pokeapi.co/api/v2/pokemon/2/' },
    { name: 'venusaur', url: 'https://pokeapi.co/api/v2/pokemon/3/' },
    { name: 'charmander', url: 'https://pokeapi.co/api/v2/pokemon/4/' },
    { name: 'charmeleon', url: 'https://pokeapi.co/api/v2/pokemon/5/' },
    { name: 'charizard', url: 'https://pokeapi.co/api/v2/pokemon/6/' },
    { name: 'squirtle', url: 'https://pokeapi.co/api/v2/pokemon/7/' },
```

#### Denoの場合
`Deno`の場合には、標準で`Fetch API`がサポートされているため、インストールすることなく使用することができます。

```js:fetch.js
const res = await fetch('https://pokeapi.co/api/v2/pokemon/1')
const json = res.json()
const data = await json
console.log(data)
```

おっと、実行するときには`--allow-net`フラグを渡すことを忘れないでください！

```bash
$ deno run --allow-net fetch.js

{
 count: 964,
 next: "https://pokeapi.co/api/v2/pokemon/?offset=20&limit=20",
 previous: null,
 results: [
    { name: "bulbasaur", url: "https://pokeapi.co/api/v2/pokemon/1/" },
    { name: "ivysaur", url: "https://pokeapi.co/api/v2/pokemon/2/" },
    { name: "venusaur", url: "https://pokeapi.co/api/v2/pokemon/3/" },
    { name: "charmander", url: "https://pokeapi.co/api/v2/pokemon/4/" },
    { name: "charmeleon", url: "https://pokeapi.co/api/v2/pokemon/5/" },
    { name: "charizard", url: "https://pokeapi.co/api/v2/pokemon/6/" },
    { name: "squirtle", url: "https://pokeapi.co/api/v2/pokemon/7/" },
```

`fetch API`の他にも幅広いWeb APIを実装していることにより、ブラウザとの互換性を向上させました。

### Denoによるテスト
`Deno`にはテストランナーも含まれています。

`Deno.test`を呼ぶことでテストをすることができます。

```js:test.js
Deno.test("hello world", () => {
  const x = 1 + 2;
  if (x !== 3) {
    throw Error("x should be equal to 3");
  }
});
```

 [標準テストモジュール](https://deno.land/std/testing)から、アサーションを使用することができます。

```js:test.js
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";

Deno.test("hello world", () => {
  const x = 2 + 2;
  assertEquals(x, 3);
});
```

テストを実行するときは、`Deno test`です。

```bash
$ deno test test.js
Download https://deno.land/std/testing/asserts.ts
Warning Implicitly using master branch https://deno.land/std/testing/asserts.ts
Compile https://deno.land/std/testing/asserts.ts
Download https://deno.land/std/fmt/colors.ts
Download https://deno.land/std/testing/diff.ts
Warning Implicitly using master branch https://deno.land/std/testing/diff.ts
Warning Implicitly using master branch https://deno.land/std/fmt/colors.ts
Compile file:///Users/deno-test/.deno.test.ts

running 1 tests
test hello world ... FAILED (5ms)

failures:

hello world
AssertionError: Values are not equal:


    [Diff] Actual / Expected


-   4
+   3

    at assertEquals (https://deno.land/std/testing/asserts.ts:167:9)
    at test.js:5:3
    at asyncOpSanitizer ($deno$/testing.ts:36:11)
    at Object.resourceSanitizer [as fn] ($deno$/testing.ts:70:11)
    at TestApi.[Symbol.asyncIterator] ($deno$/testing.ts:264:22)
    at TestApi.next (<anonymous>)
    at Object.runTests ($deno$/testing.ts:346:20)

failures:

        hello world

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (5ms)
```

初回なのでダウンロードとコンパイルが入りますね。
テストも失敗しているので修正しておきましょう。

```js:test.js
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";

Deno.test("hello world", () => {
  const x = 1 + 2;
  assertEquals(x, 3);
});
```

2回目以降のテストはすぐに実行できます。

```js
$ deno test test.js
running 1 tests
test hello world ... ok (6ms)

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (8ms)
```

これでOKですね。

# おわりに
新しいJavaScriptのランタイムである`Deno`についてざっくりと触れてみました。
いつの日か、`Node.js`に取って代わる日が来るのでしょうか、とても気になりますね。