@rsc-kit/core 0.20.5 → 0.20.7

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.
@@ -8,7 +8,16 @@ import type { Route, RevalidateTarget } from "../routes.js";
8
8
  export declare function visit(url: Route, opts?: {
9
9
  replace?: boolean;
10
10
  }): Promise<void>;
11
- export declare function prefetch(url: Route, cacheForMs?: number): void;
11
+ /**
12
+ * Fetch a page's payload before a navigation asks for it.
13
+ *
14
+ * `intent: true` also decodes it as it lands, which loads the client chunks
15
+ * the page names - for a link the visitor is about to follow, not for one
16
+ * that may never be.
17
+ */
18
+ export declare function prefetch(url: Route, cacheForMs?: number, opts?: {
19
+ intent?: boolean;
20
+ }): void;
12
21
  /**
13
22
  * Ask the server for part of the current page again.
14
23
  *
package/dist/js/router.js CHANGED
@@ -11,12 +11,19 @@ export function visit(url, opts) {
11
11
  }
12
12
  return nav(url, opts);
13
13
  }
14
- export function prefetch(url, cacheForMs) {
14
+ /**
15
+ * Fetch a page's payload before a navigation asks for it.
16
+ *
17
+ * `intent: true` also decodes it as it lands, which loads the client chunks
18
+ * the page names - for a link the visitor is about to follow, not for one
19
+ * that may never be.
20
+ */
21
+ export function prefetch(url, cacheForMs, opts) {
15
22
  const fn = window.__rsc_prefetch;
16
23
  if (!fn) {
17
24
  throw new Error("RSC navigation not initialized. Ensure createViteRscApp has been called.");
18
25
  }
19
- fn(url, cacheForMs);
26
+ fn(url, cacheForMs, opts?.intent ?? false);
20
27
  }
