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

Drop legacy RSC handling in client for pages #40472

Merged
merged 9 commits into from Sep 12, 2022
Merged
26 changes: 15 additions & 11 deletions packages/next/build/webpack-config.ts
Expand Up @@ -93,7 +93,6 @@ export function getDefineEnv({
isNodeServer,
isEdgeServer,
middlewareMatchers,
hasServerComponents,
}: {
dev?: boolean
distDir: string
Expand All @@ -104,7 +103,6 @@ export function getDefineEnv({
isEdgeServer?: boolean
middlewareMatchers?: MiddlewareMatcher[]
config: NextConfigComplete
hasServerComponents?: boolean
}) {
return {
// internal field to identify the plugin config
Expand Down Expand Up @@ -178,7 +176,6 @@ export function getDefineEnv({
),
'process.env.__NEXT_STRICT_MODE': JSON.stringify(config.reactStrictMode),
'process.env.__NEXT_REACT_ROOT': JSON.stringify(hasReactRoot),
'process.env.__NEXT_RSC': JSON.stringify(hasServerComponents),
'process.env.__NEXT_OPTIMIZE_FONTS': JSON.stringify(
config.optimizeFonts && !dev
),
Expand Down Expand Up @@ -540,15 +537,23 @@ export default async function getBaseWebpackConfig(
rewrites.afterFiles.length > 0 ||
rewrites.fallback.length > 0

if (isClient && !hasReactRoot) {
if (config.experimental.runtime) {
throw new Error(
'`experimental.runtime` requires React 18 to be installed.'
)
// Only error in first one compiler (client) once
if (isClient) {
if (!hasReactRoot) {
if (config.experimental.runtime) {
throw new Error(
'`experimental.runtime` requires React 18 to be installed.'
)
}
if (config.experimental.serverComponents) {
throw new Error(
'`experimental.serverComponents` requires React 18 to be installed.'
)
}
}
if (config.experimental.serverComponents) {
if (!config.experimental.appDir && config.experimental.serverComponents) {
throw new Error(
'`experimental.serverComponents` requires React 18 to be installed.'
'`experimental.serverComponents` requires experimental.appDir to be enabled.'
)
}
}
Expand Down Expand Up @@ -1680,7 +1685,6 @@ export default async function getBaseWebpackConfig(
isNodeServer,
isEdgeServer,
middlewareMatchers,
hasServerComponents,
})
),
isClient &&
Expand Down
151 changes: 2 additions & 149 deletions packages/next/client/index.tsx
Expand Up @@ -289,149 +289,6 @@ export async function initialize(opts: { webpackHMR?: any } = {}): Promise<{
return { assetPrefix: prefix }
}

let RSCComponent: (props: any) => JSX.Element
if (process.env.__NEXT_RSC) {
const getCacheKey = () => {
const { pathname, search } = location
return pathname + search
}

const {
createFromFetch,
createFromReadableStream,
} = require('next/dist/compiled/react-server-dom-webpack')
const encoder = new TextEncoder()

let initialServerDataBuffer: string[] | undefined = undefined
let initialServerDataWriter: ReadableStreamDefaultController | undefined =
undefined
let initialServerDataLoaded = false
let initialServerDataFlushed = false

function nextServerDataCallback(seg: [number, string, string]) {
if (seg[0] === 0) {
initialServerDataBuffer = []
} else {
if (!initialServerDataBuffer)
throw new Error('Unexpected server data: missing bootstrap script.')

if (initialServerDataWriter) {
initialServerDataWriter.enqueue(encoder.encode(seg[2]))
} else {
initialServerDataBuffer.push(seg[2])
}
}
}

// There might be race conditions between `nextServerDataRegisterWriter` and
// `DOMContentLoaded`. The former will be called when React starts to hydrate
// the root, the latter will be called when the DOM is fully loaded.
// For streaming, the former is called first due to partial hydration.
// For non-streaming, the latter can be called first.
// Hence, we use two variables `initialServerDataLoaded` and
// `initialServerDataFlushed` to make sure the writer will be closed and
// `initialServerDataBuffer` will be cleared in the right time.
function nextServerDataRegisterWriter(ctr: ReadableStreamDefaultController) {
if (initialServerDataBuffer) {
initialServerDataBuffer.forEach((val) => {
ctr.enqueue(encoder.encode(val))
})
if (initialServerDataLoaded && !initialServerDataFlushed) {
ctr.close()
initialServerDataFlushed = true
initialServerDataBuffer = undefined
}
}

initialServerDataWriter = ctr
}

// When `DOMContentLoaded`, we can close all pending writers to finish hydration.
const DOMContentLoaded = function () {
if (initialServerDataWriter && !initialServerDataFlushed) {
initialServerDataWriter.close()
initialServerDataFlushed = true
initialServerDataBuffer = undefined
}
initialServerDataLoaded = true
}
// It's possible that the DOM is already loaded.
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', DOMContentLoaded, false)
} else {
DOMContentLoaded()
}

const nextServerDataLoadingGlobal = ((self as any).__next_s =
(self as any).__next_s || [])
nextServerDataLoadingGlobal.forEach(nextServerDataCallback)
nextServerDataLoadingGlobal.push = nextServerDataCallback

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())
}

function useServerResponse(cacheKey: string, serialized?: string) {
let response = rscCache.get(cacheKey)
if (response) return response

if (initialServerDataBuffer) {
const readable = new ReadableStream({
start(controller) {
nextServerDataRegisterWriter(controller)
},
})
response = createFromReadableStream(readable)
} else {
if (serialized) {
const readable = new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode(serialized))
controller.close()
},
})
response = createFromReadableStream(readable)
} else {
response = createFromFetch(fetchFlight(getCacheKey()))
}
}

rscCache.set(cacheKey, response)
return response
}

const ServerRoot = ({
cacheKey,
serialized,
}: {
cacheKey: string
serialized?: string
}) => {
React.useEffect(() => {
rscCache.delete(cacheKey)
})
const response = useServerResponse(cacheKey, serialized)
return response.readRoot()
}

RSCComponent = (props: any) => {
const cacheKey = getCacheKey()
const { __flight__ } = props
return <ServerRoot cacheKey={cacheKey} serialized={__flight__} />
}
}

