Fix RenderOpts in next-server (#10776)

* Correctly pass preview data

* remove todo

* re-do change

* fix types

* Prevent regression
This commit is contained in:
Joe Haddad 2020-03-02 05:58:47 -05:00 committed by GitHub
parent 94009422c5
commit 8f01a4ae83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 90 additions and 11 deletions

View file

@ -260,6 +260,7 @@ const COOKIE_NAME_PRERENDER_BYPASS = `__prerender_bypass`
const COOKIE_NAME_PRERENDER_DATA = `__next_preview_data` const COOKIE_NAME_PRERENDER_DATA = `__next_preview_data`
export const SYMBOL_PREVIEW_DATA = Symbol(COOKIE_NAME_PRERENDER_DATA) export const SYMBOL_PREVIEW_DATA = Symbol(COOKIE_NAME_PRERENDER_DATA)
const SYMBOL_CLEARED_COOKIES = Symbol(COOKIE_NAME_PRERENDER_BYPASS)
export function tryGetPreviewData( export function tryGetPreviewData(
req: IncomingMessage, req: IncomingMessage,
@ -405,6 +406,10 @@ function setPreviewData<T>(
} }
function clearPreviewData<T>(res: NextApiResponse<T>): NextApiResponse<T> { function clearPreviewData<T>(res: NextApiResponse<T>): NextApiResponse<T> {
if (SYMBOL_CLEARED_COOKIES in res) {
return res
}
const { serialize } = require('cookie') as typeof import('cookie') const { serialize } = require('cookie') as typeof import('cookie')
const previous = res.getHeader('Set-Cookie') const previous = res.getHeader('Set-Cookie')
res.setHeader(`Set-Cookie`, [ res.setHeader(`Set-Cookie`, [
@ -432,6 +437,11 @@ function clearPreviewData<T>(res: NextApiResponse<T>): NextApiResponse<T> {
path: '/', path: '/',
}), }),
]) ])
Object.defineProperty(res, SYMBOL_CLEARED_COOKIES, {
value: true,
enumerable: false,
})
return res return res
} }

View file

@ -41,7 +41,7 @@ import pathMatch from './lib/path-match'
import { recursiveReadDirSync } from './lib/recursive-readdir-sync' import { recursiveReadDirSync } from './lib/recursive-readdir-sync'
import { loadComponents, LoadComponentsReturnType } from './load-components' import { loadComponents, LoadComponentsReturnType } from './load-components'
import { normalizePagePath } from './normalize-page-path' import { normalizePagePath } from './normalize-page-path'
import { renderToHTML } from './render' import { RenderOpts, RenderOptsPartial, renderToHTML } from './render'
import { getPagePath } from './require' import { getPagePath } from './require'
import Router, { import Router, {
DynamicRoutes, DynamicRoutes,
@ -115,6 +115,7 @@ export default class Server {
documentMiddlewareEnabled: boolean documentMiddlewareEnabled: boolean
hasCssMode: boolean hasCssMode: boolean
dev?: boolean dev?: boolean
previewProps: __ApiPreviewProps
} }
private compression?: Middleware private compression?: Middleware
private onErrorMiddleware?: ({ err }: { err: Error }) => Promise<void> private onErrorMiddleware?: ({ err }: { err: Error }) => Promise<void>
@ -165,6 +166,7 @@ export default class Server {
staticMarkup, staticMarkup,
buildId: this.buildId, buildId: this.buildId,
generateEtags, generateEtags,
previewProps: this.getPreviewProps(),
} }
// Only the `publicRuntimeConfig` key is exposed to the client side // Only the `publicRuntimeConfig` key is exposed to the client side
@ -668,13 +670,12 @@ export default class Server {
} }
} }
const previewProps = this.getPreviewProps()
await apiResolver( await apiResolver(
req, req,
res, res,
query, query,
pageModule, pageModule,
{ ...previewProps }, this.renderOpts.previewProps,
this.onErrorMiddleware this.onErrorMiddleware
) )
return true return true
@ -869,7 +870,7 @@ export default class Server {
res: ServerResponse, res: ServerResponse,
pathname: string, pathname: string,
{ components, query }: FindComponentsResult, { components, query }: FindComponentsResult,
opts: any opts: RenderOptsPartial
): Promise<string | false | null> { ): Promise<string | false | null> {
// we need to ensure the status code if /404 is visited directly // we need to ensure the status code if /404 is visited directly
if (pathname === '/404') { if (pathname === '/404') {
@ -890,7 +891,7 @@ export default class Server {
const hasStaticPaths = !!components.getStaticPaths const hasStaticPaths = !!components.getStaticPaths
// Toggle whether or not this is a Data request // Toggle whether or not this is a Data request
const isDataReq = query._nextDataReq const isDataReq = !!query._nextDataReq
delete query._nextDataReq delete query._nextDataReq
// Serverless requests need its URL transformed back into the original // Serverless requests need its URL transformed back into the original
@ -958,8 +959,11 @@ export default class Server {
}) })
} }
const previewProps = this.getPreviewProps() const previewData = tryGetPreviewData(
const previewData = tryGetPreviewData(req, res, { ...previewProps }) req,
res,
this.renderOpts.previewProps
)
const isPreviewMode = previewData !== false const isPreviewMode = previewData !== false
// Compute the SPR cache key // Compute the SPR cache key
@ -1017,15 +1021,16 @@ export default class Server {
pageData = renderResult.renderOpts.pageData pageData = renderResult.renderOpts.pageData
sprRevalidate = renderResult.renderOpts.revalidate sprRevalidate = renderResult.renderOpts.revalidate
} else { } else {
const renderOpts = { const renderOpts: RenderOpts = {
...components, ...components,
...opts, ...opts,
} }
renderResult = await renderToHTML(req, res, pathname, query, renderOpts) renderResult = await renderToHTML(req, res, pathname, query, renderOpts)
html = renderResult html = renderResult
pageData = renderOpts.pageData // TODO: change this to a different passing mechanism
sprRevalidate = renderOpts.revalidate pageData = (renderOpts as any).pageData
sprRevalidate = (renderOpts as any).revalidate
} }
return { html, pageData, sprRevalidate } return { html, pageData, sprRevalidate }