21
28
  /**
22
29
  * Ask the server for part of the current page again.
@@ -1 +1 @@
1
- {"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/js/router.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,UAAU,KAAK,CACnB,GAAU,EACV,IAA4B;IAE5B,MAAM,GAAG,GAAI,MAAc,CAAC,cAAc,CAAC;IAE3C,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;IAC9F,CAAC;IAED,OAAO,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,GAAU,EAAE,UAAmB;IACtD,MAAM,EAAE,GAAI,MAAc,CAAC,cAAc,CAAC;IAE1C,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;IAC9F,CAAC;IAED,EAAE,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;AACtB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,OAAO,CAAC,MAAyB;IAC/C,MAAM,EAAE,GAAI,MAAc,CAAC,aAAa,CAAC;IAEzC,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;IAC9F,CAAC;IAED,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;AACpB,CAAC;AAED,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;AAC5C,eAAe,MAAM,CAAC","sourcesContent":["/**\n * Programmatic navigation API for use from client components.\n *\n * Reads from window globals set by createViteRscApp, since client components\n * are built in a separate build graph and cannot directly import navigate.ts.\n */\n\nimport type { Route, RevalidateTarget } from \"../routes.js\";\n\nexport function visit(\n url: Route,\n opts?: { replace?: boolean }\n): Promise<void> {\n const nav = (window as any).__rsc_navigate;\n\n if (!nav) {\n throw new Error(\"RSC navigation not initialized. Ensure createViteRscApp has been called.\");\n }\n\n return nav(url, opts);\n}\n\nexport function prefetch(url: Route, cacheForMs?: number): void {\n const fn = (window as any).__rsc_prefetch;\n\n if (!fn) {\n throw new Error(\"RSC navigation not initialized. Ensure createViteRscApp has been called.\");\n }\n\n fn(url, cacheForMs);\n}\n\n/**\n * Ask the server for part of the current page again.\n *\n * refresh() the page, leaving the layouts mounted\n * refresh('all') the whole document\n * refresh('orders') one parallel slot\n *\n * The page form leaves the layouts alone, so anything living in one — a count\n * in a sidebar, say — will not move until you ask for 'all'.\n */\nexport function refresh(target?: RevalidateTarget): Promise<void> {\n const fn = (window as any).__rsc_refresh;\n\n if (!fn) {\n throw new Error(\"RSC navigation not initialized. Ensure createViteRscApp has been called.\");\n }\n\n return fn(target);\n}\n\nconst router = { visit, prefetch, refresh };\nexport default router;\n"]}
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/js/router.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,UAAU,KAAK,CACnB,GAAU,EACV,IAA4B;IAE5B,MAAM,GAAG,GAAI,MAAc,CAAC,cAAc,CAAC;IAE3C,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;IAC9F,CAAC;IAED,OAAO,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AACxB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAU,EAAE,UAAmB,EAAE,IAA2B;IACnF,MAAM,EAAE,GAAI,MAAc,CAAC,cAAc,CAAC;IAE1C,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;IAC9F,CAAC;IAED,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,IAAI,KAAK,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,OAAO,CAAC,MAAyB;IAC/C,MAAM,EAAE,GAAI,MAAc,CAAC,aAAa,CAAC;IAEzC,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;IAC9F,CAAC;IAED,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;AACpB,CAAC;AAED,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;AAC5C,eAAe,MAAM,CAAC","sourcesContent":["/**\n * Programmatic navigation API for use from client components.\n *\n * Reads from window globals set by createViteRscApp, since client components\n * are built in a separate build graph and cannot directly import navigate.ts.\n */\n\nimport type { Route, RevalidateTarget } from \"../routes.js\";\n\nexport function visit(\n url: Route,\n opts?: { replace?: boolean }\n): Promise<void> {\n const nav = (window as any).__rsc_navigate;\n\n if (!nav) {\n throw new Error(\"RSC navigation not initialized. Ensure createViteRscApp has been called.\");\n }\n\n return nav(url, opts);\n}\n\n/**\n * Fetch a page's payload before a navigation asks for it.\n *\n * `intent: true` also decodes it as it lands, which loads the client chunks\n * the page names - for a link the visitor is about to follow, not for one\n * that may never be.\n */\nexport function prefetch(url: Route, cacheForMs?: number, opts?: { intent?: boolean }): void {\n const fn = (window as any).__rsc_prefetch;\n\n if (!fn) {\n throw new Error(\"RSC navigation not initialized. Ensure createViteRscApp has been called.\");\n }\n\n fn(url, cacheForMs, opts?.intent ?? false);\n}\n\n/**\n * Ask the server for part of the current page again.\n *\n * refresh() the page, leaving the layouts mounted\n * refresh('all') the whole document\n * refresh('orders') one parallel slot\n *\n * The page form leaves the layouts alone, so anything living in one — a count\n * in a sidebar, say — will not move until you ask for 'all'.\n */\nexport function refresh(target?: RevalidateTarget): Promise<void> {\n const fn = (window as any).__rsc_refresh;\n\n if (!fn) {\n throw new Error(\"RSC navigation not initialized. Ensure createViteRscApp has been called.\");\n }\n\n return fn(target);\n}\n\nconst router = { visit, prefetch, refresh };\nexport default router;\n"]}
@@ -1,13 +1,22 @@
1
1
  /**
2
- * Prefetching a link as it comes into view, where there is no pointer to hover it.
2
+ * Prefetching a link as it comes into view.
3
3
  *
4
- * On a phone the first signal a tap gives is touchstart, and the click lands
5
- * 100-300 ms after it - about a round trip - so a prefetch started there has
6
- * barely left when the navigation needs it. Next prefetches the links on
7
- * screen instead, and that is what makes its taps feel instant on a phone.
8
- * Only on a device with no hover: a pointer that can settle on a link is a
9
- * better signal than a link merely being visible, and cheaper on a page with
10
- * a hundred of them.
4
+ * A navigation is as fast as what is already in the browser when the click
5
+ * lands, and a hover is too late to fetch that: a pointer that has settled
6
+ * on a link is 200-300 ms from clicking it, and on a phone the first signal
7
+ * is touchstart, 100-300 ms before the click - about a round trip either
8
+ * way, so a fetch started there has barely left when the navigation needs
9
+ * it. Next prefetches the links on screen instead, and that is what makes
10
+ * its clicks feel like a single-page app's: the payload was there before
11
+ * the pointer moved. So, on every device, the links on screen are fetched -
12
+ * the bytes only; decoding a payload loads the chunks it names, and that
13
+ * waits for the hover or the touch that says which link is meant. It used
14
+ * to be phones only, with a hover-capable device left to the hover: a click
15
+ * that came quicker than the round trip waited for it, and desktop was
16
+ * fast but not instant.
17
+ *
18
+ * Not when the visitor asked for less data: Save-Data is the one signal a
19
+ * browser gives that speculative requests are unwelcome.
11
20
  *
12
21
  * One observer for every link, and the work is done when the browser is
13
22
  * idle - a list scrolled into view is many links at once, and the browser's
@@ -15,7 +24,7 @@
15
24
  * yet. Each link prefetches once; the router keeps the payload for its TTL.
16
25
  */
