🦕

脱Node.jsからのDenoしてる話

2023/02/25に公開

Node.jsのパッケージ周りのしんどさやPureJSとの乖離のつらさ(例えば昔はfetchがなかったとか)を感じ、個人ではDenoに切り替えていきたいと常々考えていました。実際CLIやバックエンド等はすんなり移行できたものの、最後の障壁となっていたのが現在利用している、tsc 等Node.jsで動くツールでした。これを使えないと脱することはできません。

しかしそれも過去の話。最近のDenoはnpmパッケージを正式に使えるようになり、後は tsc 等のNode.jsで動くツールをインストールできれば自分の使い方ならば十分脱Node.jsできるはずです。最近タイミングよく新しいPCを入手したので、ここ数ヶ月はNode.jsを入れない状態で個人開発を行ってみました。

初めに書いておくとフレームワークなどをあまり使わない自分の使い方だと実務向きではないとは思います。ただ、脱Node.jsの話はちらほらあるものの実際に何をしてどんなふうに使っているかの具体的な話があまりないので、何を使っているのか、どう使っているのかをざっくりとまとめていきたいと思います。

主な開発環境

  • 開発ツール
  • フロントエンド
    • tsctsconfig.json を用いたシンプルなビルド
      • Minifyやビルド系のツールは個人規模なのでほぼ使わない。
      • どうしても使うときだけ入れて使ったりするので今回は触れない。
    • WebComponents+PureJSによるWebアプリの開発
      • React等のフレームワーク未使用。
      • 実務ではフレームワーク必須なのでここに関しても触れられない。
  • バックエンド

あまり実務には向いていないものの tsc をインストールできるなら他のNode.jsのツールもインストールできるでしょう。そういう場合の参考にもなると思うので以下では自分がよく使っているツールや何をよく使っているかを紹介していきます。

Deno本体

https://deno-ja.vercel.app/manual@main/getting_started/installation

Deno標準ツール

Denoはデフォルトでリントやフォーマッター、テスト等を提供しています。正直基本的なツール類は設定を deno.json にまとめられることもあり、これを使えば良いと思います。

公式が提供するツールは色々あるので、deno --help で出てきたサブコマンドを見てみます。

SUBCOMMANDS:
    bench
            Run benchmarks
    bundle
            Bundle module and dependencies into single file
    cache
            Cache the dependencies
    check
            Type-check the dependencies
    compile
            UNSTABLE: Compile the script into a self contained executable
    completions
            Generate shell completions
    coverage
            Print coverage reports
    doc
            Show documentation for a module
    eval
            Eval script
    fmt
            Format source files
    help
            Print this message or the help of the given subcommand(s)
    info
            Show info about cache or info related to source file
    init
            Initialize a new project
    install
            Install script as an executable
    lint
            Lint source files
    lsp
            Start the language server
    repl
            Read Eval Print Loop
    run
            Run a JavaScript or TypeScript program
    task
            Run a task defined in the configuration file
    test
            Run tests
    types
            Print runtime TypeScript declarations
    uninstall
            Uninstall a script previously installed with deno install
    upgrade
            Upgrade deno executable to given version
    vendor
            Vendor remote modules into a local directory

いくつか紹介します。

bundle

bundleは単純にDenoが実行できる単一のJSを出力します。

https://deno-ja.vercel.app/manual@main/tools/bundler

処理内容にもよりますがブラウザで読めたりもするので、単純にファイルを結合したい時などに使っています。

fmt

フォーマッターです。

https://deno-ja.vercel.app/manual@main/tools/formatter

デフォルトでフォーマッターが入っているのが非常にありがたいです。設定項目は少ないもののいくつかカスタマイズ可能で、その内容は deno.json に記述します。

例えば個人で使う場合は以下の設定を行っています。

{
	"fmt": {
		"files": {
			"include": ["src/"]
		},
		"options": {
			"useTabs": true,
			"indentWidth": 4,
			"lineWidth": 160,
			"singleQuote": true
		}
	}
}

lint

lintも大体fmtと同じように設定できます。

