2021年7月14日水曜日

Denoでよく使う書き方まとめ

そろそろ Deno をメインにしても良いんじゃないかと思って使い始めてみてますが、あまり使い方がまとまってないので、自分用にまとめてみました。 これだけわかれば、普通の言語並に使いやすいんじゃないかな。 よく使うとこだけのまとめなので、困った時は公式のリファレンスを見ましょう。 Node.js でよく使う書き方との対比付きです。



sprintf → 標準ライブラリで可能

import { printf, sprintf } from "https://deno.land/std/fmt/printf.ts";
printf("hello %s", "world");
console.log(sprintf("hello %s", "world"));

printf (改行なし) → 少し面倒

REPL 上だとうまく動かないのが難点…。 このコードを見てもわかるように、Deno だと TextEncoder が至るところで登場してきます。
 await Deno.stdout.write(new TextEncoder().encode("hello: "));

コマンドライン引数 → Node.js より簡潔

Node だと配列のインデックスが 2 から始まるのでわかりにくいけど、Deno だと 0 から始まるため直感的でいい。
if (Deno.args.length < 3) {
  console.log("USAGE: test.js [file]");
  Deno.exit(1);
}
for (let i = 0; i < Deno.args.length; i++){
  console.log(Deno.args[i]);
}

sleep → 外部モジュールで OK

Node.js で真っ先にハマるのは sleep でしょう。 たかが sleep のために npm install はしたくないのが Node.js ですが、書き方は色々あるし事故りやすいです。 しかし Deno は外部モジュールを気楽に使えます。 たとえば以下で良いと思う。楽ちん。
import { sleep } from "https://deno.land/x/sleep/mod.ts";
await sleep(5);

ファイル読み込み (一度に全部) → Node.js より少し楽

const text = Deno.readTextFile("input.txt").then(response => {
  console.log(response);
});

ファイル読み込み (複数を同期的に1行ずつ) → Node.js より超楽

readLines が用意されてたので超簡単になりました。 しかし Node.js と同様に終端で empty string を返してくるのが厄介。 個人的バグの踏みやすさ No.1 は間違いなくこれ。 そんな仕様は Node.js と Deno くらいでいつも疑問なのだけど、undefined チェックするしかないのかな。
import { readLines } from "https://deno.land/std/io/mod.ts";

for (let i=0; i<3; i++) {
  const fileReader = await Deno.open(i + ".txt");
  for await (const line of readLines(fileReader)) {
    if (!line) continue;
    console.log(line);
  }
}
今のところ 1行あたりの文字数が多いと動かない問題を確認している。 そんな時は readLines ではなく readStringDelim を使わないといけない (仕様?)。 readLines も readStringDelim を呼び出しているだけなので、何らかの limit に引っ掛かっているみたいだけど、よくわからない。 サイズが大きくなった時は、いろいろ注意しながら作らないと予期せぬバグが発生しやすい。
import { readStringDelim } from "https://deno.land/std/io/mod.ts";

const fileReader = await Deno.open("long-line.txt");
for await (const line of readStringDelim(fileReader, "\n")) {
  if (!line) continue;
  console.log(line);
}

encoding → あまり想定されてない?

Shift JIS のファイル読み込みなどは、いまのところできないように見えます。 パッと見では想定されてないように見えます。 Windows が面倒そう。 一応ライブラリを使えばいけるみたい (下)。

ファイル書き込み

Deno.writeTextFile("./hello.txt", "Hello World!");

ファイルとディレクトリの判別

関数が変数になったり、細かなところが少し変わった。
const stat = await Deno.stat("/dir/path");
if (stat.isDirectory) {
  console.log("This is a directory");
}

glob → 標準ライブラリで可能に

import { expandGlob } from "https://deno.land/std/fs/expand_glob.ts";

for await (const file of expandGlob("**/*.txt", { globstar: true })) {
  console.log(file);
}

時間計測 → Node.js と一緒

console.time("hoge");
// 処理
console.timeEnd("hoge");

シェル実行 → Node.js のほうが楽

