diff --git a/packages/next/build/babel/plugins/next-ssg-transform.ts b/packages/next/build/babel/plugins/next-ssg-transform.ts index e8d5ad60cb2e..ef20ebb297f1 100644 --- a/packages/next/build/babel/plugins/next-ssg-transform.ts +++ b/packages/next/build/babel/plugins/next-ssg-transform.ts @@ -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' @@ -37,64 +35,42 @@ function decorateSsgExport( path: NodePath, 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) }, }) } diff --git a/packages/next/client/index.js b/packages/next/client/index.js index 1e1721ae1abe..56f722114f77 100644 --- a/packages/next/client/index.js +++ b/packages/next/client/index.js @@ -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( @@ -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') @@ -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. diff --git a/packages/next/client/page-loader.js b/packages/next/client/page-loader.js index 84aded64d62c..d2c72bb34dd4 100644 --- a/packages/next/client/page-loader.js +++ b/packages/next/client/page-loader.js @@ -175,7 +175,7 @@ export default class PageLoader { } loadPage(route) { - return this.loadPageScript(route).then(v => v.page) + return this.loadPageScript(route) } loadPageScript(route) { diff --git a/packages/next/next-server/lib/router/router.ts b/packages/next/next-server/lib/router/router.ts index 3cc1ee2eae69..b6a3abb12a15 100644 --- a/packages/next/next-server/lib/router/router.ts +++ b/packages/next/next-server/lib/router/router.ts @@ -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 @@ -57,6 +59,8 @@ export type PrefetchOptions = { type RouteInfo = { Component: ComponentType + __N_SSG?: boolean + __N_SSP?: boolean props?: any err?: Error error?: any @@ -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.__N_SSG, + __N_SSP: initialProps.__N_SSP, + } } this.components['/_app'] = { Component: App } @@ -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 @@ -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, { @@ -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) .then((routeInfo: RouteInfo) => { - const { Component } = routeInfo + const { Component, __N_SSG, __N_SSP } = routeInfo if (process.env.NODE_ENV !== 'production') { const { isValidElementType } = require('react-is') @@ -595,9 +616,9 @@ export default class Router implements BaseRouter { } return this._getData(() => - (Component as any).__N_SSG + __N_SSG ? this._getStaticData(as) - : (Component as any).__N_SSP + : __N_SSP ? this._getServerData(as) : this.getInitialProps( Component, @@ -726,13 +747,13 @@ export default class Router implements BaseRouter { }) } - async fetchComponent(route: string): Promise { + async fetchComponent(route: string): Promise { 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( @@ -746,7 +767,7 @@ export default class Router implements BaseRouter { this.clc = null } - return Component + return componentResult } _getData(fn: () => Promise): Promise { diff --git a/packages/next/next-server/lib/utils.ts b/packages/next/next-server/lib/utils.ts index 8c1fa8175041..e1eaabd0d7ad 100644 --- a/packages/next/next-server/lib/utils.ts +++ b/packages/next/next-server/lib/utils.ts @@ -134,6 +134,8 @@ export type AppPropsType< > = AppInitialProps & { Component: NextComponentType router: R + __N_SSG?: boolean + __N_SSP?: boolean } export type DocumentContext = NextPageContext & { diff --git a/packages/next/next-server/server/load-components.ts b/packages/next/next-server/server/load-components.ts index 3b8fa7accd0e..3a1b88a3bba8 100644 --- a/packages/next/next-server/server/load-components.ts +++ b/packages/next/next-server/server/load-components.ts @@ -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' @@ -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 @@ -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 || {}, @@ -118,8 +100,6 @@ export async function loadComponents( const { getServerSideProps, getStaticProps, getStaticPaths } = ComponentMod - addComponentPropsId(Component, getStaticProps, getServerSideProps) - return { App, Document, diff --git a/packages/next/next-server/server/render.tsx b/packages/next/next-server/server/render.tsx index c77bccb00371..4a112b313611 100644 --- a/packages/next/next-server/server/render.tsx +++ b/packages/next/next-server/server/render.tsx @@ -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' @@ -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) { @@ -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, diff --git a/packages/next/pages/_app.tsx b/packages/next/pages/_app.tsx index 32727d7f2ea9..ca491052467e 100644 --- a/packages/next/pages/_app.tsx +++ b/packages/next/pages/_app.tsx @@ -41,7 +41,8 @@ export default class App

extends React.Component< } render() { - const { router, Component, pageProps } = this.props as AppProps + const { router, Component, pageProps, __N_SSG, __N_SSP } = this + .props as AppProps return ( extends React.Component< { // 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) } : {}) } /> ) diff --git a/test/unit/babel-plugin-next-ssg-transform.test.js b/test/unit/babel-plugin-next-ssg-transform.test.js index bb374923c6f2..55188a4b5c4c 100644 --- a/test/unit/babel-plugin-next-ssg-transform.test.js +++ b/test/unit/babel-plugin-next-ssg-transform.test.js @@ -40,7 +40,7 @@ describe('babel plugin (next-ssg-transform)', () => { } `) expect(output).toMatchInlineSnapshot( - `"var __NEXT_COMP=function Test(){return __jsx(\\"div\\",null);};__NEXT_COMP.__N_SSG=true export default __NEXT_COMP;"` + `"export var __N_SSG=true;export default function Test(){return __jsx(\\"div\\",null);}"` ) }) @@ -53,7 +53,7 @@ describe('babel plugin (next-ssg-transform)', () => { } `) expect(output).toMatchInlineSnapshot( - `"var __NEXT_COMP=function Test(){return __jsx(\\"div\\",null);};__NEXT_COMP.__N_SSG=true export default __NEXT_COMP;"` + `"export var __N_SSG=true;export default function Test(){return __jsx(\\"div\\",null);}"` ) }) @@ -66,7 +66,7 @@ describe('babel plugin (next-ssg-transform)', () => { } `) expect(output).toMatchInlineSnapshot( - `"export{foo,bar as baz}from'.';var __NEXT_COMP=function Test(){return __jsx(\\"div\\",null);};__NEXT_COMP.__N_SSG=true export default __NEXT_COMP;"` + `"export var __N_SSG=true;export{foo,bar as baz}from'.';export default function Test(){return __jsx(\\"div\\",null);}"` ) }) @@ -86,7 +86,7 @@ describe('babel plugin (next-ssg-transform)', () => { `) expect(output).toMatchInlineSnapshot( - `"var __NEXT_COMP=function Test(){return __jsx(\\"div\\",null);};__NEXT_COMP.__N_SSG=true export default __NEXT_COMP;"` + `"export var __N_SSG=true;export default function Test(){return __jsx(\\"div\\",null);}"` ) }) @@ -106,7 +106,7 @@ describe('babel plugin (next-ssg-transform)', () => { `) expect(output).toMatchInlineSnapshot( - `"var __NEXT_COMP=function Test(){return __jsx(\\"div\\",null);};__NEXT_COMP.__N_SSG=true export default __NEXT_COMP;"` + `"export var __N_SSG=true;export default function Test(){return __jsx(\\"div\\",null);}"` ) }) @@ -124,7 +124,7 @@ describe('babel plugin (next-ssg-transform)', () => { `) expect(output).toMatchInlineSnapshot( - `"export function Noop(){}var __NEXT_COMP=function Test(){return __jsx(\\"div\\",null);};__NEXT_COMP.__N_SSG=true export default __NEXT_COMP;"` + `"export var __N_SSG=true;export function Noop(){}export default function Test(){return __jsx(\\"div\\",null);}"` ) }) @@ -144,7 +144,7 @@ describe('babel plugin (next-ssg-transform)', () => { `) expect(output).toMatchInlineSnapshot( - `"var __NEXT_COMP=function Test(){return __jsx(\\"div\\",null);};__NEXT_COMP.__N_SSG=true export default __NEXT_COMP;"` + `"export var __N_SSG=true;export default function Test(){return __jsx(\\"div\\",null);}"` ) }) @@ -164,7 +164,7 @@ describe('babel plugin (next-ssg-transform)', () => { `) expect(output).toMatchInlineSnapshot( - `"var __NEXT_COMP=function Test(){return __jsx(\\"div\\",null);};__NEXT_COMP.__N_SSG=true export default __NEXT_COMP;"` + `"export var __N_SSG=true;export default function Test(){return __jsx(\\"div\\",null);}"` ) }) @@ -184,7 +184,7 @@ describe('babel plugin (next-ssg-transform)', () => { `) expect(output).toMatchInlineSnapshot( - `"export const foo=2;var __NEXT_COMP=function Test(){return __jsx(\\"div\\",null);};__NEXT_COMP.__N_SSG=true export default __NEXT_COMP;"` + `"export var __N_SSG=true;export const foo=2;export default function Test(){return __jsx(\\"div\\",null);}"` ) }) @@ -202,7 +202,7 @@ describe('babel plugin (next-ssg-transform)', () => { `) expect(output).toMatchInlineSnapshot( - `"var __NEXT_COMP=function Test(){return __jsx(\\"div\\",null);};__NEXT_COMP.__N_SSG=true export default __NEXT_COMP;"` + `"export var __N_SSG=true;export default function Test(){return __jsx(\\"div\\",null);}"` ) }) @@ -220,7 +220,7 @@ describe('babel plugin (next-ssg-transform)', () => { `) expect(output).toMatchInlineSnapshot( - `"const a=2;var __NEXT_COMP=function Test(){return __jsx(\\"div\\",null);};__NEXT_COMP.__N_SSG=true export default __NEXT_COMP;"` + `"const a=2;export var __N_SSG=true;export default function Test(){return __jsx(\\"div\\",null);}"` ) }) @@ -238,7 +238,7 @@ describe('babel plugin (next-ssg-transform)', () => { `) expect(output).toMatchInlineSnapshot( - `"var __NEXT_COMP=function Test(){return __jsx(\\"div\\",null);};__NEXT_COMP.__N_SSG=true export default __NEXT_COMP;"` + `"export var __N_SSG=true;export default function Test(){return __jsx(\\"div\\",null);}"` ) }) @@ -258,7 +258,7 @@ describe('babel plugin (next-ssg-transform)', () => { `) expect(output).toMatchInlineSnapshot( - `"export class MyClass{}var __NEXT_COMP=function Test(){return __jsx(\\"div\\",null);};__NEXT_COMP.__N_SSG=true export default __NEXT_COMP;"` + `"export var __N_SSG=true;export class MyClass{}export default function Test(){return __jsx(\\"div\\",null);}"` ) }) @@ -305,7 +305,7 @@ describe('babel plugin (next-ssg-transform)', () => { `) expect(output).toMatchInlineSnapshot( - `"import keep_me from'hello';import{keep_me2}from'hello2';import*as keep_me3 from'hello3';import{but_not_me}from'bar';var leave_me_alone=1;function dont_bug_me_either(){}var __NEXT_COMP=function Test(){return __jsx(\\"div\\",null);};__NEXT_COMP.__N_SSG=true export default __NEXT_COMP;"` + `"import keep_me from'hello';import{keep_me2}from'hello2';import*as keep_me3 from'hello3';import{but_not_me}from'bar';var leave_me_alone=1;function dont_bug_me_either(){}export var __N_SSG=true;export default function Test(){return __jsx(\\"div\\",null);}"` ) }) @@ -346,7 +346,7 @@ describe('babel plugin (next-ssg-transform)', () => { `) expect(output).toMatchInlineSnapshot( - `"var __NEXT_COMP=class Test extends React.Component{render(){return __jsx(\\"div\\",null);}};__NEXT_COMP.__N_SSG=true export default __NEXT_COMP;"` + `"export var __N_SSG=true;export default class Test extends React.Component{render(){return __jsx(\\"div\\",null);}}"` ) }) @@ -366,7 +366,7 @@ describe('babel plugin (next-ssg-transform)', () => { `) expect(output).toMatchInlineSnapshot( - `"class Test extends React.Component{render(){return __jsx(\\"div\\",null);}}var __NEXT_COMP=Test;__NEXT_COMP.__N_SSG=true export default __NEXT_COMP;"` + `"class Test extends React.Component{render(){return __jsx(\\"div\\",null);}}export var __N_SSG=true;export default Test;"` ) }) @@ -384,7 +384,7 @@ describe('babel plugin (next-ssg-transform)', () => { `) expect(output).toMatchInlineSnapshot( - `"function El(){return __jsx(\\"div\\",null);}El.__N_SSG=true export{El as default};"` + `"function El(){return __jsx(\\"div\\",null);}export var __N_SSG=true;export{El as default};"` ) }) @@ -404,7 +404,7 @@ describe('babel plugin (next-ssg-transform)', () => { `) expect(output).toMatchInlineSnapshot( - `"function El(){return __jsx(\\"div\\",null);}const a=5;El.__N_SSG=true export{El as default,a};"` + `"function El(){return __jsx(\\"div\\",null);}const a=5;export var __N_SSG=true;export{El as default,a};"` ) }) @@ -426,7 +426,7 @@ describe('babel plugin (next-ssg-transform)', () => { `) expect(output).toMatchInlineSnapshot( - `"class El extends React.Component{render(){return __jsx(\\"div\\",null);}}const a=5;El.__N_SSG=true export{El as default,a};"` + `"class El extends React.Component{render(){return __jsx(\\"div\\",null);}}const a=5;export var __N_SSG=true;export{El as default,a};"` ) }) }) diff --git a/test/unit/next-babel-loader.test.js b/test/unit/next-babel-loader.test.js index 5bf447095083..8efca060c38c 100644 --- a/test/unit/next-babel-loader.test.js +++ b/test/unit/next-babel-loader.test.js @@ -303,7 +303,7 @@ describe('next-babel-loader', () => { { resourcePath: pageFile, isServer: false } ) expect(code).toMatchInlineSnapshot( - `"import\\"core-js\\";import{bar}from\\"a\\";import baz from\\"b\\";import*as React from\\"react\\";import{yeet}from\\"c\\";import baz3,{cats}from\\"d\\";import{c,d}from\\"e\\";import{e as ee}from\\"f\\";var __NEXT_COMP=function(){return cats+bar();};__NEXT_COMP.__N_SSG=true export default __NEXT_COMP;"` + `"import\\"core-js\\";import{bar}from\\"a\\";import baz from\\"b\\";import*as React from\\"react\\";import{yeet}from\\"c\\";import baz3,{cats}from\\"d\\";import{c,d}from\\"e\\";import{e as ee}from\\"f\\";export var __N_SSG=true;export default function(){return cats+bar();}"` ) }) @@ -325,7 +325,7 @@ describe('next-babel-loader', () => { { resourcePath: pageFile, isServer: false } ) expect(code).toMatchInlineSnapshot( - `"var __jsx=React.createElement;import\\"core-js\\";import{bar}from\\"a\\";import baz from\\"b\\";import*as React from\\"react\\";import{yeet}from\\"c\\";import baz3,{cats}from\\"d\\";import{c,d}from\\"e\\";import{e as ee}from\\"f\\";var __NEXT_COMP=function(){return __jsx(\\"div\\",null,cats+bar());};__NEXT_COMP.__N_SSG=true export default __NEXT_COMP;"` + `"var __jsx=React.createElement;import\\"core-js\\";import{bar}from\\"a\\";import baz from\\"b\\";import*as React from\\"react\\";import{yeet}from\\"c\\";import baz3,{cats}from\\"d\\";import{c,d}from\\"e\\";import{e as ee}from\\"f\\";export var __N_SSG=true;export default function(){return __jsx(\\"div\\",null,cats+bar());}"` ) })