MENU

Denoに触れてみる ~ 導入から かんたんなアプリケーションまで ~

f:id:toranoana-lab:20200624095910p:plain

こんにちは、皆さん雨の季節をいかがお過ごしでしょうか? ラボのおっくんです。

最近、「天気の子」の BD が届いたんですが、雨で思い出す作品の枠が更新してしまいましたね。
ちなみに前は「言葉の庭」でした。

さて、去る 2020 年 5 月 13 日に「deno 1.0」が公開されました。

deno.land

今回は、「deno」を導入から簡単なアプリケーションを作るところまで触る中で、気が付いたことなどをレポートしたいと思います。

実行環境

  • OS:macOS : Catalina 10.15.4

deno を導入

インストール方法は、Denodenoland / deno_installに記載があります。
(github の名前が Deno Land なのが、なんだか楽しそうです。)

deno.land

github.com

これらの記載がある中で、今回は以下のコマンドで導入しました。

$ curl -fsSL https://deno.land/x/install/install.sh | sh

2~3 分ほどで、完了します。 パスの追加を以下のように求められます。

Deno was installed successfully to /Users/[ログインユーザー名]/.deno/bin/deno
Manually add the directory to your $HOME/.bash_profile (or similar)
  export DENO_INSTALL="/Users/[ログインユーザー名]/.deno" 
  export PATH="$DENO_INSTALL/bin:$PATH"
Run '/User/[ログインユーザー名]/.deno/bin/deno --help' to get started

~/.bash_profileに記載し、source ~/.bash_profileを実行します。

導入できたか確認します。

$ which deno
/Users/[ログインユーザー名]/.deno/bin/deno

$ deno --version
deno 1.1.0v
8 8.4.300
typescript 3.9.2

deno 1.1.0 が導入できたことがわかります。 (既に1.1.0なんですね。)

deno って何だろう?

Deno 1.0 の解説では、以下のように記されています。

Deno is a new runtime for executing JavaScript and TypeScript outside of the web browser.

訳すと「deno は、ブラウザの外で、JavaScript と TypeScript を実行する新しいランタイムです。」 とのこと。

そうか、JavaScript と TypeScript がうごくのか、うんうん。

Like a web browser, it knows how to fetch external code. In Deno, a single file can define arbitrarily complex behavior without any other tooling.

訳すと、「deno はブラウザのように、外部のコードを取得する仕組みを持っていて、常に一つのファイルであり、外部ツールを持たない。」ということだそう。
ここで言う外部ツールとは、Node.js でいうところの「npm」のことです。

例として、以下のソースコードが示されています。

(sample1.ts)

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

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

ブラウザのように、url で表現された外部のコードを読み込んで、web サーバーが起動するコードです。
普段なら、npm install hogehogeなどとしていた事前準備が不要です。
試しに、先に示したサンプルコードを実行してみます。

deno run sample1.ts