17
26
  /**
18
- * Prefetch when the link is on screen, on a device with no hover. Returns
19
- * the function that stops watching; a no-op where this does not apply.
27
+ * Prefetch when the link is on screen. Returns the function that stops
28
+ * watching; a no-op where this does not apply.
20
29
  */
21
30
  export declare function prefetchWhenVisible(element: Element | null, fire: () => void): () => void;
@@ -1,13 +1,22 @@
1
1
  /**
2
- * Prefetching a link as it comes into view, where there is no pointer to hover it.
2
+ * Prefetching a link as it comes into view.
3
3
  *
4
- * On a phone the first signal a tap gives is touchstart, and the click lands
5
- * 100-300 ms after it - about a round trip - so a prefetch started there has
6
- * barely left when the navigation needs it. Next prefetches the links on
7
- * screen instead, and that is what makes its taps feel instant on a phone.
8
- * Only on a device with no hover: a pointer that can settle on a link is a
9
- * better signal than a link merely being visible, and cheaper on a page with
10
- * a hundred of them.
4
+ * A navigation is as fast as what is already in the browser when the click
5
+ * lands, and a hover is too late to fetch that: a pointer that has settled
6
+ * on a link is 200-300 ms from clicking it, and on a phone the first signal
7
+ * is touchstart, 100-300 ms before the click - about a round trip either
8
+ * way, so a fetch started there has barely left when the navigation needs
9
+ * it. Next prefetches the links on screen instead, and that is what makes
10
+ * its clicks feel like a single-page app's: the payload was there before
11
+ * the pointer moved. So, on every device, the links on screen are fetched -
12
+ * the bytes only; decoding a payload loads the chunks it names, and that
13
+ * waits for the hover or the touch that says which link is meant. It used
14
+ * to be phones only, with a hover-capable device left to the hover: a click
15
+ * that came quicker than the round trip waited for it, and desktop was
16
+ * fast but not instant.
17
+ *
18
+ * Not when the visitor asked for less data: Save-Data is the one signal a
19
+ * browser gives that speculative requests are unwelcome.
11
20
  *
12
21
  * One observer for every link, and the work is done when the browser is
13
22
  * idle - a list scrolled into view is many links at once, and the browser's
@@ -16,8 +25,9 @@
16
25
  */
17
26
  let observer = null;
18
27
  const pending = new WeakMap();