View file

@ -124,7 +124,7 @@ function render(
return { html, head } return { html, head }
} }
type RenderOpts = LoadComponentsReturnType & { export type RenderOptsPartial = {
staticMarkup: boolean staticMarkup: boolean
buildId: string buildId: string
canonicalBase: string canonicalBase: string
@ -147,6 +147,8 @@ type RenderOpts = LoadComponentsReturnType & {
previewProps: __ApiPreviewProps previewProps: __ApiPreviewProps
} }
export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial
function renderDocument( function renderDocument(
Document: DocumentType, Document: DocumentType,
{ {
@ -517,6 +519,7 @@ export async function renderToHTML(
props.pageProps = data.props props.pageProps = data.props
// pass up revalidate and props for export // pass up revalidate and props for export
// TODO: change this to a different passing mechanism
;(renderOpts as any).revalidate = data.revalidate ;(renderOpts as any).revalidate = data.revalidate
;(renderOpts as any).pageData = props ;(renderOpts as any).pageData = props
} }

View file

@ -8,6 +8,7 @@ import {
findPort, findPort,
initNextServerScript, initNextServerScript,
killApp, killApp,
launchApp,
nextBuild, nextBuild,
nextStart, nextStart,
renderViaHTTP, renderViaHTTP,
@ -201,6 +202,66 @@ const startServerlessEmulator = async (dir, port) => {
} }
describe('Prerender Preview Mode', () => { describe('Prerender Preview Mode', () => {
describe('Development Mode', () => {
beforeAll(async () => {
await fs.remove(nextConfigPath)
})
let appPort, app
it('should start development application', async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
let previewCookieString
it('should enable preview mode', async () => {
const res = await fetchViaHTTP(appPort, '/api/preview', { lets: 'goooo' })
expect(res.status).toBe(200)
const cookies = res.headers
.get('set-cookie')
.split(',')
.map(cookie.parse)
expect(cookies.length).toBe(2)
previewCookieString =
cookie.serialize('__prerender_bypass', cookies[0].__prerender_bypass) +
'; ' +
cookie.serialize('__next_preview_data', cookies[1].__next_preview_data)
})
it('should return cookies to be expired after dev server reboot', async () => {
await killApp(app)
app = await launchApp(appDir, appPort)
const res = await fetchViaHTTP(
appPort,
'/',
{},
{ headers: { Cookie: previewCookieString } }
)
expect(res.status).toBe(200)
const body = await res.text()
// "err":{"name":"TypeError","message":"Cannot read property 'previewModeId' of undefined"
expect(body).not.toContain('err')
expect(body).not.toContain('TypeError')
expect(body).not.toContain('previewModeId')
const cookies = res.headers
.get('set-cookie')
.replace(/(=\w{3}),/g, '$1')
.split(',')
.map(cookie.parse)
expect(cookies.length).toBe(2)
})
afterAll(async () => {
await killApp(app)
})
})
describe('Server Mode', () => { describe('Server Mode', () => {
beforeAll(async () => { beforeAll(async () => {
await fs.remove(nextConfigPath) await fs.remove(nextConfigPath)