Node.js に変わる Deno という選択肢・その1

Node.js ご存知でしょうか。
昨今のフロントエンド開発はこれなくして考えられない、それぐらい有名な JavaScript の実行環境です。
といっても、直接利用するよりは付属の npm を使ってパッケージ管理をすることのほうが多いです。

さて、そんな Node.js ですが、色々と課題もあります。
Node.js 製作者の Ryan Dahl さんがそれらに触れた動画をこちらで視聴できます。

それらの課題を解決するため同氏によって再設計された実行環境、それが Deno というわけです。

インストール

公式サイトをご覧ください。
OS や利用ターミナルによって手順が少し異なるものの、基本的にはコマンド1つでインストールできます。

個人的に思う Deno の良いところ

  • TypeScript が標準で利用できる
  • package.json のようなパッケージ管理用ファイルが不要
  • test / lint / format が標準で利用できる

サンプルコードは各項目の説明の後にあります。

TypeScript が標準で利用できる

最近は JavaScript ではなく TypeScript でコードを書くことも増えてきました。
TypeScript は JavaScript のスーパーセットで、JavaScript では出来なかった「型の明示」や「インターフェースの定義」などが出来るようになります。
関数の I/O を明示できる、意図せぬ型での処理を防げるなど、「型がある」というのは開発効率にも大きく寄与します。
ただ、Node.js を使っている場合、TypeScript の実行環境は別途パッケージをインストールする必要があります。
ところが Deno では特に準備の必要なく TypeScript を利用できます。

package.json のようなパッケージ管理用ファイルが不要

Node.js (npm) で外部ライブラリを利用する際、package.json に依存ライブラリを記載し、インストールコマンド (npm i) を実行する必要があります。
※もっと細かいことを言うとグローバルインストールされているものだったり環境変数 NODE_PATH だったり参照先は色々あるんですが、一旦ここでは触れません。
Deno ではファイルから直接インポートすることで、上記の手間を省略できます。
コードがバージョン管理を兼ねているというわけですね。

test / lint / format が標準で利用できる

個人的にこれが一番嬉しかったです。
個人にせよチームにせよ、コード品質を担保するために test / lint / format については重要な役割を担っていると思っています。
CI で自動実行されるようにしているプロダクトも多いかと思います。

Node.js ではこれらを実行するためのライブラリも都度追加する必要があり、設定もプロダクトによってまちまちということが多いです。
これが標準ライブラリで実施できてしまいます。

サンプル

上記にあげた点を、以下サンプルコードをベースに説明していきます。

// main.ts
// インターフェースを定義することで関数間でやりとりする型を明示できる
interface ValueObject {
  id: number;
  value: string;
}

export default function(vo: ValueObject): string { // TypeScript が使えるので型を明示できる
  return `Id: ${vo.id}, Name: ${vo.value}`;
}

// main_test.ts
import {
  assertEquals,
} from "https://deno.land/std@0.65.0/testing/asserts.ts"; // ローカルに保存されていない場合、実行時にダウンロードされる
import mainFunc from './main.ts'; // Node.js と違い拡張子は省略不可

Deno.test("Test", () => {
  ['hoge', 'fuga', 'piyo'].forEach((value, idx) => {
    const vo = { id: idx + 1, value }; // インターフェースの定義を満たしていれば(今回だと id, value が指定した型で定義されていれば)OK
    assertEquals(mainFunc(vo), `Id: ${vo.id}, Name: ${vo.value}`);
  });
});
test
$ deno test main_test.ts # テスト用コマンドが標準で利用できる
Download https://deno.land/std@0.65.0/testing/asserts.ts
Download https://deno.land/std@0.65.0/fmt/colors.ts
Download https://deno.land/std@0.65.0/testing/diff.ts
Check file:///path/to/main_test.ts
running 1 test from file:///path/to/main_test.ts
test hello world #1 ... ok (23ms)

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

Deno 側であらかじめ決められたルールがあるので、それに則っているかをチェックします。
上記サンプルコードを修正します。

interface ValueObject {
  id: number,
  va_lue: string; // camelcase ではない
}

// 未使用変数を定義
const unusedValue = 1;

export default function(vo: ValueObject): string {
  return 'Id: ' + vo.id + ', Name: ' + vo.va_lue
}; // 末尾にセミコロンがある
$ deno lint main.ts 
(camelcase) Identifier 'va_lue' is not in camel case.
  va_lue: string; // camelcase ではない
  ^^^^^^
    at /path/to/main.ts:3:2

    hint: Consider renaming `va_lue` to `vaLue`, or wrapping it in quotation mark like `"va_lue"`
    help: for further information visit https://lint.deno.land/#camelcase

(no-unused-vars) `unusedValue` is never used
const unusedValue = 1;
      ^^^^^^^^^^^
    at /path/to/main.ts:7:6

    hint: If this is intentional, prefix it with an underscore like `_unusedValue`
    help: for further information visit https://lint.deno.land/#no-unused-vars

(no-extra-semi) Unnecessary semicolon.
}; // 末尾にセミコロンがある
 ^
    at /path/to/main.ts:11:1

    hint: Remove the extra (and unnecessary) semi-colon
    help: for further information visit https://lint.deno.land/#no-extra-semi

Found 3 problems
Checked 1 file

エラーになったルールと修正のヒントについて出力してくれるので、これを見ながら修正していくことになります。

format

機械的に変換可能なものは自動でフォーマットしてくれます。
ただ、自動でやってくれるのはあくまで一部です(自動でやってくれても良いのではというルールもあります)が、前述の lint コマンドでエラー箇所についてはチェックできるので修正はそこまで難しくないと思います。

$ deno fmt main.ts 
/path/to/main.ts
Checked 1 file
interface ValueObject {
  id: number;
  va_lue: string; // camelcase ではない -> 自動ではやってくれない
}

// 未使用変数を定義 -> 自動ではやってくれない
const unusedValue = 1;

export default function (vo: ValueObject): string {
  return "Id: " + vo.id + ", Name: " + vo.va_lue;
}// 末尾にセミコロンがある -> 消えた

まとめ

ここにあげたものだけでもかなり便利であることがわかるのではないかと思います。
ただ、ここに書いたもの以外にも「非同期処理の標準が Promise になっている」「必要な権限のみを都度付与する」など、便利な点はまだあります。
それらについては「その2」として次回書きたいと思います(→書きました