From 850334827522e2fc4e1393e2455d0a3c1f854446 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 2 Jul 2019 13:28:46 -0700 Subject: [PATCH 1/9] Provide AppContainer to getInitialProps for getDataFromTree --- examples/with-apollo-auth/lib/withApollo.js | 15 +-- .../with-apollo/lib/with-apollo-client.js | 16 ++-- packages/next-server/lib/router/router.ts | 5 + packages/next-server/lib/utils.ts | 1 + packages/next-server/server/render.tsx | 93 ++++++++++--------- packages/next/client/index.js | 70 +++++++------- 6 files changed, 106 insertions(+), 94 deletions(-) diff --git a/examples/with-apollo-auth/lib/withApollo.js b/examples/with-apollo-auth/lib/withApollo.js index 416b0db4607dd..837ba6bf9bb70 100644 --- a/examples/with-apollo-auth/lib/withApollo.js +++ b/examples/with-apollo-auth/lib/withApollo.js @@ -19,6 +19,7 @@ export default App => { static async getInitialProps (ctx) { const { + AppContainer, Component, router, ctx: { req, res } @@ -49,12 +50,14 @@ export default App => { try { // Run all GraphQL queries await getDataFromTree( - + + + ) } catch (error) { // Prevent Apollo Client GraphQL errors from crashing SSR. diff --git a/examples/with-apollo/lib/with-apollo-client.js b/examples/with-apollo/lib/with-apollo-client.js index 4f058d66f3e7b..85efd3de9377e 100644 --- a/examples/with-apollo/lib/with-apollo-client.js +++ b/examples/with-apollo/lib/with-apollo-client.js @@ -7,7 +7,7 @@ export default App => { return class Apollo extends React.Component { static displayName = 'withApollo(App)' static async getInitialProps (ctx) { - const { Component, router } = ctx + const { AppContainer, Component, router } = ctx let appProps = {} if (App.getInitialProps) { @@ -21,12 +21,14 @@ export default App => { try { // Run all GraphQL queries await getDataFromTree( - + + + ) } catch (error) { // Prevent Apollo Client GraphQL errors from crashing SSR. diff --git a/packages/next-server/lib/router/router.ts b/packages/next-server/lib/router/router.ts index 4405bfb2239eb..016f79f1b5761 100644 --- a/packages/next-server/lib/router/router.ts +++ b/packages/next-server/lib/router/router.ts @@ -50,6 +50,7 @@ export default class Router implements BaseRouter { * Map of all components loaded in `Router` */ components: { [pathname: string]: RouteInfo } + AppContainer: ComponentType sub: Subscription clc: ComponentLoadCancel pageLoader: any @@ -65,6 +66,7 @@ export default class Router implements BaseRouter { initialProps, pageLoader, App, + AppContainer, Component, err, subscription, @@ -74,6 +76,7 @@ export default class Router implements BaseRouter { pageLoader: any Component: ComponentType App: ComponentType + AppContainer: ComponentType err?: Error } ) { @@ -102,6 +105,7 @@ export default class Router implements BaseRouter { this.asPath = as this.sub = subscription this.clc = null + this.AppContainer = AppContainer if (typeof window !== 'undefined') { // in order for `e.state` to work on the `onpopstate` event @@ -560,6 +564,7 @@ export default class Router implements BaseRouter { const { Component: App } = this.components['/_app'] const props = await loadGetInitialProps>(App, { + AppContainer: this.AppContainer, Component, router: this, ctx, diff --git a/packages/next-server/lib/utils.ts b/packages/next-server/lib/utils.ts index 2133484453725..5eeefd21bcce1 100644 --- a/packages/next-server/lib/utils.ts +++ b/packages/next-server/lib/utils.ts @@ -98,6 +98,7 @@ export interface NextPageContext { } export type AppContextType = { + AppContainer: NextComponentType Component: NextComponentType router: R ctx: NextPageContext diff --git a/packages/next-server/server/render.tsx b/packages/next-server/server/render.tsx index 6dfd7eddd40d6..108f5686dc177 100644 --- a/packages/next-server/server/render.tsx +++ b/packages/next-server/server/render.tsx @@ -314,8 +314,42 @@ export async function renderToHTML( await DocumentMiddleware(ctx) } + let dataManager: DataManager | undefined + if (ampBindInitData) { + dataManager = new DataManager() + } + + const ampState = { + ampFirst: pageConfig.amp === true, + hasQuery: Boolean(query.amp), + hybrid: pageConfig.amp === 'hybrid', + } + + const reactLoadableModules: string[] = [] + + const AppContainer = ({ children }: any) => ( + + + + + reactLoadableModules.push(moduleName)} + > + {children} + + + + + + ) + try { - props = await loadGetInitialProps(App, { Component, router, ctx }) + props = await loadGetInitialProps(App, { + AppContainer, + Component, + router, + ctx, + }) } catch (err) { if (!dev || !err) throw err ctx.err = err @@ -332,18 +366,7 @@ export async function renderToHTML( ...getPageFiles(buildManifest, '/_app'), ]), ] - let dataManager: DataManager | undefined - if (ampBindInitData) { - dataManager = new DataManager() - } - const ampState = { - ampFirst: pageConfig.amp === true, - hasQuery: Boolean(query.amp), - hybrid: pageConfig.amp === 'hybrid', - } - - const reactLoadableModules: string[] = [] const renderElementToString = staticMarkup ? renderToStaticMarkup : renderToString @@ -381,23 +404,13 @@ export async function renderToHTML( } = enhanceComponents(options, App, Component) const Application = () => ( - - - - - reactLoadableModules.push(moduleName)} - > - - - - - - + + + ) const element = @@ -434,21 +447,13 @@ export async function renderToHTML( return render( renderElementToString, - - - - reactLoadableModules.push(moduleName)} - > - - - - - , + + + , ampState ) } diff --git a/packages/next/client/index.js b/packages/next/client/index.js index 35b2967739aac..76aebecd3c722 100644 --- a/packages/next/client/index.js +++ b/packages/next/client/index.js @@ -63,7 +63,7 @@ window.__NEXT_P = [] window.__NEXT_P.push = register const headManager = new HeadManager() -const appContainer = document.getElementById('__next') +const appElement = document.getElementById('__next') let lastAppProps let webpackHMR @@ -159,6 +159,7 @@ export default async ({ webpackHMR: passedWebpackHMR } = {}) => { pageLoader, App, Component, + AppContainer, err: initialErr, subscription: ({ Component, props, err }, App) => { render({ App, Component, props, err, emitter }) @@ -224,6 +225,28 @@ function renderReactElement (reactEl, domEl) { } } +function AppContainer ({ children }) { + return ( + + renderError({ App, err: error }).catch(err => + console.error('Error rendering page: ', err) + ) + } + > + Loading...}> + + + + {children} + + + + + + ) +} + async function doRender ({ App, Component, props, err }) { // Usual getInitialProps fetching is handled in next/router // this is for when ErrorComponent gets replaced by Component by HMR @@ -235,6 +258,7 @@ async function doRender ({ App, Component, props, err }) { ) { const { pathname, query, asPath } = router props = await loadGetInitialProps(App, { + AppContainer, Component, router, ctx: { err, pathname, query, asPath } @@ -257,46 +281,18 @@ async function doRender ({ App, Component, props, err }) { // In development runtime errors are caught by react-error-overlay. if (process.env.NODE_ENV === 'development') { renderReactElement( - - renderError({ App, err: error }).catch(err => - console.error('Error rendering page: ', err) - ) - } - > - Loading...}> - - - - - - - - - , - appContainer + + + , + appElement ) } else { // In production we catch runtime errors using componentDidCatch which will trigger renderError. renderReactElement( - - renderError({ App, err: error }).catch(err => - console.error('Error rendering page: ', err) - ) - } - > - Loading...}> - - - - - - - - - , - appContainer + + + , + appElement ) } From e254fb524b224adb30c0d55f085e4fddf3024f0b Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 2 Jul 2019 14:40:37 -0700 Subject: [PATCH 2/9] Update to only pass AppTree component instead of AppContainer --- examples/with-apollo-auth/lib/withApollo.js | 16 +++++++--------- examples/with-apollo/lib/with-apollo-client.js | 16 +++++++--------- .../lib/router/{router.ts => router.tsx} | 14 ++++++++++---- packages/next-server/lib/utils.ts | 6 +++--- packages/next-server/server/render.tsx | 6 +++++- packages/next/client/index.js | 5 +++++ 6 files changed, 37 insertions(+), 26 deletions(-) rename packages/next-server/lib/router/{router.ts => router.tsx} (97%) diff --git a/examples/with-apollo-auth/lib/withApollo.js b/examples/with-apollo-auth/lib/withApollo.js index 837ba6bf9bb70..73cee1936c70b 100644 --- a/examples/with-apollo-auth/lib/withApollo.js +++ b/examples/with-apollo-auth/lib/withApollo.js @@ -19,7 +19,7 @@ export default App => { static async getInitialProps (ctx) { const { - AppContainer, + AppTree, Component, router, ctx: { req, res } @@ -50,14 +50,12 @@ export default App => { try { // Run all GraphQL queries await getDataFromTree( - - - + ) } catch (error) { // Prevent Apollo Client GraphQL errors from crashing SSR. diff --git a/examples/with-apollo/lib/with-apollo-client.js b/examples/with-apollo/lib/with-apollo-client.js index 85efd3de9377e..c1e74a7e6b9cb 100644 --- a/examples/with-apollo/lib/with-apollo-client.js +++ b/examples/with-apollo/lib/with-apollo-client.js @@ -7,7 +7,7 @@ export default App => { return class Apollo extends React.Component { static displayName = 'withApollo(App)' static async getInitialProps (ctx) { - const { AppContainer, Component, router } = ctx + const { AppTree, Component, router } = ctx let appProps = {} if (App.getInitialProps) { @@ -21,14 +21,12 @@ export default App => { try { // Run all GraphQL queries await getDataFromTree( - - - + ) } catch (error) { // Prevent Apollo Client GraphQL errors from crashing SSR. diff --git a/packages/next-server/lib/router/router.ts b/packages/next-server/lib/router/router.tsx similarity index 97% rename from packages/next-server/lib/router/router.ts rename to packages/next-server/lib/router/router.tsx index 016f79f1b5761..4253955332f2f 100644 --- a/packages/next-server/lib/router/router.ts +++ b/packages/next-server/lib/router/router.tsx @@ -1,5 +1,6 @@ /* global __NEXT_DATA__ */ // tslint:disable:no-console +import React from 'react' import { ParsedUrlQuery } from 'querystring' import { ComponentType } from 'react' import { parse } from 'url' @@ -561,10 +562,15 @@ export default class Router implements BaseRouter { cancelled = true } this.clc = cancel - const { Component: App } = this.components['/_app'] - - const props = await loadGetInitialProps>(App, { - AppContainer: this.AppContainer, + const { Component: App } = this.components['/_app'] as any + const AppContainer = this.AppContainer + + const props: any = await loadGetInitialProps>(App, { + AppTree: props => ( + + + + ), Component, router: this, ctx, diff --git a/packages/next-server/lib/utils.ts b/packages/next-server/lib/utils.ts index 5eeefd21bcce1..45a3e8f76a01e 100644 --- a/packages/next-server/lib/utils.ts +++ b/packages/next-server/lib/utils.ts @@ -1,6 +1,6 @@ import { format, UrlObject, URLFormatOptions } from 'url' import { ServerResponse, IncomingMessage } from 'http' -import { ComponentType } from 'react' +import { ComponentType, Component, FunctionComponent } from 'react' import { ParsedUrlQuery } from 'querystring' import { ManifestItem } from '../server/render' import { BaseRouter } from './router/router' @@ -98,10 +98,10 @@ export interface NextPageContext { } export type AppContextType = { - AppContainer: NextComponentType Component: NextComponentType - router: R + AppTree: NextComponentType ctx: NextPageContext + router: R } export type AppInitialProps = { diff --git a/packages/next-server/server/render.tsx b/packages/next-server/server/render.tsx index 108f5686dc177..6fe2bd96771e4 100644 --- a/packages/next-server/server/render.tsx +++ b/packages/next-server/server/render.tsx @@ -345,8 +345,12 @@ export async function renderToHTML( try { props = await loadGetInitialProps(App, { - AppContainer, Component, + AppTree: (props: any) => ( + + + + ), router, ctx, }) diff --git a/packages/next/client/index.js b/packages/next/client/index.js index 76aebecd3c722..b16f2baa9c78d 100644 --- a/packages/next/client/index.js +++ b/packages/next/client/index.js @@ -205,6 +205,11 @@ export async function renderError (props) { const initProps = props.props ? props.props : await loadGetInitialProps(App, { + AppTree: props => ( + + + + ), Component: ErrorComponent, router, ctx: { err, pathname: page, query, asPath } From c1101dceb9cb68e55f05d416fbba911d3fbe5247 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 2 Jul 2019 19:30:37 -0700 Subject: [PATCH 3/9] Clean up props and remove extra imports --- packages/next-server/lib/utils.ts | 2 +- packages/next-server/server/render.tsx | 13 ++++++++----- packages/next/client/index.js | 13 ++++++++----- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/next-server/lib/utils.ts b/packages/next-server/lib/utils.ts index 45a3e8f76a01e..b9b883c426e2e 100644 --- a/packages/next-server/lib/utils.ts +++ b/packages/next-server/lib/utils.ts @@ -1,6 +1,6 @@ import { format, UrlObject, URLFormatOptions } from 'url' import { ServerResponse, IncomingMessage } from 'http' -import { ComponentType, Component, FunctionComponent } from 'react' +import { ComponentType } from 'react' import { ParsedUrlQuery } from 'querystring' import { ManifestItem } from '../server/render' import { BaseRouter } from './router/router' diff --git a/packages/next-server/server/render.tsx b/packages/next-server/server/render.tsx index 6fe2bd96771e4..e457f529dfa54 100644 --- a/packages/next-server/server/render.tsx +++ b/packages/next-server/server/render.tsx @@ -346,11 +346,14 @@ export async function renderToHTML( try { props = await loadGetInitialProps(App, { Component, - AppTree: (props: any) => ( - - - - ), + AppTree: (props: any) => { + const appProps = { Component, router, ...props } + return ( + + + + ) + }, router, ctx, }) diff --git a/packages/next/client/index.js b/packages/next/client/index.js index b16f2baa9c78d..4715a82102b32 100644 --- a/packages/next/client/index.js +++ b/packages/next/client/index.js @@ -205,11 +205,14 @@ export async function renderError (props) { const initProps = props.props ? props.props : await loadGetInitialProps(App, { - AppTree: props => ( - - - - ), + AppTree: props => { + const appProps = { Component, err, router, ...props } + return ( + + + + ) + }, Component: ErrorComponent, router, ctx: { err, pathname: page, query, asPath } From b9b3f74fecca87cfa601ea2f3bfff90bda5398a1 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 2 Jul 2019 19:35:43 -0700 Subject: [PATCH 4/9] Make updates from review --- examples/with-apollo-auth/lib/withApollo.js | 11 +---- .../with-apollo/lib/with-apollo-client.js | 11 +---- packages/next-server/lib/router/router.tsx | 2 +- packages/next-server/server/render.tsx | 2 +- packages/next/client/index.js | 46 +++++++++++-------- 5 files changed, 33 insertions(+), 39 deletions(-) diff --git a/examples/with-apollo-auth/lib/withApollo.js b/examples/with-apollo-auth/lib/withApollo.js index 73cee1936c70b..35b72d40f5347 100644 --- a/examples/with-apollo-auth/lib/withApollo.js +++ b/examples/with-apollo-auth/lib/withApollo.js @@ -20,8 +20,6 @@ export default App => { static async getInitialProps (ctx) { const { AppTree, - Component, - router, ctx: { req, res } } = ctx const apollo = initApollo( @@ -49,14 +47,7 @@ export default App => { // and extract the resulting data try { // Run all GraphQL queries - await getDataFromTree( - - ) + await getDataFromTree() } catch (error) { // Prevent Apollo Client GraphQL errors from crashing SSR. // Handle them in components via the data.error prop: diff --git a/examples/with-apollo/lib/with-apollo-client.js b/examples/with-apollo/lib/with-apollo-client.js index c1e74a7e6b9cb..139761f375a63 100644 --- a/examples/with-apollo/lib/with-apollo-client.js +++ b/examples/with-apollo/lib/with-apollo-client.js @@ -7,7 +7,7 @@ export default App => { return class Apollo extends React.Component { static displayName = 'withApollo(App)' static async getInitialProps (ctx) { - const { AppTree, Component, router } = ctx + const { AppTree } = ctx let appProps = {} if (App.getInitialProps) { @@ -20,14 +20,7 @@ export default App => { if (typeof window === 'undefined') { try { // Run all GraphQL queries - await getDataFromTree( - - ) + await getDataFromTree() } catch (error) { // Prevent Apollo Client GraphQL errors from crashing SSR. // Handle them in components via the data.error prop: diff --git a/packages/next-server/lib/router/router.tsx b/packages/next-server/lib/router/router.tsx index 1e73b7f880626..faa4e9b789c8e 100644 --- a/packages/next-server/lib/router/router.tsx +++ b/packages/next-server/lib/router/router.tsx @@ -575,7 +575,7 @@ export default class Router implements BaseRouter { const props: any = await loadGetInitialProps>(App, { AppTree: props => ( - + ), Component, diff --git a/packages/next-server/server/render.tsx b/packages/next-server/server/render.tsx index e457f529dfa54..3b575935160b7 100644 --- a/packages/next-server/server/render.tsx +++ b/packages/next-server/server/render.tsx @@ -347,7 +347,7 @@ export async function renderToHTML( props = await loadGetInitialProps(App, { Component, AppTree: (props: any) => { - const appProps = { Component, router, ...props } + const appProps = { ...props, Component, router } return ( diff --git a/packages/next/client/index.js b/packages/next/client/index.js index 4715a82102b32..82b8315bd8b9b 100644 --- a/packages/next/client/index.js +++ b/packages/next/client/index.js @@ -202,21 +202,23 @@ export async function renderError (props) { // In production we do a normal render with the `ErrorComponent` as component. // If we've gotten here upon initial render, we can use the props from the server. // Otherwise, we need to call `getInitialProps` on `App` before mounting. + const appCtx = { + AppTree: props => { + const appProps = { ...props, Component, err, router } + return ( + + + + ) + }, + Component: ErrorComponent, + router, + ctx: { err, pathname: page, query, asPath } + } + const initProps = props.props ? props.props - : await loadGetInitialProps(App, { - AppTree: props => { - const appProps = { Component, err, router, ...props } - return ( - - - - ) - }, - Component: ErrorComponent, - router, - ctx: { err, pathname: page, query, asPath } - }) + : await loadGetInitialProps(App, appCtx) await doRender({ ...props, err, Component: ErrorComponent, props: initProps }) } @@ -265,18 +267,26 @@ async function doRender ({ App, Component, props, err }) { lastAppProps.Component === ErrorComponent ) { const { pathname, query, asPath } = router - props = await loadGetInitialProps(App, { - AppContainer, - Component, + const appCtx = { + AppTree: props => { + const appProps = { ...props, Component, err, router } + return ( + + + + ) + }, router, + Component: ErrorComponent, ctx: { err, pathname, query, asPath } - }) + } + props = await loadGetInitialProps(App, appCtx) } Component = Component || lastAppProps.Component props = props || lastAppProps.props - const appProps = { Component, err, router, ...props } + const appProps = { ...props, Component, err, router } // lastAppProps has to be set before ReactDom.render to account for ReactDom throwing an error. lastAppProps = appProps From ff415c4ad521351d0700f5dcc2c4d5ac2d7e1d40 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 16 Jul 2019 16:27:37 -0700 Subject: [PATCH 5/9] De-dupe AppTree a bit --- packages/next/client/index.js | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/packages/next/client/index.js b/packages/next/client/index.js index c76541b4e5ee3..86f9be47ee6d8 100644 --- a/packages/next/client/index.js +++ b/packages/next/client/index.js @@ -213,14 +213,7 @@ export async function renderError (props) { // If we've gotten here upon initial render, we can use the props from the server. // Otherwise, we need to call `getInitialProps` on `App` before mounting. const appCtx = { - AppTree: props => { - const appProps = { ...props, Component, err, router } - return ( - - - - ) - }, + AppTree: wrapApp(App), Component: ErrorComponent, router, ctx: { err, pathname: page, query, asPath } @@ -267,6 +260,15 @@ function AppContainer ({ children }) { ) } +const wrapApp = App => props => { + const appProps = { ...props, Component, err, router } + return ( + + + + ) +} + async function doRender ({ App, Component, props, err }) { // Usual getInitialProps fetching is handled in next/router // this is for when ErrorComponent gets replaced by Component by HMR @@ -278,15 +280,8 @@ async function doRender ({ App, Component, props, err }) { ) { const { pathname, query, asPath } = router const appCtx = { - AppTree: props => { - const appProps = { ...props, Component, err, router } - return ( - - - - ) - }, router, + AppTree: wrapApp(App), Component: ErrorComponent, ctx: { err, pathname, query, asPath } } From 1f18aa25567edcfa5b97a995d9e162d2fa0cb865 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 23 Jul 2019 16:29:54 -0500 Subject: [PATCH 6/9] Re-use wrapApp in router --- .../lib/router/{router.tsx => router.ts} | 16 ++++-------- packages/next-server/server/render.tsx | 25 ++++++++----------- packages/next/client/index.js | 2 +- packages/next/client/router.ts | 5 ---- 4 files changed, 17 insertions(+), 31 deletions(-) rename packages/next-server/lib/router/{router.tsx => router.ts} (98%) diff --git a/packages/next-server/lib/router/router.tsx b/packages/next-server/lib/router/router.ts similarity index 98% rename from packages/next-server/lib/router/router.tsx rename to packages/next-server/lib/router/router.ts index 5f6cd64ce72fb..070802ec587bf 100644 --- a/packages/next-server/lib/router/router.tsx +++ b/packages/next-server/lib/router/router.ts @@ -1,6 +1,5 @@ /* global __NEXT_DATA__ */ // tslint:disable:no-console -import React from 'react' import { ParsedUrlQuery } from 'querystring' import { ComponentType } from 'react' import { parse, UrlObject } from 'url' @@ -65,12 +64,12 @@ export default class Router implements BaseRouter { * Map of all components loaded in `Router` */ components: { [pathname: string]: RouteInfo } - AppContainer: ComponentType sub: Subscription clc: ComponentLoadCancel pageLoader: any _bps: BeforePopStateCallback | undefined events: MittEmitter + _wrapApp: (App: ComponentType) => any static events: MittEmitter = mitt() @@ -82,7 +81,7 @@ export default class Router implements BaseRouter { initialProps, pageLoader, App, - AppContainer, + wrapApp, Component, err, subscription, @@ -92,7 +91,7 @@ export default class Router implements BaseRouter { pageLoader: any Component: ComponentType App: ComponentType - AppContainer: ComponentType + wrapApp: (App: ComponentType) => any err?: Error } ) { @@ -121,7 +120,7 @@ export default class Router implements BaseRouter { this.asPath = as this.sub = subscription this.clc = null - this.AppContainer = AppContainer + this._wrapApp = wrapApp if (typeof window !== 'undefined') { // in order for `e.state` to work on the `onpopstate` event @@ -584,14 +583,9 @@ export default class Router implements BaseRouter { } this.clc = cancel const { Component: App } = this.components['/_app'] as any - const AppContainer = this.AppContainer const props: any = await loadGetInitialProps>(App, { - AppTree: props => ( - - - - ), + AppTree: this._wrapApp(App), Component, router: this, ctx, diff --git a/packages/next-server/server/render.tsx b/packages/next-server/server/render.tsx index dff14e6b09534..7564312044be2 100644 --- a/packages/next-server/server/render.tsx +++ b/packages/next-server/server/render.tsx @@ -20,7 +20,6 @@ import Head, { defaultHead } from '../lib/head' // @ts-ignore types will be added later as it's an internal module import Loadable from '../lib/loadable' import { DataManagerContext } from '../lib/data-manager-context' -import { RequestContext } from '../lib/request-context' import { LoadableContext } from '../lib/loadable-context' import { RouterContext } from '../lib/router-context' import { DataManager } from '../lib/data-manager' @@ -325,19 +324,17 @@ export async function renderToHTML( const reactLoadableModules: string[] = [] const AppContainer = ({ children }: any) => ( - - - - - reactLoadableModules.push(moduleName)} - > - {children} - - - - - + + + + reactLoadableModules.push(moduleName)} + > + {children} + + + + ) try { diff --git a/packages/next/client/index.js b/packages/next/client/index.js index 86f9be47ee6d8..b0570f98913c5 100644 --- a/packages/next/client/index.js +++ b/packages/next/client/index.js @@ -167,7 +167,7 @@ export default async ({ webpackHMR: passedWebpackHMR } = {}) => { pageLoader, App, Component, - AppContainer, + wrapApp, err: initialErr, subscription: ({ Component, props, err }, App) => { render({ App, Component, props, err, emitter }) diff --git a/packages/next/client/router.ts b/packages/next/client/router.ts index 7dbaa37ed749f..40faed16da16d 100644 --- a/packages/next/client/router.ts +++ b/packages/next/client/router.ts @@ -2,7 +2,6 @@ import React from 'react' import Router, { NextRouter } from 'next-server/dist/lib/router/router' import { RouterContext } from 'next-server/dist/lib/router-context' -import { RequestContext } from 'next-server/dist/lib/request-context' type ClassArguments = T extends new (...args: infer U) => any ? U : any @@ -118,10 +117,6 @@ export function useRouter() { return React.useContext(RouterContext) } -export function useRequest() { - return React.useContext(RequestContext) -} - // INTERNAL APIS // ------------- // (do not use following exports inside the app) From 76b29173fc50a8261cae5c3390a74c06b98f805e Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 23 Jul 2019 16:43:37 -0500 Subject: [PATCH 7/9] Remove un-needed change --- packages/next-server/lib/router/router.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/next-server/lib/router/router.ts b/packages/next-server/lib/router/router.ts index 070802ec587bf..0ca5f677d4be6 100644 --- a/packages/next-server/lib/router/router.ts +++ b/packages/next-server/lib/router/router.ts @@ -582,9 +582,9 @@ export default class Router implements BaseRouter { cancelled = true } this.clc = cancel - const { Component: App } = this.components['/_app'] as any + const { Component: App } = this.components['/_app'] - const props: any = await loadGetInitialProps>(App, { + const props = await loadGetInitialProps>(App, { AppTree: this._wrapApp(App), Component, router: this, From 455d3d4e24c193b7d8f2e095f09ee3f867c76a2f Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 30 Jul 2019 11:26:42 -0500 Subject: [PATCH 8/9] revert changes to examples until on stable --- examples/with-apollo-auth/lib/withApollo.js | 12 ++++++++++-- examples/with-apollo/lib/with-apollo-client.js | 11 +++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/examples/with-apollo-auth/lib/withApollo.js b/examples/with-apollo-auth/lib/withApollo.js index 35b72d40f5347..416b0db4607dd 100644 --- a/examples/with-apollo-auth/lib/withApollo.js +++ b/examples/with-apollo-auth/lib/withApollo.js @@ -19,7 +19,8 @@ export default App => { static async getInitialProps (ctx) { const { - AppTree, + Component, + router, ctx: { req, res } } = ctx const apollo = initApollo( @@ -47,7 +48,14 @@ export default App => { // and extract the resulting data try { // Run all GraphQL queries - await getDataFromTree() + await getDataFromTree( + + ) } catch (error) { // Prevent Apollo Client GraphQL errors from crashing SSR. // Handle them in components via the data.error prop: diff --git a/examples/with-apollo/lib/with-apollo-client.js b/examples/with-apollo/lib/with-apollo-client.js index 139761f375a63..4f058d66f3e7b 100644 --- a/examples/with-apollo/lib/with-apollo-client.js +++ b/examples/with-apollo/lib/with-apollo-client.js @@ -7,7 +7,7 @@ export default App => { return class Apollo extends React.Component { static displayName = 'withApollo(App)' static async getInitialProps (ctx) { - const { AppTree } = ctx + const { Component, router } = ctx let appProps = {} if (App.getInitialProps) { @@ -20,7 +20,14 @@ export default App => { if (typeof window === 'undefined') { try { // Run all GraphQL queries - await getDataFromTree() + await getDataFromTree( + + ) } catch (error) { // Prevent Apollo Client GraphQL errors from crashing SSR. // Handle them in components via the data.error prop: From 5223c8e633db7ac345cb05c0b6b220fc442f8a90 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 30 Jul 2019 12:03:14 -0500 Subject: [PATCH 9/9] Add test for AppTree --- test/integration/app-tree/pages/_app.js | 57 +++++++++++++ test/integration/app-tree/pages/another.js | 12 +++ test/integration/app-tree/pages/index.js | 12 +++ test/integration/app-tree/test/index.test.js | 86 ++++++++++++++++++++ 4 files changed, 167 insertions(+) create mode 100644 test/integration/app-tree/pages/_app.js create mode 100644 test/integration/app-tree/pages/another.js create mode 100644 test/integration/app-tree/pages/index.js create mode 100644 test/integration/app-tree/test/index.test.js diff --git a/test/integration/app-tree/pages/_app.js b/test/integration/app-tree/pages/_app.js new file mode 100644 index 0000000000000..7952c779517d3 --- /dev/null +++ b/test/integration/app-tree/pages/_app.js @@ -0,0 +1,57 @@ +import React from 'react' +import Link from 'next/link' +import { render } from 'react-dom' +import App, { Container } from 'next/app' +import { renderToString } from 'react-dom/server' + +class MyApp extends App { + static async getInitialProps ({ Component, AppTree, router, ctx }) { + let pageProps = {} + + if (Component.getInitialProps) { + pageProps = await Component.getInitialProps(ctx) + } + + let html + const toRender = ( + + ) + + if (typeof window !== 'undefined') { + const el = document.createElement('div') + document.querySelector('body').appendChild(el) + render(toRender, el) + html = el.innerHTML + el.remove() + } else { + html = renderToString(toRender) + } + + return { pageProps, html } + } + + render () { + const { Component, pageProps, html, router } = this.props + const href = router.pathname === '/' ? '/another' : '/' + + return html ? ( + <> +
+ + to {href} + + + ) : ( + + + + ) + } +} + +export default MyApp diff --git a/test/integration/app-tree/pages/another.js b/test/integration/app-tree/pages/another.js new file mode 100644 index 0000000000000..082531006e0d3 --- /dev/null +++ b/test/integration/app-tree/pages/another.js @@ -0,0 +1,12 @@ +import { useRouter } from 'next/router' + +const Page = () => { + const { pathname } = useRouter() + return ( + <> +

page: {pathname}

+ + ) +} + +export default Page diff --git a/test/integration/app-tree/pages/index.js b/test/integration/app-tree/pages/index.js new file mode 100644 index 0000000000000..082531006e0d3 --- /dev/null +++ b/test/integration/app-tree/pages/index.js @@ -0,0 +1,12 @@ +import { useRouter } from 'next/router' + +const Page = () => { + const { pathname } = useRouter() + return ( + <> +

page: {pathname}

+ + ) +} + +export default Page diff --git a/test/integration/app-tree/test/index.test.js b/test/integration/app-tree/test/index.test.js new file mode 100644 index 0000000000000..09b66457fdb56 --- /dev/null +++ b/test/integration/app-tree/test/index.test.js @@ -0,0 +1,86 @@ +/* eslint-env jest */ +/* global jasmine */ +import path from 'path' +import fs from 'fs-extra' +import webdriver from 'next-webdriver' +import { + nextBuild, + nextStart, + findPort, + launchApp, + killApp, + renderViaHTTP, + waitFor, + killAll +} from 'next-test-utils' + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 1 + +const appDir = path.join(__dirname, '..') +const nextConfig = path.join(appDir, 'next.config.js') +let appPort +let app + +const runTests = () => { + it('should provide router context in AppTree on SSR', async () => { + let html = await renderViaHTTP(appPort, '/') + expect(html).toMatch(/page:.*?\//) + + html = await renderViaHTTP(appPort, '/another') + expect(html).toMatch(/page:.*?\/another/) + }) + + it('should provide router context in AppTree on CSR', async () => { + const browser = await webdriver(appPort, '/') + let html = await browser.eval(`document.documentElement.innerHTML`) + expect(html).toMatch(/page:.*?\//) + + browser.elementByCss('#another').click() + await waitFor(500) + html = await browser.eval(`document.documentElement.innerHTML`) + expect(html).toMatch(/page:.*?\//) + + browser.elementByCss('#home').click() + await waitFor(500) + html = await browser.eval(`document.documentElement.innerHTML`) + expect(html).toMatch(/page:.*?\/another/) + }) +} + +describe('Auto Export', () => { + describe('dev mode', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(appDir, appPort) + }) + afterAll(() => killApp(app)) + runTests() + }) + + describe('production mode', () => { + beforeAll(async () => { + await nextBuild(appDir) + appPort = await findPort() + app = await nextStart(appDir, appPort) + }) + afterAll(() => killAll(app)) + runTests() + }) + + describe('serverless mode', () => { + beforeAll(async () => { + await fs.writeFile( + nextConfig, + `module.exports = { target: 'serverless' }` + ) + await nextBuild(appDir) + appPort = await findPort() + app = await nextStart(appDir, appPort) + }) + afterAll(async () => { + await killApp(app) + await fs.remove(nextConfig) + }) + runTests() + }) +})