From 0ca80875651f1a02bf96ff88a69d87bfe86d422a Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 1 Jul 2019 14:13:52 -0700 Subject: [PATCH] Add `prerender` PageConfig option (#7699) * Add prerender PageConfig option * Update PageConfig type * Add inlining of data when pre-render is set and add tests * Update types import * Add check for props * Rename prerender to experimentalPrerender for now --- packages/next-server/lib/router/router.ts | 4 + .../next-server/server/load-components.ts | 15 +--- packages/next-server/server/next-server.ts | 5 +- packages/next-server/server/render.tsx | 20 +++-- packages/next-server/types/index.d.ts | 11 ++- .../build/babel/plugins/next-page-config.ts | 68 +++++++++++---- packages/next/build/index.ts | 7 +- packages/next/build/static-checker.ts | 4 +- packages/next/build/utils.ts | 13 ++- .../webpack/loaders/next-babel-loader.js | 2 +- .../webpack/loaders/next-serverless-loader.ts | 4 +- packages/next/export/worker.js | 25 +++++- packages/next/types/index.d.ts | 18 ++-- test/integration/prerender/next.config.js | 5 ++ test/integration/prerender/pages/another.js | 7 ++ test/integration/prerender/pages/index.js | 1 + .../prerender/pages/nested/hello.js | 7 ++ .../prerender/pages/nested/old-school.js | 5 ++ .../integration/prerender/pages/old-school.js | 5 ++ test/integration/prerender/test/index.test.js | 85 +++++++++++++++++++ .../integration/production/pages/something.js | 27 ++++++ .../production/pages/to-something.js | 30 +++++++ .../integration/production/test/index.test.js | 20 +++++ .../integration/serverless/pages/something.js | 27 ++++++ .../serverless/pages/to-something.js | 30 +++++++ .../integration/serverless/test/index.test.js | 20 +++++ 26 files changed, 406 insertions(+), 59 deletions(-) create mode 100644 test/integration/prerender/next.config.js create mode 100644 test/integration/prerender/pages/another.js create mode 100644 test/integration/prerender/pages/index.js create mode 100644 test/integration/prerender/pages/nested/hello.js create mode 100644 test/integration/prerender/pages/nested/old-school.js create mode 100644 test/integration/prerender/pages/old-school.js create mode 100644 test/integration/prerender/test/index.test.js create mode 100644 test/integration/production/pages/something.js create mode 100644 test/integration/production/pages/to-something.js create mode 100644 test/integration/serverless/pages/something.js create mode 100644 test/integration/serverless/pages/to-something.js diff --git a/packages/next-server/lib/router/router.ts b/packages/next-server/lib/router/router.ts index 101f76e567..4405bfb223 100644 --- a/packages/next-server/lib/router/router.ts +++ b/packages/next-server/lib/router/router.ts @@ -377,6 +377,10 @@ export default class Router implements BaseRouter { return new Promise((resolve, reject) => { const ctx = { pathname, query, asPath: as } this.getInitialProps(Component, ctx).then(props => { + // if data is inlined during pre-render it is a string + if (props && typeof props.pageProps === 'string') { + props.pageProps = JSON.parse(props.pageProps) + } routeInfo.props = props this.components[route] = routeInfo resolve(routeInfo) diff --git a/packages/next-server/server/load-components.ts b/packages/next-server/server/load-components.ts index 143b939c9f..dafc5aac18 100644 --- a/packages/next-server/server/load-components.ts +++ b/packages/next-server/server/load-components.ts @@ -5,23 +5,16 @@ import { SERVER_DIRECTORY, } from '../lib/constants' import { join } from 'path' - +import { PageConfig } from 'next-server/types' import { requirePage } from './require' export function interopDefault(mod: any) { return mod.default || mod } -export interface IPageConfig { - amp?: boolean | 'hybrid' - api?: { - bodyParser?: boolean - } -} - export type LoadComponentsReturnType = { Component: any - PageConfig: IPageConfig + pageConfig: PageConfig buildManifest?: any reactLoadableManifest?: any Document?: any @@ -37,7 +30,7 @@ export async function loadComponents( ): Promise { if (serverless) { const Component = await requirePage(pathname, distDir, serverless) - return { Component, PageConfig: Component.config || {} } + return { Component, pageConfig: Component.config || {} } } const documentPath = join( distDir, @@ -82,6 +75,6 @@ export async function loadComponents( buildManifest, DocumentMiddleware, reactLoadableManifest, - PageConfig: ComponentMod.config || {}, + pageConfig: ComponentMod.config || {}, } } diff --git a/packages/next-server/server/next-server.ts b/packages/next-server/server/next-server.ts index 035c0d5019..d520ab9887 100644 --- a/packages/next-server/server/next-server.ts +++ b/packages/next-server/server/next-server.ts @@ -40,7 +40,6 @@ import { interopDefault, loadComponents, LoadComponentsReturnType, - IPageConfig, } from './load-components' import { renderToHTML } from './render' import { getPagePath } from './require' @@ -48,6 +47,7 @@ import Router, { route, Route, RouteMatch, Params } from './router' import { sendHTML } from './send-html' import { serveStatic } from './serve-static' import { isBlockedPage, isInternalUrl } from './utils' +import { PageConfig } from 'next-server/types' type NextConfig = any @@ -317,7 +317,7 @@ export default class Server { const resolverModule = require(resolverFunction) if (resolverModule.config) { - const config: IPageConfig = resolverModule.config + const config: PageConfig = resolverModule.config if (config.api && config.api.bodyParser === false) { bodyParser = false } @@ -516,7 +516,6 @@ export default class Server { return renderToHTML(req, res, pathname, query, { ...result, ...opts, - PageConfig: result.PageConfig, }) } diff --git a/packages/next-server/server/render.tsx b/packages/next-server/server/render.tsx index cf3d0dbd74..6dfd7eddd4 100644 --- a/packages/next-server/server/render.tsx +++ b/packages/next-server/server/render.tsx @@ -28,7 +28,7 @@ import { getPageFiles, BuildManifest } from './get-page-files' import { AmpStateContext } from '../lib/amp-context' import optimizeAmp from './optimize-amp' import { isInAmpMode } from '../lib/amp' -import { IPageConfig } from './load-components' +import { PageConfig } from 'next-server/types' export type ManifestItem = { id: number | string @@ -145,13 +145,15 @@ type RenderOpts = { hybridAmp?: boolean buildManifest: BuildManifest reactLoadableManifest: ReactLoadableManifest - PageConfig: IPageConfig + pageConfig: PageConfig Component: React.ComponentType Document: DocumentType DocumentMiddleware: (ctx: NextPageContext) => void App: AppType ErrorDebug?: React.ComponentType<{ error: Error }> ampValidator?: (html: string, pathname: string) => Promise + isPrerender?: boolean + pageData?: any } function renderDocument( @@ -250,7 +252,7 @@ export async function renderToHTML( ampPath = '', App, Document, - PageConfig, + pageConfig, DocumentMiddleware, Component, buildManifest, @@ -259,7 +261,7 @@ export async function renderToHTML( } = renderOpts await Loadable.preloadAll() // Make sure all dynamic imports are loaded - let isStaticPage = false + let isStaticPage = Boolean(pageConfig.experimentalPrerender) if (dev) { const { isValidElementType } = require('react-is') @@ -336,9 +338,9 @@ export async function renderToHTML( } const ampState = { - ampFirst: PageConfig.amp === true, + ampFirst: pageConfig.amp === true, hasQuery: Boolean(query.amp), - hybrid: PageConfig.amp === 'hybrid', + hybrid: pageConfig.amp === 'hybrid', } const reactLoadableModules: string[] = [] @@ -490,9 +492,13 @@ export async function renderToHTML( const inAmpMode = isInAmpMode(ampState) const hybridAmp = ampState.hybrid - // update renderOpts so export knows it's AMP state + // update renderOpts so export knows current state renderOpts.inAmpMode = inAmpMode renderOpts.hybridAmp = hybridAmp + renderOpts.pageData = props && props.pageProps + renderOpts.isPrerender = + pageConfig.experimentalPrerender === true || + pageConfig.experimentalPrerender === 'inline' let html = renderDocument(Document, { ...renderOpts, diff --git a/packages/next-server/types/index.d.ts b/packages/next-server/types/index.d.ts index 56b4e1541c..b415a9b937 100644 --- a/packages/next-server/types/index.d.ts +++ b/packages/next-server/types/index.d.ts @@ -1 +1,10 @@ -declare module 'react-ssr-prepass' +/** + * `Config` type, use it for export const config + */ +export type PageConfig = { + amp?: boolean | 'hybrid' + api?: { + bodyParser?: boolean + } + experimentalPrerender?: boolean | 'inline' | 'legacy' +} diff --git a/packages/next/build/babel/plugins/next-page-config.ts b/packages/next/build/babel/plugins/next-page-config.ts index 4f24af8b86..6ba8bef1a5 100644 --- a/packages/next/build/babel/plugins/next-page-config.ts +++ b/packages/next/build/babel/plugins/next-page-config.ts @@ -1,12 +1,14 @@ import { PluginObj } from '@babel/core' import { NodePath } from '@babel/traverse' import * as BabelTypes from '@babel/types' +import { PageConfig } from 'next-server/types' -interface PageConfig { - amp?: boolean | 'hybrid' -} +const configKeys = new Set(['amp', 'experimentalPrerender']) +export const inlineGipIdentifier = '__NEXT_GIP_INLINE__' +export const dropBundleIdentifier = '__NEXT_DROP_CLIENT_FILE__' -function replacePath(path: any, t: typeof BabelTypes) { +// replace progam path with just a variable with the drop identifier +function replaceBundle(path: any, t: typeof BabelTypes) { path.parentPath.replaceWith( t.program( [ @@ -15,8 +17,8 @@ function replacePath(path: any, t: typeof BabelTypes) { t.identifier('config'), t.assignmentExpression( '=', - t.identifier('no'), // this can't be empty but is required - t.stringLiteral(`__NEXT_DROP_CLIENT_FILE__ ${Date.now()}`) + t.identifier(dropBundleIdentifier), + t.stringLiteral(`${dropBundleIdentifier} ${Date.now()}`) ) ), ]), @@ -26,18 +28,20 @@ function replacePath(path: any, t: typeof BabelTypes) { ) } +interface ConfigState { + setupInlining?: boolean + bundleDropped?: boolean +} + export default function nextPageConfig({ types: t, }: { types: typeof BabelTypes -}): PluginObj<{ - insertedDrop?: boolean - hasAmp?: boolean -}> { +}): PluginObj { return { visitor: { Program: { - enter(path, state: any) { + enter(path, state: ConfigState) { path.traverse( { ExportNamedDeclaration( @@ -45,7 +49,7 @@ export default function nextPageConfig({ state: any ) { if ( - state.replaced || + state.bundleDropped || !path.node.declaration || !(path.node.declaration as any).declarations ) @@ -58,13 +62,24 @@ export default function nextPageConfig({ for (const prop of declaration.init.properties) { const { name } = prop.key - if (name === 'amp') config.amp = prop.value.value + if (configKeys.has(name)) { + // @ts-ignore + config[name] = prop.value.value + } } } if (config.amp === true) { - replacePath(path, t) - state.replaced = true + replaceBundle(path, t) + state.bundleDropped = true + return + } + + if ( + config.experimentalPrerender === true || + config.experimentalPrerender === 'inline' + ) { + state.setupInlining = true } }, }, @@ -72,6 +87,29 @@ export default function nextPageConfig({ ) }, }, + // handles Page.getInitialProps = () => {} + AssignmentExpression(path, state: ConfigState) { + if (!state.setupInlining) return + const { property } = (path.node.left || {}) as any + const { name } = property + if (name !== 'getInitialProps') return + // replace the getInitialProps function with an identifier for replacing + path.node.right = t.functionExpression( + null, + [], + t.blockStatement([ + t.returnStatement(t.stringLiteral(inlineGipIdentifier)), + ]) + ) + }, + // handles class { static async getInitialProps() {} } + FunctionDeclaration(path, state: ConfigState) { + if (!state.setupInlining) return + if ((path.node.id && path.node.id.name) !== 'getInitialProps') return + path.node.body = t.blockStatement([ + t.returnStatement(t.stringLiteral(inlineGipIdentifier)), + ]) + }, }, } } diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 9e3be41a63..0ebfbaebea 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -334,7 +334,7 @@ export default async function build(dir: string, conf = null): Promise { } } - if (customAppGetInitialProps === false && nonReservedPage) { + if (nonReservedPage) { try { await staticCheckSema.acquire() const result: any = await new Promise((resolve, reject) => { @@ -348,7 +348,10 @@ export default async function build(dir: string, conf = null): Promise { }) staticCheckSema.release() - if (result.isStatic) { + if ( + (result.static && customAppGetInitialProps === false) || + result.prerender + ) { staticPages.add(page) isStatic = true } diff --git a/packages/next/build/static-checker.ts b/packages/next/build/static-checker.ts index d8f3a87081..a8f9b5d356 100644 --- a/packages/next/build/static-checker.ts +++ b/packages/next/build/static-checker.ts @@ -6,7 +6,7 @@ export default function worker( ) { try { const { serverBundle, runtimeEnvConfig } = options || ({} as any) - const isStatic = isPageStatic(serverBundle, runtimeEnvConfig) + const result = isPageStatic(serverBundle, runtimeEnvConfig) // clear require.cache to prevent running out of memory // since the cache is persisted by default @@ -19,7 +19,7 @@ export default function worker( } }) - callback(null, { isStatic }) + callback(null, result) } catch (error) { callback(error) } diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index fd0269efbd..bb2c8c9bc3 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -266,17 +266,22 @@ export async function getPageSizeInKb( export function isPageStatic( serverBundle: string, runtimeEnvConfig: any -): boolean { +): { static?: boolean; prerender?: boolean } { try { nextEnvConfig.setConfig(runtimeEnvConfig) - const Comp = require(serverBundle).default + const mod = require(serverBundle) + const Comp = mod.default if (!Comp || !isValidElementType(Comp) || typeof Comp === 'string') { throw new Error('INVALID_DEFAULT_EXPORT') } - return typeof (Comp as any).getInitialProps !== 'function' + + return { + static: typeof (Comp as any).getInitialProps !== 'function', + prerender: mod.config && mod.config.experimentalPrerender, + } } catch (err) { - if (err.code === 'MODULE_NOT_FOUND') return false + if (err.code === 'MODULE_NOT_FOUND') return {} throw err } } diff --git a/packages/next/build/webpack/loaders/next-babel-loader.js b/packages/next/build/webpack/loaders/next-babel-loader.js index 81b5f9885c..d9eee7db43 100644 --- a/packages/next/build/webpack/loaders/next-babel-loader.js +++ b/packages/next/build/webpack/loaders/next-babel-loader.js @@ -77,7 +77,7 @@ module.exports = babelLoader.custom(babel => { options.presets = [...options.presets, presetItem] } - if (!isServer && filename.indexOf('pages') !== -1) { + if (!isServer) { const pageConfigPlugin = babel.createConfigItem( [require('../../babel/plugins/next-page-config')], { type: 'plugin' } diff --git a/packages/next/build/webpack/loaders/next-serverless-loader.ts b/packages/next/build/webpack/loaders/next-serverless-loader.ts index 70bf533460..d04fcec2e5 100644 --- a/packages/next/build/webpack/loaders/next-serverless-loader.ts +++ b/packages/next/build/webpack/loaders/next-serverless-loader.ts @@ -55,7 +55,7 @@ const nextServerlessLoader: loader.Loader = function() { import * as ComponentInfo from '${absolutePagePath}'; const Component = ComponentInfo.default export default Component - export const PageConfig = ComponentInfo['confi' + 'g'] || {} + export const config = ComponentInfo['confi' + 'g'] || {} export const _app = App export async function renderReqToHTML(req, res, fromExport) { const options = { @@ -74,7 +74,7 @@ const nextServerlessLoader: loader.Loader = function() { const renderOpts = Object.assign( { Component, - PageConfig, + pageConfig: config, dataOnly: req.headers && (req.headers.accept || '').indexOf('application/amp.bind+json') !== -1, nextExport: fromExport }, diff --git a/packages/next/export/worker.js b/packages/next/export/worker.js index 72d5ec99e1..f089e8607e 100644 --- a/packages/next/export/worker.js +++ b/packages/next/export/worker.js @@ -2,14 +2,16 @@ import mkdirpModule from 'mkdirp' import { promisify } from 'util' import { extname, join, dirname, sep } from 'path' import { renderToHTML } from 'next-server/dist/server/render' -import { writeFile, access } from 'fs' +import { writeFile, access, readFile } from 'fs' import { Sema } from 'async-sema' import AmpHtmlValidator from 'amphtml-validator' import { loadComponents } from 'next-server/dist/server/load-components' +import { inlineGipIdentifier } from '../build/babel/plugins/next-page-config' const envConfig = require('next-server/config') const mkdirp = promisify(mkdirpModule) const writeFileP = promisify(writeFile) +const readFileP = promisify(readFile) const accessP = promisify(access) global.__NEXT_DATA__ = { @@ -106,6 +108,27 @@ process.on( } } + // inline pageData for getInitialProps + if (curRenderOpts.isPrerender && curRenderOpts.pageData) { + const dataStr = JSON.stringify(curRenderOpts.pageData) + .replace(/"/g, '\\"') + .replace(/'/g, "\\'") + + const bundlePath = join( + distDir, + 'static', + buildId, + 'pages', + (path === '/' ? 'index' : path) + '.js' + ) + + const bundleContent = await readFileP(bundlePath, 'utf8') + await writeFileP( + bundlePath, + bundleContent.replace(inlineGipIdentifier, dataStr) + ) + } + const validateAmp = async (html, page) => { const validator = await AmpHtmlValidator.getInstance() const result = validator.validateString(html) diff --git a/packages/next/types/index.d.ts b/packages/next/types/index.d.ts index b29fdc8ead..06c6353c85 100644 --- a/packages/next/types/index.d.ts +++ b/packages/next/types/index.d.ts @@ -11,6 +11,8 @@ import { NextApiRequest, } from 'next-server/dist/lib/utils' +import { PageConfig } from 'next-server/types' + // Extend the React types with missing properties declare module 'react' { // support @@ -43,14 +45,10 @@ export type NextPage

= { getInitialProps?(ctx: NextPageContext): Promise } -/** - * `Config` type, use it for export const config - */ -export type PageConfig = { - amp?: boolean | 'hybrid' - api?: { - bodyParser?: boolean - } +export { + NextPageContext, + NextComponentType, + NextApiResponse, + NextApiRequest, + PageConfig, } - -export { NextPageContext, NextComponentType, NextApiResponse, NextApiRequest } diff --git a/test/integration/prerender/next.config.js b/test/integration/prerender/next.config.js new file mode 100644 index 0000000000..2a00c13aec --- /dev/null +++ b/test/integration/prerender/next.config.js @@ -0,0 +1,5 @@ +module.exports = { + experimental: { + autoExport: true + } +} diff --git a/test/integration/prerender/pages/another.js b/test/integration/prerender/pages/another.js new file mode 100644 index 0000000000..7147114d0f --- /dev/null +++ b/test/integration/prerender/pages/another.js @@ -0,0 +1,7 @@ +const Page = ({ name }) =>

Pre-render page {name}

+ +Page.getInitialProps = async () => ({ name: 'John Deux' }) + +export const config = { experimentalPrerender: true } + +export default Page diff --git a/test/integration/prerender/pages/index.js b/test/integration/prerender/pages/index.js new file mode 100644 index 0000000000..dc05b59c6b --- /dev/null +++ b/test/integration/prerender/pages/index.js @@ -0,0 +1 @@ +export default () =>

An autoExported page

diff --git a/test/integration/prerender/pages/nested/hello.js b/test/integration/prerender/pages/nested/hello.js new file mode 100644 index 0000000000..1821a8d0e0 --- /dev/null +++ b/test/integration/prerender/pages/nested/hello.js @@ -0,0 +1,7 @@ +export const config = { experimentalPrerender: true } + +const Page = ({ title }) =>

{title}

+ +Page.getInitialProps = async () => ({ title: 'something' }) + +export default Page diff --git a/test/integration/prerender/pages/nested/old-school.js b/test/integration/prerender/pages/nested/old-school.js new file mode 100644 index 0000000000..5486ef5fe0 --- /dev/null +++ b/test/integration/prerender/pages/nested/old-school.js @@ -0,0 +1,5 @@ +const Page = () =>

I'm just an old SSR page

+ +Page.getInitialProps = () => ({}) + +export default Page diff --git a/test/integration/prerender/pages/old-school.js b/test/integration/prerender/pages/old-school.js new file mode 100644 index 0000000000..5486ef5fe0 --- /dev/null +++ b/test/integration/prerender/pages/old-school.js @@ -0,0 +1,5 @@ +const Page = () =>

I'm just an old SSR page

+ +Page.getInitialProps = () => ({}) + +export default Page diff --git a/test/integration/prerender/test/index.test.js b/test/integration/prerender/test/index.test.js new file mode 100644 index 0000000000..86afc7a262 --- /dev/null +++ b/test/integration/prerender/test/index.test.js @@ -0,0 +1,85 @@ +/* eslint-env jest */ +/* global jasmine */ +import fs from 'fs-extra' +import path from 'path' +import { + nextBuild, + nextStart, + findPort, + killApp, + renderViaHTTP +} from 'next-test-utils' + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 3 + +const appDir = path.join(__dirname, '../') +let buildId +let appPort +let app + +describe('Pre-rendering pages', () => { + beforeAll(async () => { + await nextBuild(appDir) + appPort = await findPort() + app = await nextStart(appDir, appPort) + buildId = ( + (await fs.readFile(path.join(appDir, '.next/BUILD_ID'), 'utf8')) || '' + ).trim() + }) + afterAll(() => killApp(app)) + + it('should render the correct files', async () => { + let files = ['nested/hello', 'another', 'index'] + + for (const file of files) { + expect( + await fs.exists( + path.join( + appDir, + '.next/server/static', + buildId, + 'pages', + file + '.html' + ) + ) + ).toBe(true) + } + + files = ['nested/old-school', 'old-school'] + + for (const file of files) { + expect( + await fs.exists( + path.join( + appDir, + '.next/server/static', + buildId, + 'pages', + file + '.js' + ) + ) + ).toBe(true) + } + }) + + it('should have called getInitialProps during pre-render', async () => { + const hello = await renderViaHTTP(appPort, '/nested/hello') + expect(hello).toMatch(/something/) + + const another = await renderViaHTTP(appPort, '/another') + expect(another).toMatch(/John Deux/) + }) + + it('should call getInitialProps for SSR pages', async () => { + const oldSchool1 = await renderViaHTTP(appPort, '/old-school') + expect(oldSchool1).toMatch(/I.*?m just an old SSR page/) + + const oldSchool2 = await renderViaHTTP(appPort, '/nested/old-school') + expect(oldSchool2).toMatch(/I.*?m just an old SSR page/) + }) + + it('should autoExport correctly', async () => { + const index = await renderViaHTTP(appPort, '/') + expect(index).toMatch(/An autoExported page/) + }) +}) diff --git a/test/integration/production/pages/something.js b/test/integration/production/pages/something.js new file mode 100644 index 0000000000..f779debae9 --- /dev/null +++ b/test/integration/production/pages/something.js @@ -0,0 +1,27 @@ +import Link from 'next/link' + +export const config = { + experimentalPrerender: 'inline' +} + +const Page = ({ data }) => { + return ( + <> +

{data}

+ + Click to to-something + + + ) +} + +Page.getInitialProps = async () => { + if (typeof window !== 'undefined') { + throw new Error(`this shouldn't be called`) + } + return { + data: 'this is some data to be inlined!!!' + } +} + +export default Page diff --git a/test/integration/production/pages/to-something.js b/test/integration/production/pages/to-something.js new file mode 100644 index 0000000000..a859c16b88 --- /dev/null +++ b/test/integration/production/pages/to-something.js @@ -0,0 +1,30 @@ +import React from 'react' +import Link from 'next/link' + +export const config = { + experimentalPrerender: true +} + +class Page extends React.Component { + static async getInitialProps () { + if (typeof window !== 'undefined') { + throw new Error(`this shouldn't be called`) + } + return { + title: 'some interesting title' + } + } + + render () { + return ( + <> +

{this.props.title}

+ + Click to something + + + ) + } +} + +export default Page diff --git a/test/integration/production/test/index.test.js b/test/integration/production/test/index.test.js index aef56b8030..ce6ea8b8aa 100644 --- a/test/integration/production/test/index.test.js +++ b/test/integration/production/test/index.test.js @@ -468,6 +468,26 @@ describe('Production Usage', () => { } }) + it('should pre-render pages with data correctly', async () => { + const toSomething = await renderViaHTTP(appPort, '/to-something') + expect(toSomething).toMatch(/some interesting title/) + + const something = await renderViaHTTP(appPort, '/something') + expect(something).toMatch(/this is some data to be inlined/) + }) + + it('should have inlined the data correctly in pre-render', async () => { + const browser = await webdriver(appPort, '/to-something') + await browser.elementByCss('#something').click() + + let text = await browser.elementByCss('h3').text() + expect(text).toMatch(/this is some data to be inlined/) + + await browser.elementByCss('#to-something').click() + text = await browser.elementByCss('h3').text() + expect(text).toMatch(/some interesting title/) + }) + dynamicImportTests(context, (p, q) => renderViaHTTP(context.appPort, p, q)) processEnv(context) diff --git a/test/integration/serverless/pages/something.js b/test/integration/serverless/pages/something.js new file mode 100644 index 0000000000..f779debae9 --- /dev/null +++ b/test/integration/serverless/pages/something.js @@ -0,0 +1,27 @@ +import Link from 'next/link' + +export const config = { + experimentalPrerender: 'inline' +} + +const Page = ({ data }) => { + return ( + <> +

{data}

+ + Click to to-something + + + ) +} + +Page.getInitialProps = async () => { + if (typeof window !== 'undefined') { + throw new Error(`this shouldn't be called`) + } + return { + data: 'this is some data to be inlined!!!' + } +} + +export default Page diff --git a/test/integration/serverless/pages/to-something.js b/test/integration/serverless/pages/to-something.js new file mode 100644 index 0000000000..a859c16b88 --- /dev/null +++ b/test/integration/serverless/pages/to-something.js @@ -0,0 +1,30 @@ +import React from 'react' +import Link from 'next/link' + +export const config = { + experimentalPrerender: true +} + +class Page extends React.Component { + static async getInitialProps () { + if (typeof window !== 'undefined') { + throw new Error(`this shouldn't be called`) + } + return { + title: 'some interesting title' + } + } + + render () { + return ( + <> +

{this.props.title}

+ + Click to something + + + ) + } +} + +export default Page diff --git a/test/integration/serverless/test/index.test.js b/test/integration/serverless/test/index.test.js index 9791db0348..eacb1ce329 100644 --- a/test/integration/serverless/test/index.test.js +++ b/test/integration/serverless/test/index.test.js @@ -113,6 +113,26 @@ describe('Serverless', () => { } }) + it('should pre-render pages with data correctly', async () => { + const toSomething = await renderViaHTTP(appPort, '/to-something') + expect(toSomething).toMatch(/some interesting title/) + + const something = await renderViaHTTP(appPort, '/something') + expect(something).toMatch(/this is some data to be inlined/) + }) + + it('should have inlined the data correctly in pre-render', async () => { + const browser = await webdriver(appPort, '/to-something') + await browser.elementByCss('#something').click() + + let text = await browser.elementByCss('h3').text() + expect(text).toMatch(/this is some data to be inlined/) + + await browser.elementByCss('#to-something').click() + text = await browser.elementByCss('h3').text() + expect(text).toMatch(/some interesting title/) + }) + describe('With basic usage', () => { it('should allow etag header support', async () => { const url = `http://localhost:${appPort}/`