Skip to content

Commit 3457163

Browse files
wardpeetLekoArts
andauthored
feat(gatsby): enable concurrent features (#31394)
Co-authored-by: Lennart <[email protected]>
1 parent 5ba1ac2 commit 3457163

File tree

3 files changed

+122
-32
lines changed

3 files changed

+122
-32
lines changed

packages/gatsby/cache-dir/app.js

+71-23
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ module.hot.accept([
3131

3232
window.___emitter = emitter
3333

34+
if (
35+
process.env.GATSBY_EXPERIMENTAL_CONCURRENT_FEATURES &&
36+
!ReactDOM.unstable_createRoot
37+
) {
38+
throw new Error(
39+
`The GATSBY_EXPERIMENTAL_CONCURRENT_FEATURES flag is not compatible with your React version. Please install "[email protected]" and "[email protected]" or higher.`
40+
)
41+
}
42+
3443
const loader = new DevLoader(asyncRequires, matchPaths)
3544
setLoader(loader)
3645
loader.setApiRunner(apiRunner)
@@ -127,12 +136,25 @@ apiRunnerAsync(`onClientEntry`).then(() => {
127136
const rootElement = document.getElementById(`___gatsby`)
128137

129138
const focusEl = document.getElementById(`gatsby-focus-wrapper`)
139+
140+
// Client only pages have any empty body so we just do a normal
141+
// render to avoid React complaining about hydration mis-matches.
142+
let defaultRenderer = ReactDOM.render
143+
if (focusEl && focusEl.children.length) {
144+
if (
145+
process.env.GATSBY_EXPERIMENTAL_CONCURRENT_FEATURES &&
146+
ReactDOM.unstable_createRoot
147+
) {
148+
defaultRenderer = ReactDOM.unstable_createRoot
149+
} else {
150+
defaultRenderer = ReactDOM.hydrate
151+
}
152+
}
153+
130154
const renderer = apiRunner(
131155
`replaceHydrateFunction`,
132156
undefined,
133-
// Client only pages have any empty body so we just do a normal
134-
// render to avoid React complaining about hydration mis-matches.
135-
focusEl && focusEl.children.length > 0 ? ReactDOM.hydrate : ReactDOM.render
157+
defaultRenderer
136158
)[0]
137159

138160
let dismissLoadingIndicator
@@ -166,31 +188,57 @@ apiRunnerAsync(`onClientEntry`).then(() => {
166188
]).then(() => {
167189
navigationInit()
168190

191+
function onHydrated() {
192+
apiRunner(`onInitialClientRender`)
193+
194+
// Render query on demand overlay
195+
if (
196+
process.env.GATSBY_QUERY_ON_DEMAND_LOADING_INDICATOR &&
197+
process.env.GATSBY_QUERY_ON_DEMAND_LOADING_INDICATOR === `true`
198+
) {
199+
const indicatorMountElement = document.createElement(`div`)
200+
indicatorMountElement.setAttribute(
201+
`id`,
202+
`query-on-demand-indicator-element`
203+
)
204+
document.body.append(indicatorMountElement)
205+
206+
if (renderer === ReactDOM.unstable_createRoot) {
207+
renderer(indicatorMountElement).render(
208+
<LoadingIndicatorEventHandler />
209+
)
210+
} else {
211+
renderer(<LoadingIndicatorEventHandler />, indicatorMountElement)
212+
}
213+
}
214+
}
215+
216+
function App() {
217+
const onClientEntryRanRef = React.useRef(false)
218+
219+
React.useEffect(() => {
220+
if (!onClientEntryRanRef.current) {
221+
onClientEntryRanRef.current = true
222+
223+
onHydrated()
224+
}
225+
}, [])
226+
227+
return <Root />
228+
}
229+
169230
domReady(() => {
170231
if (dismissLoadingIndicator) {
171232
dismissLoadingIndicator()
172233
}
173234

174-
renderer(<Root />, rootElement, () => {
175-
apiRunner(`onInitialClientRender`)
176-
177-
// Render query on demand overlay
178-
if (
179-
process.env.GATSBY_QUERY_ON_DEMAND_LOADING_INDICATOR &&
180-
process.env.GATSBY_QUERY_ON_DEMAND_LOADING_INDICATOR === `true`
181-
) {
182-
const indicatorMountElement = document.createElement(`div`)
183-
indicatorMountElement.setAttribute(
184-
`id`,
185-
`query-on-demand-indicator-element`
186-
)
187-
document.body.append(indicatorMountElement)
188-
ReactDOM.render(
189-
<LoadingIndicatorEventHandler />,
190-
indicatorMountElement
191-
)
192-
}
193-
})
235+
if (renderer === ReactDOM.unstable_createRoot) {
236+
renderer(rootElement, {
237+
hydrate: true,
238+
}).render(<App />)
239+
} else {
240+
renderer(<App />, rootElement)
241+
}
194242
})
195243
})
196244
})

packages/gatsby/cache-dir/production-app.js

+27-9
Original file line numberDiff line numberDiff line change
@@ -175,24 +175,42 @@ apiRunnerAsync(`onClientEntry`).then(() => {
175175
}
176176
).pop()
177177

178-
const App = () => <GatsbyRoot>{SiteRoot}</GatsbyRoot>
178+
const App = function App() {
179+
const onClientEntryRanRef = React.useRef(false)
180+
181+
React.useEffect(() => {
182+
if (!onClientEntryRanRef.current) {
183+
onClientEntryRanRef.current = true
184+
performance.mark(`onInitialClientRender`)
185+
186+
apiRunner(`onInitialClientRender`)
187+
}
188+
}, [])
189+
190+
return <GatsbyRoot>{SiteRoot}</GatsbyRoot>
191+
}
179192

180193
const renderer = apiRunner(
181194
`replaceHydrateFunction`,
182195
undefined,
183-
ReactDOM.hydrate
196+
process.env.GATSBY_EXPERIMENTAL_CONCURRENT_FEATURES
197+
? ReactDOM.unstable_createRoot
198+
: ReactDOM.hydrate
184199
)[0]
185200

186201
domReady(() => {
187-
renderer(
188-
<App />,
202+
const container =
189203
typeof window !== `undefined`
190204
? document.getElementById(`___gatsby`)
191-
: void 0,
192-
() => {
193-
apiRunner(`onInitialClientRender`)
194-
}
195-
)
205+
: null
206+
207+
if (renderer === ReactDOM.unstable_createRoot) {
208+
renderer(container, {
209+
hydrate: true,
210+
}).render(<App />)
211+
} else {
212+
renderer(<App />, container)
213+
}
196214
})
197215
})
198216
})

packages/gatsby/src/utils/flags.ts

+24
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,30 @@ const activeFlags: Array<IFlag> = [
176176
umbrellaIssue: `https://gatsby.dev/functions-feedback`,
177177
testFitness: (): fitnessEnum => true,
178178
},
179+
{
180+
name: `CONCURRENT_FEATURES`,
181+
env: `GATSBY_EXPERIMENTAL_CONCURRENT_FEATURES`,
182+
command: `all`,
183+
telemetryId: `ConcurrentFeatures`,
184+
experimental: true,
185+
description: `Enable React's concurrent features`,
186+
// umbrellaIssue: `https://gatsby.dev/concurrent-features`,
187+
testFitness: (): fitnessEnum => {
188+
// Because of this, this flag will never show up
189+
const semverConstraints = {
190+
react: `^0.0.0-experimental-57768ef90`,
191+
"react-dom": `^0.0.0-experimental-57768ef90`,
192+
}
193+
194+
if (satisfiesSemvers(semverConstraints)) {
195+
return true
196+
} else {
197+
// react & react-dom is either not installed or not new enough so
198+
// just disable — it won't work anyways.
199+
return false
200+
}
201+
},
202+
},
179203
]
180204

181205
export default activeFlags

0 commit comments

Comments
 (0)