Deno v1.19で追加されたdeno vendorコマンドについて
はじめに
2022年2月17日にDeno v1.19がリリースされました。
このリリースではdeno vendor
という新しいコマンドが追加されています。
この記事ではこのdeno vendor
コマンドの使い方などについて解説します。
deno vendor
コマンドとは?
下記の問題を解消するべく追加されたコマンドです。
- サードパーティモジュールをバージョン管理に含めるのが難しい
- サードパーティモジュールのデバッグが難しい
従来では、サードパーティモジュールをバージョン管理に含めるためには、DENO_DIR
という環境変数を操作する必要がありました。
$ DENO_DIR=.deno deno run mod.ts
これにより、サードパーティモジュールは.deno
に保存されるようになるため、このディレクトリをバージョン管理に追加すれば、目的を達成できます。
しかし、この方法には下記のような問題があります。
- ダウンロードされたサードパーティモジュールはファイル名がハッシュ化された状態で保存されるため、特定のファイルを探したりデバッグするのが困難
-
DENO_DIR
にはダウンロードされたサードパーティモジュールだけでなく、トランスパイル後のJavaScriptファイルも保存されるため、容量が大きくなってしまう。- また、Denoのバージョンアップにより
DENO_DIR
の構造が変わると、互換性を維持できなくなるという問題もあります。
- また、Denoのバージョンアップにより
これらの問題を解消するために、deno vendor
コマンドが追加されました。
これから、具体的にdeno vendor
の使い方を説明していきます。
使い方
使い方はdeno run
やdeno cache
コマンドなどに似ていて、スクリプトのエントリポイントとなるファイルをdeno vendor
の引数として指定します。
$ deno vendor mod.ts
Vendored 70 modules into vendor/ directory.
To use vendored modules, specify the `--import-map` flag when invoking deno subcommands:
deno run -A --import-map vendor/import_map.json mod.ts
これにより、Denoはmod.ts
及びそこから再帰的にimport
されているモジュールを分析し、依存されている全てのサードパーティモジュールをvendor
ディレクトリへ保存します。
vendor
├── deno.land
│ ├── std@0.127.0
│ │ ├── _util
│ │ │ ├── assert.ts
│ │ │ └── os.ts
│ │ ├── async
│ │ │ └── deferred.ts
│ │ ├── bytes
│ │ │ ├── bytes_list.ts
│ │ │ ├── equals.ts
│ │ │ └── mod.ts
│ │ ├── encoding
│ │ │ └── base64.ts
│ │ ├── fmt
│ │ │ └── colors.ts
│ │ ├── fs
│ │ │ └── exists.ts
│ │ ├── io
│ │ │ ├── buffer.ts
│ │ │ ├── bufio.ts
│ │ │ ├── files.ts
│ │ │ ├── types.d.ts
│ │ │ └── util.ts
│ │ ├── path
│ │ │ ├── _constants.ts
│ │ │ ├── _interface.ts
│ │ │ ├── _util.ts
│ │ │ ├── common.ts
│ │ │ ├── glob.ts
│ │ │ ├── mod.ts
│ │ │ ├── posix.ts
│ │ │ ├── separator.ts
│ │ │ └── win32.ts
│ │ ├── streams
│ │ │ └── conversion.ts
│ │ └── testing
│ │ ├── _diff.ts
│ │ └── asserts.ts
│ └── std@0.84.0
│ └── encoding
│ └── utf8.ts
├── import_map.json
... 省略 ...
このvendor
ディレクトリをGitなどのバージョン管理システムに追加することで、サードパーティモジュールのバージョン管理が実現できます。
また、もう一つ重要な点としてvendor/import_map.json
というファイルが生成されています。
{
"imports": {
"https://deno.land/": "./deno.land/",
"https://unpkg.com/": "./unpkg.com/"
},
"scopes": {
"./unpkg.com/": {
"./unpkg.com/puppeteer@13.3.2/lib/esm/puppeteer/common/helper.js": "./unpkg.com/puppeteer@13.3.2/lib/esm/puppeteer/common/helper.js"
}
}
}
--import-map
オプションでこのファイルを指定することで、Denoがvendor
ディレクトリに保存されたサードパーティモジュールを読み込んでくれるようになります。
# スクリプトの実行
$ deno run --import-map=vendor/import_map.json mod.ts
# テストの実行
$ deno test --import-map=vendor/import_map.json test.ts
サードパーティモジュールのデバッグについて
deno vendor
はDENO_DIR
を変更するアプローチと異なり、ダウンロードしたサードパーティモジュールのファイル名は基本的にそのまま維持してくれます。
それにより、サードパーティモジュールのデバッグが容易になるというメリットがあります。
そのため、deno vendor
コマンドはサードパーティモジュールに関わるデバッグ用途でも便利です。
まず、deno vendor
でvendor
ディレクトリを生成しましょう。
$ deno vendor mod.ts
その後、vendor
ディレクトリに保存されたファイルにconsole.log
を仕込んでみましょう。
$ vi vendor/deno.land/std@0.127.0/fs/exists.ts
# console.log("foobar")を追加
$ deno run --import-map=vendor/import_map.json mod.ts
...
foobar
デバッグが済んでvendor
ディレクトリが不要になったら、削除するだけで後片付けができます。
$ rm -rf ./vendor
より詳細にデバッグを行いたいときは、debugger文をvendor
ディレクトリ内のコードに仕込むとよいでしょう。
debugger;
debugger
文を仕込んだ後、下記コマンドを実行することで、Chrome DevToolsを使用してデバッグができます。
$ deno run --import-map=vendor/import_map.json --inspect-brk mod.ts
Denoでのデバッグに関しては、下記も参照ください。
サードパーティモジュールのアップデートについて
例えば、deno_stdのバージョンをv0.126.0からv0.127.0へアップデートしたいとします。
ソースコード中のimport
文を変更後、deno vendor
を実行すると、次のようなエラーが発生してしまいます。
$ deno vendor mod.ts
error: Output directory was not empty. Please specify an empty directory or use --force to ignore this error and potentially overwriteits contents.
この問題は次のように--force
オプションを指定すれば解決します。
$ deno vendor --force mod.ts
しかし、このやり方だと、古いバージョンのdeno_std(v0.126.0)が残ったままになってしまいます。
$ tree vendor/deno.land
vendor/deno.land
├── std@0.126.0
│ ├── _util
│ │ ├── assert.ts
│ │ └── os.ts
│ ... 省略 ...
├── std@0.127.0
│ ├── _util
│ │ ├── assert.ts
│ │ └── os.ts
... 省略 ...
この場合は、手動で古いバージョンを削除すれば解決します。
$ rm -rf ./vendor/deno.land/std@0.126.0
# (補足) すでに`vendor`がバージョン管理されている場合は`rm`ではなく`git rm`を使いましょう
ただし、このやり方だと古いバージョンのモジュールを消し忘れる可能性があります。
可能であれば、vendor
ディレクトリを削除してから再度deno vendor
コマンドを実行すると安全でしょう。
$ rm -rf ./vendor
$ deno vendor mod.ts
vendor
ディレクトリをdeno lint
やdeno fmt
の対象から外す
deno vendor
でダウンロードされたサードパーティモジュールは./vendor
ディレクトリに保存されるため、そのままではdeno lint
やdeno fmt
の適用対象として扱われてしまいます。
これを避けるためには、deno.jsonというファイルを用意するとよいです。
Denoはdeno.json
やdeno.jsonc
という名前の設定ファイルを用意しておくことで挙動をカスタマイズすることができます。(Deno v1.18以降のバージョンであれば、deno.json(c)
は自動で読み込まれます)
下記のような内容でdeno.json
を作成してみましょう。
{
"lint": {
"files": {
"exclude": ["vendor/"]
}
},
"fmt": {
"files": {
"exclude": ["vendor/"]
}
}
}
これにより、vendor
ディレクトリのサードパーティモジュールをdeno lint
やdeno fmt
の対象外とすることができます。
--import-map
の指定が面倒なときは
VelociraptorやMakeなどのツールを使う
deno vendor
で保存されたサードパーティモジュールを利用するには、毎回--import-map
オプションを指定する必要があります。
これが面倒な場合、VelociraptorやMakeなどのツールを使用することで負担を軽減できます。
例えば、Velociraptorであれば、下記のような設定ファイルを用意しておくことで--import-map
オプションの指定の手間を省けます。
scripts:
start:
cmd: mod.ts
importMap: vendor/import_map.json
deno.json(c)
でのサポートについて
現在、deno.json(c)
ファイルでImport mapファイルの指定をサポートするPRが作成され、すでにマージされています。
この機能がDeno本体に入れば、Velociraptorなどのツールに頼らなくても--import-map
オプションを指定する手間が省けます。
{
"importMap": "./vendor/import_map.json"
}
この機能は、次のマイナーアップデートであるDeno v1.20で入る可能性が高そうなので、deno vendor
を利用する際などに活用できそうです。
おわりに
Deno v1.19では、deno vendor
コマンド以外にも様々な変更が行われています。
-
--prompt
オプションの挙動がデフォルト化 - CompressionStreamとDecompressionStreamのサポート
-
Deno.File
のDeno.FsFile
へのリネーム - シグナルリスナAPIの安定化
これらの詳細については、公式のリリースポストで触れられているため、興味のある方はそちらも参照してみてください!
Discussion