19
- function noHover() {
20
- return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(hover: none)").matches;
28
+ function savingData() {
29
+ const connection = navigator.connection;
30
+ return connection?.saveData === true;
21
31
  }
22
32
  function whenIdle(fn) {
23
33
  const idle = window.requestIdleCallback;
@@ -48,11 +58,11 @@ function observerFor() {
48
58
  return observer;
49
59
  }
50
60
  /**
51
- * Prefetch when the link is on screen, on a device with no hover. Returns
52
- * the function that stops watching; a no-op where this does not apply.
61
+ * Prefetch when the link is on screen. Returns the function that stops
62
+ * watching; a no-op where this does not apply.
53
63
  */
54
64
  export function prefetchWhenVisible(element, fire) {
55
- if (!element || !noHover())
65
+ if (!element || savingData())
56
66
  return () => { };
57
67
  const io = observerFor();
58
68
  if (!io)
@@ -1 +1 @@
1
- {"version":3,"file":"viewportPrefetch.js","sourceRoot":"","sources":["../../src/js/viewportPrefetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,IAAI,QAAQ,GAAgC,IAAI,CAAA;AAChD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAuB,CAAA;AAElD,SAAS,OAAO;IACd,OAAO,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,UAAU,IAAI,MAAM,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC,OAAO,CAAA;AAC/H,CAAC;AAED,SAAS,QAAQ,CAAC,EAAc;IAC9B,MAAM,IAAI,GAAI,MAAyF,CAAC,mBAAmB,CAAA;IAE3H,IAAI,IAAI;QAAE,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;;QAChC,UAAU,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;AACzB,CAAC;AAED,SAAS,WAAW;IAClB,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAA;IAC7B,IAAI,OAAO,oBAAoB,KAAK,WAAW;QAAE,OAAO,IAAI,CAAA;IAE5D,QAAQ,GAAG,IAAI,oBAAoB,CACjC,CAAC,OAAO,EAAE,EAAE;QACV,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,CAAC,KAAK,CAAC,cAAc;gBAAE,SAAQ;YAEnC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;YAEtC,QAAS,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;YACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;YAE5B,IAAI,IAAI;gBAAE,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC1B,CAAC;IACH,CAAC;IACD,sEAAsE;IACtE,sBAAsB;IACtB,EAAE,UAAU,EAAE,OAAO,EAAE,CACxB,CAAA;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAuB,EAAE,IAAgB;IAC3E,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,EAAE;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;IAE3C,MAAM,EAAE,GAAG,WAAW,EAAE,CAAA;IAExB,IAAI,CAAC,EAAE;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;IAExB,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;IAC1B,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAEnB,OAAO,GAAG,EAAE;QACV,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QACvB,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IACvB,CAAC,CAAA;AACH,CAAC","sourcesContent":["/**\n * Prefetching a link as it comes into view, where there is no pointer to hover it.\n *\n * On a phone the first signal a tap gives is touchstart, and the click lands\n * 100-300 ms after it - about a round trip - so a prefetch started there has\n * barely left when the navigation needs it. Next prefetches the links on\n * screen instead, and that is what makes its taps feel instant on a phone.\n * Only on a device with no hover: a pointer that can settle on a link is a\n * better signal than a link merely being visible, and cheaper on a page with\n * a hundred of them.\n *\n * One observer for every link, and the work is done when the browser is\n * idle - a list scrolled into view is many links at once, and the browser's\n * per-origin connections should not all be taken by pages nobody has tapped\n * yet. Each link prefetches once; the router keeps the payload for its TTL.\n */\n\nlet observer: IntersectionObserver | null = null\nconst pending = new WeakMap<Element, () => void>()\n\nfunction noHover(): boolean {\n return typeof window !== \"undefined\" && typeof window.matchMedia === \"function\" && window.matchMedia(\"(hover: none)\").matches\n}\n\nfunction whenIdle(fn: () => void): void {\n const idle = (window as { requestIdleCallback?: (fn: () => void, opts?: { timeout: number }) => void }).requestIdleCallback\n\n if (idle) idle(fn, { timeout: 1000 })\n else setTimeout(fn, 50)\n}\n\nfunction observerFor(): IntersectionObserver | null {\n if (observer) return observer\n if (typeof IntersectionObserver === \"undefined\") return null\n\n observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue\n\n const fire = pending.get(entry.target)\n\n observer!.unobserve(entry.target)\n pending.delete(entry.target)\n\n if (fire) whenIdle(fire)\n }\n },\n // A little ahead of the fold: a link about to scroll into view is one\n // about to be tapped.\n { rootMargin: \"200px\" },\n )\n\n return observer\n}\n\n/**\n * Prefetch when the link is on screen, on a device with no hover. Returns\n * the function that stops watching; a no-op where this does not apply.\n */\nexport function prefetchWhenVisible(element: Element | null, fire: () => void): () => void {\n if (!element || !noHover()) return () => {}\n\n const io = observerFor()\n\n if (!io) return () => {}\n\n pending.set(element, fire)\n io.observe(element)\n\n return () => {\n pending.delete(element)\n io.unobserve(element)\n }\n}\n"]}
1
+ {"version":3,"file":"viewportPrefetch.js","sourceRoot":"","sources":["../../src/js/viewportPrefetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,IAAI,QAAQ,GAAgC,IAAI,CAAA;AAChD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAuB,CAAA;AAElD,SAAS,UAAU;IACjB,MAAM,UAAU,GAAI,SAAqD,CAAC,UAAU,CAAA;IAEpF,OAAO,UAAU,EAAE,QAAQ,KAAK,IAAI,CAAA;AACtC,CAAC;AAED,SAAS,QAAQ,CAAC,EAAc;IAC9B,MAAM,IAAI,GAAI,MAAyF,CAAC,mBAAmB,CAAA;IAE3H,IAAI,IAAI;QAAE,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;;QAChC,UAAU,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;AACzB,CAAC;AAED,SAAS,WAAW;IAClB,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAA;IAC7B,IAAI,OAAO,oBAAoB,KAAK,WAAW;QAAE,OAAO,IAAI,CAAA;IAE5D,QAAQ,GAAG,IAAI,oBAAoB,CACjC,CAAC,OAAO,EAAE,EAAE;QACV,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,CAAC,KAAK,CAAC,cAAc;gBAAE,SAAQ;YAEnC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;YAEtC,QAAS,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;YACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;YAE5B,IAAI,IAAI;gBAAE,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC1B,CAAC;IACH,CAAC;IACD,sEAAsE;IACtE,sBAAsB;IACtB,EAAE,UAAU,EAAE,OAAO,EAAE,CACxB,CAAA;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAuB,EAAE,IAAgB;IAC3E,IAAI,CAAC,OAAO,IAAI,UAAU,EAAE;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;IAE7C,MAAM,EAAE,GAAG,WAAW,EAAE,CAAA;IAExB,IAAI,CAAC,EAAE;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;IAExB,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;IAC1B,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAEnB,OAAO,GAAG,EAAE;QACV,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QACvB,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IACvB,CAAC,CAAA;AACH,CAAC","sourcesContent":["/**\n * Prefetching a link as it comes into view.\n *\n * A navigation is as fast as what is already in the browser when the click\n * lands, and a hover is too late to fetch that: a pointer that has settled\n * on a link is 200-300 ms from clicking it, and on a phone the first signal\n * is touchstart, 100-300 ms before the click - about a round trip either\n * way, so a fetch started there has barely left when the navigation needs\n * it. Next prefetches the links on screen instead, and that is what makes\n * its clicks feel like a single-page app's: the payload was there before\n * the pointer moved. So, on every device, the links on screen are fetched -\n * the bytes only; decoding a payload loads the chunks it names, and that\n * waits for the hover or the touch that says which link is meant. It used\n * to be phones only, with a hover-capable device left to the hover: a click\n * that came quicker than the round trip waited for it, and desktop was\n * fast but not instant.\n *\n * Not when the visitor asked for less data: Save-Data is the one signal a\n * browser gives that speculative requests are unwelcome.\n *\n * One observer for every link, and the work is done when the browser is\n * idle - a list scrolled into view is many links at once, and the browser's\n * per-origin connections should not all be taken by pages nobody has tapped\n * yet. Each link prefetches once; the router keeps the payload for its TTL.\n */\n\nlet observer: IntersectionObserver | null = null\nconst pending = new WeakMap<Element, () => void>()\n\nfunction savingData(): boolean {\n const connection = (navigator as { connection?: { saveData?: boolean } }).connection\n\n return connection?.saveData === true\n}\n\nfunction whenIdle(fn: () => void): void {\n const idle = (window as { requestIdleCallback?: (fn: () => void, opts?: { timeout: number }) => void }).requestIdleCallback\n\n if (idle) idle(fn, { timeout: 1000 })\n else setTimeout(fn, 50)\n}\n\nfunction observerFor(): IntersectionObserver | null {\n if (observer) return observer\n if (typeof IntersectionObserver === \"undefined\") return null\n\n observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue\n\n const fire = pending.get(entry.target)\n\n observer!.unobserve(entry.target)\n pending.delete(entry.target)\n\n if (fire) whenIdle(fire)\n }\n },\n // A little ahead of the fold: a link about to scroll into view is one\n // about to be tapped.\n { rootMargin: \"200px\" },\n )\n\n return observer\n}\n\n/**\n * Prefetch when the link is on screen. Returns the function that stops\n * watching; a no-op where this does not apply.\n */\nexport function prefetchWhenVisible(element: Element | null, fire: () => void): () => void {\n if (!element || savingData()) return () => {}\n\n const io = observerFor()\n\n if (!io) return () => {}\n\n pending.set(element, fire)\n io.observe(element)\n\n return () => {\n pending.delete(element)\n io.unobserve(element)\n }\n}\n"]}
package/dist/vite.js CHANGED
@@ -5558,7 +5558,11 @@ export function rscKit(options = {}) {
5558
5558
  // documents, for as long as the browser holds the script.
5559
5559
  nitro.options.routeRules = {
5560
5560
  ...(nitro.options.routeRules ?? {}),
5561
- "/sw.js": { ...(nitro.options.routeRules?.["/sw.js"] ?? {}), headers: { "cache-control": "no-cache", ...(nitro.options.routeRules?.["/sw.js"]?.headers ?? {}) } },
5561
+ // Spelled with max-age=0 rather than no-cache: a CDN's browser
5562
+ // cache TTL - Cloudflare's, four hours - applies where the origin
5563
+ // names no max-age, and rewrote no-cache to max-age=14400; a
5564
+ // max-age of zero it leaves alone, as it leaves the documents'.
5565
+ "/sw.js": { ...(nitro.options.routeRules?.["/sw.js"] ?? {}), headers: { "cache-control": "public, max-age=0, must-revalidate", ...(nitro.options.routeRules?.["/sw.js"]?.headers ?? {}) } },
5562
5566
  };
5563
5567
  // The names of functions survive Nitro's bundle. The pages were
5564
5568
  // prerendered against Vite's own server bundles, where a dependency
@@ -5812,6 +5816,17 @@ export function rscKit(options = {}) {
5812
5816
  rollupOptions: {
5813
5817
  input: { index: join(genDir, "entry.rsc.tsx") },
5814
5818
  external: serverExternals,
5819
+ // The server-references map is `id: () => import(file)` for
5820
+ // every "use server" file, and the page that passes an action
5821
+ // to <Form> imports the same file statically. Rolldown says
5822
+ // so for each one: "dynamically imported by
5823
+ // virtual:vite-rsc/server-references but also statically
5824
+ // imported by page.tsx, dynamic import will not move module
5825
+ // into another chunk". Nothing was meant to move: a server
5826
+ // bundle has no chunk to lazy-load from a browser, and the
5827
+ // map is a lookup table, not a split point. One line per
5828
+ // action in every build was a paragraph nobody could act on.
5829
+ checks: { ineffectiveDynamicImport: false },
5815
5830
  },
5816
5831
  },
5817
5832
  resolve: { noExternal: bundledClientPackages },
@@ -5822,6 +5837,8 @@ export function rscKit(options = {}) {
5822
5837
  rollupOptions: {
5823
5838
  input: { index: join(genDir, "entry.ssr.tsx") },
5824
5839
  external: serverExternals,
5840
+ // Same table, same reason.
5841
+ checks: { ineffectiveDynamicImport: false },
5825
5842
  },
5826
5843
  },
5827
5844
  resolve: { noExternal: bundledClientPackages },