🍋

Deno製のWEBフレームワーク Fresh でチャットアプリを作成するまでの軌跡 〜第一章〜

2022/09/25に公開

はじめに

Denoを知ったのは、デブサミで登壇していたDenoコントリビュータのセッションがきっかけです。

日頃はNode.jsを利用して開発をしてますので、Node.jsとの違いに着目しながら、Denoの特徴をまとめたいと思います。

Denoの特徴は別サイトでも列挙されているものを取り上げますが、私独自の視点で感じた事などをナレッジとして書き残します。

想定読者

日頃Node.jsを利用して開発している方やNode.jsを使って開発したことがある方

ゴール

  • 以下のDenoの特徴を深掘りして理解すること

    1. ライブラリ読込方式がパッケージマネージャではなく、URL直指定である
    2. パーミッションをプログラムに対して明示的に与える必要がある
    3. ブラウザ互換APIが利用できる
    4. TypeScriptを標準でサポートしている
    5. deno.landに一度公開されたモジュールは何があっても削除されない
  • Denoに興味を持ってくれる人が増えること

特徴1 ライブラリ読込方式がパッケージマネージャではなく、URL直指定である

読込方式がURL直指定になったことによるメリット・デメリットは下記です。

メリット

  1. プロジェクトサイズの肥大化を抑えることができる点
  2. 動作未確認のバージョンが勝手に入ってしまうことがない点

デメリット

  1. npmのようなエコシステムが使えない点

メリット1 プロジェクトサイズの肥大化を抑えることができる点

Node.jsの場合

npmyarnなどのパッケージマネージャを利用して、必要なライブラリをpackage.jsonで管理してました。リモートコードの実態をnode_modulesに保管するので、どんどん肥大化していましたね。依存パッケージが依存しているパッケージは無数にあり、結局node-modules/ディレクトリが数百MB〜1GB以上に膨れ上がったりしますよね。

肥大化の弊害として例えば下記があります。

https://zenn.dev/xxpiyomaruxx/articles/d7419ec1138d6a

devDependenciesの切り分けを実施していても、Node.js製のシステムをAWS Lambdaにアップロードする際に容量制限に引っかかったことが何度もあります。

Denoの場合

以下のようにURLから直接ライブラリをインポートできます。プロジェクトルートに保存されません。

import { helpers, RouterContext } from 'https://deno.land/x/oak@v6.5.0/mod.ts';

初回はダウンロードするのに数秒かかりますが、2回目はホストにキャッシュされますので早いです。ダウンロードされたライブラリの情報はdeno infoコマンドで確認が可能です。

また、関数実行時にライブラリをダウンロードしてくるため、それよりも前にインポートしたければ下記で対応可能です。

deno cache https://deno.land/std/log/mod.ts

Denoの場合は1MB以下に抑えることが可能と思います。

素朴な疑問その1

素朴な疑問その2

メリット2 動作未確認のバージョンが勝手に入ってしまうことがない点

Node.jsの場合

Semantic Versioningを使ってバージョン解決してました。

    "axios": "^0.24.0",
    "crypto-js": "^4.1.1",
    "express": "^4.17.1",

これの弊害として下記があります。

例えば、ライブラリの作者が悪意のあるコードをパッチリリースでリリースした場合に、Semantic Versioningで管理をしていると、npmが「あ、パッチバージョンが上がってるやんけ!アップデートしといたろ!」と気を利かせてくれます。そこで自動でライブラリのアップデートが行われ、動作確認されていないバージョンのコードが紛れ混む可能性があります。

2022年1月にも以下のような事案が発生しています。

https://www.itmedia.co.jp/news/articles/2201/11/news160.html

Denoの場合

下記のようにライブラリ名の後に@v1.0.0のようにバージョン指定します。

import { copy } from "https://deno.land/std@0.74.0/fs/copy.ts";

Semantic Versioningを使わず特定のバージョンを指し示すことができるため、動作未確認のバージョンが勝手に入ってしまうことはありません。悪意のあるバージョンが勝手に依存関係に入り込むこともありません。

ちなみにバージョン(上記で言うと、@0.74.0の部分)は省略可能です。その場合、最新版をダウンロードしてくることになりますが、Denoの公式ドキュメントでは、明示的にバージョン指定することを強く推奨してました。

最新版はいつでも変更される可能性があり、コンパイルエラーや予期できない動作を起こす可能性があるマスターブランチのコードにリンクするより、変更されないイミュータブルのライブラリバージョンを指定せよ、とのことです。

デメリット1 npmのようなエコシステムが使えない点

Denoを使ってみて思ったこととして、npm というエコシステムが使えないのはデメリットであるが挙げられるなとも思います。npmで提供されるさまざまなライブラリヤフレームワークを利用することができませんでした。

とはいえ、以下のような動きもあるようです。

https://www.publickey1.jp/blog/22/deno3npmjavascripthttp.html

