Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Control <Image /> prefetching with React #18904

Merged
merged 12 commits into from
Nov 6, 2020

Conversation

Timer
Copy link
Member

@Timer Timer commented Nov 6, 2020

This pull request fixes <Image /> not updating when new props are passed by removing external DOM mutations and relying on React to do it instead.

As an added bonus, I've extracted the intersection observer from both the <Image /> and <Link /> component, as their instance can be shared!

The increase in size is minor (+3B), and actually a decrease for apps using both <Image /> and <Link />.


Fixes #18698
Fixes #18369

@ijjk ijjk added the type: next label Nov 6, 2020
@vercel vercel bot temporarily deployed to Preview November 6, 2020 17:26 Inactive
@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@vercel vercel bot temporarily deployed to Preview November 6, 2020 17:55 Inactive
@vercel vercel bot temporarily deployed to Preview November 6, 2020 18:27 Inactive
@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@vercel vercel bot temporarily deployed to Preview November 6, 2020 21:39 Inactive
@Timer Timer requested review from atcastle and styfle and removed request for atcastle November 6, 2020 21:40
@Timer Timer requested a review from styfle November 6, 2020 21:49
@Timer Timer marked this pull request as ready for review November 6, 2020 21:52
@ijjk
Copy link
Member

ijjk commented Nov 6, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary Timer/next.js hotfix/link-updating Change
buildDuration 11.7s 11.6s -93ms
nodeModulesSize 84.9 MB 84.9 MB -911 B
Page Load Tests Overall increase ✓
vercel/next.js canary Timer/next.js hotfix/link-updating Change
/ failed reqs 0 0
/ total time (seconds) 2.172 2.149 -0.02
/ avg req/sec 1151.16 1163.21 +12.05
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.168 1.158 -0.01
/error-in-render avg req/sec 2140.66 2158.05 +17.39
Client Bundles (main, webpack, commons)
vercel/next.js canary Timer/next.js hotfix/link-updating Change
677f882d2ed8..7d3b.js gzip 11.3 kB 11.3 kB
framework.HASH.js gzip 39 kB 39 kB
main-d942eb5..343f.js gzip 7.4 kB 7.4 kB
webpack-e067..f178.js gzip 751 B 751 B
Overall change 58.4 kB 58.4 kB
Client Bundles (main, webpack, commons) Modern
vercel/next.js canary Timer/next.js hotfix/link-updating Change
677f882d2ed8..dule.js gzip 7.04 kB 7.04 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-34e64ff..dule.js gzip 6.39 kB 6.39 kB
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 53.1 kB 53.1 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js hotfix/link-updating Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_app-7231d4b..5856.js gzip 1.28 kB 1.28 kB
_error-fca3d..2eb1.js gzip 3.44 kB 3.44 kB
hooks-d4591d..e7c2.js gzip 887 B 887 B
index-17468f..5d83.js gzip 227 B 227 B
link-03d24c6..d1c7.js gzip 1.35 kB N/A N/A
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
link-8a9d104..0e42.js gzip N/A 1.55 kB N/A
Overall change 7.75 kB 7.96 kB ⚠️ +206 B
Client Pages Modern Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..dule.js gzip 2.29 kB 2.29 kB
hooks-cbf13f..dule.js gzip 387 B 387 B
index-b9a643..dule.js gzip 226 B 226 B
link-dfbbfbd..dule.js gzip 1.32 kB N/A N/A
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
link-d1cfb87..dule.js gzip N/A 1.49 kB N/A
Overall change 5.41 kB 5.59 kB ⚠️ +178 B
Client Build Manifests
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 330 B 330 B
Overall change 652 B 652 B
Rendered Page Sizes Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
index.html gzip 1 kB 1 kB
link.html gzip 1.01 kB 1.01 kB ⚠️ +3 B
withRouter.html gzip 995 B 995 B
Overall change 3 kB 3.01 kB ⚠️ +3 B

Diffs

