Deno

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

Denoは、Node.jsの製作者であるRyan Dahlによって作られた、新しいJS/TSランタイムです。簡単に説明すると、Node.jsのイケてなかったところを改良したものがDenoになります。

はじめに

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

Denoとはなにか

deno.png

↑かわいい。

Deno(ディーノ)という名前について、聞いたことがありますでしょうか。私も最近まで知りませんでしたが、実は v1.0 がリリースされたのが 2020/5/13 とごく最近のことです。開発自体は 2 年前から行われておりましたが、結構新しめの技術です。 その証拠(?)にDenoでググると担々麺ばっかりでてきます。(2019/5/18 現在)

スクリーンショット 20200518 0.02.53.png

結局Denoってなんなの?

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

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 Node.js における設計ミス By Ryan Dahl

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

実際にDenoを使ってみる🦕

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

インストール

まずはインストールをします。私は Mac を使っているので、Homebrew を使用してインストールしました。

$ brew install deno

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

$ deno -V
deno 1.0.0

Denoを実行する

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

$ 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 回目以降の動作は、初回と変わってきます。

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

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

変更点1 TypeScriptをそのままサポート

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

サンプルコードを見てみる

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

server.js
import { serve } from "https://deno.land/[email protected]/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 で書いた同じようなコードです。

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

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

変更点2 npmがない

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

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

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

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

import { serve } from "https://deno.land/[email protected]/http/server.ts";

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

変更点3 requireがなくなった

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

Node.js

const http = require('http');

Deno

 
import { serve } from "https://deno.land/[email protected]/http/server.ts";

CommonJSの代わりに ES Module(importexport)をデフォルトのモジュールとシステムとして使用します。

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

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

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

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

スクリーンショット 20200518 1.40.50.png

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

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

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

(@YoshiTheChinchilla さんご指摘ありがとうございます!)

実行してみる

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

$ deno run server.js
Download https://deno.land/[email protected]/http/server.ts
Compile https://deno.land/[email protected]/http/server.ts
Download https://deno.land/[email protected]/encoding/utf8.ts
Download https://deno.land/[email protected]/io/bufio.ts
Download https://deno.land/[email protected]/testing/asserts.ts
Download https://deno.land/[email protected]/async/mod.ts
Download https://deno.land/[email protected]/http/_io.ts
Download https://deno.land/[email protected]/io/util.ts
Download https://deno.land/[email protected]/path/mod.ts
Download https://deno.land/[email protected]/path/win32.ts
Download https://deno.land/[email protected]/path/posix.ts
Download https://deno.land/[email protected]/path/common.ts
Download https://deno.land/[email protected]/path/separator.ts
Download https://deno.land/[email protected]/path/interface.ts
Download https://deno.land/[email protected]/path/glob.ts
Download https://deno.land/[email protected]/path/_constants.ts
Download https://deno.land/[email protected]/path/_util.ts
Download https://deno.land/[email protected]/fmt/colors.ts
Download https://deno.land/[email protected]/testing/diff.ts
Download https://deno.land/[email protected]/path/_globrex.ts
Download https://deno.land/[email protected]/async/deferred.ts
Download https://deno.land/[email protected]/async/delay.ts
Download https://deno.land/[email protected]/async/mux_async_iterator.ts
Download https://deno.land/[email protected]/textproto/mod.ts
Download https://deno.land/[email protected]/http/http_status.ts
Download https://deno.land/[email protected]/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/[email protected]/http/server.ts:261:20)
    at file:///Users/asaiippei/deno-test/server.js:2:11

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

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

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

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

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

無事成功しましたね。 http://localhost:8000/ にアクセスすると次のように表示されているはずです! スクリーンショット 20200518 1.56.19.png

変更点5 Web APIの実装によるブラウザとの互換性の向上

Web APIは Web ブラウザに組み込まれている機能です。ブラウザやコンピュータの環境の情報を取得し、これを使って役に立つややこしいことを行えるようにするものです。 代表的なものでいえば、DOM,Canvas API,Storage,fetch API などがあります。

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

Node.jsの場合

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

fetch('https://pokeapi.co/api/v2/pokemon/')
  .then(res => res.json())
  .then(data => console.log(data) )
$ 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 をインストールする必要があります。

npm i node-fetch
// これを追加
const fetch = require('node-fetch')
 
fetch('https://pokeapi.co/api/v2/pokemon/')
  .then(res => res.json())
  .then(data => console.log(data) )
$ 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 がサポートされているため、インストールすることなく使用できます。

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 フラグを渡すことを忘れないでください!

$ 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 を呼ぶことでテストをできます。

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

標準テストモジュールから、アサーションを使用できます。

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 です。

$ 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)

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

test.js
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
 
Deno.test("hello world", () => {
  const x = 1 + 2;
  assertEquals(x, 3);
});

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

$ 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 に取って代わる日が来るのでしょうか、とても気になりますね。


Contributors

> GitHub で修正を提案する
この記事をシェアする
はてなブックマークに追加

関連記事