はじめに
Deno
というものが面白そうだったので、これを書きたいと思います。
Denoとはなにか
↑かわいい
Deno(ディーノ)という名前について、聞いたことがありますでしょうか。私も最近まで知りませんでしたが、実はv1.0がリリースされたのが2020/5/13とごく最近のことです。開発自体は2年前から行われておりましたが、結構新しめの技術です。
その証拠(?)にDenoでググると担々麺ばっかりでてきます。(2019/5/18現在)
結局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 🦕
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と表示させます。
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
で書いた同じようなコードです。
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を渡します。
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
(import
とexport
)をデフォルトのモジュールとシステムとして使用します。
変更点4 トップレベルのawait
あ!サンプルコードのこの部分、間違ってますよ!ほら!
for await (const req of s) {
req.respond({ body: "Hello World\n" });
}
await
はasync
関数の中でしか動作できないんですよー、ほらVSCode
だって怒ってる。
...え、これができるようになった?
そうです、もうawait
使いたさにわざわざasync
関数で囲む必要はありません。やったね。
このトップレベルawaitに関してはDenoでの特徴ではなく、ECMAScriptの仕様と言うほうが正しいですね。
(@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/
にアクセスすると次のように表示されているはずです!
変更点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.js
でFetch 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
がサポートされているため、インストールすることなく使用することができます。
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
を呼ぶことでテストをすることができます。
Deno.test("hello world", () => {
const x = 1 + 2;
if (x !== 3) {
throw Error("x should be equal to 3");
}
});
標準テストモジュールから、アサーションを使用することができます。
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)
初回なのでダウンロードとコンパイルが入りますね。
テストも失敗しているので修正しておきましょう。
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
に取って代わる日が来るのでしょうか、とても気になりますね。