🦕

Denoで知ったJavaScriptの楽しさ

2022/03/21に公開

先日、DenoでTwitter APIを叩く簡単なスクリプトを書いてみて、その書き心地に驚かされたので、自分なりにJS/TSとDenoについてまとめたものを書いてみます。

そもそもJavaScriptとは何なのか

まずJavaScriptという言語に対する理解が曖昧だったことに気付かされました。普段静的言語しかいじらないということもあるのですが、「なんかよくわからんけどフロントエンドには必須の言語」「フレームワークがめっちゃあってすごい」「仕様が奇っ怪なところがあり、脱JSを試みるWASMプロジェクトとかもけっこう見かける」…程度の認識でした。どうしても触らなければいけないときは、なるべく早く書き終わりたいという気持ちも強かったです。なんせ動く理由も動かない理由もよく分からないことがあるので。こんな理解では使いこなせるようにならないのは当たり前ですね。

そもそもJavaScriptは元々「Webブラウザで動くプログラミング言語」として作られたこと、Web開発には欠かせない言語であることは言うまでもありません。MDNも「JavaScript はウェブページにて複雑な機能をできるようにするプログラミング言語です」と言い切っています

JavaScriptはどうやって動いているのか

JSはインタープリタ言語であり、いくつかの種類がある「JavaScriptエンジン」を通し、通常ブラウザで実行されます。このエンジンは、ECMAScriptという標準化された言語仕様に基づいています。
主なエンジンには以下のものがあります。

ブラウザ エンジン
Chrome V8
Firefox SpiderMonkey
Safari JavaScriptCore

これ以外に新たにECMAを実装することを目指しているエンジンもあります。たとえば、なんでも再実装したいことで有名なRustコミュニティではBoaというエンジンがECMAScript準拠100%を目指して活発に開発中のようです。

Node.jsって何なの?

Node.jsは「V8上に構築されたJavaScriptランタイム」です。
ん? エンジンの上にランタイムが乗ってるのか? と、あらためて調べ始めた時は混乱しましたが、元々「ブラウザ+エンジン」上でしか動かなかったJSが、Node.jsの登場によって「Node.js on V8」上で動くようになった…ということなんですね。
これによって、JavaScriptはブラウザの外側でも動くようになりました。
現在ではこの「ブラウザの外」というのは、乱暴に言えば「サーバーサイド」ということになっていて、Node.jsといえばサーバーサイド開発のためのランタイム、という把握の仕方が一般的になっているように思います。しかしもちろん、「ブラウザの外」はサーバーだけではなく、Electronなどによってデスクトップアプリも作れたりするように、JSの利用シーンは本来もっと幅広いもののはずです。
このNode.js理解は、Webの強さそのものと関連が深いように思いますが、それはさておき…。

じゃあDenoは?

Denoとは、そんなNode.jsの創始者・Ryan Dahl本人によって2018年に発表された、これもやはり「V8上に構築されたJavaScriptランタイム」です。これだけ見るとまったく同じに見えますが…

いろいろと違う

ざっくり書くとDenoは:

  • よりセキュア
  • Rustで実装(Node.jsはC++)
  • エコシステムはまだまだこれから

…と理解しています。今回はNode.jsとDenoの大小さまざまある差異のひとつひとつには立ち入りません(Ryan Dahlの10 Things I Regret About Node.js - Ryan Dahl - JSConf EU 2018、もしくはこの動画について書かれたたくさんの解説記事がありますのでそちらをご覧ください)。語りたいのは、「ユースケースの違い」についてです。

そもそもNode.jsのユースケースって?

先述したようにNode.jsのユースケースはサーバーサイドアプリがメインです。
いや、クライアントサイドのフレームワークもnpmでインストールするじゃん…と思ってしまいますが、npmの隆盛に伴い、単なるJavaScriptのライブラリもnpmで配布することに違和感がなくなって現在に至る…というのが僕の理解です。間違っていたらご指摘ください。
それはともかく、Node.jsはWeb開発と決定的に結びついています。それは「ブラウザで動く言語」として誕生したJSの素性を考えれば当然すぎるほど当然のことです。
では、同じく「V8上に構築されたJavaScriptランタイム」であるDenoも、Web開発と不可分のランタイム…つまり、Node.jsを完全に代替するためのソフトウェアなのでしょうか。
これは人によって答えが違うように思います。コアの開発者が考えていること、ヘビーユーザーが感じていること、ちょっと触っただけの僕が思うことは違うはずです。コア開発者の抱くゴールはゴールとして、以下は僕の感じた、Denoが特にフィットするユースケースについて書いてみます。

