From 6f8cf675d32bc244883113059622a10a7d8dc49a Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Fri, 10 Dec 2021 11:22:31 +0100 Subject: [PATCH] Add unstable_useRefreshRoot (#32342) ## 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` --- .../next-middleware-ssr-loader/render.ts | 8 +++ packages/next/client/index.tsx | 64 +++++++++++++------ packages/next/client/rsc.ts | 7 ++ packages/next/rsc.d.ts | 1 + packages/next/rsc.js | 3 + packages/next/server/render.tsx | 4 +- 6 files changed, 67 insertions(+), 20 deletions(-) create mode 100644 packages/next/client/rsc.ts create mode 100644 packages/next/rsc.d.ts create mode 100644 packages/next/rsc.js diff --git a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts index 70a3ae3596e..78bb79ab590 100644 --- a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts +++ b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts @@ -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, @@ -73,6 +80,7 @@ export function getRender({ supportsDynamicHTML: true, concurrentFeatures: true, renderServerComponentData, + serverComponentProps, serverComponentManifest: isServerComponent ? rscManifest : null, ComponentMod: null, } diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index 85a902d3244..e7ab94e6b57 100644 --- a/packages/next/client/index.tsx +++ b/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' @@ -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' /// @@ -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() } 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, @@ -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 @@ -672,16 +687,10 @@ 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 } @@ -689,14 +698,31 @@ if (process.env.__NEXT_RSC) { 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 ( - - - + + + + + ) } } diff --git a/packages/next/client/rsc.ts b/packages/next/client/rsc.ts new file mode 100644 index 00000000000..7caa504a4de --- /dev/null +++ b/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) +} diff --git a/packages/next/rsc.d.ts b/packages/next/rsc.d.ts new file mode 100644 index 00000000000..1e63004efc5 --- /dev/null +++ b/packages/next/rsc.d.ts @@ -0,0 +1 @@ +export * from './dist/client/rsc' diff --git a/packages/next/rsc.js b/packages/next/rsc.js new file mode 100644 index 00000000000..792373906a6 --- /dev/null +++ b/packages/next/rsc.js @@ -0,0 +1,3 @@ +module.exports = { + unstable_useRefreshRoot: require('./dist/client/rsc').useRefreshRoot, +} diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index 927e237a72a..56ae6780be9 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -209,6 +209,7 @@ export type RenderOptsPartial = { resolvedAsPath?: string serverComponentManifest?: any renderServerComponentData?: boolean + serverComponentProps?: any distDir?: string locale?: string locales?: string[] @@ -356,6 +357,7 @@ export async function renderToHTML( getServerSideProps, serverComponentManifest, renderServerComponentData, + serverComponentProps, isDataReq, params, previewProps, @@ -1004,7 +1006,7 @@ export async function renderToHTML( if (renderServerComponentData) { const stream: ReadableStream = renderToReadableStream( - , + , serverComponentManifest ) const reader = stream.getReader()