2019-04-22 19:55:03 +02:00
import { format , UrlObject , URLFormatOptions } from 'url'
2019-05-23 21:31:22 +02:00
import { ServerResponse , IncomingMessage } from 'http'
2019-04-22 19:55:03 +02:00
import { ComponentType } from 'react'
import { ParsedUrlQuery } from 'querystring'
2019-06-29 02:48:28 +02:00
import { ManifestItem } from '../server/render'
2019-07-11 19:35:39 +02:00
import { NextRouter } from './router/router'
2019-04-22 19:55:03 +02:00
/ * *
* Types used by both next and next - server
* /
2019-05-23 21:31:22 +02:00
export type NextComponentType <
C extends BaseContext = NextPageContext ,
IP = { } ,
P = { }
> = ComponentType < P > & {
2019-07-07 20:52:59 +02:00
getInitialProps ? ( context : C ) : IP | Promise < IP >
2019-04-22 19:55:03 +02:00
}
2019-05-23 21:31:22 +02:00
export type DocumentType = NextComponentType <
DocumentContext ,
DocumentInitialProps ,
DocumentProps
>
2019-04-22 19:55:03 +02:00
2019-05-23 21:31:22 +02:00
export type AppType = NextComponentType <
AppContextType ,
AppInitialProps ,
AppPropsType
>
2019-04-22 19:55:03 +02:00
2019-09-09 18:23:34 +02:00
export type AppTreeType = ComponentType <
AppInitialProps & { [ name : string ] : any }
>
2019-04-22 19:55:03 +02:00
export type Enhancer < C > = ( Component : C ) = > C
export type ComponentsEnhancer =
2019-05-23 21:31:22 +02:00
| {
enhanceApp? : Enhancer < AppType >
2019-05-29 13:57:26 +02:00
enhanceComponent? : Enhancer < NextComponentType >
2019-05-23 21:31:22 +02:00
}
2019-04-22 19:55:03 +02:00
| Enhancer < NextComponentType >
2019-05-23 21:31:22 +02:00
export type RenderPageResult = {
html : string
head? : Array < JSX.Element | null >
2019-05-29 13:57:26 +02:00
dataOnly? : true
2019-05-23 21:31:22 +02:00
}
2019-04-22 19:55:03 +02:00
2019-05-23 21:31:22 +02:00
export type RenderPage = (
2019-05-29 13:57:26 +02:00
options? : ComponentsEnhancer
2019-05-23 21:31:22 +02:00
) = > RenderPageResult | Promise < RenderPageResult >
2019-04-22 19:55:03 +02:00
2019-04-26 09:37:57 +02:00
export type BaseContext = {
2019-04-22 19:55:03 +02:00
res? : ServerResponse
2019-05-29 13:57:26 +02:00
[ k : string ] : any
2019-04-22 19:55:03 +02:00
}
2019-05-06 23:42:04 +02:00
export type NEXT_DATA = {
2019-04-22 19:55:03 +02:00
dataManager : string
props : any
page : string
query : ParsedUrlQuery
buildId : string
assetPrefix? : string
runtimeConfig ? : { [ key : string ] : any }
nextExport? : boolean
2019-09-15 20:35:14 +02:00
autoExport? : boolean
2019-08-06 22:26:01 +02:00
skeleton? : boolean
2019-04-22 19:55:03 +02:00
dynamicIds? : string [ ]
2019-05-29 13:57:26 +02:00
err? : Error & { statusCode? : number }
2019-04-22 19:55:03 +02:00
}
2019-05-23 21:31:22 +02:00
/ * *
* ` Next ` context
* /
2019-05-06 23:42:04 +02:00
// tslint:disable-next-line interface-name
export interface NextPageContext {
2019-05-23 21:31:22 +02:00
/ * *
* Error object if encountered during rendering
* /
2019-04-22 19:55:03 +02:00
err? : Error & { statusCode? : number } | null
2019-05-23 21:31:22 +02:00
/ * *
* ` HTTP ` request object .
* /
2019-04-22 19:55:03 +02:00
req? : IncomingMessage
2019-05-23 21:31:22 +02:00
/ * *
* ` HTTP ` response object .
* /
2019-04-22 19:55:03 +02:00
res? : ServerResponse
2019-05-23 21:31:22 +02:00
/ * *
* Path section of ` URL ` .
* /
2019-04-22 19:55:03 +02:00
pathname : string
2019-05-23 21:31:22 +02:00
/ * *
* Query string section of ` URL ` parsed as an object .
* /
2019-04-22 19:55:03 +02:00
query : ParsedUrlQuery
2019-05-23 21:31:22 +02:00
/ * *
* ` String ` of the actual path including query .
* /
2019-04-22 19:55:03 +02:00
asPath? : string
2019-08-13 11:33:48 +02:00
/ * *
* ` Component ` the tree of the App to use if needing to render separately
* /
2019-09-09 18:23:34 +02:00
AppTree : AppTreeType
2019-04-22 19:55:03 +02:00
}
2019-07-11 19:35:39 +02:00
export type AppContextType < R extends NextRouter = NextRouter > = {
2019-05-06 23:42:04 +02:00
Component : NextComponentType < NextPageContext >
2019-09-09 18:23:34 +02:00
AppTree : AppTreeType
2019-05-29 13:57:26 +02:00
ctx : NextPageContext
2019-07-30 20:00:19 +02:00
router : R
2019-04-22 19:55:03 +02:00
}
2019-04-26 09:37:57 +02:00
export type AppInitialProps = {
2019-05-29 13:57:26 +02:00
pageProps : any
2019-04-22 19:55:03 +02:00
}
2019-05-23 21:31:22 +02:00
export type AppPropsType <
2019-07-11 19:35:39 +02:00
R extends NextRouter = NextRouter ,
2019-05-23 21:31:22 +02:00
P = { }
> = AppInitialProps & {
2019-05-06 23:42:04 +02:00
Component : NextComponentType < NextPageContext , any , P >
2019-05-29 13:57:26 +02:00
router : R
2019-04-22 19:55:03 +02:00
}
2019-05-06 23:42:04 +02:00
export type DocumentContext = NextPageContext & {
2019-05-29 13:57:26 +02:00
renderPage : RenderPage
2019-04-22 19:55:03 +02:00
}
2019-04-26 09:37:57 +02:00
export type DocumentInitialProps = RenderPageResult & {
2019-07-10 07:39:07 +02:00
styles? : React.ReactElement [ ] | React . ReactFragment
2019-04-22 19:55:03 +02:00
}
2019-04-26 09:37:57 +02:00
export type DocumentProps = DocumentInitialProps & {
2019-05-06 23:42:04 +02:00
__NEXT_DATA__ : NEXT_DATA
2019-04-22 19:55:03 +02:00
dangerousAsPath : string
ampPath : string
2019-06-27 16:22:24 +02:00
inAmpMode : boolean
hybridAmp : boolean
2019-04-22 19:55:03 +02:00
staticMarkup : boolean
devFiles : string [ ]
files : string [ ]
dynamicImports : ManifestItem [ ]
2019-05-29 13:57:26 +02:00
assetPrefix? : string
canonicalBase : string
2019-04-22 19:55:03 +02:00
}
/ * *
2019-06-05 13:22:09 +02:00
* Next ` API ` route request
* /
export type NextApiRequest = IncomingMessage & {
/ * *
* Object of ` query ` values from url
* /
query : {
[ key : string ] : string | string [ ]
}
/ * *
* Object of ` cookies ` from header
* /
cookies : {
[ key : string ] : string
}
body : any
}
/ * *
* Send body of response
2019-04-22 19:55:03 +02:00
* /
2019-07-09 19:02:46 +02:00
type Send < T > = ( body : T ) = > void
2019-04-22 19:55:03 +02:00
2019-06-05 13:22:09 +02:00
/ * *
* Next ` API ` route response
* /
2019-07-09 19:02:46 +02:00
export type NextApiResponse < T = any > = ServerResponse & {
2019-06-05 13:22:09 +02:00
/ * *
2019-07-10 16:43:04 +02:00
* Send data ` any ` data in response
2019-06-05 13:22:09 +02:00
* /
2019-07-09 19:02:46 +02:00
send : Send < T >
2019-06-05 13:22:09 +02:00
/ * *
2019-07-10 16:43:04 +02:00
* Send data ` json ` data in response
2019-06-05 13:22:09 +02:00
* /
2019-07-09 19:02:46 +02:00
json : Send < T >
status : ( statusCode : number ) = > NextApiResponse < T >
2019-06-05 13:22:09 +02:00
}
/ * *
* Utils
* /
2019-07-16 13:07:08 +02:00
export function execOnce ( this : any , fn : ( . . . args : any ) = > any ) {
2019-04-22 19:55:03 +02:00
let used = false
return ( . . . args : any ) = > {
if ( ! used ) {
used = true
fn . apply ( this , args )
}
}
}
export function getLocationOrigin() {
const { protocol , hostname , port } = window . location
return ` ${ protocol } // ${ hostname } ${ port ? ':' + port : '' } `
}
export function getURL() {
const { href } = window . location
const origin = getLocationOrigin ( )
return href . substring ( origin . length )
}
export function getDisplayName ( Component : ComponentType < any > ) {
2019-05-23 21:31:22 +02:00
return typeof Component === 'string'
? Component
: Component . displayName || Component . name || 'Unknown'
2019-04-22 19:55:03 +02:00
}
export function isResSent ( res : ServerResponse ) {
return res . finished || res . headersSent
}
2019-05-23 21:31:22 +02:00
export async function loadGetInitialProps <
C extends BaseContext ,
IP = { } ,
P = { }
2019-07-26 20:09:16 +02:00
> ( Component : NextComponentType < C , IP , P > , ctx : C ) : Promise < IP > {
2019-04-22 19:55:03 +02:00
if ( process . env . NODE_ENV !== 'production' ) {
if ( Component . prototype && Component . prototype . getInitialProps ) {
2019-05-23 21:31:22 +02:00
const message = ` " ${ getDisplayName (
2019-05-29 13:57:26 +02:00
Component
2019-05-23 21:31:22 +02:00
) } . getInitialProps ( ) " is defined as an instance method - visit https : //err.sh/zeit/next.js/get-initial-props-as-an-instance-method for more information.`
2019-04-22 19:55:03 +02:00
throw new Error ( message )
}
}
2019-04-30 23:28:25 +02:00
// when called from _app `ctx` is nested in `ctx`
const res = ctx . res || ( ctx . ctx && ctx . ctx . res )
2019-04-22 19:55:03 +02:00
2019-04-25 10:11:05 +02:00
if ( ! Component . getInitialProps ) {
2019-07-05 17:00:23 +02:00
return { } as any
2019-04-25 10:11:05 +02:00
}
2019-04-22 19:55:03 +02:00
const props = await Component . getInitialProps ( ctx )
2019-04-30 23:28:25 +02:00
if ( res && isResSent ( res ) ) {
2019-04-22 19:55:03 +02:00
return props
}
if ( ! props ) {
2019-05-23 21:31:22 +02:00
const message = ` " ${ getDisplayName (
2019-05-29 13:57:26 +02:00
Component
2019-05-23 21:31:22 +02:00
) } . getInitialProps ( ) " should resolve to an object. But found " $ { props } " instead . `
2019-04-22 19:55:03 +02:00
throw new Error ( message )
}
2019-08-22 19:06:30 +02:00
if ( process . env . NODE_ENV !== 'production' ) {
if ( Object . keys ( props ) . length === 0 && ! ctx . ctx ) {
console . warn (
` ${ getDisplayName (
Component
) } returned an empty object from \ ` getInitialProps \` . This de-optimizes and prevents automatic prerendering. https://err.sh/zeit/next.js/empty-object-getInitialProps `
)
}
}
2019-04-22 19:55:03 +02:00
return props
}
2019-05-23 21:31:22 +02:00
export const urlObjectKeys = [
'auth' ,
'hash' ,
'host' ,
'hostname' ,
'href' ,
'path' ,
'pathname' ,
'port' ,
'protocol' ,
'query' ,
'search' ,
'slashes' ,
]
export function formatWithValidation (
url : UrlObject ,
2019-05-29 13:57:26 +02:00
options? : URLFormatOptions
2019-05-23 21:31:22 +02:00
) {
2019-04-22 19:55:03 +02:00
if ( process . env . NODE_ENV === 'development' ) {
if ( url !== null && typeof url === 'object' ) {
2019-05-29 13:57:26 +02:00
Object . keys ( url ) . forEach ( key = > {
2019-04-22 19:55:03 +02:00
if ( urlObjectKeys . indexOf ( key ) === - 1 ) {
2019-05-23 21:31:22 +02:00
console . warn (
2019-05-29 13:57:26 +02:00
` Unknown key passed via urlObject into url.format: ${ key } `
2019-05-23 21:31:22 +02:00
)
2019-04-22 19:55:03 +02:00
}
} )
}
}
return format ( url as any , options )
}
2019-08-09 21:43:29 +02:00
export const SUPPORTS_PERFORMANCE = typeof performance !== 'undefined'
export const SUPPORTS_PERFORMANCE_USER_TIMING =
SUPPORTS_PERFORMANCE &&
typeof performance . mark === 'function' &&
typeof performance . measure === 'function'