この記事はBASE Advent Calendar 2021の4日目の記事です
ごあいさつ
はじめましての人ははじめまして、こんにちは!フロントエンドエンジニアのがっちゃん( @gatchan0807 )です
今回は、フロントエンドエンジニア界隈で話題になっているDenoについて調査し、プラスでSlack Botを書いてみることで現時点のDenoはどんな感じで使えるのかを検証したので、その経験をシェアできればと思っています!(実際にSlack Botを書くのは次回の記事の予定です)
つらつらとDenoを触っていて気になったポイントを書いたためにかなり長い記事になってしまいましたが、お付き合いいただけますと幸いです!
Denoの基礎知識
DenoはNode.jsの作者、Ryan Dahl( https://github.com/ry )さんがNode.jsの反省点を活かして よりセキュアで生産的になるように 開発されているJavaScirpt / TypeScriptランタイムです
2018年5月14日に Initial commit が発行され、執筆時現在の最新版は v1.16.3
が最新となっていますが、開発スピードは非常に速く、執筆開始時点から約1週間でminorバージョン1つ、patchバージョンが3つ進んでいるので最新バージョンの情報は公式ページを確認してください⚠
個人的には、日本Node.jsユーザグループ代表の古川さんの記事をきっかけに国内では話題になっていて知った記憶があります( Node.js における設計ミス By Ryan Dahl ) ※上記の記事の元になった、Ryan Dahlさん本人による発表・資料はこちら(YouTubeの発表動画 / 発表資料PDF)
また、国内コミュニティには日本語Slackチャンネルがあり、日本人Denoコミッターのkt3kさんもいらっしゃって、そこで色々情報交換などもされているので、少しでもDenoに興味があればぜひ参加されることをおすすめします(Deno-ja Slackへの参加方法)
公式でとりわけ特徴として上げている部分
ここからは公式ドキュメントで取り上げられているDenoの推しポイントリストを紹介しつつ、普段Node.js環境で開発をしている自分が触っていて特徴的だなと感じた部分も合わせて紹介していきます。
特徴的な機能
- 可能な限りWeb互換で利用できるようにする。例えば、ESModuleやfetchの対応など
- 明示的に指定しない限りネットワークやファイル、その他環境へのアクセスは行えず、セキュアに保つ
- インストールしてすぐにTypeScriptが使える(サポートしている)
- 単一の実行可能ファイル(
deno
) - ビルトインで様々なユーティリティツールが利用可能。(
deno fmt
: フォーマット機能、deno lint
: リンター機能、deno test
: テストランナー機能) - Denoチームによって動作保証(レビュー)されている標準ライブラリ
- 単一JavaScriptファイルへのバンドル機能
原文: - Web compatible where possible, for example through usage of ES modules, and support for fetch. - Secure by default. No file, network, or environment access (unless explicitly enabled). - Supports TypeScript out of the box. - Ships a single executable (deno). - Has built-in utilities like a code formatter (deno fmt), a linter (deno lint), and a test runner (deno test). - Has a set of reviewed (audited) standard library that are guaranteed to work with Deno. - Can bundle scripts into a single JavaScript file. https://deno.land/manual#feature-highlights
他にも概要を調べていて個人的に特徴的だな〜と思ったのが↓のあたりのポイントです
- URLによる依存ライブラリの取得と隠蔽されたパッケージファイル管理(No
package.json
&npm
)。- 一度ロードされたパッケージはローカルにキャッシュされている(バージョンごとに自動で入るので、手でパッケージ内の一部ファイルを消してしまったとかのよほどのことが無いと気にすることはなさそう)
- Comparison to Node.js(Node.jsとの比較)内にかかれている
Deno always dies on uncaught errors.
の通り、適切にキャッチして処理されないエラーは常に死ぬようになっている点
適切にキャッチして処理されないエラーは常に死ぬようになっている点に関しての補足(クリックで開閉)
これを見て、最初は「とはいえ、死なないことなんて起きるのか…?」と思って調べていたのですが、Node.jsでは uncaughtException
/ unhandledRejection
のイベントリスナーを設定することで、どこからもエラーがキャッチされずにprocessまで上がってしまった場合の後処理が書けることを知りました。
uncaughtException
/ unhandledRejection
のイベントリスナーは本来、メモリリーク防止の為の後処理や、開いたまま終わると問題になるネットワーク・ファイルのクローズ処理を書くためにあるもので、Node.jsの公式ドキュメントにも注意して利用するように記載されています
(https://nodejs.org/api/process.html#process_event_uncaughtexception)
デフォルトではprocessまでエラーが上がった場合にはスタックトレースに出力してexit-code 1で終了させるだけですが、ここを書き換えることによってexit-codeを0で終了させたり無理やり復旧させることができてしまうのが問題になっていました
そのため、Denoでは適切に処理されない場合には常に死ぬように設定されているようです
また、ちょうどこの辺りを調べていた時にDeno側でちょっと面白い議論を見つけて、記事執筆時の2日前に動きがあったのであわせて共有しておきます( https://github.com/denoland/deno/issues/7013#issuecomment-737621469 )
ざっくりいうと、「ライブラリ側で何らかの理由でキャッチされずに放置されたReject
(Promise
の失敗時の対応)があった場合に有無を言わせずに死ぬので、どこで死んだかの調査がとても大変&アプリケーションになると現実的に無理ぽ。」というお話
これはLinterでもチェックされないし、人間は完璧にコードを書けるわけじゃないから対応してほしいよ〜😢というIssueがあり、ちょうど2日前に、ひとまずNode.js互換モードとして onunhandledrejection
の実装が決まったようです
https://github.com/denoland/deno/issues/7013#issuecomment-974256318
Denoの性質上、すべての非同期処理がPromiseで返されるので Unhandled Rejection
を対応できるようにする方法は Uncaught Exception
を対応できるようにする方法と同義になるんじゃね?とかのコメントを見て全体像が理解できたので、個人的にすごく学びがあったIssueでした
あとついでに Foot-gun というワードとその意味を知れたのも学び https://twitter.com/MengTangmu/status/1069751647771877376?s=20
Deno CLIから提供されるツール群
Denoの基本的な思想も面白いのですが、CLIコマンドが豊富でNode.js環境だと乱立しているテストランナーやフォーマッターなどがランタイムに標準装備されているのも面白いポイントです(JSConfでも、yosuke_furukawaさんとkt3kさんが「揺り戻しのよう全部入りのランタイムになってきているというのが面白い」という話がありました)
主だったツールのコマンドと利用シーン
- 定義されたテストを実行する
deno test
- chaiやsinonなどと同時に使うことも可能
- 指定のコードをフォーマットする
deno fmt
- Lint実行を行う
deno lint
- CLI上でJS/TSの実行結果をインスタントに試せる
deno repl
- 依存ライブラリ一覧を表示する
deno info
- ファイル指定を行わずに実行すればライブラリキャッシュの在り処を知れる
- スクリプトをCLIコマンドとしてインストールする
deno install
- インストール時にきちんと
--allow-net
などで必要な権限を付与した上で実行する必要があるので注意
- インストール時にきちんと
- 複数JS/TSファイルを実行可能・インストール可能な形にコンパイルする
deno compile
- コンパイル時にも必要な権限を付与して実行する必要があるので注意
- 複数JS/TSファイルをES Module形式でまとめられる
deno bundle
- 出来上がったファイルはES Moduleなので、直接ブラウザに読み込むことも可能
- ドキュメントを自動生成する
deno doc
Denoの環境を作って触っていく
ここまででざっくり基礎知識を得たので、実際にDeno環境を作って肌感覚を得ていこうと思います
今回は最近GAになり、なおかつ個人アカウントの場合に無料で利用できる GitHub Codespaces に環境構築を行おうと思います
雑なコードが残っているだけですが、こちらのリポジトリに実際に試した設定ファイルなどが残っているので良ければご覧ください
Codespacesの準備
この辺りはおなじみの光景なのでサクッと飛ばしますが、まずリポジトリを作成します
適当に README.md
ファイルなどを置いて、ファイル一覧の右上に表示される < > Codeボタンを押下するとCodespaceを作成するボタンが出てくるので New codespaceボタンをポチッとしてください
しばらく待つと、下記のように見慣れたVS Codeの画面がブラウザに表示され、ターミナルも起動されるので準備完了🙆
(作成中に表示される緑色の Open this codespace in VS Code Desktop
ボタンを押すと、ブラウザではなく手元のPCのVSCodeが起動して、そこからCodespacesに接続する形になります)
Denoのインストール
ここからはCodespaces上に構築されたLinux環境にDenoをインストールしていきます
まずは公式ドキュメントに書かれている通りにCurl / ダウンロードされるShellScriptを実行してダウンロード → deno -V
でインストールの完了を確認します
$ curl -fsSL https://deno.land/x/install/install.sh | sh
サンプルコードの実行
動作環境が用意できたので、サンプルコードの実行を行っていきます
ドキュメントに書かれている通り、 deno run
コマンドの引数にURLでTypeScriptファイルを指定し、実行してみましょう
$ deno run https://deno.land/std/examples/welcome.ts
すると下記のように自動的にファイルがダウンロードされ、実行するファイルに必要な権限などのチェック後、そのファイルに書かれたコードが実行されます このようにコマンド一つでファイルのダウンロードから実行まで行ってくれるのもDenoの特徴ですね
※今回の例で使っているコマンドにはバージョン指定が入っていないので、「自動で最新のバージョンのコマンド実行するよ。」というWarningが出ていますが基本問題はないのでスルーでOKです
Codespacesのコンテナ設定でDeno環境を整えるように変更
続いて、毎回Codespacesの環境にDenoのインストールするところから始めるのは面倒なので、devcontainer.json
という仕組みを使ってDenoを利用する設定をコード化し、Git管理化に設定ファイルを置いておきましょう
やることはVSCodeコマンドを実行して、選択肢をポチポチするだけでOKです
まずCodespacesが起動している状態で、 Cmd + Shift + P
でCommand Palleteを起動し、add development
と入力して下記の Codespaces: Add Development Container Configuration Files...
のVS Codeコマンドを実行します
続いて、(最初は deno
を入力しても、よく使われるDefinitionsの選択肢には出てこないので、) Show All Definitions...
をクリックして再度 deno
を検索してください
最後に、ベースになるDebianのOSバージョンを聞かれるので、デフォルトの最新安定版を選択することで…
※ bullseye
はDebianのバージョン11のコードネームです( https://www.debian.org/releases/index.ja.html )
完成! .devcontainer
ディレクトリと、その配下に devcontainer.json
/ Dockerfile
ができているはずなので、それらをコミットしておいてあげれば次回Codespaceを起動しなおしたときに自動的にDenoの環境構築がされたコンテナが適用されます
ちなみに、テンプレで作成されるDockerfileも基本やってることは一緒
# [Choice] Debian OS version: bullseye, buster ARG VARIANT=bullseye FROM --platform=linux/amd64 mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT} ENV DENO_INSTALL=/deno RUN mkdir -p /deno \ && curl -fsSL https://deno.land/x/install/install.sh | sh \ && chown -R vscode /deno ENV PATH=${DENO_INSTALL}/bin:${PATH} \ DENO_DIR=${DENO_INSTALL}/.cache/deno
便利ポイントとしては同時にDeno用の公式VS Code拡張のインストールや初期設定をしてくれる
https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno
他にもREADMEや解説記事などでは初期設定の実行 Deno: Initialize Workspace Configuration
を行うように書かれている事が多いですが、下記の通りすでに設定が入っているのでスルーでOKです
{ "name": "Deno", "runArgs": ["--init"], "build": { "dockerfile": "Dockerfile", // Update 'VARIANT' to pick an Debian OS version: bullseye, buster "args": { "VARIANT": "bullseye" } }, "settings": { // Enables the project as a Deno project "deno.enable": true, // Enables Deno linting for the project "deno.lint": true, // Sets Deno as the default formatter for the project "editor.defaultFormatter": "denoland.vscode-deno" }, // This will install the vscode-deno extension "extensions": [ "denoland.vscode-deno" ], "remoteUser": "vscode" }
公式ドキュメントを確認していく
以上でDeno環境の構築が完了したので、公式ドキュメントのTOPページを上からなぞって、書かれている公式ドキュメント等の役割をあわせて紹介していきます!
公式Docsのローディング画面めっちゃかわいい
https://doc.deno.land/builtin/stable
ビルトインの関数・メソッドたちを確認できるドキュメントを開くと、可愛すぎるローディングアニメーションを見ることができます。一旦可愛い恐竜くんたちを愛でましょう
その後にちゃんと内容を見て、 Deno
名前空間と WebAssembly
名前空間が使われてるんだ〜とか思ったりしましたが、細かいところは使ってみながらの方が理解しやすそうなので、自分は一旦流し見で終了しました
公式マニュアルを眺めながら構築した環境で触っていく
続いて公式マニュアル(Denoの開発方針から何から細かいことが書かれているドキュメント)を見始めたのですが、ここはボリュームがあるので後述の項で別途まとめていきます!
標準ライブラリ(標準モジュール)のドキュメントをざっくり確認
http
モジュールとか crypto
モジュールとか uuid
モジュールとかのNode.jsと同じような各種モジュールが std
配下から色々提供されています。これらは3rdパーティモジュールと同じようにURL経由で提供されていますが、Denoチームがメンテナンスし、動作を保証してくれているものです
バージョンがものすごいスピードで上がっていて、この記事を書いてる5日の間にもにょきにょきバージョンが伸びていて、直近いろいろな改修が加えられているのをひしひしと感じています👀
3rdパーティモジュールをWebから読み込める
こちらは deno.land/x
配下にGitHubでホスティングされているパブリックライブラリを登録できる仕組みです
実際には直接JSをダウンロードできるURLがあれば、ここに登録せずにそのURLからライブラリのコードを配布しても良いのですが、ライブラリ提供者がここに登録して提供することで、Denoユーザーが覚えやすいURL( deno.land/x
で現在は固定)を払い出すことができるので、ぜひやってね〜というテンションで運用されているようです
GitHub WebhookとGit Tagを使って設定を行うことで、特定のバージョンごとにDeno上にキャッシュを残してくれる動きをします
ライブラリのドキュメントを自動で書き出してブラウザからも見れる
ライブラリのコード内にJSDoc記法で書かれたドキュメントを取得して、Web UIとして表示できるジェネレーター機能を持ったページです
利用しているライブラリのURLを直接指定してあげれば、自動で deno doc --json
で出力されたJSONファイルを元にページをジェネレートして表示してくれます(ライブラリ作者はこのJSONファイルも一緒に提供しておくのが推奨されています)
公式マニュアルをなぞってDenoを触っていくぞ!!
一通りDenoの環境構築と、Denoが提供している公式ドキュメントの全体像がわかってきたので、公式マニュアルを参考にGetting Startedからやっていこうと思います!
流石にここまでStep by Stepで書くのはあんまり意味ない気がするので、普段JavaScript/TypeScript + Node.jsを触っている人間が気になったポイントだけまとめていこうと思います
セルフアップデートできるの便利よ
https://deno.land/manual/getting_started/installation#updating
Node.jsだととてもカオスになってたランタイムバージョン管理がランタイム自体でできるの地味に嬉しいですね
nvm
やら nodenv
やら nodebrew
やら n
やら ndenv
やらが不要なのが地味に嬉しいですね
特定バージョンを指定してのインストールもここでできるみたいです👀
CodespacesでもTCPリクエストのサンプルコード全然動かせる
ここはDenoがどうこうという話ではなく、Codespacesの機能としてちょっと不安だったローカルのネットワークを触るサンプルコードがちゃんと動くのかを試して、問題なく動くのが確認できて感動した話です
上記の結果はこのサンプルコードを実行して出てきたものなのですが、コード実行時にポップアップが出てきて、そのままブラウザの別タブに遷移できるようにしてくれるのでほとんど気にすることがなかったのが嬉しかったです
またCodespacesの場合にのみ表示されるPORTSタブでは、自分でポートフォワーディングの設定を登録することも可能で、自動でドメインを発行して指定したポートをGitHub Authで守られた状態で自動的にバインドしてくれたりもします
さらに、ここで特定のポートをクリックひとつで公開状態にできるので、バインドされたURLを他の人に共有してかんたんに動作確認をしてもらうことも可能なのが便利で嬉しいポイントです!
基本的にはコンフィグファイルいらないよ!!という圧を感じる
{ "compilerOptions": { "allowJs": true, "lib": ["deno.window"], "strict": true }, "lint": { "files": { "include": ["src/"], "exclude": ["src/testdata/"] }, "rules": { "tags": ["recommended"], "include": ["ban-untagged-todo"], "exclude": ["no-unused-vars"] } }, "fmt": { "files": { "include": ["src/"], "exclude": ["src/testdata/"] }, "options": { "useTabs": true, "lineWidth": 80, "indentWidth": 4, "singleQuote": true, "proseWrap": "preserve" } } }
https://deno.land/manual/getting_started/configuration_file#example
こんな感じで設定をファイルにまとめてできる(実際のスキーマはこっち)のですが、基本的にはデフォルトを使ってね。複数人プロジェクトでどうしても必要な場合に作ってね。というニュアンスで書かれていました
また、このファイル自体は将来的に自動で探してきてロードするようになる予定なので deno.json
/ deno.jsonc
で名前つけておくのがおすすめとのこと
デフォルトではできないことリスト
https://deno.land/manual/getting_started/permissions#permissions-list
Denoではランタイムオプションを指定しないとできないことが意外とあるので、ちゃんとリストを知っておけると良さそうだなと思いました
一応、 -A, --allow-all
: 全許可 なんてものもありはしますが、せっかくDeno使っているのであんまり使わないほうが良さそうかなぁ…と思います
また、このランタイムオプションには特定のコマンド / ファイル / URL / IPのみアクセスを許可することもできて、結構柔軟なので、ライブラリや動作環境をセキュアに保つために設定しておくのが良さそうに感じました🙆
The Runtimeに定義されているAPI群をみて感じたWeb互換の意識の強さ
全体的に、普段ブラウザJSを書いていると馴染んでいるAPIが並んでいてびっくりしました
Permission APIsはPWA機能を使うときによく叩くAPIだし、Program lifecycleはページロード時の処理とほぼ一緒だし、イベントの処理とかも普段使っている感じのやつだし…
特に Location API
/ Web Storage API
に関してはそこまで考えて挙動作ってるの…?って思うぐらいに作られていてシンプルにすごいなと思いました…
ただ、この辺りで1点わからないポイントがあり、1.16で追加された fetch
のファイル読み込み機能はファイルの動的インポートに使うのが想定ユースケースなのかなんなのか…?という疑問点が残っているので、有識者の方いらっしゃったらコメント等いただけますと嬉しいです…🙏
Denoで新規プロジェクトを始めるときに気になりそうなポイント
ここからは、次回のカレンダー(2021/12/12の1と2がいっぱいある日に公開予定)で実際にSlack Botを書いていく予定なので、Denoでコードを書く時のポイントの理解だけではなく、プロジェクトとしてすすめるのに必要そうな部分をまとめて紹介して行こうと思います
lock fileの存在
Denoはnpm / package.jsonが無い代わりにURLベースの依存ライブラリ管理方法を取っているため、パッケージ配布元のURLが変わったり、該当URLのライブラリが乗っ取られて悪意のあるコードが埋め込まれて内容に変わってしまった場合に気づく術が無いため、Denoではデフォルトで整合性チェック用のHashを持っています
1人で1端末で開発しているだけならDenoがデフォルトで管理されている、 /deno/.cache/deno/deps/https/deno.land
あたりの [hash].meta.json
ファイルたちだけで問題はありません
しかし、複数人での開発の場合はそれらのバージョン情報を共有する必要があるので、プロジェクトコードに lock.json
を作り、Git管理下に置く方法が紹介されていました
--lock=[path/to/lock.json] --lock-write
をコマンド実行時に指定することでいい感じに作ってくれるので、プロジェクトではエントリポイント指定してGit管理下に置いておいた方が良さそうですね👍
Import mapsの存在
https://deno.land/manual/linking_to_external_code/import_maps#import-maps
同様に、package.jsonがないことのデメリットとして、インポートする依存ライブラリの指定が各ファイルに散らばってしまうこと(個人的にどう解決してるのか一番気になってた問題)の解決方法として、WICGで仕様が定義されている import-maps
を使って一箇所で依存ライブラリのURL・バージョン指定をできるようにしています(公式ではこの方法が推奨されているっぽいですが、他にも deps.ts
というファイルを作り、そこでライブラリインポートを管理して、他のファイルからそれをまるっと読み込むパターンも有るようでした)
なので、プロジェクトの初期にこのファイルを作成しておいて、ライブラリの追加時にはここに追記して実際のアプリケーション側からはバージョン情報などを省いた省略記法で記述していくのが良さそうですね
この辺りを見て個人的には、
deno.json
=> Linter、Bundler、Formatter等の各種設定ファイルの集合体import_map.json
=> package.jsonの依存ライブラリ指定部分の代替lock.json
=> package-lock.json / yarn.lockの代替
として3ファイルを作るのが自然なのかなという理解をしたので、次回のSlack Bot作成ではこの形で各設定を管理し、実装を行っていこうと思います!
Deno基礎知識 + 環境構築編のまとめ
さて、ここまでDenoの基礎知識から実際に触れる環境を用意してマニュアルをなぞってざっくり全体感を理解してきました
2年半ほど前の発表時に話題になっているのを見つけ、そこからの進化をちょろっと横目で見ていた程度だったので、どういう機能が実装されているのかなどを知らなかったのですが、これである程度はDeno環境でコードを書けるようになりました
なので、次回の記事では社内のSlackで動くBotを実際に作って、Denoの触り心地をより理解していこうと思います!
先日Slack社がSlack Appの次世代開発プラットフォームをDenoを使って提供していくことを発表しました。
自分はこれにクローズドベータの申込みをしたものの、まだ参加できていないので、
もし次回の記事執筆までにクローズドベータに参加できれば、そちらの新しいプラットフォームを使って slack
コマンドを使ったApps開発をまるっと構築をする。参加できなければ(非公式ではあるものの) deno-slack-sdk
で管理されているDenoライブラリが公開されているのでそちらを使ってSlack Botを構築してみる。という形で記事を書こうと思います!
では、長々とお付き合いいただきありがとうございました!次回もお楽しみに!
明日のアドベントカレンダーは基板チームの右京さんの記事です!こちらもお楽しみに!