Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 18 support #166 #213

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
22 changes: 21 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,24 @@ dist-ssr
_*

**/worker/*
pnpm-lock.yaml
pnpm-lock.yaml

# Visual Studio cache/options directory
.vs/

# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace

# Local History for Visual Studio Code
.history/

# Built Visual Studio Code Extensions
*.vsix

# Ignore log files like yarn-error.log
*.log
4 changes: 2 additions & 2 deletions examples/react-apollo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"dependencies": {
"@apollo/client": "^3.3.18",
"graphql": "^15.5.0",
"react": "^17.0.0",
"react-dom": "^17.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-helmet-async": "^1.0.9",
"react-router-dom": "^6.2.2",
"styled-components": "^5.3.0"
Expand Down
4 changes: 2 additions & 2 deletions examples/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"serve:node": "node ../node-server/index react"
},
"dependencies": {
"react": "^17.0.0",
"react-dom": "^17.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-helmet-async": "^1.0.9",
"react-router-dom": "^6.2.2"
},
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
"@vitejs/plugin-react": "^3.0.0",
"@vitejs/plugin-vue": "^4.0.0",
"@vueuse/head": "0.x",
"react": "^17",
"react-dom": "^17",
"react": "^18",
"react-dom": "^18",
"react-helmet-async": "^1.0.0",
"react-router-dom": "^6",
"vite": "^4.0.0",
Expand Down Expand Up @@ -92,16 +92,16 @@
"connect": "^3.7.0",
"node-fetch": "^2.6.1",
"react-router-dom": "^6.2.2",
"react-ssr-prepass": "^1.4.0"
"react-ssr-prepass": "^1.5.0"
},
"devDependencies": {
"@types/connect": "^3.4.34",
"@types/express": "^4.17.13",
"@types/fs-extra": "^9.0.12",
"@types/node": "^16.4.7",
"@types/node-fetch": "^2.5.9",
"@types/react": "^17.0.40",
"@types/react-dom": "^17.0.13",
"@types/react": "18.2.7",
"@types/react-dom": "18.2.7",
"execa": "^5.1.1",
"express": "^4.17.1",
"fs-extra": "^10.0.0",
Expand Down
11 changes: 7 additions & 4 deletions src/react/components.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import {
useState,
useEffect,
createElement,
Fragment,
FunctionComponent,
PropsWithChildren,
ReactElement,
createElement,
createContext as reactCreateContext,
useContext as reactUseContext,
useEffect,
useState,
} from 'react'
import type { Context } from './types'

export const ClientOnly: FunctionComponent = ({ children }) => {
export const ClientOnly: FunctionComponent<PropsWithChildren> = ({
children,
}) => {
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true))

Expand Down
26 changes: 16 additions & 10 deletions src/react/entry-client.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import React, { ReactElement } from 'react'
import ReactDOM from 'react-dom'
import createClientContext from '../core/entry-client.js'
import { BrowserRouter, useNavigate } from 'react-router-dom'
import { createRoot, hydrateRoot } from 'react-dom/client'
import { HelmetProvider } from 'react-helmet-async'
import {
BrowserRouter,
BrowserRouterProps,
useNavigate,
} from 'react-router-dom'
import createClientContext from '../core/entry-client.js'
import { withoutSuffix } from '../utils/route'
import { createRouter } from './utils'
import type { ClientHandler, Context } from './types'
import { createRouter } from './utils'

import { provideContext } from './components.js'
export { ClientOnly, useContext } from './components.js'
Expand Down Expand Up @@ -53,8 +57,7 @@ export const viteSSR: ClientHandler = async function (
HelmetProvider,
{},
React.createElement(
// @ts-ignore
BrowserRouter,
BrowserRouter as React.FunctionComponent<BrowserRouterProps>,
{ basename: routeBase },
React.createElement(
React.Suspense,
Expand All @@ -70,13 +73,16 @@ export const viteSSR: ClientHandler = async function (
}

if (debug.mount !== false) {
// @ts-ignore
const el = document.getElementById(__CONTAINER_ID__)
const el = document.getElementById(__CONTAINER_ID__)!

styles && styles.cleanup && styles.cleanup()

// @ts-ignore
__DEV__ ? ReactDOM.render(app, el) : ReactDOM.hydrate(app, el)
if (__DEV__) {
const root = createRoot(el)
root.render(app)
} else {
hydrateRoot(el, app)
}
}
}

Expand Down
18 changes: 8 additions & 10 deletions src/react/entry-server.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import React, { ReactElement } from 'react'
import ssrPrepass from 'react-ssr-prepass'
import { renderToString } from 'react-dom/server.js'
import { StaticRouter } from 'react-router-dom/server'
import { renderToString } from 'react-dom/server'
import { HelmetProvider } from 'react-helmet-async'
import { getFullPath, withoutSuffix } from '../utils/route'
import { createRouter } from './utils'
import { StaticRouter, StaticRouterProps } from 'react-router-dom/server'
import ssrPrepass from 'react-ssr-prepass'
import coreViteSSR from '../core/entry-server.js'
import { getFullPath, withoutSuffix } from '../utils/route'
import type { Context, SsrHandler } from './types'
import { createRouter } from './utils'

import { provideContext } from './components.js'
export { ClientOnly, useContext } from './components.js'

let render: (element: ReactElement) => string | Promise<string> = renderToString
let render: (component: ReactElement) => string | Promise<string> =
renderToString

// @ts-ignore
if (__USE_APOLLO_RENDERER__) {
// Apollo does not support Suspense so it needs its own
// renderer in order to await for async queries.
// @ts-ignore
import('@apollo/client/react/ssr')
.then(({ renderToStringWithData }) => {
render = renderToStringWithData
Expand Down Expand Up @@ -62,8 +61,7 @@ const viteSSR: SsrHandler = function (
HelmetProvider,
{ context: helmetContext },
React.createElement(
// @ts-ignore
StaticRouter,
StaticRouter as React.FunctionComponent<StaticRouterProps>,
{ basename: routeBase, location: fullPath },
provideContext(React.createElement(App, context), context)
)
Expand Down
7 changes: 7 additions & 0 deletions src/react/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// <reference types="vite/client" />

interface ImportMetaEnv {}

interface ImportMeta {
readonly env: ImportMetaEnv
}
7 changes: 7 additions & 0 deletions src/react/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
declare global {
const __DEV__: boolean
const __CONTAINER_ID__: string
const __USE_APOLLO_RENDERER__: boolean
}

export default global
15 changes: 7 additions & 8 deletions src/react/style-collectors/emotion.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { Context } from '../types'
import { createElement, ReactElement } from 'react'
// @ts-ignore
import createCache from '@emotion/cache'
// @ts-ignore
import { CacheProvider } from '@emotion/react'
import { createElement, ReactElement } from 'react'
import type { Context } from '../types'

function getCache() {
const cache = createCache({ key: 'css' })
Expand All @@ -15,9 +13,11 @@ async function ssrCollector(context: Context) {
// A subdependency of this dependency calls Buffer on import,
// so it must be imported only in Node environment.
// https://github.com/emotion-js/emotion/issues/2446
// @ts-ignore
let createEmotionServer: any = await import('@emotion/server/create-instance')
createEmotionServer = createEmotionServer.default || createEmotionServer
const createEmotionServerImport = await import(
'@emotion/server/create-instance'
)
const createEmotionServer =
createEmotionServerImport.default || createEmotionServerImport

const cache = getCache()
const { extractCriticalToChunks, constructStyleTagsFromChunks } =
Expand All @@ -44,5 +44,4 @@ function clientProvider(context: Context) {
}
}

// @ts-ignore
export default import.meta.env.SSR ? ssrCollector : clientProvider
6 changes: 1 addition & 5 deletions src/react/style-collectors/material-ui-core-v4.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ServerStyleSheets } from '@material-ui/core/styles'
import type { ReactElement } from 'react'
import type { Context } from '../types'
// @ts-ignore
import { ServerStyleSheets } from '@material-ui/core/styles'

const key = 'jss-server-side'

Expand All @@ -10,13 +9,11 @@ function ssrCollector(context: Context) {

return {
collect(app: ReactElement) {
//@ts-ignore
return sheet.collect(app)
},
toString() {
let css = sheet.toString()

// @ts-ignore
if (import.meta.env.PROD) {
css = css.replace(/\s{2,}/gm, ' ')
}
Expand All @@ -37,5 +34,4 @@ function clientProvider(context: Context) {
}
}

// @ts-ignore
export default import.meta.env.SSR ? ssrCollector : clientProvider
5 changes: 1 addition & 4 deletions src/react/style-collectors/styled-components.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import type { ReactElement } from 'react'
import type { Context } from '../types'
// @ts-ignore
import { ServerStyleSheet } from 'styled-components'
import type { Context } from '../types'

function ssrCollector(context: Context) {
const sheet = new ServerStyleSheet()

return {
collect(app: ReactElement) {
// @ts-ignore
return sheet.collectStyles(app)
},
toString() {
Expand All @@ -20,5 +18,4 @@ function ssrCollector(context: Context) {
}
}

// @ts-ignore
export default import.meta.env.SSR ? ssrCollector : null
10 changes: 10 additions & 0 deletions src/react/styled-components.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
declare module 'styled-components' {
export class ServerStyleSheet {
constructor()
_emitSheetCSS: () => string
collectStyles: (children: any) => JSX.Element
getStyleTags: () => string
interleaveWithNodeStream: (input: Readable) => streamInternal.Transform
seal: () => void
}
}
5 changes: 5 additions & 0 deletions src/react/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig-esm.json",
"exclude": [],
"include": ["**/*"]
}
3 changes: 2 additions & 1 deletion src/react/types.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import type { FunctionComponent, ReactElement, ReactNode } from 'react'
import type { Router } from './utils'
import type {
Meta,
Renderer,
SharedContext,
SharedOptions,
} from '../utils/types'
import type { Router } from './utils'

