Help us understand the problem. What is going on with this article?

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/std@0.50.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で書いた同じようなコードです。

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/std@0.50.0/http/server.ts";

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

変更点3 requireがなくなった

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

Node.js

const http = require('http');

Deno

import { serve } from "https://deno.land/std@0.50.0/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の仕様と言うほうが正しいですね。

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

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

実行してみる

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

$ 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フラグを付与して実行する必要があります。
もう一度やってみましょう。

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

azukiazusa
今日も #リングフィットアドベンチャー をするなどする。
https://app-blog-1ef41.web.app/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした