hono-decks 1.2.4 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ja.md CHANGED
@@ -294,7 +294,7 @@ export default defineConfig({
294
294
  });
295
295
  ```
296
296
 
297
- pluginはVite起動前にcompileし、deck rootと`hono-decks.config.ts`をVite watcherへ登録します。再compileに成功するとfull reloadを通知するため、手動更新は不要です。どちらの構成でも通常の`bun run dev`だけでauthoringできます。
297
+ pluginはVite起動前にcompileし、deck rootと`hono-decks.config.ts`をVite watcherへ登録します。再compileに成功すると、安全にislandを維持できる場合はスライドの内容だけを更新し、現在のスライド・ステップとislandの状態を保ちます。islandのコード、props、Markdownのコンテキスト、構造などが変わり、安全に引き継げない場合は表示ページを再読み込みし、スライドとステップを復元します。どちらの構成でも通常の`bun run dev`だけでauthoringできます。
298
298
 
299
299
  ## Runtime configuration
300
300
 
package/README.md CHANGED
@@ -295,7 +295,7 @@ export default defineConfig({
295
295
  });
296
296
  ~~~
297
297
 
298
- The plugin compiles before Vite starts and adds the deck root and <code>hono-decks.config.ts</code> to the Vite watcher. After a successful recompile, it requests a full browser reload. In both setups, the application's normal <code>dev</code> command is enough for authoring.
298
+ The plugin compiles before Vite starts and adds the deck root and <code>hono-decks.config.ts</code> to the Vite watcher. After a successful recompile, open presentation pages update their deck content without reloading when it is safe to retain the existing islands. The current slide, step, and island state are preserved. If the update cannot safely retain islands (for example, their code, props, Markdown context, or structure changed), the presentation page reloads instead and restores the slide and step. In both setups, the application's normal <code>dev</code> command is enough for authoring.
299
299
 
300
300
  ## Runtime configuration
301
301
 
package/dist/advanced.js CHANGED
@@ -804,19 +804,94 @@ function escapeHtml$7(value) {
804
804
  function renderLiveReloadScript(eventsPath, nonce) {
805
805
  return `<script${documentNonceAttribute(nonce)}>
