diff --git a/e2e-tests/development-runtime/cypress/integration/hot-reloading/gatsby-browser.js b/e2e-tests/development-runtime/cypress/integration/hot-reloading/gatsby-browser.js
new file mode 100644
index 0000000000000..f1007cde510df
--- /dev/null
+++ b/e2e-tests/development-runtime/cypress/integration/hot-reloading/gatsby-browser.js
@@ -0,0 +1,24 @@
+const TEST_ID = `gatsby-browser-hmr`
+
+describe(`hot reloading above page template (gatsby-browser)`, () => {
+ beforeEach(() => {
+ cy.visit(`/`).waitForRouteChange()
+ })
+ it(`displays placeholder content on launch`, () => {
+ cy.getTestElement(TEST_ID).should(
+ `contain.text`,
+ `%TEST_HMR_IN_GATSBY_BROWSER%`
+ )
+ })
+
+ it(`hot reloads with new content`, () => {
+ const text = `HMR_IN_GATSBY_BROWSER_WORKS`
+ cy.exec(
+ `npm run update -- --file src/wrap-root-element.js --replacements "TEST_HMR_IN_GATSBY_BROWSER:${text}"`
+ )
+
+ cy.waitForHmr()
+
+ cy.getTestElement(TEST_ID).should(`contain.text`, text)
+ })
+})
diff --git a/e2e-tests/development-runtime/gatsby-browser.js b/e2e-tests/development-runtime/gatsby-browser.js
index 1709cf2c75113..d8a65b3cc6595 100644
--- a/e2e-tests/development-runtime/gatsby-browser.js
+++ b/e2e-tests/development-runtime/gatsby-browser.js
@@ -1,4 +1,5 @@
-const Wrapper = require(`./src/wrap-root-element`).default
+import WrapRootElement from "./src/wrap-root-element"
+import * as React from "react"
if (typeof window !== `undefined`) {
window.___PageComponentLifecycleCallsLog = []
@@ -11,16 +12,17 @@ const addLogEntry = (action, location) => {
})
}
-exports.onPreRouteUpdate = ({ location }) => {
+export const onPreRouteUpdate = ({ location }) => {
addLogEntry(`onPreRouteUpdate`, location)
}
-exports.onRouteUpdate = ({ location }) => {
+export const onRouteUpdate = ({ location }) => {
addLogEntry(`onRouteUpdate`, location)
}
-
-exports.onPrefetchPathname = ({ pathname }) => {
+export const onPrefetchPathname = ({ pathname }) => {
addLogEntry(`onPrefetchPathname`, pathname)
}
-exports.wrapRootElement = Wrapper
+export const wrapRootElement = ({ element }) => (
+
+)
diff --git a/e2e-tests/development-runtime/src/wrap-root-element.js b/e2e-tests/development-runtime/src/wrap-root-element.js
index fb0a02975b6d9..a15637a9d2710 100644
--- a/e2e-tests/development-runtime/src/wrap-root-element.js
+++ b/e2e-tests/development-runtime/src/wrap-root-element.js
@@ -22,6 +22,9 @@ const WrapRootElement = ({ element }) => (
StaticQuery in wrapRootElement test (should show site title):
{title}
+
+ %TEST_HMR_IN_GATSBY_BROWSER%
+
>
)}
diff --git a/packages/gatsby/cache-dir/app.js b/packages/gatsby/cache-dir/app.js
index 3676dd72be5e0..569cdb9d751e3 100644
--- a/packages/gatsby/cache-dir/app.js
+++ b/packages/gatsby/cache-dir/app.js
@@ -13,16 +13,15 @@ import asyncRequires from "$virtual/async-requires"
// Generated during bootstrap
import matchPaths from "$virtual/match-paths.json"
import { LoadingIndicatorEventHandler } from "./loading-indicator"
-
+import Root from "./root"
+import { init as navigationInit } from "./navigation"
// ensure in develop we have at least some .css (even if it's empty).
// this is so there is no warning about not matching content-type when site doesn't include any regular css (for example when css-in-js is used)
// this also make sure that if all css is removed in develop we are not left with stale commons.css that have stale content
import "./blank.css"
-// Enable fast-refresh for virtual sync-requires
-module.hot.accept(`$virtual/async-requires`, () => {
- // Manually reload
-})
+// Enable fast-refresh for virtual sync-requires and gatsby-browser
+module.hot.accept([`$virtual/async-requires`, `./api-runner-browser`])
window.___emitter = emitter
@@ -160,8 +159,8 @@ apiRunnerAsync(`onClientEntry`).then(() => {
loader.loadPage(`/404.html`),
loader.loadPage(window.location.pathname),
]).then(() => {
- const preferDefault = m => (m && m.default) || m
- const Root = preferDefault(require(`./root`))
+ navigationInit()
+
domReady(() => {
if (dismissLoadingIndicator) {
dismissLoadingIndicator()
diff --git a/packages/gatsby/cache-dir/root.js b/packages/gatsby/cache-dir/root.js
index 95e541de4084b..09220ca79fcf7 100644
--- a/packages/gatsby/cache-dir/root.js
+++ b/packages/gatsby/cache-dir/root.js
@@ -2,19 +2,13 @@ import React from "react"
import { Router, Location, BaseContext } from "@reach/router"
import { ScrollContext } from "gatsby-react-router-scroll"
-import {
- shouldUpdateScroll,
- init as navigationInit,
- RouteUpdates,
-} from "./navigation"
+import { shouldUpdateScroll, RouteUpdates } from "./navigation"
import { apiRunner } from "./api-runner-browser"
import loader from "./loader"
import { PageQueryStore, StaticQueryStore } from "./query-result-store"
import EnsureResources from "./ensure-resources"
import FastRefreshOverlay from "./fast-refresh-overlay"
-navigationInit()
-
// In gatsby v2 if Router is used in page using matchPaths
// paths need to contain full path.
// For example:
@@ -103,7 +97,7 @@ const Root = () => (
)
// Let site, plugins wrap the site e.g. for Redux.
-const WrappedRoot = apiRunner(
+const rootWrappedWithWrapRootElement = apiRunner(
`wrapRootElement`,
{ element: },
,
@@ -112,8 +106,12 @@ const WrappedRoot = apiRunner(
}
).pop()
-export default () => (
-
- {WrappedRoot}
-
-)
+function RootWrappedWithOverlayAndProvider() {
+ return (
+
+ {rootWrappedWithWrapRootElement}
+
+ )
+}
+
+export default RootWrappedWithOverlayAndProvider
diff --git a/packages/gatsby/src/utils/webpack-utils.ts b/packages/gatsby/src/utils/webpack-utils.ts
index 53c973731133d..47ecafe3f96f6 100644
--- a/packages/gatsby/src/utils/webpack-utils.ts
+++ b/packages/gatsby/src/utils/webpack-utils.ts
@@ -731,13 +731,32 @@ export const createWebpackUtils = (
}
): CssMinimizerPlugin => new CssMinimizerPlugin(options)
- plugins.fastRefresh = (): Plugin =>
- new ReactRefreshWebpackPlugin({
+ plugins.fastRefresh = ({ modulesThatUseGatsby }): Plugin => {
+ const regExpToHack = /node_modules/
+ regExpToHack.test = (modulePath: string): boolean => {
+ // when it's not coming from node_modules we treat it as a source file.
+ if (!vendorRegex.test(modulePath)) {
+ return false
+ }
+
+ // If the module uses Gatsby as a dependency
+ // we want to treat it as src because of shadowing
+ return !modulesThatUseGatsby.some(module =>
+ modulePath.includes(module.path)
+ )
+ }
+
+ return new ReactRefreshWebpackPlugin({
overlay: {
sockIntegration: `whm`,
module: path.join(__dirname, `fast-refresh-module`),
},
+ // this is a bit hacky - exclude expect string or regexp or array of those
+ // so this is tricking ReactRefreshWebpackPlugin with providing regexp with
+ // overwritten .test method
+ exclude: regExpToHack,
})
+ }
plugins.extractText = (options: any): Plugin =>
new MiniCssExtractPlugin({
diff --git a/packages/gatsby/src/utils/webpack.config.js b/packages/gatsby/src/utils/webpack.config.js
index af93ae208e58c..18b21a4e15e3b 100644
--- a/packages/gatsby/src/utils/webpack.config.js
+++ b/packages/gatsby/src/utils/webpack.config.js
@@ -216,7 +216,7 @@ module.exports = async (
case `develop`: {
configPlugins = configPlugins
.concat([
- plugins.fastRefresh(),
+ plugins.fastRefresh({ modulesThatUseGatsby }),
plugins.hotModuleReplacement(),
plugins.noEmitOnErrors(),
plugins.eslintGraphqlSchemaReload(),