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
|
|
|
|
|
|
|
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
|
|
|
|
dynamicBuildId: boolean
|
|
|
|
assetPrefix?: string
|
|
|
|
runtimeConfig?: { [key: string]: any }
|
|
|
|
nextExport?: boolean
|
|
|
|
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-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-04-22 19:55:03 +02:00
|
|
|
router: R
|
2019-05-29 13:57:26 +02:00
|
|
|
ctx: NextPageContext
|
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-25 15:58:24 +02:00
|
|
|
>(Component: NextComponentType<C, IP, P>, ctx: C): Promise<IP | null> {
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|