Skip to content

Commit

Permalink
Update method for attaching GS(S)P identifier to page (vercel#10859)
Browse files Browse the repository at this point in the history
* Update to use separate export instead of attaching to Component to identify GS(S)P pages

* Handle initialProps being undefined
  • Loading branch information
ijjk authored and chibicode committed Mar 6, 2020
1 parent 0d6e494 commit 1099108
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 111 deletions.
76 changes: 26 additions & 50 deletions packages/next/build/babel/plugins/next-ssg-transform.ts
Expand Up @@ -6,8 +6,6 @@ import {
SERVER_PROPS_ID,
} from '../../../next-server/lib/constants'

const pageComponentVar = '__NEXT_COMP'

export const EXPORT_NAME_GET_STATIC_PROPS = 'getStaticProps'
export const EXPORT_NAME_GET_STATIC_PATHS = 'getStaticPaths'
export const EXPORT_NAME_GET_SERVER_PROPS = 'getServerSideProps'
Expand Down Expand Up @@ -37,64 +35,42 @@ function decorateSsgExport(
path: NodePath<BabelTypes.Program>,
state: PluginState
) {
path.traverse({
ExportDefaultDeclaration(path) {
if (state.done) {
return
}
state.done = true
const gsspName = state.isPrerender ? STATIC_PROPS_ID : SERVER_PROPS_ID
const gsspId = t.identifier(gsspName)

const prev = path.node.declaration
if (prev.type.endsWith('Declaration')) {
prev.type = prev.type.replace(/Declaration$/, 'Expression') as any
}
const addGsspExport = (
path: NodePath<
BabelTypes.ExportDefaultDeclaration | BabelTypes.ExportNamedDeclaration
>
) => {
if (state.done) {
return
}
state.done = true

// @ts-ignore invalid return type
const [pageCompPath] = path.replaceWithMultiple([
// @ts-ignore invalid return type
const [pageCompPath] = path.replaceWithMultiple([
t.exportNamedDeclaration(
t.variableDeclaration(
// We use 'var' instead of 'let' or 'const' for ES5 support. Since
// this runs in `Program#exit`, no ES2015 transforms (preset env)
// will be ran against this code.
'var',
[t.variableDeclarator(t.identifier(pageComponentVar), prev as any)]
),
t.assignmentExpression(
'=',
t.memberExpression(
t.identifier(pageComponentVar),
t.identifier(state.isPrerender ? STATIC_PROPS_ID : SERVER_PROPS_ID)
),
t.booleanLiteral(true)
[t.variableDeclarator(gsspId, t.booleanLiteral(true))]
),
t.exportDefaultDeclaration(t.identifier(pageComponentVar)),
])
path.scope.registerDeclaration(pageCompPath)
[t.exportSpecifier(gsspId, gsspId)]
),
path.node,
])
path.scope.registerDeclaration(pageCompPath)
}

path.traverse({
ExportDefaultDeclaration(path) {
addGsspExport(path)
},
ExportNamedDeclaration(path) {
if (state.done) {
return
}

// Look for a `export { _ as default }` specifier
const defaultSpecifier = path.node.specifiers.find(s => {
return s.exported.name === 'default'
})
if (!defaultSpecifier) {
return
}
state.done = true

path.replaceWithMultiple([
t.assignmentExpression(
'=',
t.memberExpression(
t.identifier((defaultSpecifier as any).local.name),
t.identifier(state.isPrerender ? STATIC_PROPS_ID : SERVER_PROPS_ID)
),
t.booleanLiteral(true)
),
path.node,
])
addGsspExport(path)
},
})
}
Expand Down
7 changes: 3 additions & 4 deletions packages/next/client/index.js
Expand Up @@ -100,7 +100,7 @@ class Container extends React.Component {
(isFallback ||
(data.nextExport &&
(isDynamicRoute(router.pathname) || location.search)) ||
(Component && Component.__N_SSG && location.search))
(props.__N_SSG && location.search))
) {
// update query on mount for exported pages
router.replace(
Expand Down Expand Up @@ -180,7 +180,7 @@ export default async ({ webpackHMR: passedWebpackHMR } = {}) => {
let initialErr = err

try {
Component = await pageLoader.loadPage(page)
;({ page: Component } = await pageLoader.loadPage(page))

if (process.env.NODE_ENV !== 'production') {
const { isValidElementType } = require('react-is')
Expand Down Expand Up @@ -273,8 +273,7 @@ export async function renderError(props) {

// Make sure we log the error to the console, otherwise users can't track down issues.
console.error(err)

ErrorComponent = await pageLoader.loadPage('/_error')
;({ page: ErrorComponent } = await pageLoader.loadPage('/_error'))

// 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.
Expand Down
2 changes: 1 addition & 1 deletion packages/next/client/page-loader.js
Expand Up @@ -175,7 +175,7 @@ export default class PageLoader {
}

loadPage(route) {
return this.loadPageScript(route).then(v => v.page)
return this.loadPageScript(route)
}

loadPageScript(route) {
Expand Down
41 changes: 31 additions & 10 deletions packages/next/next-server/lib/router/router.ts
Expand Up @@ -31,6 +31,8 @@ const prepareRoute = (path: string) =>

type Url = UrlObject | string

type ComponentRes = { page: ComponentType; mod: any }

export type BaseRouter = {
route: string
pathname: string
Expand All @@ -57,6 +59,8 @@ export type PrefetchOptions = {

type RouteInfo = {
Component: ComponentType
__N_SSG?: boolean
__N_SSP?: boolean
props?: any
err?: Error
error?: any
Expand Down Expand Up @@ -179,7 +183,13 @@ export default class Router implements BaseRouter {
// Otherwise, this cause issues when when going back and
// come again to the errored page.
if (pathname !== '/_error') {
this.components[this.route] = { Component, props: initialProps, err }
this.components[this.route] = {
Component,
props: initialProps,
err,
__N_SSG: initialProps && initialProps.__N_SSG,
__N_SSP: initialProps && initialProps.__N_SSP,
}
}

this.components['/_app'] = { Component: App }
Expand Down Expand Up @@ -284,7 +294,12 @@ export default class Router implements BaseRouter {
throw new Error(`Cannot update unavailable route: ${route}`)
}

const newData = { ...data, Component }
const newData = {
...data,
Component,
__N_SSG: mod.__N_SSG,
__N_SSP: mod.__N_SSP,
}
this.components[route] = newData

// pages/_app.js updated
Expand Down Expand Up @@ -542,7 +557,8 @@ export default class Router implements BaseRouter {

resolve(
this.fetchComponent('/_error')
.then(Component => {
.then(res => {
const { page: Component } = res
const routeInfo: RouteInfo = { Component, err }
return new Promise(resolve => {
this.getInitialProps(Component, {
Expand Down Expand Up @@ -578,12 +594,17 @@ export default class Router implements BaseRouter {
}

this.fetchComponent(route).then(
Component => resolve({ Component }),
res =>
resolve({
Component: res.page,
__N_SSG: res.mod.__N_SSG,
__N_SSP: res.mod.__N_SSP,
}),
reject
)
}) as Promise<RouteInfo>)
.then((routeInfo: RouteInfo) => {
const { Component } = routeInfo
const { Component, __N_SSG, __N_SSP } = routeInfo

if (process.env.NODE_ENV !== 'production') {
const { isValidElementType } = require('react-is')
Expand All @@ -595,9 +616,9 @@ export default class Router implements BaseRouter {
}

return this._getData<RouteInfo>(() =>
(Component as any).__N_SSG
__N_SSG
? this._getStaticData(as)
: (Component as any).__N_SSP
: __N_SSP
? this._getServerData(as)
: this.getInitialProps(
Component,
Expand Down Expand Up @@ -726,13 +747,13 @@ export default class Router implements BaseRouter {
})
}

async fetchComponent(route: string): Promise<ComponentType> {
async fetchComponent(route: string): Promise<ComponentRes> {
let cancelled = false
const cancel = (this.clc = () => {
cancelled = true
})

const Component = await this.pageLoader.loadPage(route)
const componentResult = await this.pageLoader.loadPage(route)

if (cancelled) {
const error: any = new Error(
Expand All @@ -746,7 +767,7 @@ export default class Router implements BaseRouter {
this.clc = null
}

return Component
return componentResult
}

_getData<T>(fn: () => Promise<T>): Promise<T> {
Expand Down
2 changes: 2 additions & 0 deletions packages/next/next-server/lib/utils.ts
Expand Up @@ -134,6 +134,8 @@ export type AppPropsType<
> = AppInitialProps & {
Component: NextComponentType<NextPageContext, any, P>
router: R
__N_SSG?: boolean
__N_SSP?: boolean
}

export type DocumentContext = NextPageContext & {
Expand Down
20 changes: 0 additions & 20 deletions packages/next/next-server/server/load-components.ts
Expand Up @@ -3,8 +3,6 @@ import {
CLIENT_STATIC_FILES_PATH,
REACT_LOADABLE_MANIFEST,
SERVER_DIRECTORY,
STATIC_PROPS_ID,
SERVER_PROPS_ID,
} from '../lib/constants'
import { join } from 'path'
import { requirePage } from './require'
Expand All @@ -22,20 +20,6 @@ export function interopDefault(mod: any) {
return mod.default || mod
}

function addComponentPropsId(
Component: any,
getStaticProps: any,
getServerSideProps: any
) {
// Mark the component with the SSG or SSP id here since we don't run
// the SSG babel transform for server mode
if (getStaticProps) {
Component[STATIC_PROPS_ID] = true
} else if (getServerSideProps) {
Component[SERVER_PROPS_ID] = true
}
}

export type ManifestItem = {
id: number | string
name: string
Expand Down Expand Up @@ -68,8 +52,6 @@ export async function loadComponents(
const Component = await requirePage(pathname, distDir, serverless)
const { getStaticProps, getStaticPaths, getServerSideProps } = Component

addComponentPropsId(Component, getStaticProps, getServerSideProps)

return {
Component,
pageConfig: Component.config || {},
Expand Down Expand Up @@ -118,8 +100,6 @@ export async function loadComponents(

const { getServerSideProps, getStaticProps, getStaticPaths } = ComponentMod

addComponentPropsId(Component, getStaticProps, getServerSideProps)

return {
App,
Document,
Expand Down
15 changes: 14 additions & 1 deletion packages/next/next-server/server/render.tsx
Expand Up @@ -10,7 +10,11 @@ import {
} from '../../lib/constants'
import { isInAmpMode } from '../lib/amp'
import { AmpStateContext } from '../lib/amp-context'
import { AMP_RENDER_TARGET } from '../lib/constants'
import {
AMP_RENDER_TARGET,
STATIC_PROPS_ID,
SERVER_PROPS_ID,
} from '../lib/constants'
import Head, { defaultHead } from '../lib/head'
import Loadable from '../lib/loadable'
import { LoadableContext } from '../lib/loadable-context'
Expand Down Expand Up @@ -471,6 +475,11 @@ export async function renderToHTML(
router,
ctx,
})

if (isSSG) {
props[STATIC_PROPS_ID] = true
}

let previewData: string | false | object | undefined

if ((isSSG || getServerSideProps) && !isFallback) {
Expand Down Expand Up @@ -534,6 +543,10 @@ export async function renderToHTML(
;(renderOpts as any).pageData = props
}

if (getServerSideProps) {
props[SERVER_PROPS_ID] = true
}

if (getServerSideProps && !isFallback) {
const data = await getServerSideProps({
req,
Expand Down
7 changes: 3 additions & 4 deletions packages/next/pages/_app.tsx
Expand Up @@ -41,17 +41,16 @@ export default class App<P = {}, CP = {}, S = {}> extends React.Component<
}

render() {
const { router, Component, pageProps } = this.props as AppProps<CP>
const { router, Component, pageProps, __N_SSG, __N_SSP } = this
.props as AppProps<CP>

return (
<Component
{...pageProps}
{
// we don't add the legacy URL prop if it's using non-legacy
// methods like getStaticProps and getServerSideProps
...(!((Component as any).__N_SSG || (Component as any).__N_SSP)
? { url: createUrl(router) }
: {})
...(!(__N_SSG || __N_SSP) ? { url: createUrl(router) } : {})
}
/>
)
Expand Down

0 comments on commit 1099108

Please sign in to comment.