Skip to content

Commit 569dc6e

Browse files
committed
feat: react18 legacy mode & react17 support
1 parent ef8ab7d commit 569dc6e

File tree

7 files changed

+83
-28
lines changed

7 files changed

+83
-28
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,11 @@ export default defineConfig({
609609
})
610610
```
611611

612+
### React17 Support
613+
614+
- for react18, with flag `useReact17: true`, it will use the legacy `render` and `hydrate` methods.
615+
- for react17, on top of above, you will need minor update to react and react-dom [example](https://github.com/jesse23/webpack-test-bed/blob/main/scripts/define-react-exports.js) to polyfill the mjs import and the `react-dom/client`.
616+
612617
## Roadmap
613618

614619
- [x] Preload assets

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@
8585
"beasties": "^0.1.0",
8686
"critters": "^0.0.24",
8787
"prettier": "*",
88-
"react": "^18.0.0",
89-
"react-dom": "^18.0.0",
88+
"react": "^17.0.2||^18.0.0",
89+
"react-dom": "^17.0.2||^18.0.0",
9090
"react-router-dom": "^6.14.1",
9191
"styled-components": "^6.0.0",
9292
"vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0"

src/client/index.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react'
22
import { createRoot as ReactDOMCreateRoot, hydrateRoot } from 'react-dom/client'
3+
import { hydrate, render } from 'react-dom'
34
import { HelmetProvider } from 'react-helmet-async'
45
import { RouterProvider, createBrowserRouter, matchRoutes } from 'react-router-dom'
56
import type { RouteRecord, RouterOptions, ViteReactSSGClientOptions, ViteReactSSGContext } from '../types'
@@ -117,15 +118,25 @@ export function ViteReactSSG(
117118
)
118119
const isSSR = document.querySelector('[data-server-rendered=true]') !== null
119120
if (!isSSR && process.env.NODE_ENV === 'development') {
120-
const root = ReactDOMCreateRoot(container)
121-
React.startTransition(() => {
122-
root.render(app)
123-
})
121+
if (options.useReact17) {
122+
render(app, container)
123+
}
124+
else {
125+
const root = ReactDOMCreateRoot(container)
126+
React.startTransition(() => {
127+
root.render(app)
128+
})
129+
}
124130
}
125131
else {
126-
React.startTransition(() => {
127-
hydrateRoot(container, app)
128-
})
132+
if (options.useReact17) {
133+
hydrate(app, container)
134+
}
135+
else {
136+
React.startTransition(() => {
137+
hydrateRoot(container, app)
138+
})
139+
}
129140
}
130141
})()
131142
}

src/client/single-page.tsx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { ReactNode } from 'react'
22
import { createRoot as ReactDOMCreateRoot, hydrateRoot } from 'react-dom/client'
33
import { HelmetProvider } from 'react-helmet-async'
44
import React from 'react'
5+
import { hydrate, render } from 'react-dom'
56
import type { ViteReactSSGClientOptions, ViteReactSSGContext } from '../types'
67
import { documentReady } from '../utils/document-ready'
78
import { deserializeState } from '../utils/state'
@@ -87,18 +88,28 @@ export function ViteReactSSG(
8788
<HelmetProvider>
8889
{App}
8990
</HelmetProvider>
90-
) as ReactNode
91+
) as JSX.Element
9192
const isSSR = document.querySelector('[data-server-rendered=true]') !== null
9293
if (!isSSR && process.env.NODE_ENV === 'development') {
93-
const root = ReactDOMCreateRoot(container)
94-
React.startTransition(() => {
95-
root.render(app)
96-
})
94+
if (options.useReact17) {
95+
render(app, container)
96+
}
97+
else {
98+
const root = ReactDOMCreateRoot(container)
99+
React.startTransition(() => {
100+
root.render(app)
101+
})
102+
}
97103
}
98104
else {
99-
React.startTransition(() => {
100-
hydrateRoot(container, app)
101-
})
105+
if (options.useReact17) {
106+
hydrate(app, container)
107+
}
108+
else {
109+
React.startTransition(() => {
110+
hydrateRoot(container, app)
111+
})
112+
}
102113
}
103114
})()
104115
}

src/client/tanstack.tsx

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import React from 'react'
2-
import { createRoot as ReactDOMCreateRoot, hydrateRoot } from 'react-dom/client'
32
import { HelmetProvider } from 'react-helmet-async'
43
import type { AnyContext, AnyRouter, LoaderFnContext } from '@tanstack/react-router'
54
import { RouterProvider } from '@tanstack/react-router'
65
import { Meta, StartClient } from '@tanstack/start'
6+
import { createRoot as ReactDOMCreateRoot, hydrateRoot } from 'react-dom/client'
7+
import { hydrate, render } from 'react-dom'
78
import type { ViteReactSSGContext as BaseViteReactSSGContext, ViteReactSSGClientOptions } from '../types'
89
import { documentReady } from '../utils/document-ready'
910
import { deserializeState } from '../utils/state'
@@ -172,24 +173,44 @@ export function Experimental_ViteReactSSG(
172173
const { router } = await createRoot(true)
173174
const isSSR = document.querySelector('[data-server-rendered=true]') !== null
174175
if (!isSSR && process.env.NODE_ENV === 'development') {
175-
const root = ReactDOMCreateRoot(container)
176-
React.startTransition(() => {
177-
root.render(
176+
if (options.useReact17) {
177+
render(
178178
<HelmetProvider>
179179
<RouterProvider router={router} />
180180
</HelmetProvider>,
181+
container,
181182
)
182-
})
183+
}
184+
else {
185+
const root = ReactDOMCreateRoot(container)
186+
React.startTransition(() => {
187+
root.render(
188+
<HelmetProvider>
189+
<RouterProvider router={router} />
190+
</HelmetProvider>,
191+
)
192+
})
193+
}
183194
}
184195
else {
185-
React.startTransition(() => {
186-
hydrateRoot(
187-
container,
196+
if (options.useReact17) {
197+
hydrate(
188198
<HelmetProvider>
189199
<StartClient router={router} />
190200
</HelmetProvider>,
201+
container,
191202
)
192-
})
203+
}
204+
else {
205+
React.startTransition(() => {
206+
hydrateRoot(
207+
container,
208+
<HelmetProvider>
209+
<StartClient router={router} />
210+
</HelmetProvider>,
211+
)
212+
})
213+
}
193214
}
194215
})()
195216
}

src/node/serverRenderer.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,20 @@
77

88
import { Writable } from 'node:stream'
99
import type { ReactNode } from 'react'
10-
import { renderToPipeableStream } from 'react-dom/server'
10+
import * as ReactDomServer from 'react-dom/server'
1111

1212
export async function renderStaticApp(app: ReactNode): Promise<string> {
13+
// fallback to react17
14+
if (!ReactDomServer.renderToPipeableStream) {
15+
return ReactDomServer.renderToString(app)
16+
};
17+
1318
// Inspired from
1419
// https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
1520
// https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/cache-dir/static-entry.js
1621
const writableStream = new WritableAsPromise()
1722

18-
const { pipe } = renderToPipeableStream(app, {
23+
const { pipe } = ReactDomServer.renderToPipeableStream(app, {
1924
onError(error) {
2025
writableStream.destroy(error as Error)
2126
},

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ export interface ViteReactSSGClientOptions {
168168
*/
169169
ssrWhenDev?: boolean
170170
getStyleCollector?: (() => StyleCollector | Promise<StyleCollector>) | null
171+
// true if the app is based on react17, or react18 with old API
172+
useReact17?: boolean
171173
}
172174

173175
interface CommonRouteOptions {

0 commit comments

Comments
 (0)