2021年8月9日月曜日

【Denoまとめ】Node.jsと比較してみた。Denoの特徴は、Rustで出来ており、ネイティブでTypeScriptに対応しております。Denoが提供する非同期APIはPromiseを返しますので、callbackによりネストが深くなってしまう心配もありません。

https://tech-blog.rakus.co.jp/entry/20210806/deno
シェアしました。

f:id:tech-rakus:20210806140304p:plain

こんにちは。
今回は前々から気になっていたNode.jsの後継Denoについて調べましたので、Node.jsと比較しながら紹介していきたいと思います。

Denoとは

Node.jsの作者Ryan Dahlによって作られた、Rust製の新しいJavaScript/TypeScript実行環境です。 Node.jsと同じくV8エンジンを採用しています。 以下動画をみると"ディノ"と発音されるようです。
How to pronounce Deno (officially) - YouTube

環境

以下バージョンで動作確認を行なっています。

  • Deno: v1.12.0
  • deno_std: v0.101.0

開発環境のセットアップ

Deno CLIのセットアップ

公式サイトのGetting StartedをみながらDeno CLIをインストールします。
今回はHomebrew経由でインストールします。

$ brew install deno
$ deno --version

各種IDEのセットアップ

Deno CLIのインストールが完了したら、以下公式ページを参考に各IDEでのセットアップを行います。 https://deno.land/manual@v1.12.1/getting_started/setup_your_environment#editors-and-ides

VSCodeの場合にはdenoland.vscode-denoをインストールし、プロジェクトを開いた後、
Ctrl+Shift+P(MacであればCmd+P)よりDeno: Initialize Workspace Configurationを実行し、.vscode/settings.jsonを作成すれば補完などが効くようになります。

Denoの特徴

Deno公式に記載がありますので、1つ1つ見ていきましょう。

Secure by default. No file, network, or environment access (unless explicitly enabled).

Denoのまず大きな特徴として、Node.jsとは異なり実行時に権限を細かく制御できる点が挙げられます。
デフォルトではfetch APIを利用したネットワークアクセスすら行うことができません。

$ echo 'const res = await fetch("https://example.com");' > main.ts
$ deno run main.ts
error: Uncaught PermissionDenied: Requires net access to "example.com", run again with the --allow-net flag

実行時に明示的にネットワークアクセスを許可する必要があります。

$ deno run --allow-net=example.com main.ts

Supports TypeScript out of the box.

Node.jsではモジュールのインストールなどが必要でしたが、Denoは設定なしでTypeScriptを実行できます。

const add = (a: number, b: number) => {
    return a + b;
};
console.info(add(1, 2));
$ deno run main.ts
3

Ships a single executable (deno).

実行環境は単一のバイナリとして提供されるため、単純にdenoのバイナリをDLするだけでdenoを実行できます。
denoのバイナリはgithub.com/denoland/deno/releasesからDLできます。

Has built-in utilities like a dependency inspector (deno info) and a code formatter (deno fmt).

Linter(deno_lint)やFormatter(dprint), テストランナー(Deno.test)といった、今や開発時には欠かせないツールが標準で準備されています。
それぞれdeno lintdeno fmtdeno testといったコマンドで実行することが可能です。

Has a set of reviewed (audited) standard modules that are guaranteed to work with Deno.

Denoはdeno_stdという標準モジュール群を有しています。
deno_stdのREADMEにも記載がありますが、Go's standard libraryを大いに参考にしているようです。
2021年7月現在、deno_stdの最新は0.102.0であり、まだメジャーリリースされていません。変更が加わる可能性もあるため、バージョンを固定してimportすることが強く推奨されています(バージョンを固定したimportについては後述)。

Can bundle scripts into a single JavaScript file.

エントリーポイントとなるファイルを指定して、単一のjsファイルにバンドルすることができます。

// add.ts
export const add = (a: number, b: number) => {
    return a + b;
};

// main.ts
import { add } from "./add.ts";
console.info(add(1, 2));
$ deno bundle main.ts main.bundle.js
// main.bundle.js
const add = (a, b)=>{
    return a + b;
};
console.info(add(1, 2));

Node.jsとの違い

TypeScriptにネイティブ対応

Denoは以下のコマンド一発でTypeScriptを実行できます。

