From 66fb77fb15c2f7ad7164612eaa99ca29916a4676 Mon Sep 17 00:00:00 2001 From: m-sato Date: Wed, 30 Sep 2026 14:44:49 +0900 Subject: [PATCH 1/8] =?UTF-8?q?3-2=20=E3=81=A7=20showPosts=20=E3=81=8B?= =?UTF-8?q?=E3=82=89=20fetch=20=E3=82=92=E6=B6=88=E3=81=97=E3=81=9F?= =?UTF-8?q?=E7=9B=B4=E5=BE=8C=E3=81=AF=E3=80=81=E6=8A=95=E7=A8=BF=E3=81=8C?= =?UTF-8?q?=E5=86=8D=E8=AA=AD=E3=81=BF=E8=BE=BC=E3=81=BF=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=81=BE=E3=81=A7=E4=B8=80=E8=A6=A7=E3=81=AB=E5=87=BA=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=81=93=E3=81=A8=E3=82=92=E6=9B=B8=E3=81=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 2026/board-advanced/slide.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/2026/board-advanced/slide.md b/2026/board-advanced/slide.md index 67afa9f1..01802b6a 100644 --- a/2026/board-advanced/slide.md +++ b/2026/board-advanced/slide.md @@ -1210,6 +1210,8 @@ const source = new EventSource(`${API}/events`); // すでにある行 **成功**: ページを再読み込みすると、一覧が出る +この時点では、投稿しても再読み込みするまで一覧に出ません。 + --- ## 3-2. 届いた1件は `e.data` に文字列で入っている From e17f58772ac7d00c199f1abce807b652c18bef1d Mon Sep 17 00:00:00 2001 From: m-sato Date: Wed, 30 Sep 2026 14:46:18 +0900 Subject: [PATCH 2/8] =?UTF-8?q?4-2=20=E3=81=A7=20let=20connection=20?= =?UTF-8?q?=E3=82=92=E8=B6=B3=E3=81=99=E4=BD=8D=E7=BD=AE=E3=82=92=E3=80=81?= =?UTF-8?q?script.js=20=E3=81=AB=E3=82=82=E3=81=82=E3=82=8B=20posts=20?= =?UTF-8?q?=E3=81=A7=E3=81=AF=E3=81=AA=E3=81=8F=20server.js=20=E3=81=AB?= =?UTF-8?q?=E3=81=97=E3=81=8B=E3=81=AA=E3=81=84=E3=82=B3=E3=83=A1=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=81=A7=E7=A4=BA=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 2026/board-advanced/slide.md | 1 + 1 file changed, 1 insertion(+) diff --git a/2026/board-advanced/slide.md b/2026/board-advanced/slide.md index 01802b6a..7f4f8454 100644 --- a/2026/board-advanced/slide.md +++ b/2026/board-advanced/slide.md @@ -1476,6 +1476,7 @@ res.setHeader('Cache-Control', 'no-cache');
```javascript {data-file=server.js} +// 投稿の置き場。再起動すると空に戻る。 const posts = []; // すでにある行 @@let connection = null;@@ // 現在の接続。新しい接続が来ると上書きされる ``` From c5f0c3b861bfc1a0edadce022d58505429ae2ff4 Mon Sep 17 00:00:00 2001 From: m-sato Date: Wed, 30 Sep 2026 11:59:27 +0900 Subject: [PATCH 3/8] =?UTF-8?q?=E9=85=8D=E5=B8=83=E3=82=B3=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=81=AE=20script.js=20=E3=81=AB=E3=80=813=E7=AB=A0?= =?UTF-8?q?=E3=81=A7=20posts=20=E3=81=A8=20loadPosts=20=E3=82=92=E6=9B=B8?= =?UTF-8?q?=E3=81=8F=E5=A0=B4=E6=89=80=E3=81=AE=E7=9B=AE=E5=8D=B0=E3=82=92?= =?UTF-8?q?=E5=85=A5=E3=82=8C=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 2026/board-advanced/examples/snapshots/ch1.js | 2 ++ 2026/board-advanced/examples/snapshots/ch2.js | 2 ++ 2026/board-advanced/template/public/script.js | 2 ++ 3 files changed, 6 insertions(+) diff --git a/2026/board-advanced/examples/snapshots/ch1.js b/2026/board-advanced/examples/snapshots/ch1.js index 1c42a441..cc7ca044 100644 --- a/2026/board-advanced/examples/snapshots/ch1.js +++ b/2026/board-advanced/examples/snapshots/ch1.js @@ -3,6 +3,8 @@ const API = 'https://example.deno.net'; +// ▼ 3章: ここに posts と loadPosts を追加する + async function showPosts() { const res = await fetch(`${API}/posts`); const posts = await res.json(); diff --git a/2026/board-advanced/examples/snapshots/ch2.js b/2026/board-advanced/examples/snapshots/ch2.js index df08b806..2b5295ce 100644 --- a/2026/board-advanced/examples/snapshots/ch2.js +++ b/2026/board-advanced/examples/snapshots/ch2.js @@ -3,6 +3,8 @@ const API = 'https://example.deno.net'; +// ▼ 3章: ここに posts と loadPosts を追加する + async function showPosts() { const res = await fetch(`${API}/posts`); const posts = await res.json(); diff --git a/2026/board-advanced/template/public/script.js b/2026/board-advanced/template/public/script.js index b6095dfb..5d1c2f00 100644 --- a/2026/board-advanced/template/public/script.js +++ b/2026/board-advanced/template/public/script.js @@ -1,5 +1,7 @@ const API = 'https://example.deno.net'; +// ▼ 3章: ここに posts と loadPosts を追加する + async function showPosts() { const res = await fetch(`${API}/posts`); const posts = await res.json(); From 2f73e907f9669d78242a848ec3df9dd8dd4c18fa Mon Sep 17 00:00:00 2001 From: m-sato Date: Wed, 30 Sep 2026 12:02:37 +0900 Subject: [PATCH 4/8] =?UTF-8?q?=E3=82=B9=E3=83=A9=E3=82=A4=E3=83=89?= =?UTF-8?q?=E3=81=AE=E3=82=B3=E3=83=BC=E3=83=89=E7=89=87=E3=81=AB=E5=9B=B2?= =?UTF-8?q?=E3=82=93=E3=81=A7=E3=81=84=E3=82=8B=E8=A1=8C=E3=81=A8=E7=9C=81?= =?UTF-8?q?=E7=95=A5=E8=A8=98=E5=8F=B7=E3=82=92=E5=85=A5=E3=82=8C=E3=80=81?= =?UTF-8?q?=E8=89=B2=E3=81=AA=E3=81=97=E3=81=AE=E8=A1=8C=E3=82=92=E6=9B=B8?= =?UTF-8?q?=E3=81=8F=E5=A0=B4=E6=89=80=E3=81=AE=E7=9B=AE=E5=8D=B0=E3=81=A8?= =?UTF-8?q?=E3=81=97=E3=81=A6=E8=AA=AC=E6=98=8E=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 2026/board-advanced/slide.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/2026/board-advanced/slide.md b/2026/board-advanced/slide.md index 7f4f8454..1a364aad 100644 --- a/2026/board-advanced/slide.md +++ b/2026/board-advanced/slide.md @@ -468,13 +468,14 @@ style: | 手を動かしてもらうスライドには、右上に 記述 のバッジが出ます。 黄色い行だけを書き、赤く取り消された行は消します。 -`______` は空欄です。前の説明のスライドを見て、自分で埋めます。 +`______` は空欄で、前の説明のスライドを見て自分で埋めます。`// …` は省いた行です。 ```javascript {data-file=public/script.js} -const posts = []; // 色なし: すでにある行。この下に書く +const posts = []; // 色なし: すでにある行 (書く場所の目印) @@let connection = null;@@ // 黄色: 書いてもらう行 @@const max = ______;@@ // 空欄: 自分で埋める %%setInterval(showPosts, 10000);%% // 赤: 消してもらう行 +showPosts(); // 色なし: 目印が書く行の下に来ることもある ``` 早く終わった人は、章の最後のスライドの左上にあるリンクから応用課題に進んでください。 @@ -1167,6 +1168,7 @@ source.@@onmessage@@ = showPosts;
```javascript {data-file=public/script.js} +// ▼ 3章: ここに posts と loadPosts を追加する @@const posts = [];@@ // ブラウザ側で持つ投稿。画面と同じ並び @@async function loadPosts() {@@ // 開いたときに1回だけ呼ぶ @@ -1240,6 +1242,7 @@ const source = new EventSource(`${API}/events`); // すでにある行 `posts` に追加してから `showPosts` を呼ぶと、その1件が一覧に表示されます。 ```javascript {data-file=public/script.js} +const source = new EventSource(`${API}/events`); // すでにある行 %%source.onmessage = showPosts;%% @@function receivePost(e) {@@ @@ posts.push(JSON.parse(e.data));@@ // 届いた文字列をオブジェクトに戻して追加する @@ -1299,7 +1302,9 @@ const source = new EventSource(`${API}/events`); // すでにある行 投稿した1件は `receivePost` が一覧に出すので、この `showPosts` は消します。 ```javascript {data-file=public/script.js} - document.getElementById('text-input').value = ''; // ここから下 +async function addPost() { + // … + document.getElementById('text-input').value = ''; %% showPosts();%% } ``` @@ -1621,6 +1626,8 @@ data: {"name":"たろう","text":"やっほー"} ``` ```javascript {data-file=server.js} + if (req.method === 'GET' && url.pathname === '/events') { + // … %% connection = res;%% %% console.log('接続を受け付けた');%% @@ connections.push(res);@@ @@ -1700,7 +1707,10 @@ data: {"name":"たろう","text":"やっほー"}
```javascript {data-file=server.js} - console.log(`接続数: ${connections.length}`); // すでにある行 + if (req.method === 'GET' && url.pathname === '/events') { + // … + connections.push(res); + console.log(`接続数: ${connections.length}`); @@ function removeConnection() {@@ @@ connections.splice(connections.indexOf(res), 1);@@ @@ console.log(`接続数: ${connections.length}`);@@ @@ -1720,7 +1730,8 @@ data: {"name":"たろう","text":"やっほー"} ## 5-3. 答え ```javascript {data-file=server.js} - console.log(`接続数: ${connections.length}`); // すでにある行 + connections.push(res); + console.log(`接続数: ${connections.length}`); function removeConnection() { connections.splice(connections.indexOf(res), 1); console.log(`接続数: ${connections.length}`); From 944449feca889a93169128fcd4a6083840904304 Mon Sep 17 00:00:00 2001 From: m-sato Date: Wed, 30 Sep 2026 14:46:31 +0900 Subject: [PATCH 5/8] =?UTF-8?q?=E8=A8=98=E8=BF=B0=E3=82=B9=E3=83=A9?= =?UTF-8?q?=E3=82=A4=E3=83=89=E3=81=AE=E6=9C=AC=E6=96=87=E3=81=AB=E3=80=81?= =?UTF-8?q?=E6=9B=B8=E3=81=8F=E5=A0=B4=E6=89=80=E3=82=92=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E3=81=AE=E4=B8=80=E7=95=AA=E4=B8=8B=E3=82=84?= =?UTF-8?q?=E5=85=88=E9=A0=AD=E3=81=A8=E3=81=84=E3=81=A3=E3=81=9F=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE=E3=81=A7=E6=9B=B8=E3=81=8D=E6=B7=BB=E3=81=88=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 2026/board-advanced/slide.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/2026/board-advanced/slide.md b/2026/board-advanced/slide.md index 1a364aad..4b558632 100644 --- a/2026/board-advanced/slide.md +++ b/2026/board-advanced/slide.md @@ -694,6 +694,7 @@ setInterval(showPosts, 10000);
10 秒ごとに `showPosts()` を実行して、投稿一覧を読み直します。 +`public/script.js` の一番下に1行書き足します。 ```javascript {data-file=public/script.js} document.getElementById('reload-btn').addEventListener('click', showPosts); @@ -963,6 +964,7 @@ source.onmessage = handleMessage; // 関数を登録する。() は付けな
一定間隔の自動更新をやめて、サーバーからの通知に切り替えます。 +ファイルの一番下にある `setInterval` の行を消して、そこに書きます。 ```javascript {data-file=public/script.js} document.getElementById('reload-btn').addEventListener('click', showPosts); @@ -1240,6 +1242,7 @@ const source = new EventSource(`${API}/events`); // すでにある行
`posts` に追加してから `showPosts` を呼ぶと、その1件が一覧に表示されます。 +ファイルの一番下にある `source.onmessage` の行を置き換えます。 ```javascript {data-file=public/script.js} const source = new EventSource(`${API}/events`); // すでにある行 @@ -1364,7 +1367,7 @@ async function addPost() {
-共有サーバーの URL が入っている `API` を書き換えます。 +ファイルの先頭にある、共有サーバーの URL が入っている `API` を書き換えます。 ```javascript {data-file=public/script.js} %%const API = 'https://example.deno.net';%% // 共有サーバーの URL が入っている @@ -1482,7 +1485,7 @@ res.setHeader('Cache-Control', 'no-cache'); ```javascript {data-file=server.js} // 投稿の置き場。再起動すると空に戻る。 -const posts = []; // すでにある行 +const posts = []; // すでにある行。ファイルの上のほう @@let connection = null;@@ // 現在の接続。新しい接続が来ると上書きされる ``` @@ -1650,7 +1653,7 @@ data: {"name":"たろう","text":"やっほー"}
-1本に書いていたところを、配列ぶん繰り返します。 +`POST /posts` の中で1本に書いていたところを、配列ぶん繰り返します。 ```javascript {data-file=server.js} const data = JSON.stringify(post); // 変わらない行 @@ -1779,7 +1782,7 @@ data: {"name":"たろう","text":"やっほー"} @@const API = 'https://example.deno.net';@@ ``` -共有サーバーの URL を入れ直します。 +ファイルの先頭にある `API` に、共有サーバーの URL を入れ直します。 **成功**: ページを再読み込みすると他の人の投稿が表示される From 884ec0acef0ac10e7578bb9b62665724b5305886 Mon Sep 17 00:00:00 2001 From: m-sato Date: Wed, 30 Sep 2026 15:09:21 +0900 Subject: [PATCH 6/8] =?UTF-8?q?=E6=BC=94=E7=BF=92=E3=81=AE=E9=80=B2?= =?UTF-8?q?=E3=82=81=E6=96=B9=E3=81=AB=E3=80=81=E3=83=97=E3=83=AC=E3=83=93?= =?UTF-8?q?=E3=83=A5=E3=83=BC=E3=81=8C=E3=83=9D=E3=83=BC=E3=83=88=203000?= =?UTF-8?q?=20=E3=81=AB=E3=81=A4=E3=81=AA=E3=81=8C=E3=82=89=E3=81=AA?= =?UTF-8?q?=E3=81=84=E7=94=BB=E9=9D=A2=E3=81=AB=E3=81=AA=E3=81=A3=E3=81=9F?= =?UTF-8?q?=E3=82=89=20Reload=20=E3=82=92=E6=8A=BC=E3=81=99=E3=81=93?= =?UTF-8?q?=E3=81=A8=E3=82=92=E6=9B=B8=E3=81=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 2026/board-advanced/slide.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/2026/board-advanced/slide.md b/2026/board-advanced/slide.md index 4b558632..77f4bf02 100644 --- a/2026/board-advanced/slide.md +++ b/2026/board-advanced/slide.md @@ -481,6 +481,9 @@ showPosts(); // 色なし: 目印が書く行の下に来る 早く終わった人は、章の最後のスライドの左上にあるリンクから応用課題に進んでください。 書きかけのコードがエラーで止まっても本編はそのまま動くので、次の章が始まったら途中のまま戻ってきて大丈夫です。 +プレビューが「Unable to connect to port 3000」の画面になったら、コードを書き終えてから、その画面の Reload を押してください。 +それでも戻らないときは、ブラウザで StackBlitz のページごと再読み込みしてください。書いたコードは消えません。 + --- From 47f32989194dc31bfdd229ff9646ea3aa362b396 Mon Sep 17 00:00:00 2001 From: m-sato Date: Wed, 30 Sep 2026 15:35:13 +0900 Subject: [PATCH 7/8] =?UTF-8?q?StackBlitz=20=E3=81=A7=E3=81=AF=E5=88=87?= =?UTF-8?q?=E6=96=AD=E3=81=8C=E3=82=B5=E3=83=BC=E3=83=90=E3=83=BC=E3=81=AB?= =?UTF-8?q?=E5=B1=8A=E3=81=8B=E3=81=AA=E3=81=84=E3=81=AE=E3=81=A7=E3=80=81?= =?UTF-8?q?5-3=20=E3=81=AE=E7=A9=B4=E5=9F=8B=E3=82=81=E3=81=A8=E5=BF=9C?= =?UTF-8?q?=E7=94=A8=E8=AA=B2=E9=A1=8C3=E3=82=92=E3=82=84=E3=82=81?= =?UTF-8?q?=E3=80=81=E7=A2=BA=E3=81=8B=E3=82=81=E3=82=89=E3=82=8C=E3=81=AA?= =?UTF-8?q?=E3=81=84=E6=88=90=E5=8A=9F=E6=9D=A1=E4=BB=B6=E3=82=92=E5=A4=96?= =?UTF-8?q?=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 2026/board-advanced/slide.md | 75 ++---------------------------------- 1 file changed, 4 insertions(+), 71 deletions(-) diff --git a/2026/board-advanced/slide.md b/2026/board-advanced/slide.md index 77f4bf02..061f695a 100644 --- a/2026/board-advanced/slide.md +++ b/2026/board-advanced/slide.md @@ -1710,7 +1710,7 @@ data: {"name":"たろう","text":"やっほー"} ## 5-3. 切れた接続を消す -
+
```javascript {data-file=server.js} if (req.method === 'GET' && url.pathname === '/events') { @@ -1721,7 +1721,7 @@ data: {"name":"たろう","text":"やっほー"} @@ connections.splice(connections.indexOf(res), 1);@@ @@ console.log(`接続数: ${connections.length}`);@@ @@ }@@ -@@ req.on(______);@@ // 接続が切れたら removeConnection を呼ぶ +@@ req.on('close', removeConnection);@@ // 接続が切れたら removeConnection を呼ぶ ``` | 書き方 | 意味 | @@ -1729,24 +1729,9 @@ data: {"name":"たろう","text":"やっほー"} | `<配列>.indexOf(<値>)` | <値>が<配列>の何番目にあるかを返す。先頭は `0` | | `<配列>.splice(<位置>, <個数>)` | <配列>の<位置>から、<個数>ぶんの要素を抜く | -**成功**: タブを閉じると、ターミナルの接続数が1減る - ---- - -## 5-3. 答え - -```javascript {data-file=server.js} - connections.push(res); - console.log(`接続数: ${connections.length}`); - function removeConnection() { - connections.splice(connections.indexOf(res), 1); - console.log(`接続数: ${connections.length}`); - } - req.on(@@'close', removeConnection@@); -``` +**成功**: `server.js` を保存したあと、ターミナルにエラーが出ない -- 1つ目はイベントの名前 `'close'`、2つ目は切れたときに呼ぶ関数 `removeConnection` -- 関数は `()` を付けずに渡す +StackBlitz の中では、タブを閉じても切断がサーバーに伝わらないので、接続数は減りません。ふつうの Node で動かすと、閉じたときに1減ります。 --- @@ -1756,7 +1741,6 @@ data: {"name":"たろう","text":"やっほー"} - 接続を配列 `connections` に持ち、投稿を全員に書き込むようにした - 接続が切れたら、`close` イベントで `connections` から抜くようにした -- ターミナルの接続数が、開いているページの数と同じになる (StackBlitz のプレビューも1つと数える) 早く終わった人は → [5章の応用課題](#adv-ch5) {.jump #ch5-end} @@ -2184,7 +2168,6 @@ data: {"name":"たろう","text":"やっほー"} 1. ハートビートを送る: 15秒ごとに、接続している全員へコメント行 `: ping\n\n` を書く関数 `sendPing` を作る。データが流れない時間が続くと、プロキシなどが接続を切ることがある ([付録: SSE のコメント行](#comment-line)) 2. 空の投稿を受け付けない: `text` が空なら保存せず、ステータス `400` と `{"message": "..."}` を返す。Network タブで、空の投稿の `posts` の行が 400 になれば成功 -3. 接続数を全員に送る: 接続したときと切れたときに、`event: count\ndata: 3\n\n` を全員に書く関数 `sendCount` を呼ぶ。ブラウザ側は、受け取って画面に出す関数 `showCount` を `source.addEventListener('count', showCount)` で登録する --- @@ -2227,56 +2210,6 @@ const connections = []; // すでにある行 - `readPost` は `text` がないときも `''` にするので、`''` と比べれば足りる - `sendJson` はステータスが `200` で決まっているので、`res.writeHead` で直接書く ---- - - - -## 5章の応用課題の答え3: 接続数を全員に送る - -[← 5章のふりかえりに戻る](#ch5-end) {.jump} - -```javascript {data-file=server.js} -const connections = []; // すでにある行 -@@function sendCount() {@@ -@@ for (let i = 0; i < connections.length; i++) {@@ -@@ connections[i].write(`event: count\ndata: ${connections.length}\n\n`);@@ -@@ }@@ -@@}@@ -``` - -```javascript {data-file=server.js} - console.log(`接続数: ${connections.length}`); // すでにある行 -@@ sendCount();@@ - - function removeConnection() { - connections.splice(connections.indexOf(res), 1); - console.log(`接続数: ${connections.length}`); -@@ sendCount();@@ - } -``` - ---- - - - -## 5章の応用課題の答え3: 接続数を画面に出す - -[← 5章のふりかえりに戻る](#ch5-end) {.jump} - -```html {data-file=public/index.html} -

みんなの投稿

-@@ @@ -``` - -```javascript {data-file=public/extra.js} -@@function showCount(e) {@@ -@@ document.getElementById('count').textContent = `${e.data}人が接続中`;@@ -@@}@@ -@@source.addEventListener('count', showCount);@@ -``` - -- `event:` の行を付けたデータは `onmessage` には届かず、同じ名前で登録した関数だけが呼ばれる -