最近所用でNext.jsを集中的に触る機会があった。一番好きな言語はScalaで、仕事でもプライベートでもよく書いているのだけれど、Next.jsの体験がとても良く、楽しかったのでたくさん試行錯誤したくなった。この体験を良い感じにScalaに還元できたら面白いなと思ったので、 普段Scala(主にバックエンド)を書いている人目線で見たNext.js開発の良さを書いていこうと思う。 雑多なメモなので、相互に矛盾していたり重複などあるかもしれませんのであしからず。
- フロントエンドだから?
- TSX?
- TypeScriptだから?
- Prisma ORMの体験がめちゃくちゃ良かったから?
- ドキュメンテーション
- routingが独特だけど慣れるとこれでいいやという気持ちになる
- Server Actions
- 愛
- コンパイルが速いから?
- ホットリロードがあるから?
- 初期状態でなんかいい感じのCSSが勝手にやってきてヤル気が出る
- フロントとバックエンドが一気通貫しているから?
フロントエンドだから?
フロントエンドだから当然なのだが、触ったらすぐ目に見えて派手なことが起こるのが良いと思った。バックエンド開発だと地味な画面とずっと格闘することになるので、フィードバックが地味(だからバックエンド開発が悪いとかいうことではもちろんない)。
Scalaでフロントエンド開発をするためのライブラリとしてScala.jsもあるけど、フレームワークではなくライブラリなので、ヨーイドンまでの段階が多い気がする。PoCをデリバリーするまでの時間で考えると、とにかく一瞬で全てが出来上がってほしい。
Nextだとwebpackがデフォルトで入ってて、create-next-app
したらそのまま高速に開発サーバが立つ。いちおう、Scala.jsでもsbt new
すると一気にVite + Scala.jsのプロジェクトを立ち上げられるし、pnpm i && pnpm run dev
するとそのまま開発サーバが立つ。
TSX?
Next.jsとは直接は関係ないけれど、tsx(jsx)が使えるのが地味に良い。Scalaでもscala-domを使ったLaminar開発ができるけれど、こういうシンタックスなのでちょっと面食らうかもしれない。
def logoutButton(client: Auth0Client): Element = { button( tpe := "button", "logout", onClick --> { event => client.logout() } ) }
tsxはHTMLにかなり近いので、デザイナとの協業もやりやすい。
export function LogoutButton(props: { client: Auth0Client }) { <button type="button" onClick={() => client.logout()}> logout </button> }
また、CSSを部分的にimportしていい感じにコンポーネントに割り当てられるのも良い。というのも、多くのUIライブラリにはCSSに型を付けた型定義ファイルがTS向けに提供されているし、CSSから型を生やすツールもあるためだ。これがあると補完やcss moduleといったpost processing技術の恩恵を受けやすい。Scala.jsだとこれできるっけ(Viteを通すからうまくやれば出来そうだけど、今調べているところ)。
ちなみにDOM構造をコンポーネント化するのは別にLaminarでもやれる。Reactなどと違って仮想DOMを扱う方式ではなく、直接内部に対応するDOMを持つというメカニズムであるという違いはあるものの、「なんらかのpropsを渡してパーツごとにコンポーネントを表示する」という仕組みは完全に実現可能だ(前掲のコードでもそれをやっている)。
TypeScriptだから?
もし仮にNext.jsのTS部分だけそっくりScalaになって、あとはそのままだとしたらどうだろう?ということを考えてみた。型付けなどの差異はあるけれど、たぶん開発効率は劇的には変わらないと思う。どちらもimmutableを指向しているし、すぐれた型システムがある。もちろんTSは原理的に完全なimmutableを貫徹していないし、Scalaと型システムを比べるのは酷だけれど、どちらでもうまくいきそうな雰囲気がある。
自分は完全にTS初心者ではなくて、数年は使っている(ただし、ヘヴィーに使っているわけではない)ので、ある程度書き味の違いは分かる。なんとなくだが、コード片の再利用性が高い気がする。しかしそれは記述力が弱くて再利用しなければならないだけ、という気もしていて、Scalaならこれ一発なのに、という局面もあった。TypeScriptだと型が柔軟なので(また、そうであるからこそ)コードの再利用を積極的にやっていて、Scalaだと型が厳格なのでそもそも再利用しなければならない局面にはあまり遭遇しないのではないか、と思った。このへんは無根拠なポエム。
ところで自分はJS/TSのobject notation / object typeがかなり好み。こういうやつ。
const foo = { windy: 42, melt: false, };
Scalaだと無名オブジェクトをポンッと作るのは少し面倒な感じになってしまう。
val foo = new { val windy = 42 val melt = false }
加えて、型システムの型付けとしてTSはstructural typingを採用しているがScalaはnominal typingを採用しているという差異があるため、以下のようなコードは動作しない。
val w = new { val windy = 42 } trait W { val windy: Int } val ww: W = w
trait
のかわりにtype
を使うとstructural typingでコンパイルを通したりできるが、TSほど簡単にはいかず、無名オブジェクトを扱うのは結構骨が折れる。
TSではオブジェクトはかなり透明に見える。言語機能として完全に統合されていて、オブジェクトを扱っているという意識すら無い状態でデータを直接いじっている感覚になれる。Scalaの無名オブジェクトではこの感覚はあまり得られない。
ちなみに今同僚のid:taraoがこのへんを良くするライブラリを開発してくださっているので、ウキウキしながら待っている状態。
TypeScriptは難しい要素をあまり見えなくしているけれど、Scalaは難しい要素は本質的に難しいので最初から露出させる、というスタンスをとりがちと思っていて、TypeScriptよりもScalaのほうが、「考えながら」書いている気がする。それが良いのか悪いのかはわからない。
Prisma ORMの体験がめちゃくちゃ良かったから?
今回の開発ではPrismaを使ってGoogleのCloud SQLにデータを保管した。Prismaを使ってCloud SQLに「クエリ」を投げ付けていくさまは快刀乱麻を断つ勢いであった。これがServer Actionと組み合わさって、フロントエンドからさほど遠くない距離でデータが保存されるのが新鮮だった。
はてなエンジニアはあまりORMを使わない傾向にあって、プレーンなSQLをそのまま書くことをよしとしがちなのだが、素朴なアプリケーションではこういう感じのORMぜんぜんありだなと思った。ただORMの欠点はORMに寄せすぎるとデータ === DB上のレコードになってしまって、ドメイン領域を表現できなくなりがちなこと。
また、Prismaは周辺ツールが充実していてnpx prisma push
とかすると勝手にスキーマが突っ込まれたりするのも良かった。DXが意識されている感じがした。
SQLでDDL書かなくていいのがかなりうれしい。
ドキュメンテーション
ググったら出てくるという嬉しさがあった。天下の(?)Next.jsなのでめちゃくちゃおカネがかけられていて、とりあえず調べえばなんか出てくるでしょという安心感があった。
Scalaやってる人はあまり記事書いてくれない、というか母数もそんなに多くないので記事が出てこない。雑でもいいからもっと記事書いてくれ!!!!!!「調べれば分かるよね」じゃなくて書いてほしい!!!!!!
routingが独特だけど慣れるとこれでいいやという気持ちになる
Scala.jsだとWaypointというライブラリでフロントエンドルーティングをすることになるし、バックエンドだけでやるにしてもがんばってルーティングを書くのだが、Next.jsだともっとザックリしていて、ファイル構造を巻き込んでいておもしろい。あーディレクトリでルーティングしてもいいんだ、という気付きがあった。このへんの割り切りの良さは、フレームワークならではだと思う。
Server Actions
ちょっとしたデータを保存したいという局面があったのだが、LocalStorageとHookとでいろいろ工夫するより、Server Actionsでサーバサイドのコードを呼び付けてそのままCloudSQLに保存してしまえたのも衝撃的だった。おもしれ〜
もちろん乱用するとセキュリティ的に問題がありそうだが、そこまで気にしなくてよい初期段階のプロダクトではもうこれで良い気がする。Scalaにも似たやつがあるので、うまいこと使えると面白そう。
→やった
愛
JS/TSまわりのライブラリやプロダクトは、とりこになるような何かがあったり、ハートフルで楽しい要素が随所に散りばめられていがちな気がする。自分もそういう要素をScalaに増やしていきたいな〜
コンパイルが速いから?
TypeScriptのコンパイル速度は速い。速いとどうなるかというとフィードバックループが爆速で回って、結果として開発速度も上がるし、PoCが出てくるまでのリードタイムが小さくなる。難しい用語を使わずとも、とにかく速いほうがDX良いという話。
Scalaでコンパイルが爆速だったら?まあ嬉しいけどNextほどの喜びがあるかというとわからない。でも普通に嬉しいな・・・
ホットリロードがあるから?
これはScala.jsでもJVMのScalaにもあるので、あまり関係ないかも。導入までの敷居の高さが違うとかはあるかもしれない。
初期状態でなんかいい感じのCSSが勝手にやってきてヤル気が出る
何も無かったり、質素なのもアリだけど、ちょっと色気があるCSSがついてくるとオ~楽しそうみたいな気持ちになる。このへんはDXみたいな話題だと思う。最低限のシンプルさがライブラリのデフォルト状態として良いのか、それなりにもてなしてやるのが良いのか、みたいな話。
フロントとバックエンドが一気通貫しているから?
特急列車感があった。乗ったら着く。開発速度がとにかく速いので、結果として開発体験の良さにつながっている。さっきも書いたけど、速いほうがいい。ビジネスのスピード感というか、機を見たら逃さないぜ!という勢いを感じるのは圧倒的にフロントエンド世界のほう。
「動く」までの速さがある。結局現代アプリケーションはブラウザで動かすのが普通になっちゃってるからな。
Next.jsの場合は「フレームワーク」の良さが出ているように思う。「達成したいこと」はもうフレームワーク側が決めてしまっているので、あとはその通りに走るだけという世界観。Scalaだとモジュラリティを重んじているので、ライブラリ指向になりがち。ではフレームワークであるPlay(こちらはバックエンドだが)ではどうか?という問いも可能。
最近のウェブは、フレームワーク自体がシーンをリードするみたいなところあるよな〜と思う。既存のユースケースをやりやすくするフレームワークが出るのではなく、新たなアーキテクチャをフレームワークが提示して、それが問題を動かしてしまうみたいな。
— Windymelt(めるくん)🚀❤️🔥 (@windymelt) 2023年11月6日
今回は重厚なDDDという「アーキテクチャ」があまり挟まらず、ほぼDB色塗りアプリケーションだったがそれが逆に良かったのかもしれない。これによってNext.jsの良さが最大限発揮されたようにも思う。