function renderApp(App: AppComponent, appProps: AppProps) {
return <App {...appProps} />
}
Expand Down Expand Up @@ -691,18 +548,15 @@ function Root({
}

function doRender(input: RenderRouteInfo): Promise<any> {
let { App, Component, props, err, __N_RSC }: RenderRouteInfo = input
let { App, Component, props, err }: RenderRouteInfo = input
let styleSheets: StyleSheetTuple[] | undefined =
'initial' in input ? undefined : input.styleSheets
Component = Component || lastAppProps.Component
props = props || lastAppProps.props

const isRSC =
process.env.__NEXT_RSC && 'initial' in input ? !!initialData.rsc : !!__N_RSC

const appProps: AppProps = {
...props,
Component: isRSC ? RSCComponent : Component,
Component,
err,
router,
}
Expand Down Expand Up @@ -1026,7 +880,6 @@ export async function hydrate(opts?: { beforeRender?: () => Promise<void> }) {
defaultLocale,
domainLocales: initialData.domainLocales,
isPreview: initialData.isPreview,
isRsc: initialData.rsc,
})

initialMatchesMiddleware = await router._initialMatchesMiddlewarePromise
Expand Down
1 change: 0 additions & 1 deletion packages/next/server/dev/next-dev-server.ts
Expand Up @@ -526,7 +526,6 @@ export default class DevServer extends Server {
hasReactRoot: this.hotReloader?.hasReactRoot,
isNodeServer,
isEdgeServer,
hasServerComponents: this.hotReloader?.hasServerComponents,
})

Object.keys(plugin.definitions).forEach((key) => {
Expand Down
15 changes: 0 additions & 15 deletions packages/next/server/render.tsx
Expand Up @@ -375,7 +375,6 @@ export async function renderToHTML(
getStaticProps,
getStaticPaths,
getServerSideProps,
serverComponentManifest,
isDataReq,
params,
previewProps,
Expand All @@ -384,18 +383,11 @@ export async function renderToHTML(
supportsDynamicHTML,
images,
runtime: globalRuntime,
ComponentMod,
App,
} = renderOpts

let Document = renderOpts.Document

// We don't need to opt-into the flight inlining logic if the page isn't a RSC.
const isServerComponent =
!!process.env.__NEXT_REACT_ROOT &&
!!serverComponentManifest &&
!!ComponentMod.__next_rsc__?.server

// Component will be wrapped by ServerComponentWrapper for RSC
let Component: React.ComponentType<{}> | ((props: any) => JSX.Element) =
renderOpts.Component
Expand All @@ -412,12 +404,6 @@ export async function renderToHTML(
// next internal queries should be stripped out
stripInternalQueries(query)

if (isServerComponent) {
throw new Error(
'Server Components are not supported from the pages/ directory.'
huozhi marked this conversation as resolved.
Show resolved Hide resolved
)
}

const callMiddleware = async (method: string, args: any[], props = false) => {
let results: any = props ? {} : []

Expand Down Expand Up @@ -1417,7 +1403,6 @@ export async function renderToHTML(
err: renderOpts.err ? serializeError(dev, renderOpts.err) : undefined, // Error if one happened, otherwise don't sent in the resulting HTML
gsp: !!getStaticProps ? true : undefined, // whether the page is getStaticProps
gssp: !!getServerSideProps ? true : undefined, // whether the page is getServerSideProps
rsc: isServerComponent ? true : undefined, // whether the page is a server components page
customServer, // whether the user is using a custom server
gip: hasPageGetInitialProps ? true : undefined, // whether the page has getInitialProps
appGip: !defaultAppGetInitialProps ? true : undefined, // whether the _app has getInitialProps
Expand Down
1 change: 0 additions & 1 deletion packages/next/shared/lib/constants.ts
Expand Up @@ -86,7 +86,6 @@ export const TEMPORARY_REDIRECT_STATUS = 307
export const PERMANENT_REDIRECT_STATUS = 308
export const STATIC_PROPS_ID = '__N_SSG'
export const SERVER_PROPS_ID = '__N_SSP'
export const FLIGHT_PROPS_ID = '__N_RSC'
export const GOOGLE_FONT_PROVIDER = 'https://fonts.googleapis.com/'
export const OPTIMIZED_FONT_PROVIDERS = [
{ url: GOOGLE_FONT_PROVIDER, preconnect: 'https://fonts.gstatic.com' },
Expand Down