$ deno run main.ts

Node.jsの場合TypeScriptの実行のために依存パッケージを追加しなければならないのに対し、DenoはDenoのバイナリさえあればTypeScriptファイルを実行可能なので、
さくっとスクリプトを書きたいときなどにも非常に便利だと思います。

Promiseファースト

Denoが提供する非同期APIはPromiseを返しますので、callbackによりネストが深くなってしまう心配もありません。
しかしNode.jsの非同期APIでも徐々にPromiseを利用できるようになってきており(fs/promisesなど)、そこまで大きな差ではなくなってくるかもしれません。

モジュールシステム

Node.jsとの一番大きな違いはこの点です。

package.jsonとnpmの廃止

Denoはnpm、node_modules、package.jsonを使用しません。

URLを利用したimport

Node.jsのようにnode_modulesを使用する代わりに、URLを指定してモジュールをインポートします。
バージョンの違いによる意図しない変更を防ぐため、バージョンを指定してのimportが強く推奨されています。

// 常にstdの最新版からimport
import { copy } from "https://deno.land/std/io/util.ts";

// v0.101.0のstdからimport(recommended)
import { copy } from "https://deno.land/std@0.101.0/io/util.ts";

また、Node.jsのようなpackage.jsonでの依存管理ではなく、deps.tsimport mapsで依存管理を行います。

deps.ts

deps.tsで外部モジュールをimport、re-exportし、依存モジュールを管理する方式です。

// deps.ts
export {
  copy,
  readAll,
  writeAll,
} from "https://deno.land/std@0.101.0/io/util.ts";


// main.ts
import { copy, readAll, writeAll } from "./deps.ts";

import maps

import_map.json

{
  "imports": {
    "io/": "https://deno.land/std@0.101.0/io/"
  }
}
// main.ts
import { copy, readAll, writeAll } from "io/util.ts";

実行時には--import-mapフラグでjsonを指定して実行します。

$ deno run --import-map=./import_map.json main.ts

上の通り、import mapsは実行時に1ファイルのみ指定できます。

CommonJSからESModuleへ

DenoはNode.jsとは異なりrequireをサポートせず、import exportのみをサポートしています。
モジュールの名前解決のルールも異なるため、基本的にNode.jsの資産をそのまま使用することはできません。

その他

deno cacheでモジュールをキャッシュする

Node.jsはnpm installを実行することでローカルにモジュールを保存しますが、Denoは初回実行時にローカルにモジュールをキャッシュします。 キャッシュの保存先はdeno infoで確認することができます。

$ deno cache main.ts
$ deno info

依存モジュールのcacheの更新を行いたい場合にはdeno cache --reload main.tsで更新できます。

deno compileで実行可能バイナリを生成する

個人的に嬉しいのが、deno compile main.tsスクリプトスタンドアロンの実行可能バイナリにコンパイルしてくれる機能です。 Node.jsにもこのようなライブラリはありますが、Denoは標準機能として有しています。

以下のようなファイルを読み込んでコピーするスクリプトをシングルバイナリにコンパイルしてみたいと思います。

{
  "imports": {
    "io/": "https://deno.land/std@0.101.0/io/"
  }
}
import { copy } from "io/util.ts";

const fileName = Deno.args[0];
const src = await Deno.open(fileName);
const dst = await Deno.create(`${fileName}_copy`);
await copy(src, dst);

コンパイルdeno compileコマンドで実施します。
fileのreadとwriteを行っているので--allow-read--allow-writeフラグが必要です。

$ deno compile --allow-read --allow-write --import-
map=./import_map.json -o main main.ts
$ ./main test.txt

--targetフラグを使用してクロスコンパイルもできます。 シングルバイナリにコンパイルすることでNode.jsと比べスクリプトの配布も非常に楽になりありがたいですね。

Denoまとめ 感想

stdがまだstableでなかったり、Node.jsと比べるとやはりライブラリが少なかったりという点はありますが、 APIの安定化が進み、Node.jsライブラリのDeno対応が進めばさらにDenoの勢いは増していくのではないかと思います。
まだまだNode.jsを代替するまでは遠いかと思いますが、今後も注目していきたいと思います。

参考

0 コメント:

コメントを投稿