diff --git a/packages/gatsby-plugin-guess-js/src/gatsby-browser.js b/packages/gatsby-plugin-guess-js/src/gatsby-browser.js index fba1406b3e146..1e16ce0079505 100644 --- a/packages/gatsby-plugin-guess-js/src/gatsby-browser.js +++ b/packages/gatsby-plugin-guess-js/src/gatsby-browser.js @@ -12,65 +12,18 @@ exports.onRouteUpdate = ({ location }) => { initialPath = location.pathname } -let chunksPromise -const chunks = () => { - if (!chunksPromise) { - chunksPromise = fetch(`${window.location.origin}/webpack.stats.json`).then( - res => res.json() - ) - } - - return chunksPromise -} - -let hasPrefetched = {} -const prefetch = url => { - if (hasPrefetched[url]) { - return - } - hasPrefetched[url] = true - const link = document.createElement(`link`) - link.setAttribute(`rel`, `prefetch`) - link.setAttribute(`href`, url) - const parentElement = - document.getElementsByTagName(`head`)[0] || - document.getElementsByName(`script`)[0].parentNode - parentElement.appendChild(link) -} - -exports.onPrefetchPathname = ({ pathPrefix }, pluginOptions) => { - if (process.env.NODE_ENV === `production`) { - const matchedPaths = Object.keys( - guess({ - path: window.location.pathname, - threshold: pluginOptions.minimumThreshold, - }) - ) - - // Don't prefetch from client for the initial path as we did that - // during SSR - if (notNavigated && initialPath === window.location.pathname) { - return - } - - if (matchedPaths.length > 0) { - matchedPaths.forEach(p => { - chunks(p).then(chunk => { - // eslint-disable-next-line - const page = ___loader.getPage(p) - if (!page) return - let resources = [] - if (chunk.assetsByChunkName[page.componentChunkName]) { - resources = resources.concat( - chunk.assetsByChunkName[page.componentChunkName] - ) - } - // eslint-disable-next-line - resources.push(`static/d/${___dataPaths[page.jsonName]}.json`) - // TODO add support for pathPrefix - resources.forEach(r => prefetch(`/${r}`)) - }) - }) - } - } +exports.onPrefetchPathname = ({ getResourcesForPathname }, pluginOptions) => { + if (process.env.NODE_ENV !== `production`) return + + const matchedPaths = Object.keys( + guess({ + path: window.location.pathname, + threshold: pluginOptions.minimumThreshold, + }) + ) + + // Don't prefetch from client for the initial path as we did that + // during SSR + if (!(notNavigated && initialPath === window.location.pathname)) + matchedPaths.forEach(getResourcesForPathname) } diff --git a/packages/gatsby-plugin-offline/src/gatsby-browser.js b/packages/gatsby-plugin-offline/src/gatsby-browser.js index fc3b495b9b274..a4e5a74f18ab9 100644 --- a/packages/gatsby-plugin-offline/src/gatsby-browser.js +++ b/packages/gatsby-plugin-offline/src/gatsby-browser.js @@ -3,7 +3,7 @@ exports.registerServiceWorker = () => true let swNotInstalled = true const prefetchedPathnames = [] -exports.onPrefetchPathname = ({ pathname }) => { +exports.onPostPrefetchPathname = ({ pathname }) => { // if SW is not installed, we need to record any prefetches // that happen so we can then add them to SW cache once installed if (swNotInstalled && `serviceWorker` in navigator) { diff --git a/packages/gatsby/cache-dir/app.js b/packages/gatsby/cache-dir/app.js index 74d4a0a788067..43195a2c59f5a 100644 --- a/packages/gatsby/cache-dir/app.js +++ b/packages/gatsby/cache-dir/app.js @@ -6,11 +6,12 @@ import { hot } from "react-hot-loader" import socketIo from "./socketIo" import emitter from "./emitter" import { apiRunner, apiRunnerAsync } from "./api-runner-browser" -import loader from "./loader" +import loader, { setApiRunnerForLoader } from "./loader" import syncRequires from "./sync-requires" import pages from "./pages.json" window.___emitter = emitter +setApiRunnerForLoader(apiRunner) // Let the site/plugins run code very early. apiRunnerAsync(`onClientEntry`).then(() => { diff --git a/packages/gatsby/cache-dir/loader.js b/packages/gatsby/cache-dir/loader.js index 1f0e1c7e6cc0b..8776fe0de6f0c 100644 --- a/packages/gatsby/cache-dir/loader.js +++ b/packages/gatsby/cache-dir/loader.js @@ -5,7 +5,6 @@ import prefetchHelper from "./prefetch" const preferDefault = m => (m && m.default) || m -let prefetcher let devGetPageData let inInitialRender = true let hasFetched = Object.create(null) @@ -147,6 +146,20 @@ const handleResourceLoadError = (path, message) => { } } +const onPrefetchPathname = pathname => { + if (!prefetchTriggered[pathname]) { + apiRunner(`onPrefetchPathname`, { pathname: pathname }) + prefetchTriggered[pathname] = true + } +} + +const onPostPrefetchPathname = pathname => { + if (!prefetchCompleted[pathname]) { + apiRunner(`onPostPrefetchPathname`, { pathname: pathname }) + prefetchCompleted[pathname] = true + } +} + // Note we're not actively using the path data atm. There // could be future optimizations however around trying to ensure // we load all resources for likely-to-be-visited paths. @@ -163,6 +176,7 @@ const sortResourcesByCount = (a, b) => { let findPage let pathScriptsCache = {} let prefetchTriggered = {} +let prefetchCompleted = {} let disableCorePrefetching = false const queue = { @@ -192,12 +206,7 @@ const queue = { // Tell plugins with custom prefetching logic that they should start // prefetching this path. - if (!prefetchTriggered[path]) { - apiRunner(`onPrefetchPathname`, { - pathname: path, - }) - prefetchTriggered[path] = true - } + onPrefetchPathname(path) // If a plugin has disabled core prefetching, stop now. if (disableCorePrefetching.some(a => a)) { @@ -236,6 +245,9 @@ const queue = { prefetchResource(page.componentChunkName) } + // Tell plugins the path has been successfully prefetched + onPostPrefetchPathname(path) + return true }, @@ -375,6 +387,9 @@ const queue = { } }) } + + // Tell plugins the path has been successfully prefetched + onPostPrefetchPathname(path) }), } diff --git a/packages/gatsby/cache-dir/navigation.js b/packages/gatsby/cache-dir/navigation.js index 660e796738fbf..fadd1c7685076 100644 --- a/packages/gatsby/cache-dir/navigation.js +++ b/packages/gatsby/cache-dir/navigation.js @@ -127,7 +127,6 @@ function init() { // Temp hack while awaiting https://github.com/reach/router/issues/119 window.__navigatingToLink = false - setApiRunnerForLoader(apiRunner) window.___loader = loader window.___push = to => navigate(to, { replace: false }) window.___replace = to => navigate(to, { replace: true }) diff --git a/packages/gatsby/cache-dir/production-app.js b/packages/gatsby/cache-dir/production-app.js index 0b4f1ad661009..47dd7d2c31ff8 100644 --- a/packages/gatsby/cache-dir/production-app.js +++ b/packages/gatsby/cache-dir/production-app.js @@ -14,7 +14,7 @@ import emitter from "./emitter" window.___emitter = emitter import PageRenderer from "./page-renderer" import asyncRequires from "./async-requires" -import loader from "./loader" +import loader, { setApiRunnerForLoader } from "./loader" import loadDirectlyOr404 from "./load-directly-or-404" import EnsureResources from "./ensure-resources" @@ -25,6 +25,7 @@ window.___loader = loader loader.addPagesArray([window.page]) loader.addDataPaths({ [window.page.jsonName]: window.dataPath }) loader.addProdRequires(asyncRequires) +setApiRunnerForLoader(apiRunner) navigationInit() diff --git a/packages/gatsby/src/utils/api-browser-docs.js b/packages/gatsby/src/utils/api-browser-docs.js index d55f36fc07c8f..5d8f21b29dbd0 100644 --- a/packages/gatsby/src/utils/api-browser-docs.js +++ b/packages/gatsby/src/utils/api-browser-docs.js @@ -159,11 +159,20 @@ exports.wrapRootElement = true * Called when prefetching for a pathname is triggered. Allows * for plugins with custom prefetching logic. * @param {object} $0 - * @param {object} $0.pathname The pathname whose resources should now be prefetched - * @param {object} $0.getResourcesForPathname Function for fetching resources related to pathname + * @param {string} $0.pathname The pathname whose resources should now be prefetched + * @param {function} $0.getResourcesForPathname Function for fetching resources related to pathname */ exports.onPrefetchPathname = true +/** + * Called when prefetching for a pathname is successful. Allows + * for plugins with custom prefetching logic. + * @param {object} $0 + * @param {string} $0.pathname The pathname whose resources have now been prefetched + * @param {function} $0.getResourceURLsForPathname Function for fetching URLs for resources related to the pathname + */ +exports.onPostPrefetchPathname = true + /** * Plugins can take over prefetching logic. If they do, they should call this * to disable the now duplicate core prefetching logic.