Skip to content

Commit

Permalink
Add unstable_useRefreshRoot (#32342)
Browse files Browse the repository at this point in the history
## Feature

Resolves #32332

- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [x] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
  • Loading branch information
huozhi committed Dec 10, 2021
1 parent 55d8a14 commit 6f8cf67
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 20 deletions.
Expand Up @@ -45,7 +45,14 @@ export function getRender({
const renderServerComponentData = isServerComponent
? query.__flight__ !== undefined
: false

const serverComponentProps =
isServerComponent && query.__props__
? JSON.parse(query.__props__)
: undefined

delete query.__flight__
delete query.__props__

const req = {
url: pathname,
Expand Down Expand Up @@ -73,6 +80,7 @@ export function getRender({
supportsDynamicHTML: true,
concurrentFeatures: true,
renderServerComponentData,
serverComponentProps,
serverComponentManifest: isServerComponent ? rscManifest : null,
ComponentMod: null,
}
Expand Down
64 changes: 45 additions & 19 deletions packages/next/client/index.tsx
@@ -1,6 +1,6 @@
/* global location */
import '@next/polyfill-module'
import React from 'react'
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
import { StyleRegistry } from 'styled-jsx'
import { HeadManagerContext } from '../shared/lib/head-manager-context'
Expand Down Expand Up @@ -35,6 +35,7 @@ import { RouteAnnouncer } from './route-announcer'
import { createRouter, makePublicRouterInstance, useRouter } from './router'
import isError from '../lib/is-error'
import { trackWebVitalMetric } from './vitals'
import { RefreshContext } from './rsc'

/// <reference types="react-dom/experimental" />

Expand Down Expand Up @@ -643,12 +644,30 @@ const wrapApp =

let RSCComponent: (props: any) => JSX.Element
if (process.env.__NEXT_RSC) {
const {
createFromFetch,
} = require('next/dist/compiled/react-server-dom-webpack')
function createResponseCache() {
return new Map<string, any>()
}

const rscCache = createResponseCache()

function fetchFlight(href: string, props?: any) {
const url = new URL(href, location.origin)
const searchParams = url.searchParams
searchParams.append('__flight__', '1')
if (props) {
searchParams.append('__props__', JSON.stringify(props))
}
return fetch(url.toString())
}

const getHref = () => {
const { pathname, search } = location
return pathname + search
}

const RSCWrapper = ({
cacheKey,
serialized,
Expand All @@ -658,12 +677,8 @@ if (process.env.__NEXT_RSC) {
serialized?: string
_fresh?: boolean
}) => {
const {
createFromFetch,
} = require('next/dist/compiled/react-server-dom-webpack')
let response = rscCache.get(cacheKey)

// If there is no cache, or there is serialized data already
if (!response) {
response = createFromFetch(
serialized
Expand All @@ -672,31 +687,42 @@ if (process.env.__NEXT_RSC) {
t.writable.getWriter().write(new TextEncoder().encode(serialized))
return Promise.resolve({ body: t.readable })
})()
: (() => {
const { search, pathname } = location
const flightReqUrl =
pathname + search + (search ? '&' : '?') + '__flight__'
return fetch(flightReqUrl)
})()
: fetchFlight(getHref())
)
rscCache.set(cacheKey, response)
}

const root = response.readRoot()
return root
}

RSCComponent = (props: any) => {
const cacheKey = useRouter().asPath
const { __flight_serialized__, __flight_fresh__ } = props
const [, dispatch] = useState({})
// @ts-ignore TODO: remove when react 18 types are supported
const startTransition = React.startTransition
const renrender = () => dispatch({})
// If there is no cache, or there is serialized data already
function refreshCache(nextProps: any) {
startTransition(() => {
const href = getHref()
const response = createFromFetch(fetchFlight(href, nextProps))
// FIXME: router.asPath can be different from current location due to navigation
rscCache.set(href, response)
renrender()
})
}

return (
<React.Suspense fallback={null}>
<RSCWrapper
cacheKey={cacheKey}
serialized={__flight_serialized__}
_fresh={__flight_fresh__}
/>
</React.Suspense>
<RefreshContext.Provider value={refreshCache}>
<React.Suspense fallback={null}>
<RSCWrapper
cacheKey={cacheKey}
serialized={__flight_serialized__}
_fresh={__flight_fresh__}
/>
</React.Suspense>
</RefreshContext.Provider>
)
}
}
Expand Down
7 changes: 7 additions & 0 deletions packages/next/client/rsc.ts
@@ -0,0 +1,7 @@
import { createContext, useContext } from 'react'

export const RefreshContext = createContext((_: any) => {})

export function useRefreshRoot() {
return useContext(RefreshContext)
}
1 change: 1 addition & 0 deletions packages/next/rsc.d.ts
@@ -0,0 +1 @@
export * from './dist/client/rsc'
3 changes: 3 additions & 0 deletions packages/next/rsc.js
@@ -0,0 +1,3 @@
module.exports = {
unstable_useRefreshRoot: require('./dist/client/rsc').useRefreshRoot,
}
4 changes: 3 additions & 1 deletion packages/next/server/render.tsx
Expand Up @@ -209,6 +209,7 @@ export type RenderOptsPartial = {
resolvedAsPath?: string
serverComponentManifest?: any
renderServerComponentData?: boolean
serverComponentProps?: any
distDir?: string
locale?: string
locales?: string[]
Expand Down Expand Up @@ -356,6 +357,7 @@ export async function renderToHTML(
getServerSideProps,
serverComponentManifest,
renderServerComponentData,
serverComponentProps,
isDataReq,
params,
previewProps,
Expand Down Expand Up @@ -1004,7 +1006,7 @@ export async function renderToHTML(

if (renderServerComponentData) {
const stream: ReadableStream = renderToReadableStream(
<OriginalComponent {...props.pageProps} />,
<OriginalComponent {...props.pageProps} {...serverComponentProps} />,
serverComponentManifest
)
const reader = stream.getReader()
Expand Down

0 comments on commit 6f8cf67

Please sign in to comment.