サーバーが自分自身と同じプログラムを配信して、それをブラウザがロードして、どちらでも同じコードが実行され、サーバーだけではなくブラウザからもレスポンスを返す魔法「Service Worker Magic」を紹介します。
Service Worker Magic
こういうことです。
- サーバーはCloudflare Workers、ブラウザはService Workerのプログラムを指す
- サーバーのプログラムは
sw.js
、ブラウザで動くプログラムもsw.js
- 全く同じ内容かつ同じリソースを参照して、同じように動く
- サーバー
sw.js
が自分自身のコードsw.js
を/sw.js
というパスで配信する /
にアクセスするとsw.js
がService Workerとして登録される/sw/*
をService Workerのスコープにする/server/hello
にアクセスするとサーバーからレスポンスが返る/sw/hello
にアクセスするとService Workerがインターセプトして、ブラウザからレスポンスが返る
デモ
実際に動いているデモがこちらになります。Chrome devToolsなどでNetworkやConsoleを確認しながら遷移させると挙動がよく分かります。
Service Workerが動かなくなることがあります。その場合は一度トップ/
にアクセスするか、それでも動かなければ、ブラウザのService Workerのキャッシュをクリアしたら動きます。
/sw/hello
が「Service Worker」から配信されています。
スクリーンキャスト
以下がスクリーンキャストです。「From Server」が/server/hello
への、「From Service Worker」が/sw/hello
へのリンクとなっています。
/server/hello
に対するリクエストは通常のGETになりますが、Service Workerのスコープ内である/sw/hello
はService Workerがインターセプトします。よって、「Hello! From Service Worker!」と表示されます。また、/server/hello
だとターミナルのサーバーログへ、/sw/hello
はdevToolsへログが吐き出されています。
繰り返しますが、サーバーとService Workerは全く同じリソースを参照しています。sw.js
は一個だけです。
種明かし
種明かしをすると、Cloudflare WorkersではService Worker互換のAPIを使ってプログラムを書くため、全く同じコードがService Workerでも動くというわけです。
コード
以下が抽象化したショートバージョンです。 フレームワークにはHonoを使っています。 繰り返しますがこれがサーバーでもブラウザでも実行されるのです。
// フレームワークにHonoを使っています
import { Hono } from './hono.js'
// Middlewareのimport
import { serveStatic } from './hono.serve-static.js'
import { logger } from './hono.logger.js'
let from = 'Service Worker'
try {
// Cloudflare Workersの環境変数でFROMの値を"Server"としている
// サーバーで立ち上がった場合はFROMがServerに、クライアントの場合はService Workerになる
from = FROM
}
const app = new Hono()
// ロガーMiddleware
// `/sw/*`のログはサーバーではなくdevToolに出る
app.use('/sw/*', logger())
// `/server/*`のログはサーバー側のターミナルで出る
app.use('/server/*', logger())
// 静的ファイル配信のためのMiddleware
// *.jsファイルをそのまま配信する
app.use('/:name{.+.js}', serveStatic({ root: './' }))
// トップページはベタ書きHTMLを出力
// Service Workerを登録する
// `scope: '/sw/'` としているので `/sw/hello`へのアクセスをService Workerがインターセプトする
app.get('/', (c) => {
const html = `<html><body>
<script>
navigator.serviceWorker.register('/sw.js', { scope: '/sw/', type: 'module' })
</script>
</body></html>`
return c.html(html)
})
// ハンドラー
const handler = (c) => {
const text = `Hello! from ${from}!`
return c.text(text)
}
// ルーティング
// `/server/hello`はサーバーにアクセスがいく
app.get('/server/hello', handler)
// `/sw/hello`へのアクセスはService Workerがインターセプトする
app.get('/sw/hello', handler)
// addEventListener('fetch'...を発火
app.fire()
まとめ
この「Service Worker Magic」、すごく面白いんですが、面白さが伝わったでしょうかね。 コードは以下の通りです。ローカルでも実行可能なので、試してみてはいかがでしょうか。
loading...
宣伝
僕がスーパーバイザー(謎)を務めるトラベルブックでもテックブログやってます。