最初に、downloadなどと表示してから、以下のエラーとなります。

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:154:22)
    at serve (https://deno.land/std@0.50.0/http/server.ts:261:20)
    at file:///Users/developer/dev/deno_test1/sample1.ts:3:25

「ネットワークアクセスの権限がないぞ、--allow-net フラグをつけて実行してください」というわけなので、改めて deno run --allow-net sample1.tsとして起動します。

今度は、download hogehogeなどの表示はなく起動できます。
一度ダウンロードされたファイルは、キャッシュされます。
キャッシュされているディレクトリは、deno infoを実行すると、確認できます。

$ deno info
DENO_DIR location: "/Users/[ログインユーザー名]/Library/Caches/deno"
Remote modules cache: "/Users/[ログインユーザー名]/Library/Caches/deno/deps"
TypeScript compiler cache: "/Users/[ログインユーザー名]/Library/Caches/deno/gen"

キャッシュには、3 種類あることがわかります。 Remote modules cacheに指定されたディレクトリ以下を消してから、再度実行すると、またdownload hogehogeが実行されるようになりました。

TypeScript compiler cacheを覗いてみると、~~.ts.jsというファイルがあることがわかります。
この点は勘違いをしていたのですが、「TypeScript のランタイム=トランスパイルしない」ということではないようです。

実行の内容に戻りますが、特に何か表示があるわけではないので、ブラウザでlocalhost:8000にアクセスしてみます。
すると、Hello Worldと表示されています。どうやら、動作できているようです。

このweb サーバーを起動するにあたって、ネットワークアクセスの権限を deno に与えるために、--allow-netを付与していました。

Also like browsers, code is executed in a secure sandbox by default. Scripts cannot access the hard drive, open network connections, or make any other potentially malicious actions without permission.

意訳すると「ブラウザと同じように、コードは標準でサンドボックス上で実行されます。スクリプトは、ハードドライブへのアクセスやネットワークへ、権限を与えられずにアクセスすることはできません。」とのことです。

このことが、JavaScript and TypeScript の安全なランタイムを標榜する理由の一つになります。

今回使用した、--allow-netを含めて、権限を付与するオプションは、以下のようなものがあります。

オプション 内容
--allow-all すべての権限を許可する
--allow-env 環境変数へのアクセスを許可する
--allow-run サブプロセスの実行を許可する
--allow-net = \<allow-net > ネットワークアクセスを許可する
--allow-read = \<allow-read > ハードドライブからの読み込みを許可する
--allow-write = \<allow-write > ハードドライブへの書き込みを許可する
--allow-hrtime 高分解能の時間を使用できる
--allow-plugin プラグインの読み込みを許可する

= \~~と記述するものは、具体的にアクセスを許可する対象を定義できるものです。

ライブラリに悪意のある実装が仕込まれていたとしても、不必要なネットワークアクセスや、リソースへのアクセスを防ぐことができます。 2018年 実際に、npmパッケージに攻撃コードが含まれるという事件が発生しています。

参考:https://qiita.com/azs/items/b15bc456bee3a7892950

こういった攻撃もあることから、コードの実行者がアプリケーションに対して、明示的なアクセス権を提示できるのは一定の安心感があると感じます。

そのほかの特徴

Denoを参考にすると以下の記述がありました。

  • TypeScript をサポートします。 前述もしている通り、Typescriptをサポートします。Node.js でTypeScriptを使うのと明確に異なるのは、外部のツール(例えば、@types/node)を使用しない点です。

  • 互換性について

    • Denoは、npmパッケージと完全な互換性がありません。
    • 時間がたつにつれて、Node.jsで動作するプログラムをもっと動作できるようになる見込み。

ここまでの deno まとめ

ここまでの deno の特徴を一旦まとめます。

  • 外部ツール(例えば npm)を使わずに外部のコードを取得・参照し利用できる。
  • サンドボックスで実行され、サンドボックスの外の資産(例えばハードドライブや、環境変数、ネットワーク)を使うには、明示的な権限の付与が必要
  • Typescriptをサポートします。
  • Node.jsとの完全な互換性は無い

deno の提供する機能

deno には、deno hogehogeという具合に使用できるコマンドがいくつかあります。
これらを紹介します。

deno eval

eval を使用することで、直接文字列で与えたコードを実行できます。

例えば以下のように

$ deno eval "console.log('sample text')"
sumple text

deno cache

deno run hogehogeを実行することで、必要な外部コードが入手できることを、確認済みです。
deno cache hogehogeでは外部コードのキャッシュだけを実行することができます。

先に出てきた、sample1.tsを例にとると、以下のようになります。

$ deno cache sample1.ts
Download https://~~
Download https://~~
Download https://~~
以下省略

deno cache hogehogeを実行しておくと、deno run hogehoge実行時に外部コードのキャッシュ処理がなくなります。

deno fmt

deno には、フォーマッターが付いています。
sample1.tsを以下のように適当に改行します。

(sample1.ts フォーマット前)

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

for await (
    const req of serve({
  port: 8000,
})) {
  req.respond({
    body: "Hello World\n",
  });
}

deno fmt sample1.tsを実行すると次のようになります。

(sample1.ts フォーマット後)

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

for await (
  const req of serve({
    port: 8000,
  })
) {
  req.respond({ body: "Hello World\n" });
}

フォーマットのされ方については、prettierなど使うほうが好みの方が多いかもしれません。
こちらについて「フォーマッタをprettierからdprintに置き換える」というissueが上がっています。(もともとはprettierがフォーマッタだったんですね。)

github.com

prettierから置き換わったdprintについては、

the one used in deno supports .js, .jsx, .ts, and .tsx files and it's much faster than prettier. I would say this is a temporary step backwards, but once we sort out the bugs and implement a few additional features then we will be many steps ahead.

「denoのサポートする.js, .jsx, .ts, .tsx ファイルは、prettierよりも非常に高速です。一時的には後退ですが、バグフィックスと機能追加により多くのステップが先に進みます。」としています。

実際に、deno fmtについての改修が進んでいるようです。

github.com

期待ですね。

deno install

実は、これが一番の推し機能と思っています。

作成したコードをツールとして登録できます。素晴らしい。

sample1.tsを例にとると

$ deno install --allow-net sample1
# 上書きする場合 -f を与える
$ deno install --allow-net -f sample1

Download https://~~~
Download https://~~~
Download https://~~~
省略

$ which sample1
/Users/[ログインユーザー名]/.deno/bin/sample1

$ sample1 
# port:8000番でHello Worldとだけ返してくるサーバーが起動する

deno install hogehogeしたコマンドをhogehogeだけで起動できるようになります。
思わず、cli ツールを作りたくなる機能ですね。

/Users/[ログインユーザー名]/.deno/bin/sample1の中身は、以下の通りでした。

#!/bin/sh
# generated by deno install
deno "run" "--allow-net" "file:///Users/[ログインユーザー名]/dev/deno_test1/sample1.ts" "$@"

deno bundle

deno に限らず、TypeScriptやJavaScriptを使うとき、昨今だとバンドルツールを使うことは珍しいことではありません。
deno は、バンドルツールの機能も果たします。

何度も登場しているsample1.tsをバンドルするには、以下のように実行します。

$ deno bundle sample1.ts bundle.sample1.js

実行するには、deno run --allow-net bundle.sample1.jsのように実行します。

$ deno run --allow-net  bundle.sample1.js

error: Uncaught SyntaxError: Unexpected reserved word
  for await (const req of server_ts_2.serve({ port: 8000 })) {
      ~~~~~

SyntaxError: Unexpected reserved wordとエラーになり、実行できずでした。

調べてみると、sample1.tsとして、取り上げているものとおおむね同様の構成のコードを例にissueが上がっていました。

Top-level for-await not working in bundles

トップレベルで記述した for-awaitはbundleに問題があるようです。

解決策として、for-awaitをasync関数で包んであげる対応が紹介されています。

次のように編集しました。

(sample1.ts bundle用修正)

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

(async ()=>{
  for await (const req of serve({ port: 8000 })) {
    req.respond({ body: "Hello World\n" });
  }
})();

修正できたら再度実行してみます。

# バンドル実行
$ deno bundle sample1.ts bundle.sample1.js
# アプリケーション実行
$ deno run --allow-net bundle.sample1.js

今度は、実行できました。

ほかにも、テストツールとしてdenoを使うことができるなど、今回紹介しないサブコマンドもあるので、ぜひご自身でも触ってみてください。

簡単なアプリケーションを作ってみよう

先に出しているsample1.tsを改修して、引数で与えたファイルの内容を返すWebサーバーを作ってみます。
前述の権限付与の中で使用していないものを試したいことも踏まえ、以下の要件で進めます。

  • 引数で与えたファイルの内容を読み込み(ハードドライブからの読み込み)
  • アクセスログを書き込む(ハードドライブへの書き込み)
  • リクエストに対して読み込み済みのファイルの内容を返す(ネットワークの利用)

作成したsample2.tsは、以下の通りです。

(sample2.ts)

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

const decoder = new TextDecoder("utf-8");
const encoder = new TextEncoder();
const readfile = Deno.readTextFileSync(Deno.args[0]);

for await (const req of serve({ port: 8000 })) {
  //読み込んだファイルをプレーンテキストとして返す
  req.respond({
    headers: new Headers({ "content-type": "text/plain; charset=utf-8" }),
    body: readfile,
  });

  //リクエストヘッダーをログとして書き出す
  const now = new Date();
  const data = encoder.encode(
    `access: ${now.toString()}:${Deno.inspect(req.headers)} \n`
  );
  Deno.writeFileSync(Deno.args[1], data, { append: true });
}

作成できたら、以下のコマンドで実行します。 deno run --allow-net --allow-read=./sendfile.txt --allow-write=./log.txt sample2.ts ./sendfile.txt ./log.txt
--allow-net--allow-readを使用し、対象となるファイルを示して実行しました。

localhost:8000にアクセスすることで、sendfile.txtの内容が返ってきます。
また、./log.txtには、簡単ですが、ログの書き込みが行われています。

sample2.tsでもいくつか使用していますが、denoが提供・実行できるものに、Deno.hogehogeという形で呼び出すメソッド群があります。

こちらについて、Deno 1.0でも触れられていました。

We promise to maintain a stable API in Deno. Deno has a lot of interfaces and components, so it's important to be transparent about what we mean by "stable". The JavaScript APIs that we have invented to interact with the operating system are all found inside the "Deno" namespace (e.g. Deno.open()). These have been carefully examined and we will not be making backwards incompatible changes to them.

意訳すると、「私たちは、Denoの安定したAPIを維持することを約束します。Denoは、たくさんのコンポーネントとインターフェースを持っています、なので大事なのは、「安定」が何を意味するか透明性を保つことです。OSとやり取りするJavaScript API群は、名前空間"Deno"の中にすべてあります(例えば、Deno.open())。これらは、後方互換性を慎重に調べて変更を行います。」

これらの関数群をうまく使いこなせるとよりよさそうです。
以下のリンク先にまとまっています。

deno

サードパーティモジュール

加えて、サードパーティのモジュールも以下のリンク先にまとまっているので、こちらもアプリケーション開発の手助けになります。 Deno - Third Party Modules

denoにかけてか、恐竜の名前のモジュールも散見されるので眺めるだけでも面白いかと思います。
気になったものをいくつか抜粋しました。

  • webview_deno webviewを使用してGUIを作るモジュール
  • Pagic Denoで作成された静的HTMLページジェネレータ
  • 🦖 Jurassic REST API フレームワーク(Jurassic=ジュラ紀)

まとめ

今回は、denoを導入から簡単なアプリケーションを動かすところまで行う中で、以下の事柄を確認することができました。

「deno」は

  • 外部ツールを使わずに外部のコードを取得・参照し利用できる
  • 提供する機能に、これまでnpmパッケージを使用し行っていたものがいくつか含まれている
  • アプリケーションに、実行・アクセス権限を明示的に示すことで一定のセキュリティを担保する
  • Deno.hogehogeという形で呼び出すメソッド群がある

サードパーティモジュールも使いながら,一通りのwebアプリケーションやCLIのツールを作る土台は整っていそうだと感じました。

しかし、ブログでは以下の記述があります。

For some applications Deno may be a good choice today, for others not yet. It will depend on the requirements. We want to be transparent about these limitations to help people make informed decisions when considering to use Deno.

意訳ですが、「いくつかのアプリケーションでは、Denoは良い選択だが、それ以外ではまだです。要件により変わります。これらの制限について、私たちは透明性を保ち、Denoの使用を検討する人々の助けになるようにしたい。」

すべてにおいて、Denoが良いわけでは無いことも示されています。 内容を検討し、より良いものを作っていきたいですね。

P.S

虎の穴ラボでの開発に少しでも興味を持っていただけた方は、採用説明会やカジュアル面談という場でもっと深くお話しすることもできます。ぜひお気軽に申し込みいただければ幸いです。
カジュアル面談では虎の穴ラボのエンジニアが、開発プロセスの内容であったり、「今期何見ました?」といったオタクトークから業務の話まで何でもお応えします。

カジュアル面談や採用情報はこちらをご確認ください。
yumenosora.co.jp

また、毎週火曜、木曜にはTora-Lab Meetup!と称して虎の穴ラボのエンジニア・採用担当とお話できる機会を設けさせていただくことになりました。
虎の穴ラボに興味がある、エンジニアや採用担当に質問したいことがある、などどなたでもご参加下さい。
news.toranoana.jp

さらに、弊社では新型コロナウイルス感染症終息後もフルリモートを継続導入することになりました!
地方在住のまま働きたい人など、上記Meetupやカジュアル面談、面接すべてリモート対応していますので、ご興味のある方はぜひいずれか応募してみてください! prtimes.jp

イベント情報

7月10日には定例開催している会社説明会をオンライン開催します。 どなたでも参加できるので、とらラボがどんなところか聞いてみたいという人は是非参加してください。 yumenosora.connpass.com