47
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

「Denoにはnpmがない」は部分的に正しい

Last updated at Posted at 2021-09-11

(追記)Denoにnpmインポートが実装されました!

Deno v1.25以降、npmインポートが実装され、npmライブラリを使うことができるようになりました。

この記事は、npmインポートが存在しなかった時代に、Node.jsとDenoの間でライブラリの使い方を比較した記事になります。

新しく導入されたnpmインポートについては、Denoの新機能「npmインポート」について予習するで詳しく解説しているので、そちらもご覧ください。


Node.jsとDenoを比較する際の特徴として、「npmがない」というのがよく言及されています。
これ自体は正しいのですが、まるでパッケージ管理が貧弱かのように聞こえてしまい、誤解を生んでいるので、改めて整理していきたいと思います。

Denoのパッケージ管理についての誤解

誤解①:npmのパッケージが使えない

DenoはランタイムとしてNode.jsとの互換性を切ったものの、Node.js向けのコードをブラウザ向けのコードに変換するツールは沢山あります。Denoはブラウザ向けのコードがそのまま動くので、npmパッケージをDenoで利用することが可能です。

現時点では以下のレジストリを経由してDenoでnpmパッケージを使用できることが知られています。

ただし注意点として、Node.jsの標準ライブラリはDenoの標準ライブラリでポリフィルされているのですが、このポリフィルの実装が途中です。
なので、importしたいnpmパッケージが未実装の標準ライブラリに依存していると、エラーが出る可能性があります。

ちなみに、「ファイル中にrequire()が含まれていると使えない」というのは古い情報です。

誤解②:実行のたびに外部パッケージのURLにアクセスする

外部URLからのimportを用いた場合、初回実行時のみモジュールのURLにアクセスし、2回目以降の実行ではローカルのキャッシュを使用します。
キャッシュに使うディレクトリは環境変数DENO_DIRで制御することが出来ます。

誤解③:パッケージマネージャが無い

Node.jsの場合、npmやyarnといったパッケージマネージャが外部コマンドとして存在します。
Denoの場合はどうでしょうか。

@ry: URLインポートの考え方は、Denoが独自のパッケージマネージャーとして機能するというものです。補助的なツールの必要性を明確に避けたいと思います。
https://github.com/denoland/deno/issues/47#issuecomment-395405713

上記コメントにある通り、Deno自体がパッケージマネージャの役割を果たします
依存関係の解決、依存関係のダウンロード、依存関係の一覧表示など、Node.jsでは外部コマンドとして存在したものがDeno本体に組み込まれています

パッケージマネージャが無いというよりは、パッケージマネージャの外部コマンドが無いと言ったほうが正しいと思います。

余談ですが、Denoはサードパーティ製ツールの必要性を極力無くすように作られています。フォーマッター、リンター、分散サーバーレスコンピューティング(deno deploy)等がDenoから提供されています。
今まではサードパーティ製ツールの数でエコシステムの成熟度を判断しているような所がありましたが、Denoにおいては公式が提供するものだけで一通り揃うようになっています。

誤解④:import文に必ずURLを使用する必要がある

Node.jsとブラウザでは、import文でモジュールを指定する方法が異なります。

ブラウザ
// 「モジュールが配信されているURL」を指定する
import _ from "https://esm.sh/lodash/";
Node.js
// 「モジュール名」を指定する
import _ from 'lodash'

本来、Node.jsとブラウザでは(それぞれに固有のAPIを使用しなければ)全く同じコードが利用できるはずです。それなのに、import文の書き方が違うだけで、バンドラ等を利用して実行前に変換作業を行う必要が出てきます。面倒です。

Denoでは、URLを利用したimportが導入されたので、ブラウザと同形のコードが利用できます。バンドラ等の変換プロセスを導入する必要はありません。

ブラウザ&Deno
import _ from "https://esm.sh/lodash/";

また、Node.jsとブラウザのimportを近づけるための手段として、import-mapも導入されました。これはブラウザとDenoの両方で利用可能です。

import-map.json
{
  "imports": {
    "lodash": "https://esm.sh/lodash/"
  }
}
import-mapを指定したブラウザ&Deno
import _ from 'lodash'

import-mapを利用すると、Node.jsのようなスタイルでのimportも可能です。

つまり、

  • ブラウザ互換のimport文が書ける
  • 必要に応じてNode.jsスタイルのimport文も書ける

ということです。

誤解⑤:バージョン管理が面倒

Denoでは、モジュールのバージョン情報もURLで指定するようになっています。

import { assertEquals } from "https://deno.land/std@0.106.0/testing/asserts.ts";
                                                   ^^^^^^^^

モジュールのバージョンを上げたい時はどうすればいいでしょうか。ソースコード中のimportをすべて書き換えるのは骨が折れる作業です。

