はじめに
こんにちは。今日も明治大学 advent calendarやっていきます。
アドベントカレンダー忘れてて、数週間前に貯めておいた記事でどうにか繋ぐ。
Reload 問題
nuxtとapi serverを実装して最高のSSR applicationを作りたいぜ!なんて思うでしょ?
でもね、ググって出てくるnuxt & api serverって思ったより微妙なんだ。どれも、開発時のreload
が気持ちよくない。
nuxt & expressでググって良く出てくるのは以下の2つのパターン。
- nuxt as an express middleware
- express as a nuxt middleware
1. nuxt as an express middleware
まずはこっち。nuxtが提供しているexpress middlewareを使うバターン。
Example
コードはこれ。見ての通り、 Nuxt builder middlewareをexpressで使う。api serverをNodemonとかでwatch&reloadするような開発と非常に相性が悪い。
const express = require('express')
const { Nuxt, Builder } = require("nuxt")
const app = express()
let config = require("./nuxt.config.js")
config.dev = !(process.env.NODE_ENV === "production")
const nuxt = new Nuxt(config)
if (config.dev) {
const builder = new Builder(nuxt)
builder.build()
}
const api = require("./api")
app.use("/api", api)
app.use(nuxt.render)
Pros and Cons
- Pros
- 実装が楽
- Cons
- api server開発時にnuxtを最ビルドする必要があり、DX(Developer Experience)が最悪。
2. express as a nuxt middleware
次はこっち。Nuxtのmiddlewareとしてexpressを使うパターン。
Example
なんと、NuxtがserverMiddlewareなる機能を提供してくれていて、それを使うと本当に簡単にapi serverを実装できる!しかもね、api serverの変更をwatchしてくれて、server processだけをreloadしてくれるスグレモノ。これこそ、何も考えずにssr開発できるのがnuxtの強みなんだよな。
でもね、このexampleの場合、/api/index.js
の変更しかwatchしてくれないんだ。だから、./routes/users
を変更してもserverがreloadされない。
んーーーーーーーーーーーー、、、惜しいっっっ
module.exports = {
// ...
serverMiddleware: [
// API middleware
'~/api/index.js'
]
}
const express = require('express')
// Create express instnace
const app = express()
// Require API routes
const users = require('./routes/users')
// Import API Routes
app.use(users)
// Export the server middleware
module.exports = {
path: '/api',
handler: app
}
Nuxtならできるだろ?設定でなんとかできないのか?
ということで、Nuxtのソースコードを眺めてみる。ここは、serverMiddlewareのwathcerの部分。
一応、watch
optionでwatchするファイルやディレクトリを指定することができる。が、Clientをwatchするときにも同じoptionを使うので、serverだけwatchしたいなんて要望には沿わない。
結局、できなかった。
watchServer() {
const nuxtRestartWatch = concat(
this.options.serverMiddleware // ← ここ
.filter(isString)
.map(this.nuxt.resolver.resolveAlias),
this.options.watch.map(this.nuxt.resolver.resolveAlias), // watch は watchClient() と共有
path.join(this.options.rootDir, 'nuxt.config.js')
)
this.watchers.restart = chokidar
.watch(nuxtRestartWatch, this.options.watchers.chokidar)
.on('change', (_path) => {
this.watchers.restart.close()
const { name, ext } = path.parse(_path)
this.nuxt.callHook('watch:fileChanged', this, `${name}${ext}`)
})
}
Pros and Cons
- Pros
- 実装がめっちゃ楽
- Cons
- 一応、api server only reloadはできるけど、entry pointのみのreloadになるので、完璧なDXとは言えない。
Temporary Solution
とはいえ、わざわざ、nuxt serverとapi serverを分離させてproxyで...なんて面倒なことやりたくない。nuxtがサポートしてくれるまで寝て待てばいいのだろうけど、ずっと寝ちゃいられない。ということで、苦肉の策だけど、即時的というか一時的な解決策としては、これぐらい。
webpackとかでbuildしたやつをserverMiddlewareに設定する
あれ、nuxt serverとapi serverを分離させた方が良いんじゃ、、
おわりに
もしかして良い方法ってあるの?あったら教えてください☺️
自分だったら頑張って、分離させてproxyでほげほげしちゃうかも。面倒だけどね。typescriptも使いたいし。
追記
本当は Rust で Parser Combinator 実装!みたいな内容にしたかったんだけど、Ownership と Lifetime に苦しまされていて何も実装できなかった。Rust でコンビネーターはキツくない??