Denoのユースケースは?

JSのランタイムではあるわけなので、Denoでもサーバーサイドのランタイムとして利用しようという動きは活発で、フレームワークもいくつも出てきているようです。
しかし実際に公式のGetting Startedに従っていじり始めると、単なるYet Another Node.jsとはまた違う印象を受けます。Denoを通して作られるもののすべてが最初からWebのほうを向いているかというとそんなことはなく、むしろローカルで快適にJS/TSを書き、CLIで実行すること、ここの気持ちよさがまず頭にガツンとくる感じがするのです。

冒頭に書いたTwitter APIを叩くスクリプトもまさにそんなフィーリングでした。実は別言語で書き始めていたのですが、ごちゃごちゃとやっている途中に「どうせjsonが返ってきてそれをいじくるなら、JSで書けばいいのでは?」となり、JSでのfetchおよび後処理を試すことにしました。そしてそのスクリプトをJS on Denoで書いてみたところ…なんだこの快適な体験は!と驚愕することになったのでした。それは、ブラウザやごちゃごちゃした依存関係抜きに、JSという言語そのものの良さに初めて触れることができた驚きでもありました。

たとえばjson Arrayを返すAPIを叩いてツイートのID配列を取得する関数は

const getIdArray = async (url) => {
  const res = await fetch(url, {
    headers: {
      "Authorization": token,
    },
  });
  const j = await res.json();
  const result = [];
  j.data.forEach((item) => {
    result.push(item.id);
  });
  return result;
};

とサクッと書けますし、その配列を使ってstdoutに内容を書き込むなら

const arr = await getIdArray(url);
console.log(arr);
const total = "total: " + arr.length;
console.log(total);
for (const i in arr) {
  const j = await getEachTweet(eachurl + arr[i] + params);
  console.log("No." + i);
  console.log(j.data.created_at);
  console.log(j.includes.users[0].name);
  console.log(j.data.text);
  const metric = "rt: " + j.data.public_metrics.retweet_count + " like: " +
    j.data.public_metrics.like_count;
  console.log(metric);
}

といった感じでconsole.log()を使えばOKです。型付言語を使うよりも圧倒的に早く作れてスカッとします。

こうなると、単に「セキュアだけどエコシステムがまだまだなYet Another Node.js」としてDenoを理解するのはもったいないのでは?という気持ちになってきます。個人的には、Denoとはむしろ「JS/TSそのものの良さを味わい、素直に書いていくことのできるランタイム」であり、少なくともまずそこに大きな価値を感じるソフトウェアなのです。

TypeScript on Deno

特に、Denoで書くTSは、言うなれば「プロトタイピングが楽々と出来るRust」です。型の保証がありながら、書き手の意図を吸収してくれるJS由来の柔軟性もあり、プログラミング体験はほぼノンストレス。はじめてJS/TSが楽しい、と感じさせてくれました。

JavaScriptでCLI/TUI

この「ローカルで快適にJS/TSを書き、CLIで実行する愉しさ」は決して自分の勝手な妄想ではないと思っています。たとえば公式リポジトリ上では「TUIアプリを作るためのモジュールを公式に組み入れよう」というissueが何度か立てられたりもしていて、DenoがCLI/TUIを意識する利用者層を着実に惹きつけている様子が見てとれます。この層というのは、Rust/GoでCLI/TUIを作ることに慣れ親しんだ人々とかなり重なっているのではないか、とも推察できます。Denoは最初にGo、現在ではRustで実装されたランタイムであり、それぞれの言語のコミュニティ(特にRust)においての認識率は他のコミュニティよりも高いように思われるからです。
しかし上記のような認識は、少なくとも活発にDenoに触れているコミュニティの外側ではまだまだ浸透していないというのが現状だと思います。