また、Denoによる開発においては、deno.land/xnest.landといったDeno標準のライブラリを用いることが一般的ですが、GitHubにあるコードもimport可能です。

ただし、少し注意が必要です。例えば

https://github.com/takanabe-test/deno-sample/master/hoge.ts

からfuga関数をimportしたい場合、そのままimportしようとすると、mediaTypeがUnknown というエラーが発生します。

レスポンスのContent-Typetext/htmlなので怒られる模様。
レスポンスのContent-Typetext/plainになるように調整すればエラーが起きずに実行できます。

https://github.com/denoland/deno/issues/5543


特徴2 パーミッションをプログラムに対して明示的に与える必要がある

Node.jsの場合

プログラムが環境変数、ローカルファイル、ネットワークにアクセスし放題でした。
動作するサーバに、万が一悪意あるプログラムが紛れ込んだ場合、読み書きできるディレクトリを制限したり、 接続できるネットワークを制限してないので、やられたい放題となります。

Denoの場合

デフォルトで下記が禁止されています。

  1. ネットワークアクセス(ドメインをカンマ区切りで指定してドメイン許可リストを提供することも可)
  2. ファイルシステムの読み取りアクセス(ディレクトリまたはファイルをカンマ区切りで指定して、許可リストを提供することも可)
  3. ファイルシステムの書き込みアクセス(ディレクトリまたはファイルをカンマ区切りで指定して、許可リストを提供することも可)
  4. サブプロセスの実行
  5. 環境変数の取得や設定などの環境アクセス
  6. 高分解能時間計測(タイミング攻撃やフィンガープリントに使われます)

読み書きできるファイルやディレクトリを局所的に設定することが可能であり、必要最小限の権限を局所的に与えるこの考え方(実行されるコードは信頼できないものとみなすこの姿勢)が素晴らしいと思いました。

脳死状態で--allow-allオプションを付与するのは絶対に辞めましょう。

パーミッションリストについては、Denoの日本語ドキュメントにもまとまっています。

特徴3 ブラウザ互換APIが利用できる

Web APIはWebブラウザに組み込まれている機能です。fetch APIなどが代表的ですね。

Node.jsではこれらを使用することはできません。これらのAPIはブラウザ(Google Chrome, Firefox)で利用できるものであり、Node.jsはブラウザではないからです。

Node.jsfetch APIを利用しようとすると fetch is not defined で怒られます。

例として、Denoの場合には、標準でFetch APIがサポートされているため、インストールすることなく以下のように使用することができます。

const res = await fetch('https://deno.land/testing')
const json = res.json()
const data = await json
console.log(data)

ブラウザAPIの良い所は下記だと日野さんは言っています。

  1. ブラウザと共通して使えるコードを書ける
  2. ブラウザ API はとてもきちんと定義されている
    a. 議論の質が高い
    b. 仕様書の質が高い
    c. 自動テストがある
  3. 仕様策定プロセスがあるため、簡単に変わることはない

ただし、一方でこういう意見もあります。

https://yosuke-furukawa.hatenablog.com/entry/2021/12/13/174732

賛否両論あるんだよ、という背景をしっかり理解しておきたいですね。

特徴4 TypeScriptを標準でサポートしている

ベストプラクティスと考えられているTypeScriptの設定がデフォルトで入っているので、設定無しTypeScriptが使えます。デフォルトから外れたい場合は、自分でコンパイラオプションを書くことも可能です。

Node.jsで同じことをやろうとすると、typescripttscinstallして、ついでに型定義ファイルも追加して、tsconfig.jsonを生成して、自身でルールを決めて、あっ・・・開発効率が悪いからts-node-dev導入して...などちょっと面倒です。

この辺をすっ飛ばしてTypeScriptが使えるので、敷居の低さはメリットと言えるのかもしれません。

しかしながら、気になる記事も・・・

https://qiita.com/Syoitu/items/244c6bf99a6b7bf5ab9c

こちらもnpmの話に加えて今後の動向に要注意ですね!

特徴5 deno.landに一度公開されたモジュールは何があっても削除されない

deno.landに公開されているモジュールは、モジュール管理者が勝手に既存のモジュールを削除することはないと書いてありました。

以前起きたleft-pad問題のような事案と同じ失敗を踏まないで済むのかと思います。

https://postd.cc/npm-and-left-pad/

気軽に小さなライブラリに依存し過ぎてしまう文化も良く無いのかもしれないですね。

最後に

このページでは、Denoの特徴を深掘りして、Denoの基礎を学びました。次のフェーズでは2022年7月にv1.0がリリースされたDenoのフルスタック Web フレームワーク Freshを使ってサンプルアプリを製作し、Deno Deploy を使ったプロダクションデプロイまで実施してみた結果を記事にしました。こちらも是非読んでいただければと思います。

https://zenn.dev/gonnakayama/articles/187370737f7a0c

Discussion