Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit 72e5611

Browse files
huozhitimneutkenskodiakhq[bot]
authored
Support new hydrate API in latest react 18 alpha release (vercel#26664)
* fix: react 18 new hydration API * support react 18 * compat latest react only, fix resolved version * fix tests * Some changes based on reactwg/react-18#5 * fix test Co-authored-by: Tim Neutkens <[email protected]> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent 53f6e8d commit 72e5611

File tree

11 files changed

+83
-11
lines changed

11 files changed

+83
-11
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@
115115
"pretty-bytes": "5.3.0",
116116
"pretty-ms": "7.0.0",
117117
"react": "17.0.2",
118+
"react-18": "npm:react@next",
118119
"react-dom": "17.0.2",
120+
"react-dom-18": "npm:react-dom@next",
119121
"react-ssr-prepass": "1.0.8",
120122
"release": "6.3.0",
121123
"request-promise-core": "1.1.2",

packages/next/client/index.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,8 @@ export function renderError(renderErrorProps: RenderErrorProps): Promise<any> {
489489
}
490490

491491
let reactRoot: any = null
492-
let shouldHydrate: boolean = typeof ReactDOM.hydrate === 'function'
492+
// On initial render a hydrate should always happen
493+
let shouldHydrate: boolean = true
493494

494495
function renderReactElement(
495496
domEl: HTMLElement,
@@ -503,12 +504,13 @@ function renderReactElement(
503504
const reactEl = fn(shouldHydrate ? markHydrateComplete : markRenderComplete)
504505
if (process.env.__NEXT_REACT_ROOT) {
505506
if (!reactRoot) {
506-
reactRoot = (ReactDOM as any).createRoot(domEl, {
507-
hydrate: shouldHydrate,
508-
})
507+
// Unlike with createRoot, you don't need a separate root.render() call here
508+
reactRoot = (ReactDOM as any).hydrateRoot(domEl, reactEl)
509+
// TODO: Remove shouldHydrate variable when React 18 is stable as it can depend on `reactRoot` existing
510+
shouldHydrate = false
511+
} else {
512+
reactRoot.render(reactEl)
509513
}
510-
reactRoot.render(reactEl)
511-
shouldHydrate = false
512514
} else {
513515
// The check for `.hydrate` is there to support React alternatives like preact
514516
if (shouldHydrate) {

test/integration/build-output/test/index.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ describe('Build Output', () => {
129129
expect(parseFloat(err404Size)).toBeCloseTo(gz ? 3.17 : 8.51, 1)
130130
expect(err404Size.endsWith('kB')).toBe(true)
131131

132-
expect(parseFloat(err404FirstLoad)).toBeCloseTo(gz ? 66.9 : 205, 1)
132+
expect(parseFloat(err404FirstLoad)).toBeCloseTo(gz ? 66.9 : 204, 1)
133133
expect(err404FirstLoad.endsWith('kB')).toBe(true)
134134

135135
expect(parseFloat(sharedByAll)).toBeCloseTo(gz ? 63.7 : 196, 1)

test/integration/fallback-modules/test/index.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ describe('Build Output', () => {
4949
expect(indexSize.endsWith('kB')).toBe(true)
5050

5151
expect(parseFloat(indexFirstLoad)).toBeLessThanOrEqual(
52-
process.env.NEXT_PRIVATE_TEST_WEBPACK4_MODE ? 68 : 67.9
52+
process.env.NEXT_PRIVATE_TEST_WEBPACK4_MODE ? 68.1 : 67.9
5353
)
5454
expect(parseFloat(indexFirstLoad)).toBeGreaterThanOrEqual(60)
5555
expect(indexFirstLoad.endsWith('kB')).toBe(true)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module.exports = {
2+
webpack(config) {
3+
const { alias } = config.resolve
4+
// FIXME: resolving react/jsx-runtime https://github.com/facebook/react/issues/20235
5+
alias['react/jsx-dev-runtime'] = require.resolve('react/jsx-dev-runtime.js')
6+
alias['react/jsx-runtime'] = require.resolve('react/jsx-runtime.js')
7+
8+
// Use react 18
9+
alias['react'] = require.resolve('react-18')
10+
alias['react-dom'] = require.resolve('react-dom-18')
11+
12+
return config
13+
},
14+
}

test/integration/react-18/prerelease/node_modules/react-dom/index.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

test/integration/react-18/prerelease/node_modules/react-dom/package.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"dependencies": {
3+
"react": "*",
34
"react-dom": "*"
45
}
56
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
export default function Index() {
2+
if (typeof window !== 'undefined') {
3+
window.didHydrate = true
4+
}
25
return <p>Hello</p>
36
}

test/integration/react-18/test/index.test.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
/* eslint-env jest */
22

3-
import { findPort, killApp, launchApp, runNextCommand } from 'next-test-utils'
43
import { join } from 'path'
4+
import fs from 'fs-extra'
5+
import webdriver from 'next-webdriver'
6+
import {
7+
findPort,
8+
killApp,
9+
launchApp,
10+
runNextCommand,
11+
nextBuild,
12+
nextStart,
13+
} from 'next-test-utils'
514

615
jest.setTimeout(1000 * 60 * 5)
716

@@ -67,4 +76,21 @@ describe('React 18 Support', () => {
6776
expect(output).toMatch(UNSUPPORTED_PRERELEASE)
6877
})
6978
})
79+
80+
describe('hydration', () => {
81+
const appDir = join(__dirname, '../prerelease')
82+
let app
83+
let appPort
84+
beforeAll(async () => {
85+
await fs.remove(join(appDir, '.next'))
86+
await nextBuild(appDir, [dirPrerelease])
87+
appPort = await findPort()
88+
app = await nextStart(appDir, appPort)
89+
})
90+
afterAll(async () => await killApp(app))
91+
it('hydrates correctly for normal page', async () => {
92+
const browser = await webdriver(appPort, '/')
93+
expect(await browser.eval('window.didHydrate')).toBe(true)
94+
})
95+
})
7096
})

0 commit comments

Comments
 (0)