deno_graphというモジュールを使って、ブラウザで使っているES Modulesの依存関係を分析してみたいと思います。
(余談ですが、Node.jsやTypeScriptで動くnode_modulesからのimportもES Modulesと呼ばれているようです。ここではブラウザやDenoで動く、標準のES Modulesについて扱います。)
deno_graphとは何か
deno_graphは、Denoの内部で依存関係分析に使われているRustライブラリです。
GitHubリポジトリを見ると分かりますが、主にDenoコアチームによって保守・管理されています。
https://deno.land/x/deno_graph では、そのRustライブラリをwasm化したものが配信されています。これを使うことで、コードからES Modulesの依存関係を解析することが可能です。
deno_graphの使い方
公式ドキュメントは https://doc.deno.land/https://deno.land/x/deno_graph/mod.ts を参照してください。
まずは、https://deno.land/x/deno_graph@0.22.0/mod.ts
からcreateGraph
関数をimportします。
import { createGraph } from "https://deno.land/x/deno_graph@0.22.0/mod.ts";
importしたcreateGraph
関数を使って依存関係を分析していきます。ここでは、https://deno.land/std@0.125.0/streams/mod.ts の依存関係を分析してみたいと思います。
await createGraph("<解析したいモジュールのURL>")
でモジュールグラフを作成します。
const graph = await createGraph("https://deno.land/std@0.125.0/streams/mod.ts");
createGraph
関数の返り値はModuleGraphクラスです。
依存関係を整形して出力する
graph.toString()
でモジュールの依存関係を整形した文字列を取得できます。
console.log(graph.toString());
type: TypeScript
dependencies: 10 unique (total 67KB)
https://deno.land/std@0.125.0/streams/mod.ts (169B)
├─┬ https://deno.land/std@0.125.0/streams/conversion.ts (14.83KB)
│ └─┬ https://deno.land/std@0.125.0/io/buffer.ts (30.95KB)
│ ├── https://deno.land/std@0.125.0/_util/assert.ts (405B)
│ ├── https://deno.land/std@0.125.0/bytes/bytes_list.ts (3.95KB)
│ ├─┬ https://deno.land/std@0.125.0/bytes/mod.ts (4.47KB)
│ │ └── https://deno.land/std@0.125.0/bytes/equals.ts (1.43KB)
│ └── https://deno.land/std@0.125.0/io/types.d.ts (3.26KB)
├─┬ https://deno.land/std@0.125.0/streams/delimiter.ts (4.13KB)
│ └── https://deno.land/std@0.125.0/bytes/bytes_list.ts *
└─┬ https://deno.land/std@0.125.0/streams/merge.ts (1.96KB)
└── https://deno.land/std@0.125.0/async/deferred.ts (1.45KB)
依存関係一覧を取り出す
graph.modules
で依存関係の一覧を取得できます。
for (const module of graph.modules) {
console.log("URL: ", module.specifier);
console.log("サイズ: ", module.size);
console.log("種類: ", module.kind);
console.log("メディアタイプ: ", module.mediaType);
// console.log("ソースコード: ", module.source);
console.log("型定義のみの依存関係かどうか: ", module.typesDependency);
console.log();
}
URL: https://deno.land/std@0.125.0/_util/assert.ts
サイズ: 405
種類: esm
メディアタイプ: TypeScript
型定義のみの依存関係かどうか: undefined
URL: https://deno.land/std@0.125.0/async/deferred.ts
サイズ: 1489
種類: esm
メディアタイプ: TypeScript
型定義のみの依存関係かどうか: undefined
URL: https://deno.land/std@0.125.0/bytes/bytes_list.ts
サイズ: 4048
種類: esm
メディアタイプ: TypeScript
型定義のみの依存関係かどうか: undefined
URL: https://deno.land/std@0.125.0/bytes/equals.ts
サイズ: 1467
種類: esm
メディアタイプ: TypeScript
型定義のみの依存関係かどうか: undefined
URL: https://deno.land/std@0.125.0/bytes/mod.ts
サイズ: 4573
種類: esm
メディアタイプ: TypeScript
型定義のみの依存関係かどうか: undefined
URL: https://deno.land/std@0.125.0/io/buffer.ts
サイズ: 31690
種類: esm
メディアタイプ: TypeScript
型定義のみの依存関係かどうか: undefined
URL: https://deno.land/std@0.125.0/io/types.d.ts
サイズ: 3339
種類: esm
メディアタイプ: Dts
型定義のみの依存関係かどうか: undefined
URL: https://deno.land/std@0.125.0/streams/conversion.ts
サイズ: 15187
種類: esm
メディアタイプ: TypeScript
型定義のみの依存関係かどうか: undefined
URL: https://deno.land/std@0.125.0/streams/delimiter.ts
サイズ: 4234
種類: esm
メディアタイプ: TypeScript
型定義のみの依存関係かどうか: undefined
URL: https://deno.land/std@0.125.0/streams/merge.ts
サイズ: 2011
種類: esm
メディアタイプ: TypeScript
型定義のみの依存関係かどうか: undefined
URL: https://deno.land/std@0.125.0/streams/mod.ts
サイズ: 169
種類: esm
メディアタイプ: TypeScript
型定義のみの依存関係かどうか: undefined
特定のモジュールの情報を取得する
graph.get("<URL>")
で特定のモジュールの情報を取得できます。
const module = graph.get("https://deno.land/std@0.125.0/io/buffer.ts");
if (module) {
console.log("URL: ", module.specifier);
console.log("サイズ: ", module.size);
console.log("種類: ", module.kind);
console.log("メディアタイプ: ", module.mediaType);
// console.log("ソースコード: ", module.source);
console.log("型定義のみの依存関係かどうか: ", module.typesDependency);
}
URL: https://deno.land/std@0.125.0/io/buffer.ts
サイズ: 31690
種類: esm
メディアタイプ: TypeScript
型定義のみの依存関係かどうか: undefined
まとめてJSON化する
JSON.stringify
すると、モジュールの依存関係がJSON形式の文字列で取得できます。
console.log(JSON.stringify(graph, null, 2));
{
"roots": [
"https://deno.land/std@0.125.0/streams/mod.ts"
],
"modules": [
{
"kind": "esm",
"size": 405,
"mediaType": "TypeScript",
"specifier": "https://deno.land/std@0.125.0/_util/assert.ts"
},
{
"kind": "esm",
"size": 1489,
"mediaType": "TypeScript",
"specifier": "https://deno.land/std@0.125.0/async/deferred.ts"
},
{
"kind": "esm",
"size": 4048,
"mediaType": "TypeScript",
"specifier": "https://deno.land/std@0.125.0/bytes/bytes_list.ts"
},
{
"kind": "esm",
"size": 1467,
"mediaType": "TypeScript",
"specifier": "https://deno.land/std@0.125.0/bytes/equals.ts"
},
{
"dependencies": [
{
"specifier": "./equals.ts",
"code": {
"specifier": "https://deno.land/std@0.125.0/bytes/equals.ts",
"span": {
"start": {
"line": 180,
"character": 23
},
"end": {
"line": 180,
"character": 36
}
}
}
}
],
"kind": "esm",
"size": 4573,
"mediaType": "TypeScript",
"specifier": "https://deno.land/std@0.125.0/bytes/mod.ts"
},
{
"dependencies": [
{
"specifier": "../_util/assert.ts",
"code": {
"specifier": "https://deno.land/std@0.125.0/_util/assert.ts",
"span": {
"start": {
"line": 1,
"character": 23
},
"end": {
"line": 1,
"character": 43
}
}
}
},
{
"specifier": "../bytes/bytes_list.ts",
"code": {
"specifier": "https://deno.land/std@0.125.0/bytes/bytes_list.ts",
"span": {
"start": {
"line": 2,
"character": 26
},
"end": {
"line": 2,
"character": 50
}
}
}
},
...(以下略)
まとめ
deno_graphの使い方の一部を紹介しました。
(実際は他の関数もあるのですが、使い方がよく分かりませんでした…)
勘のいい方はお気づきだと思いますが、これは実際にはdeno info
コマンドの内部実装です。
元々はDeno.info()
関数を導入してdeno info
コマンドをプログラム上から直接使用できるようにする事が検討されていました(#10758)。
しかし、この関数をDeno組み込みのAPIにするよりは、wasm化して別の場所からリリースしたほうがバージョン管理しやすいという意見があり(#11497)、外部モジュールとしてリリースされました。
こういう経緯があって、deno_graphモジュールはDeno以外の環境からも利用可能な形態になっています。
deno info
コマンドとdeno_graph
モジュールの使い分けですが、コマンドラインから依存関係分析を行いたい時は前者、プログラムから行いたい時は後者、という使い分けがいいのかなと思いました。