Deno標準モジュールを、前編と後編の2回に分けて解説します。本記事は前編です
はじめに
Deno標準モジュールはDenoコアチームによって開発・
標準モジュールを使う際には以下の例のようにhttps://
名前空間から必要な機能をインポートして使います。たとえば、HTTPサーバーを使用する例は以下のようになります。
標準モジュールのカバーする範囲は非常に多岐に渡ります。一例を上げると、ファイルシステム関連、一般的なファイルフォーマットの扱い
標準モジュールには現在23のカテゴリが登録されています。本記事では各カテゴリを俯瞰的に眺めつつ解説していきます。
Deno標準モジュールのカバー範囲
非常にカバー範囲の広い標準モジュールですが、あらゆる機能が含まれているわけではありません。
標準モジュールでは特定のベンダーのソフトウェアとのインターフェースは提供しないとされています。特定ベンダーのRDBMS
また、標準モジュールでは、フレームワーク的な機能は提供しないとされています。ここでいうフレームワークとはRailsのような、特定のステップに従えば迅速にアプリケーションを開発できるタイプのソフトウェアをさします。標準モジュールでは、そのようなアプリケーションの骨格を提供するのではなく、あくまで、アプリケーションのパーツになるような基礎的な機能を提供します。
Node.jsとの比較
Node.
それに対してDenoは、本体のAPIがまずあって、次にカバー範囲の広い標準モジュールがあり、それ以外が3rdパーティという構造になっています。
このような構成にする狙いの一つは、よりランタイムを安心して使えるようにすることです。自分のやりたいことが標準モジュールの機能として提供されていれば安心してそれを使えます。3rdパーティのモジュールを使う場合は、そのツールが本当に信頼に足るツールなのかを評価する必要がありますが、そのような評価を正しく行うのは相当に難易度が高いです
Python、Ruby、Go、Rustなどの他のメジャーな言語では標準モジュール
エコシステムレベルで考えた場合の標準モジュールの狙いは、巨大な依存ツリーを避けるというものがあります。Node.
しかし、標準モジュールを持つことはメリットだけではないかもしれません。考えられるデメリットとして、公式以外の選択肢が狭まることがありそうです。たとえば、Node.Deno.
)std/
から提供されるアサーションでほとんど事足りてしまうため、選択肢が狭まっていると言えます
モジュール解説
以下ではDeno標準モジュールの中の各モジュールについて解説していきます。
1. Archive
Archiveは書庫形式ファイルの処理が含まれる名前空間です。Archiveには現在のところTar形式の操作用のAPIが実装されています。Tarの圧縮・
import { Tar } from "https://deno.land/[email protected]/archive/tar.ts";
import { Buffer } from "https://deno.land/[email protected]/io/buffer.ts";
import { copy } from "https://deno.land/[email protected]/streams/copy.ts";
const tar = new Tar();
// Uint8Arrayからデータを追加
const content = new TextEncoder().encode("Deno.land");
await tar.append("deno.txt", {
reader: new Buffer(content),
contentSize: content.byteLength,
});
// 実ファイル(./land.txt)を指定してエントリーを追加
await tar.append("land.txt", {
filePath: "./land.txt",
});
// Tarアーカイブをファイルに書き出す
const writer = await Deno.open("./out.tar", { write: true, create: true });
await copy(tar.getReader(), writer);
writer.close();
Archiveでは将来的にはZip形式やその他のアーカイブ形式の操作用のAPIが実装される予定です。
2. Async
Asyncディレクトリ以下では、PromiseやAsyncItearatorに関連した非同期処理をサポートするためのユーティリティ類が提供されています。
- abortable:任意のプロミスにabortする機能を追加する
- deadline:任意のプロミスに、時間制限を追加する
- debounce:デバウンス
(処理の間引き) 機能 - deferred:外部から
resolve/
可能なプロミスを作るユーティリティreject - delay:一定時間でresolveするプロミスを作る
- MuxAsyncIterator:複数のAsyncIteratorを受け取って1つのAsyncIteratorにまとめる
- pooledMap:複数のプロミスを受け取って一定個数づつ実行し、結果をAsyncIteratorにする
- retry:与えられた処理を指数バックオフの時間間隔でリトライする
- tee:AsyncIteratorを任意の個数に分岐する
ここでは例としてdeadline
とretry
の使い方を紹介します。
deadline
はプロミスと、ミリ秒を受け取って、与えられたミリ秒以内にプロミスがresolve or rejectしなければ、DeadlineErrorでrejectされるようなプロミスを返す関数です。
import {
deadline,
DeadlineError,
} from "https://deno.land/[email protected]/async/deadline.ts";
try {
await deadline(fetch("https://example.com/"), 1000);
} catch (e) {
if (e instanceof DeadlineError) {
console.error("1秒以内にレスポンスがありませんでした");
} else {
throw e;
}
}
上記の例では、https://
にリクエストするプロミスを1秒1秒以内にレスポンスがありませんでした
と表示されます。
deadline
は制限時間付きで何らかの非同期処理を実行したい場合に便利な関数です。
次にretry
の例を見てみましょう。retry
は与えられた処理が成功するまで、規定回数
import { retry } from "https://deno.land/[email protected]/async/retry.ts";
await retry(() => fetch("https://my-service.example/"), {
maxAttempts: 6,
});
上記の例ではhttps://
にリクエストするという非同期処理が成功するまで6回まで再試行するという例になっています。6回すべて失敗するとスクリプト自体が失敗します。
retry
は、ある程度の不安定性を許容したいような処理を記述したい場合に便利な関数です。
3. Bytes
Bytesではバイナリ処理に関するユーティリティが実装されています。
- BytesList:複数のバイト列を1つのバイト列のように扱う
(主にパフォーマンスチューニング用) - concat:複数のバイト列をつなげて1つのバイト列にする
- copy:バイト列をコピーする
- endsWith:あるバイト列の終端が、与えられたバイト列と一致するかを判定する
- equals:2つのバイト列が等しいかどうか判定する
- includesNeedle:あるバイト列が、与えられたバイト列を内部に含んでいるかどうか判定する
- indexOfNeedle:あるバイト列が、与えれたバイト列を含んでいる場合にその最初の位置を返す
(含んでいなければ-1を返す) - lastIndexOfNeedle:あるバイト列が、与えられたバイト列を含んでいる場合にその最後の位置を返す
(含んでいなければ-1を返す) - repeat:バイト列を指定回数繰り返したバイト列を返す
- startsWith:あるバイト列の先頭が、与えられたバイト列と一致するかを判定する
ここでは例として、includesNeedle
とrepeat
を使った例を紹介します。
includesNeedle
は2つのバイト列を受け取って、1つめのバイト列が2つめのバイト列を含んでいればtrueを返します。なお、3引数目で配列のインデックスを指定すると、与えられたインデックス以降から探します。
import { includesNeedle } from "https://deno.land/[email protected]/bytes/includes_needle.ts";
const source = new Uint8Array([0, 1, 2, 1, 2, 1, 2, 3]);
const needle = new Uint8Array([1, 2]);
console.log(includesNeedle(source, needle)); // true
console.log(includesNeedle(source, needle, 6)); // false
repeat
は与えられたバイト列を指定回数繰り返したバイト列を返します。なお、繰り返し回数にマイナスの値や、整数でない値を入力すると例外になります。
import { repeat } from "https://deno.land/std@$STD_VERSION/bytes/repeat.ts";
const source = new Uint8Array([0, 1, 2]);
console.log(repeat(source, 3)); // [0, 1, 2, 0, 1, 2, 0, 1, 2]
console.log(repeat(source, 0)); // []
console.log(repeat(source, -1)); // RangeError
console.log(repeat(source, 1.5)); // Error
4. Collections
Collectionsでは配列操作、オブジェクト操作の中で一般性の高いと考えられる処理が実装されています。lodashの配列・
- aggregateGroups:すべての値が配列であるようなオブジェクトについて、各値を与えられたコールバック関数で集約する
- associateBy:配列の要素
V
に対して、与えられた関数を用いてキーK
を選択し、K
をキーにV
を値として持つようなオブジェクトを作って返す - associateWith:配列の要素
K
に対して、与えられた関数を用いて値V
を選択し、K
をキーにV
を値として持つようなオブジェクトを作って返す - BinaryHeap:二分ヒープの実装
- BinarySearchTree:二分探索木の実装
- chunk:与えられた配列を与えられた要素数のchunkに切り分ける
- deepMerge:与えられた2つの配列とオブジェクトのネスト構造を再帰的にマージする
- distinct:与えられた配列の要素から重複要素を取り除いた配列を返す
- distinctBy:配列の要素に対して関数を呼び出した結果に重複が無いように配列から要素を間引いた配列を返す
- dropLastWhile:配列の末尾から各要素に対して関数を呼び出し、最初に結果がfalseになるまでの要素を削除した配列を返す
- dropWhile:配列の先頭から各要素に対して関数を呼び出し、最初に結果がfalseになるのまでの要素を削除した配列を返す
- filterEntries:オブジェクトのキーと値のペアに対して与えられた関数を呼び出して、falseになったものを削除したオブジェクトを返す
- filterKeys:オブジェクトのキーに対して与えられた関数を呼び出して、falseになったものを削除したオブジェクトを返す
- filterValues:オブジェクトの値に対して与えられた関数を呼び出して、falseになったものを削除したオブジェクトを返す
- findSingle:配列の要素に対して関数を呼び出して、結果がtrueになるような要素が一つだけ存在した場合に、その要素を返す
- firstNotNullishOf:配列の要素に対して関数を呼び出した結果がnullish(nullかundefinedでない)にならない最初のものを返す
- groupBy:配列の要素
X
に対して関数を呼び出して、その結果をK
とする。K
をキーとして、K
がキーとなるようなX
を集めた配列を値とするオブジェクトを生成して返す。 - includesValue:オブジェクトが与えられた値をもつかどうかを判定する
- intersect:与えられた複数個の配列から共通部分を含む配列を返す
- joinToString:高機能な文字列の結合
- mapEntries:オブジェクトのキーと値のペアに対して関数を呼び出してその結果で値を置き換えたオブジェクトを返す
- mapKeys:オブジェクトのキーに対して関数を呼び出してその結果で値を置き換えたオブジェクトを返す
- mapNotNullish:配列を与えられた関数でmapし、nullish(nullかundefined)にならなかった要素だけを集めた配列を返す
- mapValues:オブジェクトの値に対して関数をよびだしてその結果で値を置き換えたオブジェクトを返す
- maxBy:配列の要素で与えられた関数を呼び出し、その結果の値が最大になるような、元の配列の要素を返す
- maxOf:配列の要素で与えられた関数を呼び出し、その結果の値が最大になる時の、結果の値を返す
- maxWith:配列の要素を与えられた2引数比較関数で比較した際に、最大になるような、元の配列の要素を返す
- minBy:配列の要素で与えられた関数を呼び出し、その結果の値が最小になるような、元の配列の要素を返す
- minOf:配列の要素で与えられた関数を呼び出し、その結果の値が最小になる時の、結果の値を返す
- minWith:配列の要素を与えられた2引数比較関数で比較した際に、最小になるような、元の配列の要素を返す
- partition:配列の要素を与えられた関数で呼び出して、trueなったものの配列と、falseになったものの配列からなる2要素の配列を返す
- permutation:配列の要素を並び替えた列のすべての組み合わせからなる配列を返す
- RedBlackTree:クラス、赤黒木の実装
- reduceGroups:値が配列であるオブジェクトの各値を与えられた関数でreduceする。
- runningReduce:配列を与えられた関数でreduceする際の計算途中の値全体の配列を返す
- sample:配列からランダムに1要素取得する
- slidingWindows:配列から、与えられた要素数の部分配列を取り出し、その取り出す操作を先頭から1要素づつずらしながら行い、その様にして生成した部分配列全体の配列を返す
- sortBy:配列の要素に対して関数を呼びその結果の値にもとづいて要素をソートする
- sumOf:配列の要素に対して関数を呼びその結果の値の合計値を返す
- takeLastWhile:配列の要素に対して末尾から順に関数を呼び出し、最初に値がfalseになるまでの間の要素を取り出した配列を返す
- takeWhile:配列の要素に対して末尾から順に関数を呼び出し、最初に値がfalseになるまでの間の要素を取り出した配列を返す
- union:複数の配列の和の配列を返す
- unzip:2-タプルの配列を受け取って、タプルの1要素目だけの配列と、2要素目だけの配列のタプルを返す
- withoutAll:与えられた配列から、与えれた複数の要素をすべて削除した配列を返す
- zip:複数の配列を受け取って、先頭から1要素ずつ取り出してタプルを作成し、その様なタプルの配列を返す
各機能の詳細は公式ドキュメントを参照してください。
5. Crypto
CryptoではWeb Crypto APIの独自拡張などの、暗号処理関連機能が実装されています。
Web Crypto APIの中のdigestが特に拡張されていて、SHA-3系、BLAKE2系、BLAKE3系のアルゴリズム等がサポートされています。
import { crypto } from "https://deno.land/[email protected]/crypto/mod.ts";
const a = await crypto.subtle.digest(
"SHA3-224",
new TextEncoder().encode("hello world"),
);
なお、SHA-256などのWeb Cryptoでサポートされているアルゴリズムが指定された場合は、内部的にWeb Cryptoの呼び出しに置き換わります。
また、Web Crypto APIのdigest関数はArrayBufferもしくはTypedArrayのみをサポートしていますが、このdigestはAsyncIterableの入力も受け付けているため、大きなデータをストリーミングしながらdigest値を得ることができます。
import { crypto } from "https://deno.land/[email protected]/crypto/mod.ts";
const file = await Deno.open("data.txt");
const a = await crypto.subtle.digest("BLAKE3", file.readable);
file.close();
他に、digestSyncという同期的なAPIも用意されています。こちらのAPIは主にNode互換性に必要というモチベーションで追加されたAPIですが、どうしても同期的にdigest値を得体場面で有用かもしれません。
import { crypto } from "https://deno.land/[email protected]/crypto/mod.ts";
const a = await crypto.subtle.digestSync(
"SHA3-224",
new TextEncoder().encode("hello world"),
);
さらにCryptoモジュールでは、タイミング攻撃を防ぐ手段として有効な、timingSafeEqual
関数なども提供されています。クレデンシャルの比較時など、タイミングからの情報漏洩が気になる場面ではこの関数が便利です。
import { timingSafeEqual } from "https://deno.land/[email protected]/crypto/timing_safe_equal.ts";
const a = new TextEncoder().encode("a".repeat(1000));
const b = new TextEncoder().encode("b".repeat(1000));
timingSafeEqual(a, a);
timingSafeEqual(a, b); // 上の比較と同じ時間がかかる
6. Datetime
Datetimeでは日付に関する処理が実装されています。具体的には以下のような関数が提供されています。
- dayOfYear:年初からの日数を返す
- difference:2つの日付の差をオブジェクト形式で返す
- format:日付を与えられたフォーマット形式に整形する
- isLeap:日付の年が閏年であるかどうかを判定する
- parse:与えられた文字列を与えられたフォーマット形式で解釈し、その日付を返す
- toIMF:与えられた日付をIMF形式に整形する
ここでは例として、format
とparse
を使う例を紹介します。
import {
format,
parse,
} from "https://deno.land/[email protected]/datetime/format.ts";
format(new Date(2019, 0, 20), "yyyy-MM-dd"); // => "2019-01-20"
parse("2019-01-20", "yyyy-MM-dd"); // => new Date(2019, 0, 20)
なお、現在JSの標準化団体TC39では、次世代の日付表現のオブジェクトとしてTemporalの策定が進んでいます。Temporalは提案としてのステージは現在3であり、近い将来に新たなJSの仕様となる可能性が高く、DenoのJSエンジンであるV8でも既に実装がある程度進んでいます。将来的にTemporalでカバーされる機能についてはstdからは提供されなくなると想定されており、現在のDatetimeモジュールの機能の多くが将来的にdeprecated
7. Dotenv
Dotenvでは.env
ファイルから環境変数を読み込む機能が実装されています。
たとえば以下のような.env
ファイルを作ります。
FOO=bar
HELLO=world
このファイルをカレントディレクトリに置いた状態で、以下のスクリプトを作成実行してみましょう。
import "https://deno.land/[email protected]/dotenv/load.ts";
console.log(Deno.env.get("FOO"));
console.log(Deno.env.get("HELLO"));
実行すると以下のようになり、.env
ファイルから環境変数が読み込まれていることが確認できます。
$ deno run --allow-env --allow-read script.js bar world
.env
ファイルの中では、他の環境変数を${}
という記法で参照することもできます。
FOO=bar
BAZ=${FOO}-123
上記の例ではBAZ
の値はbar-123
となります。
.env
ファイルで環境変数を管理する手法は、Denoに限らずアプリケーション開発で広く使われています。
8. Encoding
Encodingでは各種エンコーディング方式のエンコーダーデコーダー
現在は以下のような形式がサポートされています。
- ASCII85
- Base32
- Base58
- Base64
- Base64 URL
- binary
- CSV
- フロントマター
(マークダウンの先頭にYAML等が埋め込まれた形式) - hex
- JSON Lines、NDJSON
(改行で区切られたJSONのストリーム形式) - JSONC
- TOML
- VARINT
- YAML
例としてBase64とYAMLを使う例を紹介します。
import {
decode,
encode,
} from "https://deno.land/[email protected]/encoding/base64.ts";
const data = "aGVsbG8=";
const binaryData = decode(data);
console.log(binaryData);
// => Uint8Array(5) [ 104, 101, 108, 108, 111 ]
console.log(new TextDecoder().decode(binaryData));
// => hello
console.log(encode(binaryData));
// => aGVsbG8=
import {
parse,
stringify,
} from "https://deno.land/[email protected]/encoding/yaml.ts";
const data = parse(`
foo: bar
baz:
- qux
- quux
`);
console.log(data);
// => { foo: "bar", baz: [ "qux", "quux" ] }
const yaml = stringify({ foo: "bar", baz: ["qux", "quux"] });
console.log(yaml);
// =>
// foo: bar
// baz:
// - qux
// - quux
9. Flags
Flagsではコマンドラインオプションをパースする関数が提供されています。
例えば以下のようなオプション体系を持ったコマンドを考えてみましょう。
Usage: my-command [-h|--help] [-n|--number <num>] [--dry-run] <target> Options: -h, --help ヘルプメッセージを表示 -n, --number <num> 実行回数を指定 --dry-run 実行せず、実行の計画のみを表示する <target> 実行の対象
このオプションを実現するためには、以下のようにflags
モジュールを使います
import { parse } from "https://deno.land/[email protected]/flags/mod.ts";
const args = parse(Deno.args, {
boolean: ["help", "dry-run"],
string: ["number"],
alias: {
h: "help",
n: "number",
},
});
const help = args.help;
const number = +args.number!;
const dryRun = args.dryRun;
const target = args._[0];
以上の呼び出しで、help
、number
、dryRun
、target
などのパラメータが上記のヘルプメッセージの通りに取得できます。より詳細なparse
関数の使い方については、公式ドキュメントを参照してください。
なお、このモジュールはnpmモジュールのminimist
をベースにデザインされています。minimist
でサポートされているオプション・minimist
から変更されています。
flags
モジュールはシンプルなコマンドラインツールの作成には十分な機能を備えていますが、例えばgitコマンドのようなサブコマンドがたくさんあり、サブコマンドごとに全く異なるオプション体系を持つようなツールを開発する場合には、機能が足りないと感じる場合もあるかもしれません。その様な場合は3rdパーティモジュールのyargs
を使うことをお勧めします。yargs
は以下のように使うことができます。
import yargs from "https://deno.land/x/[email protected]/deno.ts";
import { Arguments } from "https://deno.land/x/[email protected]/deno-types.ts";
yargs(Deno.args)
.command("download <files...>", "download a list of files", (yargs: any) => {
return yargs.positional("files", {
describe: "a list of files to do something with",
});
}, (argv: Arguments) => {
console.info(argv);
})
.strictCommands()
.demandCommand(1)
.parse();
10. FMT
FMTでは各種フォーマティング
- バイトサイズの整形
- ターミナルでの色付け
- 時間間隔表現の整形
- printf
(C言語のprintf、sprintf互換のフォーマッティング)
バイトサイズのフォーマットの例は以下のようになります。ファイルサイズの出力などの際に便利です。
import { format } from "https://deno.land/[email protected]/fmt/bytes.ts";
// バイトサイズを人間に読みやすい形式にする
format(1337);
//=> '1.34 kB'
format(100);
//=> '100 B'
// 単位をbitにすることも出来る
format(1337, { bits: true });
//=> '1.34 kbit'
ターミナルに色を付けたい場合は以下のようになります。CLIツールなどで成功・
import { green, red } from "https://deno.land/[email protected]/fmt/colors.ts";
console.log(red("赤"));
console.log(green("緑"));
時間間隔を見やすく表示します。
import { format } from "https://deno.land/[email protected]/fmt/duration.ts";
// "0d 0h 1m 39s 674ms 0µs 0ns"
format(99674);
// "00:00:01:39:674:000:000"
format(99674, { style: "digital" });
// "1 minutes, 39 seconds, 674 milliseconds"
format(99674, { style: "full", ignoreZero: true });
後編に続く
Deno標準モジュールの解説は後編に続きます。また、後編ではDeno標準モジュールの今後の展望についても取り上げます。