現状のDeno受容

そもそもNode.jsの開発者だったRyan Dallが新しく作った…というストーリーが非常に印象的なため、「現状のNode.jsの使われ方≒サーバーサイドアプリケーションを作るためのランタイム」という見方がまずあった上で、そこから派生しての「Node.jsの代替≒サーバーサイドアプリのもうひとつのランタイムが出てきた」という印象を持たれた方が一定数いるのではないかと想像します。僕もまさにこんな風に思っており、Web開発を積極的にやっている身でもないので「自分には無縁」と感じていました。
その分、シンプルなスクリプティングに採用したときの驚きが大きかったわけですが…。

なぜ快適なのか

ここからは、DenoでJS/TSを書くという体験の良さについて、ざっくりですが書いてみます。

実行バイナリはひとつ。すべてこれで完結

Denoで使うコマンドはdenoのみ。Denoさえ入れればすぐ使えます。これひとつで、runもfmtもlintもtestもできてしまいます。cargoなど、モダンな開発環境に慣れてしまった身には非常にありがたいです。
また、Visual Studio Codeの公式拡張もちゃんと出来ていて、Denoさえ入っていれば、initializeすることでDeno APIもちゃんと拾えます。さらに、たとえばconstでいいところをletにしていると速攻ラインを引いてくれるなど、rust-analyzerっぽく丁寧にアシストしてくれるのが非常にナイスな体験につながっているように思います。

TSがビルドイン

導入コストゼロ・何もせずに0秒でTSを書き始めることができます。

よくわからないディレクトリとかは発生しない

モジュールはすべて下記のようなimport文で管理します。

import * as log from "https://deno.land/std@0.130.0/log/mod.ts";

依存関係を管理するためのサードパーティのモジュールもありますが、そういうものがなくても(つまりnpm installなしで)動くシンプルさが潔いです。
npmを使わないので、node_modulesももちろん存在しません。

deno compileでシングルバイナリ生成

シングルバイナリを作れないと話が始まらない。

アートワークがかわいい

かわいい。

何か作りたくて作ったもの

ターミナルに時計を表示し、さらに雨を降らせる(アートワークにちなんで)TUIアプリをさくっと作ってみました。

kyoheiu/era: A rainy clock in your terminal.

CONFIG_HOME/era/config.jsonをいじれば他のものも降らせることができます。
なんてことないアプリですが、一応ポイントとしては、サードパーティモジュールは一切インポートせず、すべてDenoビルトインのAPIでまかなっています。
また、escape sequenceをstdoutに直接書き込んでカーソルを制御しているのも特徴と言えば特徴です(ターミナルのサイズを取得するDeno.consoleSizeはまだunstableですが)。これはライブラリがまだまだ揃っていないことの裏返しでもあります。現状Denoでの開発は、自分で実装することに楽しさを感じられる人向け、かもしれません。
ここでちょっとだけ気をつけたいのは、stdoutへの書き込みについてです。
Denoでは、ブラウザと同じくconsole.log()でstdoutに書き込みができます。escape sequenceも同様にconsole.log()でいける…はずなのですが、自分の環境ではいくつかの制御文字が機能しませんでした。

では何を使うかというと、

await Deno.stdout.write(new TextEncoder().encode("\x1b[1;1f"));

こんなふうにDeno APIを使って書き込みをすれば問題なく動きます。
Denoでescape sequenceを使いたい方はご注意ください。

おわりに

「なんだ、CLI/TUIか…」と思われる方もいるかもしれません。「プログラミングのホームグラウンド」は人それぞれなので、最初からWeb! 全部Web!な人にとってはターミナルでちょこまか何かを作って表示するなんてのはあまり意味のないことでしょう。でも自分のホームグラウンドは圧倒的にターミナルなので、そこで快適に開発ができ、アプリも作れちゃうというのは大変重要なポイントなのです。そして、そうした開発体験「も」提供してくれるランタイムとして、Denoがもっと知られていくと嬉しいな、と思っています。

Discussion