これを解決するには、JavaScriptのexport * from ...という構文を利用します。これを使って、プロジェクト内のimport文を一箇所にまとめることが出来ます。

deps.ts
export * from "https://deno.land/std@0.106.0/testing/asserts.ts";
mod.ts
import { assertEquals } from "./deps.ts"

export * from ...構文を使って外部モジュールのimportを一箇所に集約することが、公式ドキュメントでも推奨されています

また、先ほど紹介したimport-mapも、同様にバージョンの一括指定に利用することが出来ます。

GitHub Actionsを利用して自動アップデートをかけることもできます。詳しくはDeno版dependabot(UDD)で自動アップデートを参照してください。

誤解⑥:ロックファイルが無い

Node.jsにはpackage.jsonのほかにpackage-lock.jsonがありました。Denoにはpackage.jsonが無いのでロックファイルも無いのでは?と思われるかもしれませんが、実行時に--lockフラグを指定してロックファイルを利用できます
https://deno.land/manual/linking_to_external_code/integrity_checking#caching-and-lock-files

npmにあってDenoに無いもの

「Denoにはnpmがない」の言葉通り、npmだけに存在する機能もあります。

package.json

Denoにはpackage.jsonがありません。
この理由は、URLからimportする時に、URLのパスを遡ってpackage.jsonを読みに行くというのはパフォーマンスが悪い上に、webの挙動と非互換になってしまうからです。
では、バージョン情報などのメタデータはどうやって指定するのかというと、gitのtagで一括管理することが推奨されています。

なお、tsconfig.jsonimport-map.jsondeno.json(設定ファイル)はDenoでも使用することができます。
これらはどれもプログラムのエントリポイントで指定されるもので、パッケージのメタデータを指定するために利用することはできません。
(deno.jsonはv1.14で登場)

node_modules

Denoにはnode_modulesがありません。
npmは、「プロジェクト内のnode_modulesフォルダ」にモジュールをダウンロードします。
この挙動により、プロジェクト毎にパッケージがダウンロードされるため、node_modulesフォルダの肥大化が問題になっていました。(インストール先をパッケージ間で共有するように変更したpnpmも存在します。)

一方Denoは、「ローカルのキャッシュフォルダ」にモジュールをダウンロードします。これはpnpmと同じ方式です。ダウンロードされたモジュールは他のプロジェクトと共有されます。

中央集権的なモジュールレジストリ

Denoには中央集権的なモジュールレジストリがありません。

Node.jsの場合、npmレジストリが唯一のレジストリです。npmではサーバーが落ちたり、悪意のあるパッケージによってトークンが流出したりするなどの障害が発生していますが、それでもnpmを使い続けなければなりません。また、一度公開されたモジュールが削除されるなどの問題も発生しています。
対してDenoは、任意のレジストリを使用することができます。ユーザーは https://deno.land/xhttps://esm.shhttps://www.skypack.dev/ などの中から好きなレジストリを選ぶことが出来ます。レジストリ間で資本主義的な競争原理が働くことで、エコシステム全体の耐障害性や安全性、永続性が向上する方向に働くことが狙いのようです。

まとめ

Denoにあるもの

  • パッケージマネージャ
  • 分散型のモジュールレジストリ
  • ブラウザ互換のimport
  • npmから利用可能な豊富なモジュール群
  • URLの代わりにモジュール名を使用してimportする方法
  • ロックファイル

Denoにないもの

  • 中央集権的なモジュールレジストリ
  • モジュール管理のために必要な外部コマンド
  • package.json
  • node_modules

感想

Denoのモジュール管理方式に対しては、「import文にURLを使うの不便じゃないの?」が一番大きな疑問じゃないかと思います。
結論から言うと、とても便利です。私はローカルで雑用作業に使っている言語(実行環境)をPythonからDenoに切り替えましたが、「ローカルにどのバージョンのパッケージがインストールされているか」を考えなくて済むというのが大きいです。他にも、「URLをctrl-clickすればドキュメントに飛べる」というのも便利です。
最新のエディタ拡張機能を使っていれば未知のURLも自動で補完されるので、入力の面倒さは大分改善されています。

逆に不便に感じた事としては、

  • node_modules内のファイルを弄って挙動を確認する」のような事が面倒
  • npm scriptに相当するものが無い

というのがあります。しかし、普通の使い方をする上では問題ないと思っています。

(追記)これらは全てDenoに機能追加されて解決されました。

  • node_modules内のファイルを弄って挙動を確認する」はdeno vendorコマンドで、
  • 「npm scriptに相当するもの」はdeno taskコマンドで

それぞれ実行することができます!便利ですね~

参考

※ライアン・ダール(作者)が「Node.jsに関して後悔している10の事」という講演で、index.jsのモジュール解決について

index.htmlみたいでかわいいと思った。しかし、モジュール解決が不必要に複雑になってしまった。

と語った事のオマージュ?ということらしい。

47
21
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
47
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?