{
	"lint": {
		"files": {
			"include": ["src/"]
		},
		"rules": {
		  "tags": ["recommended"],
		  "include": [],
		  "exclude": ["require-await"]
		}
	}
}

task

Denoをちょっと知っている人ならばちょっとハードルの高さを感じるのが権限周りではないでしょうか?

例えば今以下のようなプログラムがあったとします。

fetch('https://deno.land/').then((response) => {
	return response.text();
}).then((result) => {
	Deno.writeTextFile('download.html', result);
});

Denoはセキュリティのためにファイル読み書きやネット接続、環境変数の参照などが制限され、deno run download.ts みたいな感じでファイルをダウンロードして保存するプログラムなどは動かせません。
この場合は deno run --allow-write --allow-net download.ts のように --allow-なんとか というオプションを付ける必要があります。

正確にはこれを普通に実行すると以下のようになります。

$ deno run download.ts                                             
⚠️  ┌ Deno requests net access to "deno.land".                                                   
   ├ Requested by `fetch()` API                                                                 
   ├ Run again with --allow-net to bypass this prompt.
   └ Allow? [y/n] (y = yes, allow; n = no, deny) >

このようにこの権限使おうとしてるがどうする?っと聞かれるので y を押せば権限を許可して実行できます。このように別にフラグ指定はなくても動きますが、毎回許可する?許可する?っと聞かれるのでうざったいでしょう。
かと言ってファイルごとにフラグをいちいち覚えてつけて実行は死ぬほど面倒くさいです。

その問題を解決してくれるのがタスクランナーです。Node.jsにある npm-scripts と同じ機能です。

具体的には deno.json に以下のように記述します。

{
	"tasks": {
		"download": "deno run --allow-write --allow-net download.ts"
	}
}

この状態で deno task download と実行すれば、権限を覚えたりつける必要なく実行する事が可能になります。これがないと本当にしんどかった!!

また余談ですが例えばテストでファイルを読み込む場合等は権限が必要になるので、この後紹介する deno test を使うと上手く動かない場合もあります。
そのため、deno task tests というタスクを使って権限を設定したテストを走らせるというようなこともやったりしています。

権限周りで面倒だと思っていた方はこれでハードルが一気に下がったかと思います。

test

deno test tests/ などと指定することでフォルダ内 なんとか.test.ts 等のファイルのテストを実行してくれます。詳しくは以下を見てください。

https://deno-ja.vercel.app/manual@main/testing

assert等も提供されていて上記ページにあるような形でテストを書くことができます。

TypeScriptのインストール

Denoは色々できますがフロントの開発でもTypeScriptを使うなら tsc は外せません。
Node.jsなら typescript をインストールすれば tsc コマンドも使えるようになりますが、Denoでは実行するコマンドは1つ1つインストールする必要があります。(Denoの思想的に実行も読み込みもファイル単位できっちりやる必要がある。)

では実際に typescript に含まれる2つのコマンドをインストールしてみます。

deno install --force --allow-all npm:typescript/tsc
deno install --force --allow-all npm:typescript/tsserver

Denoはインストール時に権限を指定すると、以降コマンド実行時にその権限が付与された状態で使えるようになります。
逆に言うと --allow-net 等を指定しない場合ダウンロードやファイル読み書きが発生した時点で許可するか聞かれるので注意しましょう。

自分は完全に tsc を信頼することにして --allow-all の権限を与えてインストールしています。

これで無事いつも使っている tsc がNode.js無しで動くようになりました。

VSCodeとの連携

これに関してはほぼ一択で、公式が提供している拡張機能をインストールします。

https://github.com/denoland/vscode_deno

後はプロジェクトの設定で有効化します。

VSCodeのワークスペースを開いて deno enable などで調べれば有効化フラグは出てきますし、ファイルを直接開いて以下のように有効化することも可能です。

{
	"deno.enable": true
}

またここで deno.enablePaths という設定でDenoを有効化するフォルダを配列で指定できます。指定したフォルダ内ではDeno前提の補完となり、他ではブラウザ向けの補完になったりします。

ローカルの静的Webサーバー