Diff for _buildManifest.js
@@ -7,7 +7,7 @@ self.__BUILD_MANIFEST = {
   "/hooks": [
     "static\u002Fchunks\u002Fpages\u002Fhooks-bdd2cad07648acf22380.js"
   ],
-  "/link": ["static\u002Fchunks\u002Fpages\u002Flink-17a206ac5f5df2421f5d.js"],
+  "/link": ["static\u002Fchunks\u002Fpages\u002Flink-fcaf825022c62b02057c.js"],
   "/routerDirect": [
     "static\u002Fchunks\u002Fpages\u002FrouterDirect-2e9bfd441bd88cd3382e.js"
   ],
Diff for _buildManifest.module.js
@@ -10,7 +10,7 @@ self.__BUILD_MANIFEST = {
     "static\u002Fchunks\u002Fpages\u002Fhooks-56fa58a6f0993d7d36d7.module.js"
   ],
   "/link": [
-    "static\u002Fchunks\u002Fpages\u002Flink-16d8635e170e01799dc1.module.js"
+    "static\u002Fchunks\u002Fpages\u002Flink-ec1fbf762c0209fe4b72.module.js"
   ],
   "/routerDirect": [
     "static\u002Fchunks\u002Fpages\u002FrouterDirect-368af3dfef3c9cd99dc3.module.js"
Diff for link-16d8635..c1.module.js
@@ -76,62 +76,9 @@
 
       var _router2 = __webpack_require__("nOHt");
 
-      var cachedObserver;
-      var listeners = new Map();
-      var IntersectionObserver = true ? window.IntersectionObserver : undefined;
-      var prefetched = {};
-
-      function getObserver() {
-        // Return shared instance of IntersectionObserver if already created
-        if (cachedObserver) {
-          return cachedObserver;
-        } // Only create shared IntersectionObserver if supported in browser
-
-        if (!IntersectionObserver) {
-          return undefined;
-        }
-
-        return (cachedObserver = new IntersectionObserver(
-          entries => {
-            entries.forEach(entry => {
-              if (!listeners.has(entry.target)) {
-                return;
-              }
-
-              var cb = listeners.get(entry.target);
-
-              if (entry.isIntersecting || entry.intersectionRatio > 0) {
-                cachedObserver.unobserve(entry.target);
-                listeners.delete(entry.target);
-                cb();
-              }
-            });
-          },
-          {
-            rootMargin: "200px"
-          }
-        ));
-      }
-
-      var listenToIntersections = (el, cb) => {
-        var observer = getObserver();
-
-        if (!observer) {
-          return () => {};
-        }
+      var _useIntersection = __webpack_require__("vNVm");
 
-        observer.observe(el);
-        listeners.set(el, cb);
-        return () => {
-          try {
-            observer.unobserve(el);
-          } catch (err) {
-            console.error(err);
-          }
-
-          listeners.delete(el);
-        };
-      };
+      var prefetched = {};
 
       function prefetch(router, href, as, options) {
         if (false) {
@@ -240,38 +187,14 @@
         var child = _react.Children.only(children);
 
         var childRef = child && typeof child === "object" && child.ref;
-
-        var cleanup = _react.default.useRef();
+        var [setIntersectionRef, isVisible] = (0,
+        _useIntersection.useIntersection)({
+          rootMargin: "200px"
+        });
 
         var setRef = _react.default.useCallback(
           el => {
-            // cleanup previous event handlers
-            if (cleanup.current) {
-              cleanup.current();
-              cleanup.current = undefined;
-            }
-
-            if (
-              p &&
-              IntersectionObserver &&
-              el &&
-              el.tagName &&
-              (0, _router.isLocalURL)(href)
-            ) {
-              // Join on an invalid URI character
-              var isPrefetched = prefetched[href + "%" + as];
-
-              if (!isPrefetched) {
-                cleanup.current = listenToIntersections(el, () => {
-                  prefetch(router, href, as, {
-                    locale:
-                      typeof locale !== "undefined"
-                        ? locale
-                        : router && router.locale
-                  });
-                });
-              }
-            }
+            setIntersectionRef(el);
 
             if (childRef) {
               if (typeof childRef === "function") childRef(el);
@@ -280,9 +203,20 @@
               }
             }
           },
-          [p, childRef, href, as, router, locale]
+          [childRef, setIntersectionRef]
         );
 
+        (0, _react.useEffect)(() => {
+          var shouldPrefetch = isVisible && p && (0, _router.isLocalURL)(href);
+          var isPrefetched = prefetched[href + "%" + as];
+
+          if (shouldPrefetch && !isPrefetched) {
+            prefetch(router, href, as, {
+              locale:
+                typeof locale !== "undefined" ? locale : router && router.locale
+            });
+          }
+        }, [as, href, isVisible, locale, p, router]);
         var childProps = {
           ref: setRef,
           onClick: e => {
@@ -337,6 +271,99 @@
       var _default = Link;
       exports.default = _default;
 
+      /***/
+    },
+
+    /***/ vNVm: /***/ function(module, exports, __webpack_require__) {
+      "use strict";
+
+      exports.__esModule = true;
+      exports.useIntersection = useIntersection;
+
+      var _react = __webpack_require__("q1tI");
+
+      var hasIntersectionObserver = typeof IntersectionObserver !== "undefined";
+
+      function useIntersection(_ref) {
+        var { rootMargin, disabled } = _ref;
+        var isDisabled = disabled || !hasIntersectionObserver;
+        var unobserve = (0, _react.useRef)();
+        var [visible, setVisible] = (0, _react.useState)(false);
+        var setRef = (0, _react.useCallback)(
+          el => {
+            if (unobserve.current) {
+              unobserve.current();
+              unobserve.current = undefined;
+            }
+
+            if (isDisabled || visible) return;
+
+            if (el && el.tagName) {
+              unobserve.current = observe(
+                el,
+                isVisible => isVisible && setVisible(isVisible),
+                {
+                  rootMargin
+                }
+              );
+            }
+          },
+          [isDisabled, rootMargin, visible]
+        );
+        (0, _react.useEffect)(() => {
+          if (!hasIntersectionObserver) {
+            if (!visible) setVisible(true);
+          }
+        }, [visible]);
+        return [setRef, visible];
+      }
+
+      function observe(element, callback, options) {
+        var { id, observer, elements } = createObserver(options);
+        elements.set(element, callback);
+        observer.observe(element);
+        return function unobserve() {
+          observer.unobserve(element); // Destroy observer when there's nothing left to watch:
+
+          if (elements.size === 0) {
+            observer.disconnect();
+            observers.delete(id);
+          }
+        };
+      }
+
+      var observers = new Map();
+
+      function createObserver(options) {
+        var id = options.rootMargin || "";
+        var instance = observers.get(id);
+
+        if (instance) {
+          return instance;
+        }
+
+        var elements = new Map();
+        var observer = new IntersectionObserver(entries => {
+          entries.forEach(entry => {
+            var callback = elements.get(entry.target);
+            var isVisible = entry.isIntersecting || entry.intersectionRatio > 0;
+
+            if (callback && isVisible) {
+              callback(isVisible);
+            }
+          });
+        }, options);
+        observers.set(
+          id,
+          (instance = {
+            id,
+            observer,
+            elements
+          })
+        );
+        return instance;
+      }
+
       /***/
     }
   },
Diff for link-17a206a..df2421f5d.js
@@ -80,62 +80,9 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       var _router2 = __webpack_require__("nOHt");
 
-      var cachedObserver;
-      var listeners = new Map();
-      var IntersectionObserver = true ? window.IntersectionObserver : undefined;
-      var prefetched = {};
-
-      function getObserver() {
-        // Return shared instance of IntersectionObserver if already created
-        if (cachedObserver) {
-          return cachedObserver;
-        } // Only create shared IntersectionObserver if supported in browser
-
-        if (!IntersectionObserver) {
-          return undefined;
-        }
-
-        return (cachedObserver = new IntersectionObserver(
-          function(entries) {
-            entries.forEach(function(entry) {
-              if (!listeners.has(entry.target)) {
-                return;
-              }
-
-              var cb = listeners.get(entry.target);
-
-              if (entry.isIntersecting || entry.intersectionRatio > 0) {
-                cachedObserver.unobserve(entry.target);
-                listeners["delete"](entry.target);
-                cb();
-              }
-            });
-          },
-          {
-            rootMargin: "200px"
-          }
-        ));
-      }
-
-      var listenToIntersections = function listenToIntersections(el, cb) {
-        var observer = getObserver();
-
-        if (!observer) {
-          return function() {};
-        }
-
-        observer.observe(el);
-        listeners.set(el, cb);
-        return function() {
-          try {
-            observer.unobserve(el);
-          } catch (err) {
-            console.error(err);
-          }
+      var _useIntersection = __webpack_require__("vNVm");
 
-          listeners["delete"](el);
-        };
-      };
+      var prefetched = {};
 
       function prefetch(router, href, as, options) {
         if (false) {
@@ -255,37 +202,16 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
         var childRef = child && typeof child === "object" && child.ref;
 
-        var cleanup = _react["default"].useRef();
+        var _ref3 = (0, _useIntersection.useIntersection)({
+            rootMargin: "200px"
+          }),
+          _ref4 = _slicedToArray(_ref3, 2),
+          setIntersectionRef = _ref4[0],
+          isVisible = _ref4[1];
 
         var setRef = _react["default"].useCallback(
           function(el) {
-            // cleanup previous event handlers
-            if (cleanup.current) {
-              cleanup.current();
-              cleanup.current = undefined;
-            }
-
-            if (
-              p &&
-              IntersectionObserver &&
-              el &&
-              el.tagName &&
-              (0, _router.isLocalURL)(href)
-            ) {
-              // Join on an invalid URI character
-              var isPrefetched = prefetched[href + "%" + as];
-
-              if (!isPrefetched) {
-                cleanup.current = listenToIntersections(el, function() {
-                  prefetch(router, href, as, {
-                    locale:
-                      typeof locale !== "undefined"
-                        ? locale
-                        : router && router.locale
-                  });
-                });
-              }
-            }
+            setIntersectionRef(el);
 
             if (childRef) {
               if (typeof childRef === "function") childRef(el);
@@ -294,9 +220,26 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
               }
             }
           },
-          [p, childRef, href, as, router, locale]
+          [childRef, setIntersectionRef]
         );
 
+        (0, _react.useEffect)(
+          function() {
+            var shouldPrefetch =
+              isVisible && p && (0, _router.isLocalURL)(href);
+            var isPrefetched = prefetched[href + "%" + as];
+
+            if (shouldPrefetch && !isPrefetched) {
+              prefetch(router, href, as, {
+                locale:
+                  typeof locale !== "undefined"
+                    ? locale
+                    : router && router.locale
+              });
+            }
+          },
+          [as, href, isVisible, locale, p, router]
+        );
         var childProps = {
           ref: setRef,
           onClick: function onClick(e) {
@@ -351,6 +294,116 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
       var _default = Link;
       exports["default"] = _default;
 
+      /***/
+    },
+
+    /***/ vNVm: /***/ function(module, exports, __webpack_require__) {
+      "use strict";
+
+      var _slicedToArray = __webpack_require__("J4zp");
+
+      exports.__esModule = true;
+      exports.useIntersection = useIntersection;
+
+      var _react = __webpack_require__("q1tI");
+
+      var hasIntersectionObserver = typeof IntersectionObserver !== "undefined";
+
+      function useIntersection(_ref) {
+        var rootMargin = _ref.rootMargin,
+          disabled = _ref.disabled;
+        var isDisabled = disabled || !hasIntersectionObserver;
+        var unobserve = (0, _react.useRef)();
+
+        var _ref2 = (0, _react.useState)(false),
+          _ref3 = _slicedToArray(_ref2, 2),
+          visible = _ref3[0],
+          setVisible = _ref3[1];
+
+        var setRef = (0, _react.useCallback)(
+          function(el) {
+            if (unobserve.current) {
+              unobserve.current();
+              unobserve.current = undefined;
+            }
+
+            if (isDisabled || visible) return;
+
+            if (el && el.tagName) {
+              unobserve.current = observe(
+                el,
+                function(isVisible) {
+                  return isVisible && setVisible(isVisible);
+                },
+                {
+                  rootMargin: rootMargin
+                }
+              );
+            }
+          },
+          [isDisabled, rootMargin, visible]
+        );
+        (0, _react.useEffect)(
+          function() {
+            if (!hasIntersectionObserver) {
+              if (!visible) setVisible(true);
+            }
+          },
+          [visible]
+        );
+        return [setRef, visible];
+      }
+
+      function observe(element, callback, options) {
+        var _createObserver = createObserver(options),
+          id = _createObserver.id,
+          observer = _createObserver.observer,
+          elements = _createObserver.elements;
+
+        elements.set(element, callback);
+        observer.observe(element);
+        return function unobserve() {
+          observer.unobserve(element); // Destroy observer when there's nothing left to watch:
+
+          if (elements.size === 0) {
+            observer.disconnect();
+            observers["delete"](id);
+          }
+        };
+      }
+
+      var observers = new Map();
+
+      function createObserver(options) {
+        var id = options.rootMargin || "";
+        var instance = observers.get(id);
+
+        if (instance) {
+          return instance;
+        }
+
+        var elements = new Map();
+        var observer = new IntersectionObserver(function(entries) {
+          entries.forEach(function(entry) {
+            var callback = elements.get(entry.target);
+            var isVisible = entry.isIntersecting || entry.intersectionRatio > 0;
+
+            if (callback && isVisible) {
+              callback(isVisible);
+            }
+          });
+        }, options);
+        observers.set(
+          id,
+          (instance = {
+            id: id,
+            observer: observer,
+            elements: elements
+          })
+        );
+        return instance;
+      }
+
       /***/
     }
   },
Diff for link.html
@@ -36,7 +36,7 @@
     />
     <link
       rel="preload"
-      href="/_next/static/chunks/pages/link-16d8635e170e01799dc1.module.js"
+      href="/_next/static/chunks/pages/link-ec1fbf762c0209fe4b72.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -150,13 +150,13 @@
       type="module"
     ></script>
     <script
-      src="/_next/static/chunks/pages/link-17a206ac5f5df2421f5d.js"
+      src="/_next/static/chunks/pages/link-fcaf825022c62b02057c.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/pages/link-16d8635e170e01799dc1.module.js"
+      src="/_next/static/chunks/pages/link-ec1fbf762c0209fe4b72.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary Timer/next.js hotfix/link-updating Change
buildDuration 12.6s 13.1s ⚠️ +413ms
nodeModulesSize 84.9 MB 84.9 MB -911 B
Client Bundles (main, webpack, commons)
vercel/next.js canary Timer/next.js hotfix/link-updating Change
677f882d2ed8..7d3b.js gzip 11.3 kB 11.3 kB
framework.HASH.js gzip 39 kB 39 kB
main-d942eb5..343f.js gzip 7.4 kB 7.4 kB
webpack-e067..f178.js gzip 751 B 751 B
Overall change 58.4 kB 58.4 kB
Client Bundles (main, webpack, commons) Modern
vercel/next.js canary Timer/next.js hotfix/link-updating Change
677f882d2ed8..dule.js gzip 7.04 kB 7.04 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-34e64ff..dule.js gzip 6.39 kB 6.39 kB
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 53.1 kB 53.1 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js hotfix/link-updating Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_app-7231d4b..5856.js gzip 1.28 kB 1.28 kB
_error-fca3d..2eb1.js gzip 3.44 kB 3.44 kB
hooks-d4591d..e7c2.js gzip 887 B 887 B
index-17468f..5d83.js gzip 227 B 227 B
link-03d24c6..d1c7.js gzip 1.35 kB N/A N/A
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
link-8a9d104..0e42.js gzip N/A 1.55 kB N/A
Overall change 7.75 kB 7.96 kB ⚠️ +206 B
Client Pages Modern Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..dule.js gzip 2.29 kB 2.29 kB
hooks-cbf13f..dule.js gzip 387 B 387 B
index-b9a643..dule.js gzip 226 B 226 B
link-dfbbfbd..dule.js gzip 1.32 kB N/A N/A
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
link-d1cfb87..dule.js gzip N/A 1.49 kB N/A
Overall change 5.41 kB 5.59 kB ⚠️ +178 B
Client Build Manifests
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 330 B 330 B
Overall change 652 B 652 B
Serverless bundles Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_error.js 914 kB 914 kB
404.html 4.73 kB 4.73 kB
hooks.html 3.92 kB 3.92 kB
index.js 914 kB 914 kB
link.js 962 kB 963 kB ⚠️ +906 B
routerDirect.js 956 kB 956 kB
withRouter.js 956 kB 956 kB
Overall change 4.71 MB 4.71 MB ⚠️ +906 B
Commit: 98abfa9

@vercel vercel bot temporarily deployed to Preview November 6, 2020 22:12 Inactive
@ijjk
Copy link
Member

ijjk commented Nov 6, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary Timer/next.js hotfix/link-updating Change
buildDuration 12.8s 12.8s -37ms
nodeModulesSize 84.9 MB 84.9 MB -1.11 kB
Page Load Tests Overall increase ✓
vercel/next.js canary Timer/next.js hotfix/link-updating Change
/ failed reqs 0 0
/ total time (seconds) 2.429 2.408 -0.02
/ avg req/sec 1029.24 1038.37 +9.13
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.339 1.28 -0.06
/error-in-render avg req/sec 1867.29 1953.83 +86.54
Client Bundles (main, webpack, commons)
vercel/next.js canary Timer/next.js hotfix/link-updating Change
677f882d2ed8..7d3b.js gzip 11.3 kB 11.3 kB
framework.HASH.js gzip 39 kB 39 kB
main-d942eb5..343f.js gzip 7.4 kB 7.4 kB
webpack-e067..f178.js gzip 751 B 751 B
Overall change 58.4 kB 58.4 kB
Client Bundles (main, webpack, commons) Modern
vercel/next.js canary Timer/next.js hotfix/link-updating Change
677f882d2ed8..dule.js gzip 7.04 kB 7.04 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-34e64ff..dule.js gzip 6.39 kB 6.39 kB
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 53.1 kB 53.1 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js hotfix/link-updating Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_app-7231d4b..5856.js gzip 1.28 kB 1.28 kB
_error-fca3d..2eb1.js gzip 3.44 kB 3.44 kB
hooks-d4591d..e7c2.js gzip 887 B 887 B
index-17468f..5d83.js gzip 227 B 227 B
link-03d24c6..d1c7.js gzip 1.35 kB N/A N/A
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
link-8a9d104..0e42.js gzip N/A 1.55 kB N/A
Overall change 7.75 kB 7.96 kB ⚠️ +206 B
Client Pages Modern Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..dule.js gzip 2.29 kB 2.29 kB
hooks-cbf13f..dule.js gzip 387 B 387 B
index-b9a643..dule.js gzip 226 B 226 B
link-dfbbfbd..dule.js gzip 1.32 kB N/A N/A
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
link-d1cfb87..dule.js gzip N/A 1.49 kB N/A
Overall change 5.41 kB 5.59 kB ⚠️ +178 B
Client Build Manifests
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 330 B 330 B
Overall change 652 B 652 B
Rendered Page Sizes Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
index.html gzip 1 kB 1 kB
link.html gzip 1.01 kB 1.01 kB ⚠️ +3 B
withRouter.html gzip 995 B 995 B
Overall change 3 kB 3.01 kB ⚠️ +3 B

Diffs

Diff for _buildManifest.js
@@ -7,7 +7,7 @@ self.__BUILD_MANIFEST = {
   "/hooks": [
     "static\u002Fchunks\u002Fpages\u002Fhooks-bdd2cad07648acf22380.js"
   ],
-  "/link": ["static\u002Fchunks\u002Fpages\u002Flink-17a206ac5f5df2421f5d.js"],
+  "/link": ["static\u002Fchunks\u002Fpages\u002Flink-fcaf825022c62b02057c.js"],
   "/routerDirect": [
     "static\u002Fchunks\u002Fpages\u002FrouterDirect-2e9bfd441bd88cd3382e.js"
   ],
Diff for _buildManifest.module.js
@@ -10,7 +10,7 @@ self.__BUILD_MANIFEST = {
     "static\u002Fchunks\u002Fpages\u002Fhooks-56fa58a6f0993d7d36d7.module.js"
   ],
   "/link": [
-    "static\u002Fchunks\u002Fpages\u002Flink-16d8635e170e01799dc1.module.js"
+    "static\u002Fchunks\u002Fpages\u002Flink-ec1fbf762c0209fe4b72.module.js"
   ],
   "/routerDirect": [
     "static\u002Fchunks\u002Fpages\u002FrouterDirect-368af3dfef3c9cd99dc3.module.js"
Diff for link-16d8635..c1.module.js
@@ -76,62 +76,9 @@
 
       var _router2 = __webpack_require__("nOHt");
 
-      var cachedObserver;
-      var listeners = new Map();
-      var IntersectionObserver = true ? window.IntersectionObserver : undefined;
-      var prefetched = {};
-
-      function getObserver() {
-        // Return shared instance of IntersectionObserver if already created
-        if (cachedObserver) {
-          return cachedObserver;
-        } // Only create shared IntersectionObserver if supported in browser
-
-        if (!IntersectionObserver) {
-          return undefined;
-        }
-
-        return (cachedObserver = new IntersectionObserver(
-          entries => {
-            entries.forEach(entry => {
-              if (!listeners.has(entry.target)) {
-                return;
-              }
-
-              var cb = listeners.get(entry.target);
-
-              if (entry.isIntersecting || entry.intersectionRatio > 0) {
-                cachedObserver.unobserve(entry.target);
-                listeners.delete(entry.target);
-                cb();
-              }
-            });
-          },
-          {
-            rootMargin: "200px"
-          }
-        ));
-      }
-
-      var listenToIntersections = (el, cb) => {
-        var observer = getObserver();
-
-        if (!observer) {
-          return () => {};
-        }
+      var _useIntersection = __webpack_require__("vNVm");
 
-        observer.observe(el);
-        listeners.set(el, cb);
-        return () => {
-          try {
-            observer.unobserve(el);
-          } catch (err) {
-            console.error(err);
-          }
-
-          listeners.delete(el);
-        };
-      };
+      var prefetched = {};
 
       function prefetch(router, href, as, options) {
         if (false) {
@@ -240,38 +187,14 @@
         var child = _react.Children.only(children);
 
         var childRef = child && typeof child === "object" && child.ref;
-
-        var cleanup = _react.default.useRef();
+        var [setIntersectionRef, isVisible] = (0,
+        _useIntersection.useIntersection)({
+          rootMargin: "200px"
+        });
 
         var setRef = _react.default.useCallback(
           el => {
-            // cleanup previous event handlers
-            if (cleanup.current) {
-              cleanup.current();
-              cleanup.current = undefined;
-            }
-
-            if (
-              p &&
-              IntersectionObserver &&
-              el &&
-              el.tagName &&
-              (0, _router.isLocalURL)(href)
-            ) {
-              // Join on an invalid URI character
-              var isPrefetched = prefetched[href + "%" + as];
-
-              if (!isPrefetched) {
-                cleanup.current = listenToIntersections(el, () => {
-                  prefetch(router, href, as, {
-                    locale:
-                      typeof locale !== "undefined"
-                        ? locale
-                        : router && router.locale
-                  });
-                });
-              }
-            }
+            setIntersectionRef(el);
 
             if (childRef) {
               if (typeof childRef === "function") childRef(el);
@@ -280,9 +203,20 @@
               }
             }
           },
-          [p, childRef, href, as, router, locale]
+          [childRef, setIntersectionRef]
         );
 
+        (0, _react.useEffect)(() => {
+          var shouldPrefetch = isVisible && p && (0, _router.isLocalURL)(href);
+          var isPrefetched = prefetched[href + "%" + as];
+
+          if (shouldPrefetch && !isPrefetched) {
+            prefetch(router, href, as, {
+              locale:
+                typeof locale !== "undefined" ? locale : router && router.locale
+            });
+          }
+        }, [as, href, isVisible, locale, p, router]);
         var childProps = {
           ref: setRef,
           onClick: e => {
@@ -337,6 +271,99 @@
       var _default = Link;
       exports.default = _default;
 
+      /***/
+    },
+
+    /***/ vNVm: /***/ function(module, exports, __webpack_require__) {
+      "use strict";
+
+      exports.__esModule = true;
+      exports.useIntersection = useIntersection;
+
+      var _react = __webpack_require__("q1tI");
+
+      var hasIntersectionObserver = typeof IntersectionObserver !== "undefined";
+
+      function useIntersection(_ref) {
+        var { rootMargin, disabled } = _ref;
+        var isDisabled = disabled || !hasIntersectionObserver;
+        var unobserve = (0, _react.useRef)();
+        var [visible, setVisible] = (0, _react.useState)(false);
+        var setRef = (0, _react.useCallback)(
+          el => {
+            if (unobserve.current) {
+              unobserve.current();
+              unobserve.current = undefined;
+            }
+
+            if (isDisabled || visible) return;
+
+            if (el && el.tagName) {
+              unobserve.current = observe(
+                el,
+                isVisible => isVisible && setVisible(isVisible),
+                {
+                  rootMargin
+                }
+              );
+            }
+          },
+          [isDisabled, rootMargin, visible]
+        );
+        (0, _react.useEffect)(() => {
+          if (!hasIntersectionObserver) {
+            if (!visible) setVisible(true);
+          }
+        }, [visible]);
+        return [setRef, visible];
+      }
+
+      function observe(element, callback, options) {
+        var { id, observer, elements } = createObserver(options);
+        elements.set(element, callback);
+        observer.observe(element);
+        return function unobserve() {
+          observer.unobserve(element); // Destroy observer when there's nothing left to watch:
+
+          if (elements.size === 0) {
+            observer.disconnect();
+            observers.delete(id);
+          }
+        };
+      }
+
+      var observers = new Map();
+
+      function createObserver(options) {
+        var id = options.rootMargin || "";
+        var instance = observers.get(id);
+
+        if (instance) {
+          return instance;
+        }
+
+        var elements = new Map();
+        var observer = new IntersectionObserver(entries => {
+          entries.forEach(entry => {
+            var callback = elements.get(entry.target);
+            var isVisible = entry.isIntersecting || entry.intersectionRatio > 0;
+
+            if (callback && isVisible) {
+              callback(isVisible);
+            }
+          });
+        }, options);
+        observers.set(
+          id,
+          (instance = {
+            id,
+            observer,
+            elements
+          })
+        );
+        return instance;
+      }
+
       /***/
     }
   },
Diff for link-17a206a..df2421f5d.js
@@ -80,62 +80,9 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       var _router2 = __webpack_require__("nOHt");
 
-      var cachedObserver;
-      var listeners = new Map();
-      var IntersectionObserver = true ? window.IntersectionObserver : undefined;
-      var prefetched = {};
-
-      function getObserver() {
-        // Return shared instance of IntersectionObserver if already created
-        if (cachedObserver) {
-          return cachedObserver;
-        } // Only create shared IntersectionObserver if supported in browser
-
-        if (!IntersectionObserver) {
-          return undefined;
-        }
-
-        return (cachedObserver = new IntersectionObserver(
-          function(entries) {
-            entries.forEach(function(entry) {
-              if (!listeners.has(entry.target)) {
-                return;
-              }
-
-              var cb = listeners.get(entry.target);
-
-              if (entry.isIntersecting || entry.intersectionRatio > 0) {
-                cachedObserver.unobserve(entry.target);
-                listeners["delete"](entry.target);
-                cb();
-              }
-            });
-          },
-          {
-            rootMargin: "200px"
-          }
-        ));
-      }
-
-      var listenToIntersections = function listenToIntersections(el, cb) {
-        var observer = getObserver();
-
-        if (!observer) {
-          return function() {};
-        }
-
-        observer.observe(el);
-        listeners.set(el, cb);
-        return function() {
-          try {
-            observer.unobserve(el);
-          } catch (err) {
-            console.error(err);
-          }
+      var _useIntersection = __webpack_require__("vNVm");
 
-          listeners["delete"](el);
-        };
-      };
+      var prefetched = {};
 
       function prefetch(router, href, as, options) {
         if (false) {
@@ -255,37 +202,16 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
         var childRef = child && typeof child === "object" && child.ref;
 
-        var cleanup = _react["default"].useRef();
+        var _ref3 = (0, _useIntersection.useIntersection)({
+            rootMargin: "200px"
+          }),
+          _ref4 = _slicedToArray(_ref3, 2),
+          setIntersectionRef = _ref4[0],
+          isVisible = _ref4[1];
 
         var setRef = _react["default"].useCallback(
           function(el) {
-            // cleanup previous event handlers
-            if (cleanup.current) {
-              cleanup.current();
-              cleanup.current = undefined;
-            }
-
-            if (
-              p &&
-              IntersectionObserver &&
-              el &&
-              el.tagName &&
-              (0, _router.isLocalURL)(href)
-            ) {
-              // Join on an invalid URI character
-              var isPrefetched = prefetched[href + "%" + as];
-
-              if (!isPrefetched) {
-                cleanup.current = listenToIntersections(el, function() {
-                  prefetch(router, href, as, {
-                    locale:
-                      typeof locale !== "undefined"
-                        ? locale
-                        : router && router.locale
-                  });
-                });
-              }
-            }
+            setIntersectionRef(el);
 
             if (childRef) {
               if (typeof childRef === "function") childRef(el);
@@ -294,9 +220,26 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
               }
             }
           },
-          [p, childRef, href, as, router, locale]
+          [childRef, setIntersectionRef]
         );
 
+        (0, _react.useEffect)(
+          function() {
+            var shouldPrefetch =
+              isVisible && p && (0, _router.isLocalURL)(href);
+            var isPrefetched = prefetched[href + "%" + as];
+
+            if (shouldPrefetch && !isPrefetched) {
+              prefetch(router, href, as, {
+                locale:
+                  typeof locale !== "undefined"
+                    ? locale
+                    : router && router.locale
+              });
+            }
+          },
+          [as, href, isVisible, locale, p, router]
+        );
         var childProps = {
           ref: setRef,
           onClick: function onClick(e) {
@@ -351,6 +294,116 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
       var _default = Link;
       exports["default"] = _default;
 
+      /***/
+    },
+
+    /***/ vNVm: /***/ function(module, exports, __webpack_require__) {
+      "use strict";
+
+      var _slicedToArray = __webpack_require__("J4zp");
+
+      exports.__esModule = true;
+      exports.useIntersection = useIntersection;
+
+      var _react = __webpack_require__("q1tI");
+
+      var hasIntersectionObserver = typeof IntersectionObserver !== "undefined";
+
+      function useIntersection(_ref) {
+        var rootMargin = _ref.rootMargin,
+          disabled = _ref.disabled;
+        var isDisabled = disabled || !hasIntersectionObserver;
+        var unobserve = (0, _react.useRef)();
+
+        var _ref2 = (0, _react.useState)(false),
+          _ref3 = _slicedToArray(_ref2, 2),
+          visible = _ref3[0],
+          setVisible = _ref3[1];
+
+        var setRef = (0, _react.useCallback)(
+          function(el) {
+            if (unobserve.current) {
+              unobserve.current();
+              unobserve.current = undefined;
+            }
+
+            if (isDisabled || visible) return;
+
+            if (el && el.tagName) {
+              unobserve.current = observe(
+                el,
+                function(isVisible) {
+                  return isVisible && setVisible(isVisible);
+                },
+                {
+                  rootMargin: rootMargin
+                }
+              );
+            }
+          },
+          [isDisabled, rootMargin, visible]
+        );
+        (0, _react.useEffect)(
+          function() {
+            if (!hasIntersectionObserver) {
+              if (!visible) setVisible(true);
+            }
+          },
+          [visible]
+        );
+        return [setRef, visible];
+      }
+
+      function observe(element, callback, options) {
+        var _createObserver = createObserver(options),
+          id = _createObserver.id,
+          observer = _createObserver.observer,
+          elements = _createObserver.elements;
+
+        elements.set(element, callback);
+        observer.observe(element);
+        return function unobserve() {
+          observer.unobserve(element); // Destroy observer when there's nothing left to watch:
+
+          if (elements.size === 0) {
+            observer.disconnect();
+            observers["delete"](id);
+          }
+        };
+      }
+
+      var observers = new Map();
+
+      function createObserver(options) {
+        var id = options.rootMargin || "";
+        var instance = observers.get(id);
+
+        if (instance) {
+          return instance;
+        }
+
+        var elements = new Map();
+        var observer = new IntersectionObserver(function(entries) {
+          entries.forEach(function(entry) {
+            var callback = elements.get(entry.target);
+            var isVisible = entry.isIntersecting || entry.intersectionRatio > 0;
+
+            if (callback && isVisible) {
+              callback(isVisible);
+            }
+          });
+        }, options);
+        observers.set(
+          id,
+          (instance = {
+            id: id,
+            observer: observer,
+            elements: elements
+          })
+        );
+        return instance;
+      }
+
       /***/
     }
   },
Diff for link.html
@@ -36,7 +36,7 @@
     />
     <link
       rel="preload"
-      href="/_next/static/chunks/pages/link-16d8635e170e01799dc1.module.js"
+      href="/_next/static/chunks/pages/link-ec1fbf762c0209fe4b72.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -150,13 +150,13 @@
       type="module"
     ></script>
     <script
-      src="/_next/static/chunks/pages/link-17a206ac5f5df2421f5d.js"
+      src="/_next/static/chunks/pages/link-fcaf825022c62b02057c.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/pages/link-16d8635e170e01799dc1.module.js"
+      src="/_next/static/chunks/pages/link-ec1fbf762c0209fe4b72.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary Timer/next.js hotfix/link-updating Change
buildDuration 14.1s 14.4s ⚠️ +347ms
nodeModulesSize 84.9 MB 84.9 MB -1.11 kB
Client Bundles (main, webpack, commons)
vercel/next.js canary Timer/next.js hotfix/link-updating Change
677f882d2ed8..7d3b.js gzip 11.3 kB 11.3 kB
framework.HASH.js gzip 39 kB 39 kB
main-d942eb5..343f.js gzip 7.4 kB 7.4 kB
webpack-e067..f178.js gzip 751 B 751 B
Overall change 58.4 kB 58.4 kB
Client Bundles (main, webpack, commons) Modern
vercel/next.js canary Timer/next.js hotfix/link-updating Change
677f882d2ed8..dule.js gzip 7.04 kB 7.04 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-34e64ff..dule.js gzip 6.39 kB 6.39 kB
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 53.1 kB 53.1 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js hotfix/link-updating Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_app-7231d4b..5856.js gzip 1.28 kB 1.28 kB
_error-fca3d..2eb1.js gzip 3.44 kB 3.44 kB
hooks-d4591d..e7c2.js gzip 887 B 887 B
index-17468f..5d83.js gzip 227 B 227 B
link-03d24c6..d1c7.js gzip 1.35 kB N/A N/A
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
link-8a9d104..0e42.js gzip N/A 1.55 kB N/A
Overall change 7.75 kB 7.96 kB ⚠️ +206 B
Client Pages Modern Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..dule.js gzip 2.29 kB 2.29 kB
hooks-cbf13f..dule.js gzip 387 B 387 B
index-b9a643..dule.js gzip 226 B 226 B
link-dfbbfbd..dule.js gzip 1.32 kB N/A N/A
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
link-d1cfb87..dule.js gzip N/A 1.49 kB N/A
Overall change 5.41 kB 5.59 kB ⚠️ +178 B
Client Build Manifests
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 330 B 330 B
Overall change 652 B 652 B
Serverless bundles Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_error.js 914 kB 914 kB
404.html 4.73 kB 4.73 kB
hooks.html 3.92 kB 3.92 kB
index.js 914 kB 914 kB
link.js 962 kB 963 kB ⚠️ +906 B
routerDirect.js 956 kB 956 kB
withRouter.js 956 kB 956 kB
Overall change 4.71 MB 4.71 MB ⚠️ +906 B
Commit: a6d487f

@ijjk
Copy link
Member

ijjk commented Nov 6, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary Timer/next.js hotfix/link-updating Change
buildDuration 14.7s 15.1s ⚠️ +413ms
nodeModulesSize 84.9 MB 84.9 MB -1.11 kB
Page Load Tests Overall decrease ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
/ failed reqs 0 0
/ total time (seconds) 2.865 2.91 ⚠️ +0.04
/ avg req/sec 872.73 859 ⚠️ -13.73
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.75 1.746 0
/error-in-render avg req/sec 1428.88 1432.1 +3.22
Client Bundles (main, webpack, commons)
vercel/next.js canary Timer/next.js hotfix/link-updating Change
677f882d2ed8..7d3b.js gzip 11.3 kB 11.3 kB
framework.HASH.js gzip 39 kB 39 kB
main-d942eb5..343f.js gzip 7.4 kB 7.4 kB
webpack-e067..f178.js gzip 751 B 751 B
Overall change 58.4 kB 58.4 kB
Client Bundles (main, webpack, commons) Modern
vercel/next.js canary Timer/next.js hotfix/link-updating Change
677f882d2ed8..dule.js gzip 7.04 kB 7.04 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-34e64ff..dule.js gzip 6.39 kB 6.39 kB
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 53.1 kB 53.1 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js hotfix/link-updating Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_app-7231d4b..5856.js gzip 1.28 kB 1.28 kB
_error-fca3d..2eb1.js gzip 3.44 kB 3.44 kB
hooks-d4591d..e7c2.js gzip 887 B 887 B
index-17468f..5d83.js gzip 227 B 227 B
link-03d24c6..d1c7.js gzip 1.35 kB N/A N/A
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
link-8a9d104..0e42.js gzip N/A 1.55 kB N/A
Overall change 7.75 kB 7.96 kB ⚠️ +206 B
Client Pages Modern Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..dule.js gzip 2.29 kB 2.29 kB
hooks-cbf13f..dule.js gzip 387 B 387 B
index-b9a643..dule.js gzip 226 B 226 B
link-dfbbfbd..dule.js gzip 1.32 kB N/A N/A
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
link-d1cfb87..dule.js gzip N/A 1.49 kB N/A
Overall change 5.41 kB 5.59 kB ⚠️ +178 B
Client Build Manifests
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 330 B 330 B
Overall change 652 B 652 B
Rendered Page Sizes Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
index.html gzip 1 kB 1 kB
link.html gzip 1.01 kB 1.01 kB ⚠️ +3 B
withRouter.html gzip 995 B 995 B
Overall change 3 kB 3.01 kB ⚠️ +3 B

Diffs

Diff for _buildManifest.js
@@ -7,7 +7,7 @@ self.__BUILD_MANIFEST = {
   "/hooks": [
     "static\u002Fchunks\u002Fpages\u002Fhooks-bdd2cad07648acf22380.js"
   ],
-  "/link": ["static\u002Fchunks\u002Fpages\u002Flink-17a206ac5f5df2421f5d.js"],
+  "/link": ["static\u002Fchunks\u002Fpages\u002Flink-fcaf825022c62b02057c.js"],
   "/routerDirect": [
     "static\u002Fchunks\u002Fpages\u002FrouterDirect-2e9bfd441bd88cd3382e.js"
   ],
Diff for _buildManifest.module.js
@@ -10,7 +10,7 @@ self.__BUILD_MANIFEST = {
     "static\u002Fchunks\u002Fpages\u002Fhooks-56fa58a6f0993d7d36d7.module.js"
   ],
   "/link": [
-    "static\u002Fchunks\u002Fpages\u002Flink-16d8635e170e01799dc1.module.js"
+    "static\u002Fchunks\u002Fpages\u002Flink-ec1fbf762c0209fe4b72.module.js"
   ],
   "/routerDirect": [
     "static\u002Fchunks\u002Fpages\u002FrouterDirect-368af3dfef3c9cd99dc3.module.js"
Diff for link-16d8635..c1.module.js
@@ -76,62 +76,9 @@
 
       var _router2 = __webpack_require__("nOHt");
 
-      var cachedObserver;
-      var listeners = new Map();
-      var IntersectionObserver = true ? window.IntersectionObserver : undefined;
-      var prefetched = {};
-
-      function getObserver() {
-        // Return shared instance of IntersectionObserver if already created
-        if (cachedObserver) {
-          return cachedObserver;
-        } // Only create shared IntersectionObserver if supported in browser
-
-        if (!IntersectionObserver) {
-          return undefined;
-        }
-
-        return (cachedObserver = new IntersectionObserver(
-          entries => {
-            entries.forEach(entry => {
-              if (!listeners.has(entry.target)) {
-                return;
-              }
-
-              var cb = listeners.get(entry.target);
-
-              if (entry.isIntersecting || entry.intersectionRatio > 0) {
-                cachedObserver.unobserve(entry.target);
-                listeners.delete(entry.target);
-                cb();
-              }
-            });
-          },
-          {
-            rootMargin: "200px"
-          }
-        ));
-      }
-
-      var listenToIntersections = (el, cb) => {
-        var observer = getObserver();
-
-        if (!observer) {
-          return () => {};
-        }
+      var _useIntersection = __webpack_require__("vNVm");
 
-        observer.observe(el);
-        listeners.set(el, cb);
-        return () => {
-          try {
-            observer.unobserve(el);
-          } catch (err) {
-            console.error(err);
-          }
-
-          listeners.delete(el);
-        };
-      };
+      var prefetched = {};
 
       function prefetch(router, href, as, options) {
         if (false) {
@@ -240,38 +187,14 @@
         var child = _react.Children.only(children);
 
         var childRef = child && typeof child === "object" && child.ref;
-
-        var cleanup = _react.default.useRef();
+        var [setIntersectionRef, isVisible] = (0,
+        _useIntersection.useIntersection)({
+          rootMargin: "200px"
+        });
 
         var setRef = _react.default.useCallback(
           el => {
-            // cleanup previous event handlers
-            if (cleanup.current) {
-              cleanup.current();
-              cleanup.current = undefined;
-            }
-
-            if (
-              p &&
-              IntersectionObserver &&
-              el &&
-              el.tagName &&
-              (0, _router.isLocalURL)(href)
-            ) {
-              // Join on an invalid URI character
-              var isPrefetched = prefetched[href + "%" + as];
-
-              if (!isPrefetched) {
-                cleanup.current = listenToIntersections(el, () => {
-                  prefetch(router, href, as, {
-                    locale:
-                      typeof locale !== "undefined"
-                        ? locale
-                        : router && router.locale
-                  });
-                });
-              }
-            }
+            setIntersectionRef(el);
 
             if (childRef) {
               if (typeof childRef === "function") childRef(el);
@@ -280,9 +203,20 @@
               }
             }
           },
-          [p, childRef, href, as, router, locale]
+          [childRef, setIntersectionRef]
         );
 
+        (0, _react.useEffect)(() => {
+          var shouldPrefetch = isVisible && p && (0, _router.isLocalURL)(href);
+          var isPrefetched = prefetched[href + "%" + as];
+
+          if (shouldPrefetch && !isPrefetched) {
+            prefetch(router, href, as, {
+              locale:
+                typeof locale !== "undefined" ? locale : router && router.locale
+            });
+          }
+        }, [as, href, isVisible, locale, p, router]);
         var childProps = {
           ref: setRef,
           onClick: e => {
@@ -337,6 +271,99 @@
       var _default = Link;
       exports.default = _default;
 
+      /***/
+    },
+
+    /***/ vNVm: /***/ function(module, exports, __webpack_require__) {
+      "use strict";
+
+      exports.__esModule = true;
+      exports.useIntersection = useIntersection;
+
+      var _react = __webpack_require__("q1tI");
+
+      var hasIntersectionObserver = typeof IntersectionObserver !== "undefined";
+
+      function useIntersection(_ref) {
+        var { rootMargin, disabled } = _ref;
+        var isDisabled = disabled || !hasIntersectionObserver;
+        var unobserve = (0, _react.useRef)();
+        var [visible, setVisible] = (0, _react.useState)(false);
+        var setRef = (0, _react.useCallback)(
+          el => {
+            if (unobserve.current) {
+              unobserve.current();
+              unobserve.current = undefined;
+            }
+
+            if (isDisabled || visible) return;
+
+            if (el && el.tagName) {
+              unobserve.current = observe(
+                el,
+                isVisible => isVisible && setVisible(isVisible),
+                {
+                  rootMargin
+                }
+              );
+            }
+          },
+          [isDisabled, rootMargin, visible]
+        );
+        (0, _react.useEffect)(() => {
+          if (!hasIntersectionObserver) {
+            if (!visible) setVisible(true);
+          }
+        }, [visible]);
+        return [setRef, visible];
+      }
+
+      function observe(element, callback, options) {
+        var { id, observer, elements } = createObserver(options);
+        elements.set(element, callback);
+        observer.observe(element);
+        return function unobserve() {
+          observer.unobserve(element); // Destroy observer when there's nothing left to watch:
+
+          if (elements.size === 0) {
+            observer.disconnect();
+            observers.delete(id);
+          }
+        };
+      }
+
+      var observers = new Map();
+
+      function createObserver(options) {
+        var id = options.rootMargin || "";
+        var instance = observers.get(id);
+
+        if (instance) {
+          return instance;
+        }
+
+        var elements = new Map();
+        var observer = new IntersectionObserver(entries => {
+          entries.forEach(entry => {
+            var callback = elements.get(entry.target);
+            var isVisible = entry.isIntersecting || entry.intersectionRatio > 0;
+
+            if (callback && isVisible) {
+              callback(isVisible);
+            }
+          });
+        }, options);
+        observers.set(
+          id,
+          (instance = {
+            id,
+            observer,
+            elements
+          })
+        );
+        return instance;
+      }
+
       /***/
     }
   },
Diff for link-17a206a..df2421f5d.js
@@ -80,62 +80,9 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       var _router2 = __webpack_require__("nOHt");
 
-      var cachedObserver;
-      var listeners = new Map();
-      var IntersectionObserver = true ? window.IntersectionObserver : undefined;
-      var prefetched = {};
-
-      function getObserver() {
-        // Return shared instance of IntersectionObserver if already created
-        if (cachedObserver) {
-          return cachedObserver;
-        } // Only create shared IntersectionObserver if supported in browser
-
-        if (!IntersectionObserver) {
-          return undefined;
-        }
-
-        return (cachedObserver = new IntersectionObserver(
-          function(entries) {
-            entries.forEach(function(entry) {
-              if (!listeners.has(entry.target)) {
-                return;
-              }
-
-              var cb = listeners.get(entry.target);
-
-              if (entry.isIntersecting || entry.intersectionRatio > 0) {
-                cachedObserver.unobserve(entry.target);
-                listeners["delete"](entry.target);
-                cb();
-              }
-            });
-          },
-          {
-            rootMargin: "200px"
-          }
-        ));
-      }
-
-      var listenToIntersections = function listenToIntersections(el, cb) {
-        var observer = getObserver();
-
-        if (!observer) {
-          return function() {};
-        }
-
-        observer.observe(el);
-        listeners.set(el, cb);
-        return function() {
-          try {
-            observer.unobserve(el);
-          } catch (err) {
-            console.error(err);
-          }
+      var _useIntersection = __webpack_require__("vNVm");
 
-          listeners["delete"](el);
-        };
-      };
+      var prefetched = {};
 
       function prefetch(router, href, as, options) {
         if (false) {
@@ -255,37 +202,16 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
         var childRef = child && typeof child === "object" && child.ref;
 
-        var cleanup = _react["default"].useRef();
+        var _ref3 = (0, _useIntersection.useIntersection)({
+            rootMargin: "200px"
+          }),
+          _ref4 = _slicedToArray(_ref3, 2),
+          setIntersectionRef = _ref4[0],
+          isVisible = _ref4[1];
 
         var setRef = _react["default"].useCallback(
           function(el) {
-            // cleanup previous event handlers
-            if (cleanup.current) {
-              cleanup.current();
-              cleanup.current = undefined;
-            }
-
-            if (
-              p &&
-              IntersectionObserver &&
-              el &&
-              el.tagName &&
-              (0, _router.isLocalURL)(href)
-            ) {
-              // Join on an invalid URI character
-              var isPrefetched = prefetched[href + "%" + as];
-
-              if (!isPrefetched) {
-                cleanup.current = listenToIntersections(el, function() {
-                  prefetch(router, href, as, {
-                    locale:
-                      typeof locale !== "undefined"
-                        ? locale
-                        : router && router.locale
-                  });
-                });
-              }
-            }
+            setIntersectionRef(el);
 
             if (childRef) {
               if (typeof childRef === "function") childRef(el);
@@ -294,9 +220,26 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
               }
             }
           },
-          [p, childRef, href, as, router, locale]
+          [childRef, setIntersectionRef]
         );
 
+        (0, _react.useEffect)(
+          function() {
+            var shouldPrefetch =
+              isVisible && p && (0, _router.isLocalURL)(href);
+            var isPrefetched = prefetched[href + "%" + as];
+
+            if (shouldPrefetch && !isPrefetched) {
+              prefetch(router, href, as, {
+                locale:
+                  typeof locale !== "undefined"
+                    ? locale
+                    : router && router.locale
+              });
+            }
+          },
+          [as, href, isVisible, locale, p, router]
+        );
         var childProps = {
           ref: setRef,
           onClick: function onClick(e) {
@@ -351,6 +294,116 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
       var _default = Link;
       exports["default"] = _default;
 
+      /***/
+    },
+
+    /***/ vNVm: /***/ function(module, exports, __webpack_require__) {
+      "use strict";
+
+      var _slicedToArray = __webpack_require__("J4zp");
+
+      exports.__esModule = true;
+      exports.useIntersection = useIntersection;
+
+      var _react = __webpack_require__("q1tI");
+
+      var hasIntersectionObserver = typeof IntersectionObserver !== "undefined";
+
+      function useIntersection(_ref) {
+        var rootMargin = _ref.rootMargin,
+          disabled = _ref.disabled;
+        var isDisabled = disabled || !hasIntersectionObserver;
+        var unobserve = (0, _react.useRef)();
+
+        var _ref2 = (0, _react.useState)(false),
+          _ref3 = _slicedToArray(_ref2, 2),
+          visible = _ref3[0],
+          setVisible = _ref3[1];
+
+        var setRef = (0, _react.useCallback)(
+          function(el) {
+            if (unobserve.current) {
+              unobserve.current();
+              unobserve.current = undefined;
+            }
+
+            if (isDisabled || visible) return;
+
+            if (el && el.tagName) {
+              unobserve.current = observe(
+                el,
+                function(isVisible) {
+                  return isVisible && setVisible(isVisible);
+                },
+                {
+                  rootMargin: rootMargin
+                }
+              );
+            }
+          },
+          [isDisabled, rootMargin, visible]
+        );
+        (0, _react.useEffect)(
+          function() {
+            if (!hasIntersectionObserver) {
+              if (!visible) setVisible(true);
+            }
+          },
+          [visible]
+        );
+        return [setRef, visible];
+      }
+
+      function observe(element, callback, options) {
+        var _createObserver = createObserver(options),
+          id = _createObserver.id,
+          observer = _createObserver.observer,
+          elements = _createObserver.elements;
+
+        elements.set(element, callback);
+        observer.observe(element);
+        return function unobserve() {
+          observer.unobserve(element); // Destroy observer when there's nothing left to watch:
+
+          if (elements.size === 0) {
+            observer.disconnect();
+            observers["delete"](id);
+          }
+        };
+      }
+
+      var observers = new Map();
+
+      function createObserver(options) {
+        var id = options.rootMargin || "";
+        var instance = observers.get(id);
+
+        if (instance) {
+          return instance;
+        }
+
+        var elements = new Map();
+        var observer = new IntersectionObserver(function(entries) {
+          entries.forEach(function(entry) {
+            var callback = elements.get(entry.target);
+            var isVisible = entry.isIntersecting || entry.intersectionRatio > 0;
+
+            if (callback && isVisible) {
+              callback(isVisible);
+            }
+          });
+        }, options);
+        observers.set(
+          id,
+          (instance = {
+            id: id,
+            observer: observer,
+            elements: elements
+          })
+        );
+        return instance;
+      }
+
       /***/
     }
   },
Diff for link.html
@@ -36,7 +36,7 @@
     />
     <link
       rel="preload"
-      href="/_next/static/chunks/pages/link-16d8635e170e01799dc1.module.js"
+      href="/_next/static/chunks/pages/link-ec1fbf762c0209fe4b72.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -150,13 +150,13 @@
       type="module"
     ></script>
     <script
-      src="/_next/static/chunks/pages/link-17a206ac5f5df2421f5d.js"
+      src="/_next/static/chunks/pages/link-fcaf825022c62b02057c.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/pages/link-16d8635e170e01799dc1.module.js"
+      src="/_next/static/chunks/pages/link-ec1fbf762c0209fe4b72.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary Timer/next.js hotfix/link-updating Change
buildDuration 16.8s 16.8s -22ms
nodeModulesSize 84.9 MB 84.9 MB -1.11 kB
Client Bundles (main, webpack, commons)
vercel/next.js canary Timer/next.js hotfix/link-updating Change
677f882d2ed8..7d3b.js gzip 11.3 kB 11.3 kB
framework.HASH.js gzip 39 kB 39 kB
main-d942eb5..343f.js gzip 7.4 kB 7.4 kB
webpack-e067..f178.js gzip 751 B 751 B
Overall change 58.4 kB 58.4 kB
Client Bundles (main, webpack, commons) Modern
vercel/next.js canary Timer/next.js hotfix/link-updating Change
677f882d2ed8..dule.js gzip 7.04 kB 7.04 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-34e64ff..dule.js gzip 6.39 kB 6.39 kB
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 53.1 kB 53.1 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js hotfix/link-updating Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_app-7231d4b..5856.js gzip 1.28 kB 1.28 kB
_error-fca3d..2eb1.js gzip 3.44 kB 3.44 kB
hooks-d4591d..e7c2.js gzip 887 B 887 B
index-17468f..5d83.js gzip 227 B 227 B
link-03d24c6..d1c7.js gzip 1.35 kB N/A N/A
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
link-8a9d104..0e42.js gzip N/A 1.55 kB N/A
Overall change 7.75 kB 7.96 kB ⚠️ +206 B
Client Pages Modern Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..dule.js gzip 2.29 kB 2.29 kB
hooks-cbf13f..dule.js gzip 387 B 387 B
index-b9a643..dule.js gzip 226 B 226 B
link-dfbbfbd..dule.js gzip 1.32 kB N/A N/A
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
link-d1cfb87..dule.js gzip N/A 1.49 kB N/A
Overall change 5.41 kB 5.59 kB ⚠️ +178 B
Client Build Manifests
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 330 B 330 B
Overall change 652 B 652 B
Serverless bundles Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/link-updating Change
_error.js 914 kB 914 kB
404.html 4.73 kB 4.73 kB
hooks.html 3.92 kB 3.92 kB
index.js 914 kB 914 kB
link.js 962 kB 963 kB ⚠️ +906 B
routerDirect.js 956 kB 956 kB
withRouter.js 956 kB 956 kB
Overall change 4.71 MB 4.71 MB ⚠️ +906 B
Commit: e4bb84c

@vercel vercel bot temporarily deployed to Preview November 6, 2020 22:57 Inactive
@kodiakhq kodiakhq bot merged commit c8fa284 into vercel:canary Nov 6, 2020
@dohomi
Copy link

dohomi commented Nov 7, 2020

@Timer would it be possible to make use of useIntersection in our own code as well? That would be a nice addition to NextJS. Beside that it would be great if the settings of rootMargin could be overwritten inside of next.config.js

@ijjk ijjk deleted the hotfix/link-updating branch November 7, 2020 00:51
@vercel vercel locked as resolved and limited conversation to collaborators Jan 29, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Prop updates for next/image doesn't work [next/image] Image doesn't update on page change
5 participants