806
806
  (() => {
807
- const eventsUrl = ${JSON.stringify(eventsPath)};
808
- const events = new EventSource(eventsUrl);
809
- events.addEventListener("deck:updated", () => location.reload());
807
+ const events = new EventSource(${JSON.stringify(eventsPath)});
808
+ let running = false;
809
+ let pending = false;
810
+ const selector = "[data-hono-decks-island],[data-hono-md-island]";
811
+ const clientSelector = "script[data-hono-decks-client-entry]";
812
+ const clientText = async (url) => {
813
+ if (!url) return "";
814
+ const response = await fetch(url, { cache: "no-store" });
815
+ if (!response.ok) throw new Error("Client entry fetch failed");
816
+ return response.text();
817
+ };
818
+ const domReady = document.readyState === "loading"
819
+ ? new Promise((resolve) => document.addEventListener("DOMContentLoaded", resolve, { once: true }))
820
+ : Promise.resolve();
821
+ let clientSnapshot = domReady.then(() => clientText(document.querySelector(clientSelector)?.src)).catch(() => null);
822
+
823
+ function signature(element) {
824
+ const attributes = element.hasAttribute("data-hono-md-island")
825
+ ? ["data-hono-md-island", "data-hono-md-hydrate", "data-hono-md-props"]
826
+ : ["data-hono-decks-island", "data-hono-decks-props"];
827
+ return JSON.stringify([attributes.map((name) => element.getAttribute(name)),
828
+ element.closest("[data-hono-md-context]")?.getAttribute("data-hono-md-context") ?? null]);
829
+ }
830
+
831
+ async function update() {
832
+ const response = await fetch(location.href, { cache: "no-store" });
833
+ if (!response.ok || !response.headers.get("content-type")?.includes("text/html")) throw new Error("Deck fetch failed");
834
+ const next = new DOMParser().parseFromString(await response.text(), "text/html");
835
+ const currentDeck = document.querySelector("[data-hono-decks-deck]");
836
+ const nextDeck = next.querySelector("[data-hono-decks-deck]");
837
+ const style = document.querySelector("[data-hono-decks-presentation-style]");
838
+ const nextStyle = next.querySelector("[data-hono-decks-presentation-style]");
839
+ const runtime = window.__honoDecksPresentationRuntime;
840
+ if (!currentDeck || !nextDeck || !style || !nextStyle || !runtime?.refresh) throw new Error("Invalid deck document");
841
+ const oldSlides = Array.from(currentDeck.querySelectorAll(".slide"));
842
+ const newSlides = Array.from(nextDeck.querySelectorAll(".slide"));
843
+ if (!newSlides.length) throw new Error("Empty deck");
844
+ // Scripts parsed from fetched HTML are inert; reload so their behavior is not lost.
845
+ if (currentDeck.querySelector("script") || nextDeck.querySelector("script")) throw new Error("Deck contains scripts");
846
+ const oldIslands = oldSlides.flatMap((slide) => Array.from(slide.querySelectorAll(selector)));
847
+ const nextScript = next.querySelector(clientSelector);
848
+ const nextClient = await clientText(nextScript?.getAttribute("src"));
849
+ const previousClient = await clientSnapshot;
850
+ if (previousClient === null) throw new Error("Initial client entry fetch failed");
851
+ // Changed client code cannot safely replace a running component while retaining its state.
852
+ if (oldIslands.length && previousClient !== nextClient) throw new Error("Client code changed");
853
+ const pairs = [];
854
+ if (oldIslands.length && oldSlides.length !== newSlides.length) throw new Error("Slide structure changed");
855
+ for (let i = 0; i < oldSlides.length && i < newSlides.length; i++) {
856
+ const before = Array.from(oldSlides[i].querySelectorAll(selector));
857
+ const after = Array.from(newSlides[i].querySelectorAll(selector));
858
+ if (before.length !== after.length) throw new Error("Island structure changed");
859
+ for (let j = 0; j < before.length; j++) {
860
+ if (signature(before[j]) !== signature(after[j])) throw new Error("Island properties changed");
861
+ // Deferred Markdown islands observe their original slide. Reload rather than strand the observer.
862
+ if (before[j].hasAttribute("data-hono-md-island") && !before[j].hasAttribute("data-hono-md-mounted"))
863
+ throw new Error("Island is pending hydration");
864
+ pairs.push([before[j], after[j]]);
865
+ }
866
+ }
867
+ if (!oldIslands.length && nextDeck.querySelector(selector)) throw new Error("New islands require initialization");
868
+ for (const [oldIsland, newIsland] of pairs) newIsland.replaceWith(oldIsland);
869
+ currentDeck.replaceChildren(...nextDeck.childNodes);
870
+ style.textContent = nextStyle.textContent;
871
+ document.title = next.title;
872
+ clientSnapshot = Promise.resolve(nextClient);
873
+ runtime.refresh();
874
+ }
875
+
876
+ async function onUpdate() {
877
+ if (running) { pending = true; return; }
878
+ running = true;
879
+ try {
880
+ do { pending = false; await update(); } while (pending);
881
+ } catch (error) { console.warn("[hono-decks] Live update failed; reloading", error); location.reload(); }
882
+ finally { running = false; }
883
+ }
884
+ events.addEventListener("deck:updated", onUpdate);
810
885
  })();
811
886
  <\/script>`;
812
887
  }
813
888
  function renderClientEntryScript(clientEntry, nonce) {
814
- return `<script type="module" src="${escapeHtml$6(clientEntry)}"${documentNonceAttribute(nonce)}><\/script>`;
889
+ return `<script data-hono-decks-client-entry type="module" src="${escapeHtml$6(clientEntry)}"${documentNonceAttribute(nonce)}><\/script>`;
815
890
  }
816
891
  function renderPresentationScript(nonce) {
817
892
  return `<script${documentNonceAttribute(nonce)}>