export interface RouteRaw {
name?: string
path: string
component: any
meta?: Meta
routes?: RouteRaw[]
children?: RouteRaw[]
[key: string]: any
}

Expand Down
11 changes: 4 additions & 7 deletions src/react/utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React from 'react'
import { useLocation, useParams } from 'react-router-dom'
import { getFullPath } from '../utils/route'
import { createUrl } from '../utils/route'
import type { RouteRaw, PropsProvider as PropsProviderType } from './types'
import type { Base, Meta, State, PagePropsOptions } from '../utils/types'
import { createUrl, getFullPath } from '../utils/route'
import type { Base, Meta, PagePropsOptions, State } from '../utils/types'
import type { PropsProvider as PropsProviderType, RouteRaw } from './types'

type RouterOptions = {
base?: Base
Expand All @@ -28,7 +27,7 @@ export function createRouter({
state: null,
}

const augmentedRoute = {
const augmentedRoute: RouteRaw = {
...originalRoute,
meta,
component: (props: Record<string, any>) => {
Expand All @@ -43,7 +42,6 @@ export function createRouter({
hash,
search,
params: useParams(),
// @ts-ignore -- This should be in ES2019 ??
query: Object.fromEntries(url.searchParams),
fullPath: getFullPath(url, routeBase),
}
Expand Down Expand Up @@ -75,7 +73,6 @@ export function createRouter({
augmentedRoute.routes = originalRoute.routes.map(augmentRoute)

// Nested routes compatibility with React Router 6
// @ts-ignore
augmentedRoute.children = augmentedRoute.routes
}

Expand Down
10 changes: 7 additions & 3 deletions test/fixtures/react-ts-basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@
"serve": "node server"
},
"dependencies": {
"react": "^17.0.0",
"react-dom": "^17.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-helmet-async": "^1.0.9",
"react-router-dom": "^6.2.2"
"react-router-dom": "^6.2.2",
"@emotion/cache": "11.11.0",
"@emotion/react": "11.11.1",
"@emotion/server": "11.11.0",
"@material-ui/core": "4.12.4"
},
"devDependencies": {
"@types/connect": "^3.4.35",
Expand Down
Loading