TextDecoder が邪魔で、Node.js のほうが楽な件。 cmd でファイルを渡す時にワイルドカードが効かないのは面倒だなあ。 ワイルドカードなどは bash とかの機能だから、起動が速いなどの利点があるのかな。 ただ常用するには辛いものがあるので、bash -c での実行か、glob を利用した実行を想定しているのかも知れない。
// とりあえず実行したいとき
const p = Deno.run({ cmd: ["cp", "a", "b"] });
// 実行結果が欲しいとき
const p = Deno.run({
  cmd: ["ls', "-altr"],
  // cmd: ["ls", "-altr", "*"],  // これが動かないのはつらい
  // cmd: ["bash", "-c", "ls -altr *"],  // これなら動く
  cwd: "working-directory",
  stdout: "piped",
  stderr: "piped",
  stdin: "null" });
let [status, stdout, stderr] = await Promise.all([
  p.status(), p.output(), p.stderrOutput(),
]);
const textDecoder = new TextDecoder();
stdout = textDecoder.decode(stdout);
stderr = textDecoder.decode(stderr);
p.close();

面倒なので、exec ライブラリが今のところ人気みたいです。
import { exec, OutputMode } from "https://deno.land/x/exec/mod.ts";

const result = await exec('bash -c "ls -altr *"',
  { output:OutputMode.StdOut });
console.log(result);
ただそれでもまだ面倒なケースは多いので、私は deno_zx の利用をおすすめします

野良ビルド → 標準ライブラリで可能に

deno bundle test.js > test.js

野良ライブラリの import (npm の代替)

Deno には npm がないのでライブラリのインストール概念が希薄です。 deno.land/x に登録する方法もありますし、GitHub に直接リンクを貼って import もできます。 deno-sqlite を例に挙げてみると、以下のように特定のリビジョンや特定のバージョンも import できます。 node_modules から解法されるのは地味に嬉しい。
import { DB } from "https://deno.land/x/sqlite/mod.ts";
import { DB } from "https://deno.land/x/sqlite@v2.4.0/mod.ts";
import { DB } from "https://raw.githubusercontent.com/dyedgreen/deno-sqlite/master/mod.ts";
import { DB } from "https://raw.githubusercontent.com/dyedgreen/deno-sqlite/v2.4.0/mod.ts";
import { DB } from "https://raw.githubusercontent.com/dyedgreen/deno-sqlite/f9c35896e5a9ec1f375c6172e4e20d7330dfe14f/mod.ts";
コードが増えた時にはバージョンまで記入すると管理が大変なので、 lock files で管理したほうが良さそうです。 Deno は deps.ts に依存性をまとめて書いておくことが推奨されているので、 以下のコマンドでそれを読み取ってキャッシュを保存しておけば、依存性を保存できます。 deps.ts 自体にバージョンは記載されているので、locks.json に保存されるのは比較用のハッシュ値です。 バージョン管理されていないモジュールを使うときには比較用のハッシュ値は必要そうですが、 信頼がおけるソースからロードする場合は、locks.json もなくても良いとわかります。 GitHub にはハッシュ値があるので、最終手段としてはそれでも良さそう。
 deno cache --lock=lock.json --lock-write src/deps.ts
また npm-check-updates のような機能が欲しくなるとも思いますが、udd という便利なやつが出てきたので、これを使うのが良さそうです。


まとめ

標準ライブラリ deno_std が強いので、Node.js より楽に使えます。 セキュリティの話が絡むファイル操作などを Deno.open などの決まった名前空間で利用できるので、セキュリティ管理しやすいし、何より使いやすい。 実行時にセキュリティ権限を許可しないといけないのが面倒だけど、それ以外は良いことしかない。 Node.js より全体的に高機能になっていて、その高機能になった部分は TypeScript で管理されているから、 ブラウザで使いたいと思った時にもビルドすればいける。ビルドも早い。 他言語ならあって当然のものが await を介して自然に使えるようになった結果、かなり使いやすくなった印象です。 await を付け忘れてバグるのは気を付けるしかないけど、いつもストレスが貯まるファイル操作が楽になったのはとても良い。

Deno が一番良いと思ったのは、top-level await と仕様の簡潔さですね。 Node.js はどこまで行っても仕様が複雑で覚えることが多いのと、ベストプラクティスがわからないことが多い。 deno fmt, deno lint といったコマンドで何の準備もなくコードを綺麗にしてくれるのは素晴らしい。 Node.js にも eslint とかのサードパーティプラグインがあったけど、面倒で何もやってなかった。 私は Node.js や Webpack の無限に増え続ける混沌仕様を覚える気はないし、関わらないようにしていたけど、Deno のように全部用意してくれれば従いたくなる。 流行や仕様をよく見ていないと使いこなせない Node.js と違って、Deno はルールの少ない標準ライブラリだけ見れればいい。とても良い。 deno lint に従ってフロントエンドも lint しているとそれなりに綺麗に書けるので、割とおすすめ。 HTML/CSS/JavaScript の実装にもある程度使えて、Deno のエラーが出ないようにするだけで安定した実装ができる。 Go に近い思想で設計されてて、より使いやすく、好感度は高い。

deno lint は長らく unstable だったけど、Deno 1.11 から stable になって本格導入しやすくなってきました。 執筆時点で、unstable で困るのは fs module くらいですかね。 特に気にならないのでもう完全移行できるんじゃないかと思う。

Deno の一番の難点は、Node との互換性のなさ。 同一コードで管理する方法も欲しいですが、私は諦めたのでなるべく早く移行したほうが楽だと思いました。 とは言え最近では、 Node は Deno に寄せ、Deno は Node に寄せるようになってきたので、それほど差がある訳でもないです。 将来的には仕様の綺麗でマスコットのかわいい Node という立ち位置で安定しそうな気がします。

0 件のコメント: