はじめに
はじめての方は、はじめまして、そうでない方は、リピートありがとうございます
Dirbatoの吉田です
本記事は生成AIブームに出遅れた感を感じている週末エンジニアの自分が、AIエージェント機能のある開発環境 Google Antigravity とたわむれながら Hello, World してみる記事です
今回は Service Worker の動きを確認する必要がでてきたので、AIエージェントと Service Worker を動かせる環境を作りつつ Hello, World してみようと思います
ちなみに、これまでにAntigravityの環境構築や、html、JavaScriptで Hello, World しているのでよかったら覗いてみてください
環境など
- Windows 11 25H2
- Google Antigravity
- Google Chrome 143
Service Worker について
Service Worker はWebページと独立したバックグラウンド処理を動かすための仕掛けで、プッシュ通知の受信や、オフライン対応などを行うことができます
機能はJavaScriptに含まれており処理もJavaScriptで記載します
Let's Start
Step 1: Service Worker で Hello, World
では早速はじめていきます
まずは基礎部分を作るための依頼を、これまでの経験を踏まえてこんな感じで
他でも Hello, xxx を使いたいのでまずは Hello, ServiceWorker としてもらいます
ServiceWorker登録時に"Hello, ServiceWorker"のメッセージを送り、画面で受け取って表示させるコードを出して
htmlとして最低限のタグを盛り込んで
いままで気にしてきませんでしたが初手で初めて実行計画が出てきた気がするので、初期設定だとこれくらいの難易度のリクエストになると Antigravity が実装計画を作成して承認を求めてくるようになるようです(気のせいだったらすみません、、)
出力された実行計画(折りたたんでいます)
ServiceWorkerメッセージ送受信 実装計画
ServiceWorker登録時に"Hello, ServiceWorker"というメッセージをServiceWorkerからクライアント(画面)へ送信し、画面上に表示させる機能を実装します。
提案される変更
Frontend
ServiceWorkerの登録とメッセージ受信処理を行うHTMLファイルと、メッセージ送信を行うServiceWorkerファイルを作成します。
[NEW]
index.html
ServiceWorker (sw.js) の登録処理
ServiceWorkerからのメッセージ受信イベントリスナー (navigator.serviceWorker.onmessage または addEventListener) -受信したメッセージをDOM要素(例: <div id="message"></div>)に表示する処理
[NEW]
sw.js
activate イベントリスナー
clients.claim() を実行して即座に制御を開始
clients.matchAll() でクライアントを取得し、各クライアントに対して postMessage("Hello, ServiceWorker") を送信
検証計画
手動検証
ターミナルで簡易サーバーを起動(例: npx http-server -p 8080 または python -m http.server 8080)。
ServiceWorkerはSecure Context (HTTPSまたはlocalhost) でのみ動作するため、ファイル直接閲覧では動作しません。
ブラウザで http://localhost:8080 にアクセス。
画面に "Hello, ServiceWorker" が表示されることを確認。
コンソールログでもメッセージの送受信が正しく行われているか確認。
タスクが日本語表示になるように下記記事を参考に設定を入れております
投稿者様に感謝
参考元(2026/01/26時点):https://qiita.com/take_naka/items/e2ac7f9cd4fd698bbc12
全体像は index.html に Service Worker の登録と待ち受け処理を作成して sw.js に初期化時のメッセージ送信処理を入れる計画のよう
概ね問題ないですがJavaScriptは分離しておいてほしいので
index.html 内にJavaScriptの処理は書かないで別ファイルにして
と依頼して main.js に切り出すよう計画を変更してもらい、問題ないことが確認できたので
OKです
と承認することでコードが作成されました
できた最低限のコードがこちら
出力されたコード(折りたたんでいます)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ServiceWorker Hello</title>
</head>
<body>
<h1>ServiceWorker Message</h1>
<div id="output">Message will appear here...</div>
<script src="main.js"></script>
</body>
</html>
// ServiceWorkerの登録
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js')
.then(registration => {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
})
.catch(err => {
console.log('ServiceWorker registration failed: ', err);
});
// メッセージの受信リスナー
navigator.serviceWorker.addEventListener('message', event => {
console.log('Message received from SW:', event.data);
const output = document.getElementById('output');
output.textContent = event.data;
});
}
// sw.js
self.addEventListener('install', event => {
// インストール後すぐにアクティブにする
self.skipWaiting();
});
self.addEventListener('activate', event => {
// 即座にページをコントロールする
event.waitUntil(
self.clients.claim().then(() => {
// 全てのクライアント(開いているページ)にメッセージを送る
return self.clients.matchAll();
}).then(clients => {
clients.forEach(client => {
client.postMessage('Hello, ServiceWorker');
});
})
);
});
3ファイル作成すれば Service Worker で Hello, World を試すことができるようになりました
- 一般的なJavaScriptはブラウザで file でアクセスしても動きますが、Service Worker は Secure Context か動作せず https や localhost でアクセスしないと動きません
- localhost でアクセスするための webサーバー の簡易な起動は下記記事などを参考にいただければと思います
Step 2: 再アクセス時に別メッセージを表示
目的の Hello, World ができたところで次は Service Worker の特徴のひとつの画面とは別で動く特性を確認していこうと思います
具体的には画面をリロードした際に回数をカウントして、リロード時に登録時とは別のメッセージを出してもらおうと思います
依頼はシンプルに
リロード時に'Hello, fetch : ' + リロード回数 と表示させて
さくっとコードを出してくれて、見た感じも問題なさそうでしたが、動かしてみると思っているようには動いてくれません
あえて調べずにやっているからというのもありますが、知識が足りてないので原因の目星はもちろんのこと、自分で手を動かしていないので怪しいポイントすら思いつきません
このあたりはAIエージェント入門者の躓きポイントなのかもしれません
上手くいかない時は学びのチャンス
ということで、自分は手を動かさないと記憶が定着しない方なのでこの機会を利用して、AIエージェントと試行錯誤してみたので
その過程をダイジェストで
-
リロードの回数を保持する変数を用意して、更新の際に動く
activateイベントでインクリメントして、回数で分岐させる
→ 初回は値が返るがリロード後は何も返らず
⇒activateイベントは画面でregisterを呼んだ際に毎回動くわけではなく、呼んだ際に指定したリソースが更新されてる時 or 更新されたリソースをService Workerが読み込んだ際に実行されるため、目的に合わず -
httpリクエストがある度に実行される
fetchイベントでカウントと分岐を実装
→ リロードで分岐させられたがリロードの回数が飛び飛びに
⇒ 画面呼出し時だけではなく、jsやcss、faviconの呼び出し、get、post時にも呼ばれるためリクエストされた回数分カウントされる -
fetchイベントで画面のみに絞って実行
→ 初回は値が返るがリロード後は何も返らず
⇒fetchで値を返すと、画面でDOMの読込が完了する前にレスポンスが返されて、結果何もなかったことに -
カウントを
fetchイベントで実施して、画面でDOM完了後に 'Hello, fetch : {リロード回数}' を返すようService Workerにリクエスト
→ 目的通りの動きになりました
出来上がったコードにコメントを追加したものがこちら
出来上がったコード(折りたたんでいます)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ServiceWorker Hello</title>
</head>
<body>
<h1>ServiceWorker Message</h1>
<div id="output">Message will appear here...</div>
<script src="main.js"></script>
</body>
</html>
// ブラウザがServiceWorkerに対応しているか確認
if ('serviceWorker' in navigator) {
// ServiceWorkerを登録する
// './sw.js' はこのファイルと同じディレクトリにあることを想定
navigator.serviceWorker.register('./sw.js')
.then(registration => {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
})
.catch(err => {
console.error('ServiceWorker registration failed: ', err);
});
// ------------------------------------------------------------------------
// メッセージ受信 (ServiceWorker -> Client)
// ------------------------------------------------------------------------
navigator.serviceWorker.addEventListener('message', event => {
console.log('Message received from SW:', event.data);
// 受信したメッセージを画面上の要素に表示
const output = document.getElementById('output');
if (output) {
output.textContent = event.data;
}
});
// ------------------------------------------------------------------------
// 初期表示時のデータ取得
// ------------------------------------------------------------------------
// ページがロードされ、ServiceWorkerによって制御されている場合、
// 現在のカウント状態を取得するためにメッセージを送る
// (リロード時など、既にActiveなServiceWorkerがいる場合に有効)
if (navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage('get_count');
}
}
// リクエスト回数を保持する変数(ServiceWorkerが再起動するとリセットされます)
let count = 0;
/**
* カウント対象のURLかどうかを判定するヘルパー関数
* @param {URL} urlToCheck - チェックするURLオブジェクト
* @returns {boolean} - 対象ならtrue
*/
function isTargetUrl(urlToCheck) {
// 同一オリジン かつ (ルートパス または index.html) の場合
return urlToCheck.origin === location.origin &&
(urlToCheck.pathname === '/' || urlToCheck.pathname.endsWith('index.html'));
}
// ----------------------------------------------------------------------------
// Install Event
// ServiceWorkerが最初にブラウザに認識された時に発生
// ----------------------------------------------------------------------------
self.addEventListener('install', event => {
// skipWaiting()を呼ぶことで、待機中のServiceWorkerを即座にアクティブにする
// これにより、既存のServiceWorkerがいても制御を奪うことができる
// (通常は既存のタブが閉じるまで待機状態になる)
self.skipWaiting();
});
// ----------------------------------------------------------------------------
// Activate Event
// ServiceWorkerがアクティブになった(制御を開始できる状態になった)時に発生
// ----------------------------------------------------------------------------
self.addEventListener('activate', event => {
// waitUntil() にPromiseを渡すことで、その処理が終わるまでインストール処理とみなす
event.waitUntil(
// clients.claim() を呼ぶことで、制御下にない(リロード前の)クライアントも即座に制御下に置く
self.clients.claim().then(() => {
// 現在のServiceWorkerが制御している全てのクライアント(タブやウィンドウ)を取得
return self.clients.matchAll();
}).then(clients => {
// 各クライアントに対してメッセージを送信
clients.forEach(client => {
client.postMessage('Hello, ServiceWorker');
});
})
);
});
// ----------------------------------------------------------------------------
// Fetch Event
// ブラウザがネットワークリクエストを行う時に発生
// ----------------------------------------------------------------------------
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// 特定のURLへのアクセスのみカウントアップする
if (isTargetUrl(url)) {
count++;
}
// 実際のリクエスト処理をブラウザに任せる(通常のネットワーク通信を行う)
event.respondWith(fetch(event.request));
});
// ----------------------------------------------------------------------------
// Message Event
// クライアント(メインスレッド)からメッセージを受け取った時に発生
// ----------------------------------------------------------------------------
self.addEventListener('message', event => {
// 'get_count' というメッセージを受け取ったら、現在のカウントを返信する
if (event.data === 'get_count') {
// メッセージの送信元(source)に対して返信
event.source.postMessage('Hello, fetch : ' + count);
}
});
AIエージェントに全面的に任せて一気に解決してもらえばもっと早く出来たのかもしれませんが、これくらいの手間をかけないと自分の理解がついていけなさそうなので悩ましいところです
このあたりは今後の課題ということで
学び
- Service Worker は https や localhost でアクセスしないと動作させられない
-
activateイベントは初回登録時と資材が更新された時のみで、リロード時は動かない -
fetchイベントは全てのhttpリクエストで動く - 画面リクエストの
fetchイベントでメッセージを返すと、DOM読込完了前に返される - AIエージェント任せで実装すると躓いた時に思ってるよりも時間がかかりそう
- 問題解決のAIエージェント任せにすると理解が追いつかなそう
- バランスは今後の課題
- 本記事は個人の学習メモであり、所属組織の公式見解ではありません