818
893
  (() => {
819
- const slides = Array.from(document.querySelectorAll(".slide"));
894
+ let slides = Array.from(document.querySelectorAll(".slide"));
820
895
  const stage = document.querySelector("[data-hono-decks-stage]");
821
896
  const deck = document.querySelector("[data-hono-decks-deck]");
822
897
  const DESIGN_WIDTH = ${PRESENTATION_DESIGN_WIDTH};
@@ -1090,6 +1165,7 @@ function renderPresentationScript(nonce) {
1090
1165
  updateFires(nextStepIndex);
1091
1166
  publishState();
1092
1167
  requestAnimationFrame(() => {
1168
+ if (token !== transitionToken) return;
1093
1169
  setSlideState(outgoing, "leaving", direction, transition, timing);
1094
1170
  setSlideState(incoming, "active", direction, transition, timing);
1095
1171
  waitForSlideTransition(outgoing, incoming, direction, token);
@@ -1196,13 +1272,38 @@ function renderPresentationScript(nonce) {
1196
1272
  await document.documentElement.requestFullscreen?.();
1197
1273
  }
1198
1274
 
1199
- slides.forEach((slide, slideIndex) => {
1200
- slide.addEventListener("click", () => {
1201
- if (!document.body.hasAttribute("data-overview-mode")) return;
1202
- document.body.removeAttribute("data-overview-mode");
1203
- show(slideIndex);
1275
+ function bindSlideClicks() {
1276
+ slides.forEach((slide, slideIndex) => {
1277
+ slide.addEventListener("click", () => {
1278
+ if (!document.body.hasAttribute("data-overview-mode")) return;
1279
+ document.body.removeAttribute("data-overview-mode");
1280
+ show(slideIndex);
1281
+ });
1204
1282
  });
1205
- });
1283
+ }
1284
+
1285
+ function refresh() {
1286
+ transitionToken += 1;
1287
+ isTransitioning = false;
1288
+ pendingNavigation = null;
1289
+ slides = Array.from(deck?.querySelectorAll(".slide") ?? []);
1290
+ bindSlideClicks();
1291
+ if (document.body.hasAttribute("data-overview-mode")) {
1292
+ slides.forEach((slide) => { slide.hidden = false; slide.setAttribute("data-slide-state", "active"); });
1293
+ setFiresVisible(Array.from(deck?.querySelectorAll("[data-hono-decks-fire]") ?? []), true);
1294
+ index = Math.max(0, Math.min(slides.length - 1, index));
1295
+ stepCount = fireCountForSlide(index);
1296
+ stepIndex = Math.max(0, Math.min(stepCount, stepIndex));
1297
+ publishState();
1298
+ } else {
1299
+ applyInstantSlideChange(index, stepIndex);
1300
+ }
1301
+ document.querySelectorAll(".speaker-notes").forEach((note) => {
1302
+ note.hidden = !document.body.hasAttribute("data-presenter-mode");
1303
+ });
1304
+ fitDeck();
1305
+ }
1306
+ bindSlideClicks();
1206
1307
 
1207
1308
  document.addEventListener("keydown", (event) => {
1208
1309
  if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.altKey) return;
@@ -1225,7 +1326,7 @@ function renderPresentationScript(nonce) {
1225
1326
  handleCommand(message.action, message.index, message.stepIndex);
1226
1327
  });
1227
1328
 
1228
- window.__honoDecksPresentationRuntime = { command: handleCommand };
1329
+ window.__honoDecksPresentationRuntime = { command: handleCommand, refresh };
1229
1330
 
1230
1331
  window.addEventListener("resize", fitDeck);
1231
1332
  if (typeof ResizeObserver !== "undefined" && stage instanceof HTMLElement) {
@@ -1261,7 +1362,7 @@ function renderCompiledDeckPage(input) {
1261
1362
  <meta charset="utf-8" />
1262
1363
  <meta name="viewport" content="width=device-width, initial-scale=1" />
1263
1364
  <title>${escapeHtml$5(presentationPageTitle(deck))}</title>
1264
- <style${nonceAttribute}>${basePresentationStyle()}${deck.themeStyle ?? ""}${input.style ?? ""}</style>
1365
+ <style data-hono-decks-presentation-style${nonceAttribute}>${basePresentationStyle()}${deck.themeStyle ?? ""}${input.style ?? ""}</style>
1265
1366
  ${document.head}
1266
1367
  </head>
1267
1368
  <body${bodyAttrs.length ? ` ${bodyAttrs.join(" ")}` : ""}>
@@ -1292,7 +1393,7 @@ async function renderCompiledDeckPageAsync(input) {
1292
1393
  <meta charset="utf-8" />
1293
1394
  <meta name="viewport" content="width=device-width, initial-scale=1" />
1294
1395
  <title>${escapeHtml$5(presentationPageTitle(deck))}</title>
1295
- <style${nonceAttribute}>${basePresentationStyle()}${deck.themeStyle ?? ""}${input.style ?? ""}</style>
1396
+ <style data-hono-decks-presentation-style${nonceAttribute}>${basePresentationStyle()}${deck.themeStyle ?? ""}${input.style ?? ""}</style>
1296
1397
  ${document.head}
1297
1398
  </head>
1298
1399
  <body${bodyAttrs.length ? ` ${bodyAttrs.join(" ")}` : ""}>
package/dist/node.js CHANGED
@@ -3523,19 +3523,94 @@ function escapeHtml$8(value) {
3523
3523
  function renderLiveReloadScript(eventsPath, nonce) {
3524
3524
  return `<script${documentNonceAttribute(nonce)}>
3525
3525
  (() => {
3526
- const eventsUrl = ${JSON.stringify(eventsPath)};
3527
- const events = new EventSource(eventsUrl);
3528
- events.addEventListener("deck:updated", () => location.reload());
3526
+ const events = new EventSource(${JSON.stringify(eventsPath)});
3527
+ let running = false;
3528
+ let pending = false;
3529
+ const selector = "[data-hono-decks-island],[data-hono-md-island]";
3530
+ const clientSelector = "script[data-hono-decks-client-entry]";
3531
+ const clientText = async (url) => {
3532
+ if (!url) return "";
3533
+ const response = await fetch(url, { cache: "no-store" });
3534
+ if (!response.ok) throw new Error("Client entry fetch failed");
3535
+ return response.text();
3536
+ };
3537
+ const domReady = document.readyState === "loading"
3538
+ ? new Promise((resolve) => document.addEventListener("DOMContentLoaded", resolve, { once: true }))
3539
+ : Promise.resolve();
3540
+ let clientSnapshot = domReady.then(() => clientText(document.querySelector(clientSelector)?.src)).catch(() => null);
3541
+
3542
+ function signature(element) {
3543
+ const attributes = element.hasAttribute("data-hono-md-island")
3544
+ ? ["data-hono-md-island", "data-hono-md-hydrate", "data-hono-md-props"]
3545
+ : ["data-hono-decks-island", "data-hono-decks-props"];
3546
+ return JSON.stringify([attributes.map((name) => element.getAttribute(name)),
3547
+ element.closest("[data-hono-md-context]")?.getAttribute("data-hono-md-context") ?? null]);
3548
+ }
3549
+
3550
+ async function update() {
3551
+ const response = await fetch(location.href, { cache: "no-store" });
3552
+ if (!response.ok || !response.headers.get("content-type")?.includes("text/html")) throw new Error("Deck fetch failed");
3553
+ const next = new DOMParser().parseFromString(await response.text(), "text/html");
3554
+ const currentDeck = document.querySelector("[data-hono-decks-deck]");
3555
+ const nextDeck = next.querySelector("[data-hono-decks-deck]");
3556
+ const style = document.querySelector("[data-hono-decks-presentation-style]");
3557
+ const nextStyle = next.querySelector("[data-hono-decks-presentation-style]");
3558
+ const runtime = window.__honoDecksPresentationRuntime;
3559
+ if (!currentDeck || !nextDeck || !style || !nextStyle || !runtime?.refresh) throw new Error("Invalid deck document");
3560
+ const oldSlides = Array.from(currentDeck.querySelectorAll(".slide"));
3561
+ const newSlides = Array.from(nextDeck.querySelectorAll(".slide"));
3562
+ if (!newSlides.length) throw new Error("Empty deck");
3563
+ // Scripts parsed from fetched HTML are inert; reload so their behavior is not lost.
3564
+ if (currentDeck.querySelector("script") || nextDeck.querySelector("script")) throw new Error("Deck contains scripts");
3565
+ const oldIslands = oldSlides.flatMap((slide) => Array.from(slide.querySelectorAll(selector)));
3566
+ const nextScript = next.querySelector(clientSelector);
3567
+ const nextClient = await clientText(nextScript?.getAttribute("src"));
3568
+ const previousClient = await clientSnapshot;
3569
+ if (previousClient === null) throw new Error("Initial client entry fetch failed");
3570
+ // Changed client code cannot safely replace a running component while retaining its state.
3571
+ if (oldIslands.length && previousClient !== nextClient) throw new Error("Client code changed");
3572
+ const pairs = [];
3573
+ if (oldIslands.length && oldSlides.length !== newSlides.length) throw new Error("Slide structure changed");
3574
+ for (let i = 0; i < oldSlides.length && i < newSlides.length; i++) {
3575
+ const before = Array.from(oldSlides[i].querySelectorAll(selector));
3576
+ const after = Array.from(newSlides[i].querySelectorAll(selector));
3577
+ if (before.length !== after.length) throw new Error("Island structure changed");
3578
+ for (let j = 0; j < before.length; j++) {
3579
+ if (signature(before[j]) !== signature(after[j])) throw new Error("Island properties changed");
3580
+ // Deferred Markdown islands observe their original slide. Reload rather than strand the observer.
3581
+ if (before[j].hasAttribute("data-hono-md-island") && !before[j].hasAttribute("data-hono-md-mounted"))
3582
+ throw new Error("Island is pending hydration");
3583
+ pairs.push([before[j], after[j]]);
3584
+ }
3585
+ }
3586
+ if (!oldIslands.length && nextDeck.querySelector(selector)) throw new Error("New islands require initialization");
3587
+ for (const [oldIsland, newIsland] of pairs) newIsland.replaceWith(oldIsland);
3588
+ currentDeck.replaceChildren(...nextDeck.childNodes);
3589
+ style.textContent = nextStyle.textContent;
3590
+ document.title = next.title;
3591
+ clientSnapshot = Promise.resolve(nextClient);
3592
+ runtime.refresh();
3593
+ }
3594
+
3595
+ async function onUpdate() {
3596
+ if (running) { pending = true; return; }
3597
+ running = true;
3598
+ try {
3599
+ do { pending = false; await update(); } while (pending);
3600
+ } catch (error) { console.warn("[hono-decks] Live update failed; reloading", error); location.reload(); }
3601
+ finally { running = false; }
3602
+ }
3603
+ events.addEventListener("deck:updated", onUpdate);
3529
3604
  })();
3530
3605
  <\/script>`;
3531
3606
  }
3532
3607
  function renderClientEntryScript(clientEntry, nonce) {
3533
- return `<script type="module" src="${escapeHtml$7(clientEntry)}"${documentNonceAttribute(nonce)}><\/script>`;
3608
+ return `<script data-hono-decks-client-entry type="module" src="${escapeHtml$7(clientEntry)}"${documentNonceAttribute(nonce)}><\/script>`;
3534
3609
  }
3535
3610
  function renderPresentationScript(nonce) {
3536
3611
  return `<script${documentNonceAttribute(nonce)}>
3537
3612
  (() => {
3538
- const slides = Array.from(document.querySelectorAll(".slide"));
3613
+ let slides = Array.from(document.querySelectorAll(".slide"));
3539
3614
  const stage = document.querySelector("[data-hono-decks-stage]");
3540
3615
  const deck = document.querySelector("[data-hono-decks-deck]");
3541
3616
  const DESIGN_WIDTH = ${PRESENTATION_DESIGN_WIDTH};
@@ -3809,6 +3884,7 @@ function renderPresentationScript(nonce) {
3809
3884
  updateFires(nextStepIndex);
3810
3885
  publishState();
3811
3886
  requestAnimationFrame(() => {
3887
+ if (token !== transitionToken) return;
3812
3888
  setSlideState(outgoing, "leaving", direction, transition, timing);
3813
3889
  setSlideState(incoming, "active", direction, transition, timing);
3814
3890
  waitForSlideTransition(outgoing, incoming, direction, token);
@@ -3915,13 +3991,38 @@ function renderPresentationScript(nonce) {
3915
3991
  await document.documentElement.requestFullscreen?.();
3916
3992
  }
3917
3993
 
3918
- slides.forEach((slide, slideIndex) => {
3919
- slide.addEventListener("click", () => {
3920
- if (!document.body.hasAttribute("data-overview-mode")) return;
3921
- document.body.removeAttribute("data-overview-mode");
3922
- show(slideIndex);
3994
+ function bindSlideClicks() {
3995
+ slides.forEach((slide, slideIndex) => {
3996
+ slide.addEventListener("click", () => {
3997
+ if (!document.body.hasAttribute("data-overview-mode")) return;
3998
+ document.body.removeAttribute("data-overview-mode");
3999
+ show(slideIndex);
4000
+ });
3923
4001
  });
3924
- });
4002
+ }
4003
+
4004
+ function refresh() {
4005
+ transitionToken += 1;
4006
+ isTransitioning = false;
4007
+ pendingNavigation = null;
4008
+ slides = Array.from(deck?.querySelectorAll(".slide") ?? []);
4009
+ bindSlideClicks();
4010
+ if (document.body.hasAttribute("data-overview-mode")) {
4011
+ slides.forEach((slide) => { slide.hidden = false; slide.setAttribute("data-slide-state", "active"); });
4012
+ setFiresVisible(Array.from(deck?.querySelectorAll("[data-hono-decks-fire]") ?? []), true);
4013
+ index = Math.max(0, Math.min(slides.length - 1, index));
4014
+ stepCount = fireCountForSlide(index);
4015
+ stepIndex = Math.max(0, Math.min(stepCount, stepIndex));
4016
+ publishState();
4017
+ } else {
4018
+ applyInstantSlideChange(index, stepIndex);
4019
+ }
4020
+ document.querySelectorAll(".speaker-notes").forEach((note) => {
4021
+ note.hidden = !document.body.hasAttribute("data-presenter-mode");
4022
+ });
4023
+ fitDeck();
4024
+ }
4025
+ bindSlideClicks();
3925
4026
 
3926
4027
  document.addEventListener("keydown", (event) => {
3927
4028
  if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.altKey) return;
@@ -3944,7 +4045,7 @@ function renderPresentationScript(nonce) {
3944
4045
  handleCommand(message.action, message.index, message.stepIndex);
3945
4046
  });
3946
4047
 
3947
- window.__honoDecksPresentationRuntime = { command: handleCommand };
4048
+ window.__honoDecksPresentationRuntime = { command: handleCommand, refresh };
3948
4049
 
3949
4050
  window.addEventListener("resize", fitDeck);
3950
4051
  if (typeof ResizeObserver !== "undefined" && stage instanceof HTMLElement) {
@@ -3980,7 +4081,7 @@ async function renderCompiledDeckPageAsync(input) {
3980
4081
  <meta charset="utf-8" />
3981
4082
  <meta name="viewport" content="width=device-width, initial-scale=1" />
3982
4083
  <title>${escapeHtml$6(presentationPageTitle(deck))}</title>
3983
- <style${nonceAttribute}>${basePresentationStyle()}${deck.themeStyle ?? ""}${input.style ?? ""}</style>
4084
+ <style data-hono-decks-presentation-style${nonceAttribute}>${basePresentationStyle()}${deck.themeStyle ?? ""}${input.style ?? ""}</style>
3984
4085
  ${document.head}
3985
4086
  </head>
3986
4087
  <body${bodyAttrs.length ? ` ${bodyAttrs.join(" ")}` : ""}>
package/dist/vite.js CHANGED
@@ -2307,6 +2307,8 @@ function honoDecks(options = {}) {
2307
2307
  let mountChanged = false;
2308
2308
  let modules = /* @__PURE__ */ new Map();
2309
2309
  let routerModulePath = "";
2310
+ const eventClients = /* @__PURE__ */ new Set();
2311
+ let eventsPath = "/@hono-decks/events";
2310
2312
  const syncWatchedPaths = async () => {
2311
2313
  if (!server) return;
2312
2314
  const next = /* @__PURE__ */ new Set([
@@ -2374,7 +2376,7 @@ function honoDecks(options = {}) {
2374
2376
  environment.moduleGraph.invalidateAll();
2375
2377
  if (environment.name !== "client") environment.hot.send({ type: "full-reload" });
2376
2378
  }
2377
- server.ws.send({ type: "full-reload" });
2379
+ for (const client of eventClients) client.write("event: deck:updated\ndata: {}\n\n");
2378
2380
  }
2379
2381
  } finally {
2380
2382
  compiling = false;
@@ -2422,8 +2424,9 @@ export { createDecks };
2422
2424
  export const decks = createDecks(config);`;
2423
2425
  if (!id.startsWith(MODULE_PREFIX)) return null;
2424
2426
  const path = id.slice(MODULE_PREFIX.length);
2425
- const code = modules.get(path);
2427
+ let code = modules.get(path);
2426
2428
  if (code === void 0) return null;
2429
+ if (path === routerModulePath && resolvedConfig?.command === "serve") code = code.replace("const generatedDecks = defineDecks({", `const generatedDecks = defineDecks({\n liveReloadPath: () => ${JSON.stringify(eventsPath)},`);
2427
2430
  return (await transform(code, {
2428
2431
  loader: "ts",
2429
2432
  format: "esm",
@@ -2436,16 +2439,30 @@ export const decks = createDecks(config);`;
2436
2439
  },
2437
2440
  async configResolved(config) {
2438
2441
  resolvedConfig = config;
2442
+ eventsPath = `${config.base?.startsWith("/") ? config.base.replace(/\/$/, "") : ""}/@hono-decks/events`;
2439
2443
  projectRoot = config.resolve?.preserveSymlinks ? config.root : await realpath(config.root);
2440
2444
  configPath = resolve(projectRoot, options.configFile ?? "hono-decks.config.ts");
2441
2445
  await compile(true);
2442
2446
  },
2443
2447
  configureServer(devServer) {
2444
2448
  server = devServer;
2449
+ devServer.middlewares.use((req, res, next) => {
2450
+ if (req.url?.split("?")[0] !== eventsPath || req.method !== "GET") return next();
2451
+ res.writeHead(200, {
2452
+ "Content-Type": "text/event-stream",
2453
+ "Cache-Control": "no-cache, no-transform",
2454
+ Connection: "keep-alive"
2455
+ });
2456
+ res.write(": connected\n\n");
2457
+ eventClients.add(res);
2458
+ res.on("close", () => eventClients.delete(res));
2459
+ });
2445
2460
  syncWatchedPaths();
2446
2461
  devServer.watcher.on("all", onWatcherEvent);
2447
2462
  const cleanup = () => {
2448
2463
  if (timer) clearTimeout(timer);
2464
+ for (const client of eventClients) client.end();
2465
+ eventClients.clear();
2449
2466
  devServer.watcher.off("all", onWatcherEvent);
2450
2467
  server = void 0;
2451
2468
  watchedPaths.clear();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono-decks",
3
- "version": "1.2.4",
3
+ "version": "1.3.0",
4
4
  "description": "Slide decks for Hono and Cloudflare Workers",
5
5
  "keywords": [
6
6
  "cloudflare-workers",