Dexr:Deno+Reactのフレームワークを作ってみた

June 14th, 2020

Motivation

噂のDenoを試してみてて、せっかくだからなんかReactで作ろっかなーなんて思ってたら、Reactをユニバーサルに動かすライブラリやフレームワークがまだなかった。。。 が、ここはせっかくなので、Deno+Reactどちらの勉強もかねてSSR〜クライアントサイドでも動くReactアプリケーションフレームワークを作成してみることにしました。 ということで作成したのが表題にもあるDexrです。

今回作成したものはdeno.landの3rdパーティモジュールとして公開しています。

https://deno.land/x/dexr@v0.2.2

Deno

まず、そもそもDenoとはなんでしょう? Denoは簡単に言うとTypescriptとJavascriptのランタイム、つまりNode同様サーバーサイドで動くJavascript環境です。 ならNodeでいいじゃん、、、と思った方もいるかもしれませんが、Denoは以下のような特徴を持っています。

  • デフォルトでセキュアであり、許可しないとネットワークやファイル書き込みなどをできなくしている
  • TypescriptやJSXをデフォルトでサポートしている
  • テスト実行もデフォルトで可能
  • 実行するのは1ファイルのみ(ローカルに存在しなくてもいい)で、依存関係はインストールするのではなくURLでimportする

他にもNodeとは異なる部分が多いですが、より詳しい情報は以下の公式サイトにて。

https://deno.land/

DenoとNode

Denoは実はNodeの生みの親であるライアン・ダール先生によって作成されました。 Nodeは非常に優れたランタイムですが、ライアン・ダール先生にはNodeでやり残した公開が10個ありました。

Denoの登場でNode.jsの時代は終わるのか?

このNodeで残した後悔を払拭する為に新たに作ったもの、なので今Nodeでは破壊的変更となってしまうであろう数々の特徴を持っています。

DenoにおけるJSX

さて、Denoがなんだかはなんとなくわかってきたと思いますが、5月のv1リリースで僕は初めて存在を知り、「なんだこれ素晴らしい」と思って色々いじってたわけなんですが、その時点でDenoにはまだReactやVueなどView系のフレームワークがないことに気づきました。

Dev.toとかみてると結構SSRサンプルとかはあったんですが、なんせクライアントサイドで動かない。 動いてもStyled Componentとかいれると壊れるようなサンプルしかない。

ということで、序盤に書いた通り自分で作ってせっかくなので公開してみました。

Dexr

Dexrはクライアント・サーバーどちらでも同様に動作することを目的とした軽量フレームワークです。 oakというDenoのライブラリを利用してサーバーサイドのルーティングとかは動作させてます。

構成

Dexrは現時点では大きく以下の構成を持っています。

  • DexrApp
  • Renderer

DexrAppはアプリケーションの起動やルーティングの登録などを行ないます。 ただそれだけだとNextJSにおける_document.tsxなどの拡張ができないので、Rendererにheadの追加やレンダリング時の拡張を行えるような責務をもたせました。

これにより、exampleにもあるようにReduxやStyled Componentsなどもサポートすることが可能になりました。 細かいAPIの解説は省きますが、雰囲気だけでも見れるように本当に簡単なサンプルコードを貼っておきます。

import { delay } from 'https://deno.land/std@0.57.0/async/delay.ts'
import { createRenderer } from '../../renderer.tsx'
import { createDexr } from '../../mod.ts'
import { Props as BookProps } from './Book.tsx'
import Head from './Head.tsx'

const renderer = createRenderer().useHead(Head)

const dexr = createDexr().useRenderer(renderer)
await dexr.addPage('/', './App.tsx')

type BookParams = {
  id: string
}

type BookQuery = {
  foo?: string
}

await dexr.addPage<BookParams, BookQuery, BookProps>('/book_async/:id', '/Book.tsx', async (params, query) => {
  await delay(1000) // async callback
  return {
    id: params.id,
    foo: query.foo ?? '[default]',
  }
})

await dexr.run()

addPageの第3引数(省略可)で動的ルーティングの処理を行う感じです。 バリデーションやページComponentに渡す前処理はここで行えます。 あとはrunすればアプリケーションが起動します。

deno.landでの公開

作成したものを3rdパーティモジュールとしてdeno.landで検索可能にするには、deno.landのリポジトリ内にあるJSONファイルを更新してPRを投げるだけです。

https://github.com/denoland/deno_website2/pull/1086 ※ちょうどタイミングが悪く、テストが絶対こけるようになってたので1回こけてます・・・

ここでマージされると、https://deno.land/xで検索ができるようになりました。

Dexrの今後

現状Dexrはまだ依存ファイルのbundleとかをしておらず、.tsxのリクエストに対しコンパイルだけしたjsを返却するとかして無理やり動かしている状態です。 これはDenoのbundle時のバグ(https://github.com/denoland/deno/issues/4542)で外部ファイルの依存関係をbundleできない状態なのでこういった回避策を取っています。 bundleが無事できるようになったら

  • Reactをexport、もしくは別バージョンを指定できるように拡張
  • クライアントサイドはSPAにできるようRoutingを自動で付与
  • 依存関係をbundle
  • SSG的機能の追加
  • ドキュメントサイトの公開

などをしていこうと思っています。

感想

Denoを触ってみて

Deno自体はとても素晴らしいです。 安全性・0インストールによる作業軽量化感・ランタイムがテストやTypescriptのコンパイラをもっていることで気にすることがNodeと比べるとかなり少なかったです。 webpackは多機能・ブラックボックスが故にいじってるとはまることも結構あったので、そこらへんをランタイム側で責務をおってくれるのは開発者としてもありがたいし、なにより統一性を高めることになるので非常に嬉しいところでした。

v1でたばかりな状態なので時折バグっぽいの見つけてissue漁ったり立てたりしてたんですが、今後自分で取り組むこともやっていきたいなと思いました。 実は自分で立てたissueの原因探ろうとソース読んでたんですが、当たり前ですが難しくて右往左往している間にCloseされちゃいました(ただどちらにせよPR見た感じ、難易度高くて自分にはどうせできなかったと思います)。

フレームワークを作ってみて

ReactのSSR周り、特にStyled ComponentsとかをNextがどういうサポートの仕方をしているのかとかみながらだったのでなかなか勉強になりました。

あと、「どうだったらみんなが使いやすいのか」「他のフレームワークと比べどこまで制限してどこまで拡張の余地をもたせるのか」とかを都度考えて設計しなきゃだったので、 なかなかこれも普段の開発とはまた少し違って面白かったです。

Denoへの期待

今はまだ事例とかも少ないでしょうし、まだまだこれからだとは思いますが、いずれサーバーサイドJSのランタイムのメインになっていく可能性は全然あると思います。というかなってほしいなと思います。 なのでこのフレームワークをもうちょっと育てながら、今後もDenoの動向に注目していきたいと思います。