以下のページにあるように公式から提供されているサーバーがあります。

https://deno-ja.vercel.app/manual@main/examples/file_server

最近HTTPSかlocalhostのWebサーバー上でないと動かないフロント技術も増えてきたので、そういう場合にはこういったものを使えばさくっと開発できるでしょう。

バックエンド

実装の話になってしまうのでさらっと流しますが、以下のページにあるように serve などを使ってサーバーを立てたりすることができます。

https://deno-ja.vercel.app/manual@main/examples/http_server

サーバーからリクエストを貰って、それを返すのが基本なのでこれをいい感じに隠蔽化したりするフレームワークを使うことになると思います。
Denoが公式に提供するDeno Deployではこの機能をいい感じに上書きしてURL等を指定している部分をガン無視してデプロイを行い、外部と通信できるようにしてくれます。変なことをしなければ素直に実装するだけでデプロイが成功するはずです。

またこういう場合ルーターを導入するかと思いますが、JSに URLPattern という機能があり、これを使うとURLをいい感じに解釈して利用することができます。

const pattern = new URLPattern({
	pathname: '/api/:user/*'
});

console.log(pattern.exec('https://localhost/test'));
console.log(pattern.exec('https://localhost/api/me/info'));

実行結果は以下です。

null
{
  inputs: [ "https://localhost/api/me/info" ],
  protocol: { input: "https", groups: { "0": "https" } },
  username: { input: "", groups: { "0": "" } },
  password: { input: "", groups: { "0": "" } },
  hostname: { input: "localhost", groups: { "0": "localhost" } },
  port: { input: "", groups: { "0": "" } },
  pathname: { input: "/api/me/info", groups: { "0": "info", user: "me" } },
  search: { input: "", groups: { "0": "" } },
  hash: { input: "", groups: { "0": "" } }
}

この結果を見ればルーティングも余裕で実装できるのがわかると思います。

Deno Deploy

Deno DeployはDenoで動くプログラムを超簡単にデプロイして動かすことのできるEdge Workerです。
GitHubのリポジトリか直にコードを入力することで動かせます。

https://deno.com/deploy

またGitHubのリポジトリと連携した場合、リポジトリ内のファイルを読み込むことが可能です。ファイルの書き込みができるストレージがないため、例えば読み込みのみの個人サイトなどであれば十分利用可能かもしれませんが、サービスとなってくると厳しいものがあります。外部DBとの連携に関しては資料があるのでそれを参考にすれば色々できると思います。

ちなみに、ファイル読み書きに関しては一部のAPI(Promiseを利用しないやつ)を呼んだだけでエラーとなります。
SQLiteも読み込みなら動くのでは?っと思うかもしれませんが例えばサードパーティーの https://deno.land/x/sqlite はDeno Deployでは使用不可能です。(Deno Deployで禁止されているAPIを使い、作者もこれに関して合わせる気がないため。)

ただしこれには回避策もあり、ファイルを読み込んだ後エラーとなるAPIを再現した処理に置き換えることで回避するライブラリがあります。

https://github.com/ayame113/mock-file

こういうライブラリを使えば禁止APIを回避して読み取りも可能になるので、データ更新が発生しないならば単体でSQLiteを使うことも可能です。

まとめ

Denoを使っていて思うのは、やはりPureJSで使える機能がそのまま使えるのと、デフォルトでよく使うツールが含まれているので悩む必要がない点が非常に嬉しいポイントです。
DenoはDenoが提供するAPIを使わなければブラウでもDenoでも動くコードが書けるのが魅力です。JSの最新知識をフルに使えるのでバックエンドとフロントエンドでやり方を変えなくて済むのがすごくストレスフリーだと感じています。

個人的にはシンプルというかあまり実務で使われているようなツールやフレームワークを使わないで開発しているためあまり参考にはならないかもしれませんが、今現在の脱Node.jsしたDenoの開発環境周りの具体例をまとめてみました。個人的には非常に快適です。
他の脱Node.jsした具体例があれば参考になるので増えてくれると嬉しいなと思います。

Discussion