Denoのフロントエンド開発の動向【2021年春】
はじめに
ここ最近、Denoのフロントエンド向けのエコシステムについて、色々試してみたり、調査などをしていました。
そこで、この記事では、2021年現在のDenoのフロントエンド開発の状況であったり、所感などについてまとめます。
フレームワークについて
Node.jsにおけるフロントエンド開発では、以下のフレームワーク[1]が使われることが多いと思います。
- React
- Vue.js
- Preact
これらのフレームワークは、すでにDenoでも動作します。
またこれらをベースにしたフレームワークがいくつか開発されています。
Aleph.js
Aleph.jsはNext.jsに影響を受けたReactベースのフレームワークです。
以下のような機能が提供されています:
- HMRをサポートするdevサーバを提供
- ファイルシステムベースのルーティング
- SSR/SSGをサポート
- CSS Modules
開発の活発さや完成度などの点などから、個人的には現時点で最も有望なフレームワークなのではないかと考えています。
とはいえ、現時点ではまだメジャーリリースされておらず、本格的に採用するのは流石にリスキーだと思います。
vno
vnoはDenoでVue.jsアプリケーションを開発するためのフレームワークです。
以下のような特徴があります:
- Vue.jsのSFC(単一ファイルコンポーネント)のビルド及びバンドルをサポート
- Vue2及び3の両方をサポート
- シンプルなdevサーバを提供
使ってみた印象としては、devサーバがファイルの変更監視やHMRなどに対応しておらず、現時点だと本格的な開発に使用するのはまだ厳しい印象です (vno@v1.2.2時点)
将来的にそれらをサポートすることは検討されているので、今後に期待。
dext.ts
dext.tsはPreactをベースにしたフレームワークです。
Denoのコアコントリビュータである lucacasonato氏によって開発されています。
こちらもAleph.jsと同様に、ファイルシステムベースのルーティングやSSGなどの機能を提供しています。
ちなみに、deno lintの公式サイトはこのdext.tsとTailwind CSSをベースに構築されています。
バンドラ
現時点で、Denoで動作するバンドラはいくつか存在するものの、デファクト的な立ち位置のものはまだ存在しない印象です:
deno bundle
deno bundle
は、Denoに標準搭載されているバンドラです。
さすがにesbuildには及ばないものの、Rustで書かれたswc_bundlerを使用しているため、比較的高速に動作します。
ただし、プラグインシステムなどはないため、現時点で本格的なフロントエンド開発に使用するのは難しいのではないかと考えています。
deno-rollup
deno-rollupは、名前の通りRollupをベースにしたバンドラです。
後述するluathなどでも採用されており、開発も活発に行われていることなどから、個人的に注目しています。
denopack
denopackは、deno-rollupと同様、Rollupをベースにしたバンドラです。
先程紹介したdext.tsで採用されています。
Bundler
Bundlerは、名前の通りDenoで実装されたバンドラです。
他のバンドラにない大きな特徴として、fetch
やService Worker
、WebWorker
などもサポートされています。
No-bundlerビルドツール
Node.jsでは、ここ数年でブラウザでのES Modulesの対応が進んだこともあり、Snowpack/Vite/WMRといった、新興のビルドツール[2]が登場しました。
これらのツールの共通点として、以下の点が挙げられると思います:
- ES Modulesを前提としている
- オンデマンドでのモジュールの変換 及び HMRを提供するdevサーバを提供
- 必要になるまでモジュールをビルドしないことにより、プロジェクトサイズが増大しても、devサーバの起動が低速化しにくくなるなどの利点があります。
- Webpack/Rollupなどを利用したプロダクションビルドをサポート
DenoにおけるNo-bundlerビルドツールとしては、以下のものが存在します:
luath
luathはViteやWMRに影響を受けたビルドツールです。
現時点では、以下のような機能が提供されています:
- HMRやオンデマンドでのモジュール変換を提供するdevサーバ
- Rollupベースのプラグインシステム
- PostCSSとCSS Modulesをサポート
- JSONや画像ファイルなどのimportをサポート
試してみた限り、Reactベースのアプリを作るのであれば、現時点でもある程度は動く印象です。
Rollupベースのプラグインシステムを採用しているため、Vue.jsやSvelteを用いた開発もプラグインを用意すれば実現できるようになるかも...
公開されたばかりのツールなどで、実用にはまだ厳しいと思いますが、個人的にはかなり期待しています。
swdev
swdevは、mizchiさんにより開発されているビルドツールです。
まだPoCの段階のようですが、以下のような機能がサポートされているようです:
- Service Workerを利用したTypeScriptやSvelteファイルの変換
-
Deno.watchFs()
ベースのCache Busting - Rollupを利用したプロダクションビルド
リンタ/フォーマッタ
Denoにはリンタ(deno lint
)とフォーマッタ(deno fmt
)が標準で搭載されているので、基本的にはそれらを使用するのがよいと思います。
サードパーティモジュールの管理について
Node.jsだとサードパーティモジュールはpackage.json
+npm/yarnで管理されることが多いと思います。
Denoにはこれらの仕組みは存在しないものの、依存関係管理の方法はいくつか考案されています。
現時点でよくみかけるのは、以下の2つの方式ではないかと考えます:
- deps.tsにまとめる
- Import mapsで管理する
deps.ts
おそらく、現時点で最もよく見かける依存関係の管理方式です。
以下のように、deps.tsというファイルを用意し、そこですべての依存関係を一括して管理します。
export { exists } from "https://deno.land/std@0.90.0/fs/exists.ts";
export {
dirname,
fromFileUrl,
join,
resolve,
} from "https://deno.land/std@0.90.0/path/mod.ts";
export { BufReader } from "https://deno.land/std@0.90.0/io/bufio.ts";
// @deno-types="https://unpkg.com/puppeteer@5.5.0/lib/esm/puppeteer/web.d.ts"
export { default as puppeteer } from "https://unpkg.com/puppeteer@5.5.0/lib/esm/puppeteer/web.js";
このようにすることで、依存モジュールのアップグレードが容易になるなどのメリットがあります。(このファイルのexport
ステートメントを編集するだけで済みます。)
Import maps
Import mapsはWICGで提案されている機能ですが、Denoにもこの機能が搭載されています。
例えば、以下のような内容のJSONファイルを用意します。
{
"imports": {
"~/": "./",
"aleph": "https://deno.land/x/aleph@v0.3.0-alpha.18/mod.ts",
"aleph/": "https://deno.land/x/aleph@v0.3.0-alpha.18/",
"antd": "https://esm.sh/antd@4.14.1?external=react@17.0.1",
"react": "https://esm.sh/react@17.0.1",
"react-dom": "https://esm.sh/react-dom@17.0.1"
},
"scopes": {}
}
こうすることで、アプリケーションでは以下のようにモジュールをimportすることができます。(いわゆるBare import)
import React from "react";
deno run
コマンドでスクリプトを実行する際に、--import-map
オプションでこのファイルへのパスを指定することで、denoがBare import文を認識してくれます。
このImport mapsですが、以下の要因から、今後は使用も増えてくるのではないかと想像しています:
- Deno v1.8.0でImport mapsがstableになった。
- Aleph.jsがデフォルトで提供している依存関係の管理方式であること。
また、補足ではありますが、TrexというImport mapsベースのモジュールマネージャも存在します。
タスクランナ
Node.jsだとnpm-scriptsなどが使用されることが多いと思います。
Denoにはpackage.json
もnpm
も存在しないので、必要であればタスクランナを導入する必要があります。
現状、以下のツールをよく見かけます:
- Velociraptor
- denon
- make
Velociraptor
Velociraptorは、npm-scriptsの代替として開発されたDenoのタスクランナです。
deno install
コマンドでインストールできます。
$ deno install -qA -n vr https://deno.land/x/velociraptor@1.0.0-beta.18/cli.ts
例えば、以下のような内容でプロジェクトのルートディレクトリにscripts.json
ファイルを用意します。
{
"scripts": {
"dev": {
"cmd": "aleph dev",
"imap": "import_map.json"
}
}
}
この状態で以下のコマンドを実行します:
$ vr run
すると、scripts.json
で定義されたaleph dev
コマンドが実行されます。
denon
denonはnodemonをDenoに移植したツールですが、タスクランナとしても使用することができます。
テストについて
テストフレームワーク
Denoは組み込みのテストランナ(deno test
)を提供しています。
そのため、基本的にはdeno test
を使用すればよいのではないかと思います。
一応、deno test
以外にもRhumやwizardなどの選択肢もあり、普段の開発でJestやMochaを使用されている場合は、これらのフレームワークの方が使用しやすいかもしれません。
*-testing-library
試しに、deno_domを使用して、React Testing Libraryを動かそうと試みてみました。
結論としては、importはできるものの、動かすのはまだ厳しそうな感じでした...
import React from "https://esm.sh/react@17.0.1";
import { render, cleanup } from "https://esm.sh/@testing-library/react?external=react@17.0.1,react-dom@17.0.1&no-check";
import { Document } from "https://deno.land/x/deno_dom@v0.1.7-alpha/deno-dom-wasm.ts";
const document = new Document();
document.body = document.createElement("body");
(globalThis as any).document = document;
const Hoge = () => <div>Hoge</div>;
Deno.test("Hoge", () => {
const hoge = render(<Hoge />); // ここでエラー!
cleanup();
});
e2eテスト
残念ながら、現時点ではCypressやTestCafeなどのツールをDenoで動かすのは厳しいです。
ただし、deno-puppeteerやSincoを使えば、Denoでもe2eテストを書けないことはないとは思います。
CDN
DenoにはX-TypeScript-Typesという機能があります。
この機能をサポートするCDNを使用することで、JavaScriptで書かれたモジュールを使用する際にも、型チェックなどを適用することができます。
現時点でこの機能をサポートしたCDNには、以下のものが挙げられます:
ですので、基本的にReactなどのモジュールをimportする際は、これらのCDNを使うとよいのではないかと考えています。
特に、esm.shはAleph.jsの作者であるije氏によってメンテナンスされており、Denoとの相性も非常によく考えられているため、個人的にはとても期待しています。
おわりに
正直なところ、現時点ではDenoをフロントエンド開発に使用するのは少々リスキーというのが個人的な印象です。
色々試してみたものの、特に、Node.jsベースのエコシステム程の安定性は現状ではまだ発揮できないところが辛く感じました。
とはいえ、ここ1年くらいでAleph.jsやluathなどのフレームワークやビルドツールなども徐々に登場しつつあり、この調子で発展していけば、将来的には実用も十分あり得るのではないかと個人的には感じています。
参考
- フレームワーク
- バンドラ
- No-bundlerビルドツール
- タスクランナ
- テスト
- CDN
Discussion
濃ゆい情報ありがとうございます。とてもためになります。
バンドラもタスクランナーも、標準で用意しよう、というのがDenoの世界感だったように思いますが、やはり標準じゃ満足できなくてカオスになっていって、Node時代とあまり変わらないような気がしています。
DenoがNode上位互換なら問題なかった気がしますが残念。
まだまだ様子見な感じしました。
記事参考になりました、ありがとうございました。
pre-commit hooksに関して質問があります。
npm/yarnを使うときはhuskyを使ったりすると思うのですが、denoでは皆さんどうされたりするのでしょうか?
shunya_watanabeさん
コメントありがとうございます!
今のところ、husky相当のモジュールはDenoには存在しないようです。
そのため、GitHub Actionsで
deno fmt --check
とdeno lint
コマンドを実行し、一定のフォーマットを保証しているリポジトリが多いように感じています🙂