Skip to content

Commit

Permalink
fix(hmr): accept hot updates for modules above page templates (#29752) (
Browse files Browse the repository at this point in the history
#29835)

* fix(hmr): accept hot updates for modules above page templates

* actually use fast-refresh for gatsby-browser and shadowed theme-ui config

* exclude actually need to be instance of RegExp

* tmp

* more tmp

* init navigation after loader is set

* cleanup tmp code

* init navigation after resources for current page are loaded

* remove commented out code

* revert devtool change

(cherry picked from commit 55778eb)

Co-authored-by: Michal Piechowiak <misiek.piechowiak@gmail.com>
  • Loading branch information
GatsbyJS Bot and pieh committed Feb 28, 2021
1 parent b8d21f8 commit 34f5b8c
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 29 deletions.
@@ -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)
})
})
14 changes: 8 additions & 6 deletions 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 = []
Expand All @@ -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 }) => (
<WrapRootElement element={element} />
)
3 changes: 3 additions & 0 deletions e2e-tests/development-runtime/src/wrap-root-element.js
Expand Up @@ -22,6 +22,9 @@ const WrapRootElement = ({ element }) => (
<div>
StaticQuery in wrapRootElement test (should show site title):
<span data-testid="wrap-root-element-result">{title}</span>
<div data-testid="gatsby-browser-hmr">
%TEST_HMR_IN_GATSBY_BROWSER%
</div>
</div>
</>
)}
Expand Down
13 changes: 6 additions & 7 deletions packages/gatsby/cache-dir/app.js
Expand Up @@ -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

Expand Down Expand Up @@ -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()
Expand Down
24 changes: 11 additions & 13 deletions packages/gatsby/cache-dir/root.js
Expand Up @@ -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:
Expand Down Expand Up @@ -103,7 +97,7 @@ const Root = () => (
)

// Let site, plugins wrap the site e.g. for Redux.
const WrappedRoot = apiRunner(
const rootWrappedWithWrapRootElement = apiRunner(
`wrapRootElement`,
{ element: <Root /> },
<Root />,
Expand All @@ -112,8 +106,12 @@ const WrappedRoot = apiRunner(
}
).pop()

export default () => (
<FastRefreshOverlay>
<StaticQueryStore>{WrappedRoot}</StaticQueryStore>
</FastRefreshOverlay>
)
function RootWrappedWithOverlayAndProvider() {
return (
<FastRefreshOverlay>
<StaticQueryStore>{rootWrappedWithWrapRootElement}</StaticQueryStore>
</FastRefreshOverlay>
)
}

export default RootWrappedWithOverlayAndProvider
23 changes: 21 additions & 2 deletions packages/gatsby/src/utils/webpack-utils.ts
Expand Up @@ -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({
Expand Down
2 changes: 1 addition & 1 deletion packages/gatsby/src/utils/webpack.config.js
Expand Up @@ -216,7 +216,7 @@ module.exports = async (
case `develop`: {
configPlugins = configPlugins
.concat([
plugins.fastRefresh(),
plugins.fastRefresh({ modulesThatUseGatsby }),
plugins.hotModuleReplacement(),
plugins.noEmitOnErrors(),
plugins.eslintGraphqlSchemaReload(),
Expand Down

0 comments on commit 34f5b8c

Please sign in to comment.