2020-12-01 19:02:07 +01:00
import React , { Component , ReactElement , ReactNode , useContext } from 'react'
2021-11-07 18:36:39 +01:00
import { OPTIMIZED_FONT_PROVIDERS } from '../shared/lib/constants'
2022-02-17 19:21:40 +01:00
import type {
2019-11-11 04:24:53 +01:00
DocumentContext ,
DocumentInitialProps ,
DocumentProps ,
2021-06-30 11:43:31 +02:00
} from '../shared/lib/utils'
2021-06-30 13:44:40 +02:00
import { BuildManifest , getPageFiles } from '../server/get-page-files'
import { cleanAmpPath } from '../server/utils'
2019-11-11 04:24:53 +01:00
import { htmlEscapeJsonString } from '../server/htmlescape'
2021-07-07 18:35:50 +02:00
import Script , { ScriptProps } from '../client/script'
2021-09-16 18:06:57 +02:00
import isError from '../lib/is-error'
2018-10-03 00:08:57 +02:00
2022-02-17 19:21:40 +01:00
import { HtmlContext } from '../shared/lib/html-context'
import type { HtmlProps } from '../shared/lib/html-context'
2019-04-26 09:37:57 +02:00
export { DocumentContext , DocumentInitialProps , DocumentProps }
2019-04-22 19:55:03 +02:00
2019-04-26 09:37:57 +02:00
export type OriginProps = {
2019-04-22 19:55:03 +02:00
nonce? : string
2019-05-30 03:19:32 +02:00
crossOrigin? : string
2022-03-18 10:44:56 +01:00
children? : React.ReactNode
2019-04-22 19:55:03 +02:00
}
2020-08-14 16:20:03 +02:00
type DocumentFiles = {
sharedFiles : readonly string [ ]
pageFiles : readonly string [ ]
allFiles : readonly string [ ]
}
function getDocumentFiles (
buildManifest : BuildManifest ,
2020-12-22 04:18:01 +01:00
pathname : string ,
inAmpMode : boolean
2020-08-14 16:20:03 +02:00
) : DocumentFiles {
const sharedFiles : readonly string [ ] = getPageFiles ( buildManifest , '/_app' )
2020-12-22 04:18:01 +01:00
const pageFiles : readonly string [ ] = inAmpMode
? [ ]
: getPageFiles ( buildManifest , pathname )
2020-08-14 16:20:03 +02:00
return {
sharedFiles ,
pageFiles ,
allFiles : [ . . . new Set ( [ . . . sharedFiles , . . . pageFiles ] ) ] ,
}
}
2021-08-13 05:36:54 +02:00
function getPolyfillScripts ( context : HtmlProps , props : OriginProps ) {
2021-05-13 12:39:36 +02:00
// polyfills.js has to be rendered as nomodule without async
// It also has to be the first script to load
const {
assetPrefix ,
buildManifest ,
devOnlyCacheBusterQueryString ,
disableOptimizedLoading ,
2021-11-30 18:14:28 +01:00
crossOrigin ,
2021-05-13 12:39:36 +02:00
} = context
return buildManifest . polyfillFiles
. filter (
( polyfill ) = > polyfill . endsWith ( '.js' ) && ! polyfill . endsWith ( '.module.js' )
)
. map ( ( polyfill ) = > (
< script
key = { polyfill }
defer = { ! disableOptimizedLoading }
nonce = { props . nonce }
2021-11-30 18:14:28 +01:00
crossOrigin = { props . crossOrigin || crossOrigin }
2021-05-13 12:39:36 +02:00
noModule = { true }
src = { ` ${ assetPrefix } /_next/ ${ polyfill } ${ devOnlyCacheBusterQueryString } ` }
/ >
) )
}
2022-03-18 10:44:56 +01:00
function hasComponentProps ( child : any ) : child is React . ReactElement {
return ! ! child && ! ! child . props
}
2022-03-11 23:26:46 +01:00
function getPreNextWorkerScripts ( context : HtmlProps , props : OriginProps ) {
const { assetPrefix , scriptLoader , crossOrigin , nextScriptWorkers } = context
2022-03-26 10:28:14 +01:00
// disable `nextScriptWorkers` in edge runtime
2022-04-26 19:54:28 +02:00
if ( ! nextScriptWorkers || process . env . NEXT_RUNTIME === 'edge' ) return null
2022-03-11 23:26:46 +01:00
try {
let {
partytownSnippet ,
2022-03-31 21:44:15 +02:00
// @ts-ignore: Prevent webpack from processing this require
} = __non_webpack_require__ ( '@builder.io/partytown/integration' ! )
2022-03-11 23:26:46 +01:00
const children = Array . isArray ( props . children )
? props . children
: [ props . children ]
// Check to see if the user has defined their own Partytown configuration
const userDefinedConfig = children . find (
2022-03-18 10:44:56 +01:00
( child ) = >
hasComponentProps ( child ) &&
2022-03-11 23:26:46 +01:00
child ? . props ? . dangerouslySetInnerHTML ? . __html . length &&
'data-partytown-config' in child . props
)
return (
< >
{ ! userDefinedConfig && (
< script
data - partytown - config = ""
dangerouslySetInnerHTML = { {
__html : `
partytown = {
lib : "${assetPrefix}/_next/static/~partytown/"
} ;
` ,
} }
/ >
) }
< script
data - partytown = ""
dangerouslySetInnerHTML = { {
__html : partytownSnippet ( ) ,
} }
/ >
{ ( scriptLoader . worker || [ ] ) . map ( ( file : ScriptProps , index : number ) = > {
const { strategy , . . . scriptProps } = file
return (
< script
{ . . . scriptProps }
type = "text/partytown"
key = { scriptProps . src || index }
nonce = { props . nonce }
data - nscript = "worker"
crossOrigin = { props . crossOrigin || crossOrigin }
/ >
)
} ) }
< / >
)
} catch ( err ) {
2022-03-31 21:44:15 +02:00
if ( isError ( err ) && err . code !== 'MODULE_NOT_FOUND' ) {
console . warn (
` Warning: Partytown could not be instantiated in your application due to an error. ${ err . message } `
)
}
2022-03-11 23:26:46 +01:00
return null
}
}
2021-08-13 05:36:54 +02:00
function getPreNextScripts ( context : HtmlProps , props : OriginProps ) {
2021-11-30 18:14:28 +01:00
const { scriptLoader , disableOptimizedLoading , crossOrigin } = context
2021-05-13 12:39:36 +02:00
2022-03-11 23:26:46 +01:00
const webWorkerScripts = getPreNextWorkerScripts ( context , props )
const beforeInteractiveScripts = ( scriptLoader . beforeInteractive || [ ] ) . map (
2021-07-07 18:35:50 +02:00
( file : ScriptProps , index : number ) = > {
2021-05-13 12:39:36 +02:00
const { strategy , . . . scriptProps } = file
return (
< script
{ . . . scriptProps }
2021-07-02 22:23:11 +02:00
key = { scriptProps . src || index }
2022-03-11 17:53:09 +01:00
defer = { scriptProps . defer ? ? ! disableOptimizedLoading }
2021-05-13 12:39:36 +02:00
nonce = { props . nonce }
2021-08-20 22:48:48 +02:00
data - nscript = "beforeInteractive"
2021-11-30 18:14:28 +01:00
crossOrigin = { props . crossOrigin || crossOrigin }
2021-05-13 12:39:36 +02:00
/ >
)
}
)
2022-03-11 23:26:46 +01:00
return (
< >
{ webWorkerScripts }
{ beforeInteractiveScripts }
< / >
)
2021-05-13 12:39:36 +02:00
}
function getDynamicChunks (
2021-08-13 05:36:54 +02:00
context : HtmlProps ,
2021-05-13 12:39:36 +02:00
props : OriginProps ,
files : DocumentFiles
) {
const {
dynamicImports ,
assetPrefix ,
isDevelopment ,
devOnlyCacheBusterQueryString ,
disableOptimizedLoading ,
2021-11-30 18:14:28 +01:00
crossOrigin ,
2021-05-13 12:39:36 +02:00
} = context
return dynamicImports . map ( ( file ) = > {
if ( ! file . endsWith ( '.js' ) || files . allFiles . includes ( file ) ) return null
return (
< script
async = { ! isDevelopment && disableOptimizedLoading }
defer = { ! disableOptimizedLoading }
key = { file }
src = { ` ${ assetPrefix } /_next/ ${ encodeURI (
file
) } $ { devOnlyCacheBusterQueryString } ` }
nonce = { props . nonce }
2021-11-30 18:14:28 +01:00
crossOrigin = { props . crossOrigin || crossOrigin }
2021-05-13 12:39:36 +02:00
/ >
)
} )
}
function getScripts (
2021-08-13 05:36:54 +02:00
context : HtmlProps ,
2021-05-13 12:39:36 +02:00
props : OriginProps ,
files : DocumentFiles
) {
const {
assetPrefix ,
buildManifest ,
isDevelopment ,
devOnlyCacheBusterQueryString ,
disableOptimizedLoading ,
2021-11-30 18:14:28 +01:00
crossOrigin ,
2021-05-13 12:39:36 +02:00
} = context
const normalScripts = files . allFiles . filter ( ( file ) = > file . endsWith ( '.js' ) )
const lowPriorityScripts = buildManifest . lowPriorityFiles ? . filter ( ( file ) = >
file . endsWith ( '.js' )
)
return [ . . . normalScripts , . . . lowPriorityScripts ] . map ( ( file ) = > {
return (
< script
key = { file }
src = { ` ${ assetPrefix } /_next/ ${ encodeURI (
file
) } $ { devOnlyCacheBusterQueryString } ` }
nonce = { props . nonce }
async = { ! isDevelopment && disableOptimizedLoading }
defer = { ! disableOptimizedLoading }
2021-11-30 18:14:28 +01:00
crossOrigin = { props . crossOrigin || crossOrigin }
2021-05-13 12:39:36 +02:00
/ >
)
} )
}
2019-05-23 21:31:22 +02:00
/ * *
* ` Document ` component handles the initial ` document ` markup and renders only on the server side .
* Commonly used for implementing server side rendering for ` css-in-js ` libraries .
* /
2019-04-29 18:47:40 +02:00
export default class Document < P = {} > extends Component < DocumentProps & P > {
2019-05-23 21:31:22 +02:00
/ * *
2019-08-06 13:23:50 +02:00
* ` getInitialProps ` hook returns the context object with the addition of ` renderPage ` .
2019-05-23 21:31:22 +02:00
* ` renderPage ` callback executes ` React ` rendering logic synchronously to support server - rendering wrappers
* /
2021-09-09 10:13:50 +02:00
static getInitialProps ( ctx : DocumentContext ) : Promise < DocumentInitialProps > {
return ctx . defaultGetInitialProps ( ctx )
2018-10-03 00:08:57 +02:00
}
2019-02-20 15:46:29 +01:00
render() {
return (
< Html >
< Head / >
< body >
< Main / >
< NextScript / >
< / body >
< / Html >
)
}
}
2022-03-29 05:53:51 +02:00
// Add a special property to the built-in `Document` component so later we can
2022-03-11 21:38:09 +01:00
// identify if a user customized `Document` is used or not.
; ( Document as any ) . __next_internal_document =
function InternalFunctionDocument() {
return (
< Html >
< Head / >
< body >
< Main / >
< NextScript / >
< / body >
< / Html >
)
}
2020-06-07 01:00:03 +02:00
export function Html (
props : React.DetailedHTMLProps <
2019-06-06 15:41:31 +02:00
React . HtmlHTMLAttributes < HTMLHtmlElement > ,
HTMLHtmlElement
>
2020-06-07 01:00:03 +02:00
) {
2021-08-13 05:36:54 +02:00
const { inAmpMode , docComponentsRendered , locale } = useContext ( HtmlContext )
2020-08-24 04:42:51 +02:00
docComponentsRendered . Html = true
2020-06-07 01:00:03 +02:00
return (
< html
{ . . . props }
2020-10-07 23:11:01 +02:00
lang = { props . lang || locale || undefined }
2020-06-07 01:00:03 +02:00
amp = { inAmpMode ? '' : undefined }
data - ampdevmode = {
inAmpMode && process . env . NODE_ENV !== 'production' ? '' : undefined
}
/ >
)
2018-10-03 00:08:57 +02:00
}
2021-09-09 10:13:50 +02:00
function AmpStyles ( {
styles ,
} : {
styles? : React.ReactElement [ ] | React . ReactFragment
} ) {
if ( ! styles ) return null
// try to parse styles from fragment for backwards compat
const curStyles : React.ReactElement [ ] = Array . isArray ( styles )
? ( styles as React . ReactElement [ ] )
: [ ]
if (
// @ts-ignore Property 'props' does not exist on type ReactElement
styles . props &&
// @ts-ignore Property 'props' does not exist on type ReactElement
Array . isArray ( styles . props . children )
) {
const hasStyles = ( el : React.ReactElement ) = >
el ? . props ? . dangerouslySetInnerHTML ? . __html
// @ts-ignore Property 'props' does not exist on type ReactElement
styles . props . children . forEach ( ( child : React.ReactElement ) = > {
if ( Array . isArray ( child ) ) {
child . forEach ( ( el ) = > hasStyles ( el ) && curStyles . push ( el ) )
} else if ( hasStyles ( child ) ) {
curStyles . push ( child )
}
} )
}
/* Add custom styles before AMP styles to prevent accidental overrides */
return (
< style
amp - custom = ""
dangerouslySetInnerHTML = { {
__html : curStyles
. map ( ( style ) = > style . props . dangerouslySetInnerHTML . __html )
. join ( '' )
. replace ( /\/\*# sourceMappingURL=.*\*\//g , '' )
. replace ( /\/\*@ sourceURL=.*?\*\//g , '' ) ,
} }
/ >
)
}
2019-06-06 15:41:31 +02:00
export class Head extends Component <
OriginProps &
React . DetailedHTMLProps <
React . HTMLAttributes < HTMLHeadElement > ,
HTMLHeadElement
>
> {
2021-08-13 05:36:54 +02:00
static contextType = HtmlContext
2018-10-03 00:08:57 +02:00
2021-08-13 05:36:54 +02:00
context ! : React . ContextType < typeof HtmlContext >
2019-04-22 19:55:03 +02:00
2020-08-14 16:20:03 +02:00
getCssLinks ( files : DocumentFiles ) : JSX . Element [ ] | null {
2021-11-30 18:14:28 +01:00
const {
assetPrefix ,
devOnlyCacheBusterQueryString ,
dynamicImports ,
crossOrigin ,
optimizeCss ,
optimizeFonts ,
} = this . context
2020-08-14 16:20:03 +02:00
const cssFiles = files . allFiles . filter ( ( f ) = > f . endsWith ( '.css' ) )
2020-11-03 06:23:31 +01:00
const sharedFiles : Set < string > = new Set ( files . sharedFiles )
2019-10-02 23:47:34 +02:00
2020-11-03 06:23:31 +01:00
// Unmanaged files are CSS files that will be handled directly by the
// webpack runtime (`mini-css-extract-plugin`).
let unmangedFiles : Set < string > = new Set ( [ ] )
2021-04-21 13:18:05 +02:00
let dynamicCssFiles = Array . from (
new Set ( dynamicImports . filter ( ( file ) = > file . endsWith ( '.css' ) ) )
)
2020-08-24 07:20:11 +02:00
if ( dynamicCssFiles . length ) {
const existing = new Set ( cssFiles )
dynamicCssFiles = dynamicCssFiles . filter (
( f ) = > ! ( existing . has ( f ) || sharedFiles . has ( f ) )
)
2020-11-03 06:23:31 +01:00
unmangedFiles = new Set ( dynamicCssFiles )
2020-08-24 07:20:11 +02:00
cssFiles . push ( . . . dynamicCssFiles )
}
2020-12-01 19:02:07 +01:00
let cssLinkElements : JSX.Element [ ] = [ ]
2020-05-18 21:24:37 +02:00
cssFiles . forEach ( ( file ) = > {
2020-08-17 23:20:05 +02:00
const isSharedFile = sharedFiles . has ( file )
2020-12-01 19:02:07 +01:00
2021-11-30 18:14:28 +01:00
if ( ! optimizeCss ) {
2020-12-01 19:02:07 +01:00
cssLinkElements . push (
< link
key = { ` ${ file } -preload ` }
nonce = { this . props . nonce }
rel = "preload"
href = { ` ${ assetPrefix } /_next/ ${ encodeURI (
file
) } $ { devOnlyCacheBusterQueryString } ` }
as = "style"
2021-11-30 18:14:28 +01:00
crossOrigin = { this . props . crossOrigin || crossOrigin }
2020-12-01 19:02:07 +01:00
/ >
)
}
2020-11-03 06:23:31 +01:00
const isUnmanagedFile = unmangedFiles . has ( file )
2019-10-16 06:01:11 +02:00
cssLinkElements . push (
< link
key = { file }
nonce = { this . props . nonce }
rel = "stylesheet"
2020-02-22 10:39:36 +01:00
href = { ` ${ assetPrefix } /_next/ ${ encodeURI (
file
2020-08-03 16:22:55 +02:00
) } $ { devOnlyCacheBusterQueryString } ` }
2021-11-30 18:14:28 +01:00
crossOrigin = { this . props . crossOrigin || crossOrigin }
2020-11-03 06:23:31 +01:00
data - n - g = { isUnmanagedFile ? undefined : isSharedFile ? '' : undefined }
data - n - p = { isUnmanagedFile ? undefined : isSharedFile ? undefined : '' }
2019-10-16 06:01:11 +02:00
/ >
)
} )
2020-12-01 19:02:07 +01:00
2021-11-30 18:14:28 +01:00
if ( process . env . NODE_ENV !== 'development' && optimizeFonts ) {
2020-12-01 19:02:07 +01:00
cssLinkElements = this . makeStylesheetInert (
cssLinkElements
) as ReactElement [ ]
}
2019-10-16 06:01:11 +02:00
return cssLinkElements . length === 0 ? null : cssLinkElements
2018-10-03 00:08:57 +02:00
}
2019-03-01 20:51:13 +01:00
getPreloadDynamicChunks() {
2021-11-30 18:14:28 +01:00
const {
dynamicImports ,
assetPrefix ,
devOnlyCacheBusterQueryString ,
crossOrigin ,
} = this . context
2019-02-03 01:12:49 +01:00
2019-07-25 04:16:32 +02:00
return (
2021-04-21 13:18:05 +02:00
dynamicImports
. map ( ( file ) = > {
if ( ! file . endsWith ( '.js' ) ) {
2019-07-25 04:16:32 +02:00
return null
}
return (
< link
rel = "preload"
2021-04-21 13:18:05 +02:00
key = { file }
2019-08-29 18:56:24 +02:00
href = { ` ${ assetPrefix } /_next/ ${ encodeURI (
2021-04-21 13:18:05 +02:00
file
2020-08-03 16:22:55 +02:00
) } $ { devOnlyCacheBusterQueryString } ` }
2019-07-25 04:16:32 +02:00
as = "script"
nonce = { this . props . nonce }
2021-11-30 18:14:28 +01:00
crossOrigin = { this . props . crossOrigin || crossOrigin }
2019-07-25 04:16:32 +02:00
/ >
)
} )
// Filter out nulled scripts
. filter ( Boolean )
)
2018-10-03 00:08:57 +02:00
}
2020-08-14 16:20:03 +02:00
getPreloadMainLinks ( files : DocumentFiles ) : JSX . Element [ ] | null {
2021-11-30 18:14:28 +01:00
const {
assetPrefix ,
devOnlyCacheBusterQueryString ,
scriptLoader ,
crossOrigin ,
} = this . context
2020-08-14 16:20:03 +02:00
const preloadFiles = files . allFiles . filter ( ( file : string ) = > {
2020-11-18 19:30:00 +01:00
return file . endsWith ( '.js' )
2020-08-14 16:20:03 +02:00
} )
2018-10-03 00:08:57 +02:00
2020-12-01 19:10:16 +01:00
return [
2021-05-12 13:37:57 +02:00
. . . ( scriptLoader . beforeInteractive || [ ] ) . map ( ( file ) = > (
2020-12-01 19:10:16 +01:00
< link
key = { file . src }
nonce = { this . props . nonce }
rel = "preload"
href = { file . src }
as = "script"
2021-11-30 18:14:28 +01:00
crossOrigin = { this . props . crossOrigin || crossOrigin }
2020-12-01 19:10:16 +01:00
/ >
) ) ,
. . . preloadFiles . map ( ( file : string ) = > (
< link
key = { file }
nonce = { this . props . nonce }
rel = "preload"
href = { ` ${ assetPrefix } /_next/ ${ encodeURI (
file
) } $ { devOnlyCacheBusterQueryString } ` }
as = "script"
2021-11-30 18:14:28 +01:00
crossOrigin = { this . props . crossOrigin || crossOrigin }
2020-12-01 19:10:16 +01:00
/ >
) ) ,
]
2018-10-03 00:08:57 +02:00
}
2021-05-13 12:39:36 +02:00
getDynamicChunks ( files : DocumentFiles ) {
return getDynamicChunks ( this . context , this . props , files )
}
getPreNextScripts() {
return getPreNextScripts ( this . context , this . props )
}
getScripts ( files : DocumentFiles ) {
return getScripts ( this . context , this . props , files )
}
getPolyfillScripts() {
return getPolyfillScripts ( this . context , this . props )
}
2021-03-02 20:17:33 +01:00
handleDocumentScriptLoaderItems ( children : React.ReactNode ) : ReactNode [ ] {
const { scriptLoader } = this . context
2021-07-07 18:35:50 +02:00
const scriptLoaderItems : ScriptProps [ ] = [ ]
2021-03-02 20:17:33 +01:00
const filteredChildren : ReactNode [ ] = [ ]
React . Children . forEach ( children , ( child : any ) = > {
if ( child . type === Script ) {
2021-05-12 13:37:57 +02:00
if ( child . props . strategy === 'beforeInteractive' ) {
scriptLoader . beforeInteractive = (
scriptLoader . beforeInteractive || [ ]
2021-05-10 10:39:08 +02:00
) . concat ( [
2021-03-02 20:17:33 +01:00
{
. . . child . props ,
} ,
] )
return
2021-05-10 10:39:08 +02:00
} else if (
2022-03-11 23:26:46 +01:00
[ 'lazyOnload' , 'afterInteractive' , 'worker' ] . includes (
child . props . strategy
)
2021-05-10 10:39:08 +02:00
) {
2021-03-02 20:17:33 +01:00
scriptLoaderItems . push ( child . props )
return
}
}
filteredChildren . push ( child )
} )
this . context . __NEXT_DATA__ . scriptLoader = scriptLoaderItems
return filteredChildren
}
2020-12-01 19:02:07 +01:00
makeStylesheetInert ( node : ReactNode ) : ReactNode [ ] {
2020-07-28 12:19:28 +02:00
return React . Children . map ( node , ( c : any ) = > {
if (
2022-01-03 21:35:57 +01:00
c ? . type === 'link' &&
c ? . props ? . href &&
2021-06-02 11:43:03 +02:00
OPTIMIZED_FONT_PROVIDERS . some ( ( { url } ) = >
2022-01-03 21:35:57 +01:00
c ? . props ? . href ? . startsWith ( url )
2021-06-02 11:43:03 +02:00
)
2020-07-28 12:19:28 +02:00
) {
2022-01-03 21:35:57 +01:00
const newProps = {
. . . ( c . props || { } ) ,
'data-href' : c . props . href ,
href : undefined ,
}
return React . cloneElement ( c , newProps )
} else if ( c ? . props ? . children ) {
const newProps = {
. . . ( c . props || { } ) ,
children : this.makeStylesheetInert ( c . props . children ) ,
}
2020-07-28 12:19:28 +02:00
return React . cloneElement ( c , newProps )
}
2022-01-03 21:35:57 +01:00
2020-07-28 12:19:28 +02:00
return c
2022-01-03 21:35:57 +01:00
} ) . filter ( Boolean )
2020-07-28 12:19:28 +02:00
}
2019-03-01 20:51:13 +01:00
render() {
const {
styles ,
2019-03-20 04:53:47 +01:00
ampPath ,
2019-06-27 16:22:24 +02:00
inAmpMode ,
hybridAmp ,
2019-05-29 02:32:18 +02:00
canonicalBase ,
2019-03-01 20:51:13 +01:00
__NEXT_DATA__ ,
2019-04-15 11:26:23 +02:00
dangerousAsPath ,
2019-11-01 20:13:13 +01:00
headTags ,
2020-04-17 11:22:03 +02:00
unstable_runtimeJS ,
2021-01-19 20:38:15 +01:00
unstable_JsPreload ,
2021-05-13 12:39:36 +02:00
disableOptimizedLoading ,
2021-11-30 18:14:28 +01:00
optimizeCss ,
optimizeFonts ,
2020-08-03 16:22:55 +02:00
} = this . context
2021-05-13 12:39:36 +02:00
2020-04-17 11:22:03 +02:00
const disableRuntimeJS = unstable_runtimeJS === false
2021-05-13 12:39:36 +02:00
const disableJsPreload =
unstable_JsPreload === false || ! disableOptimizedLoading
2018-10-03 00:08:57 +02:00
2020-08-24 04:42:51 +02:00
this . context . docComponentsRendered . Head = true
2020-08-03 16:22:55 +02:00
let { head } = this . context
2020-12-30 23:12:46 +01:00
let cssPreloads : Array < JSX.Element > = [ ]
let otherHeadElements : Array < JSX.Element > = [ ]
if ( head ) {
head . forEach ( ( c ) = > {
if (
c &&
c . type === 'link' &&
c . props [ 'rel' ] === 'preload' &&
c . props [ 'as' ] === 'style'
) {
cssPreloads . push ( c )
} else {
c && otherHeadElements . push ( c )
}
} )
head = cssPreloads . concat ( otherHeadElements )
}
2021-04-05 19:47:03 +02:00
let children = React . Children . toArray ( this . props . children ) . filter ( Boolean )
2018-10-03 00:08:57 +02:00
// show a warning if Head contains <title> (only in development)
if ( process . env . NODE_ENV !== 'production' ) {
2019-04-22 19:55:03 +02:00
children = React . Children . map ( children , ( child : any ) = > {
2020-05-02 06:52:56 +02:00
const isReactHelmet = child ? . props ? . [ 'data-react-helmet' ]
2020-07-07 07:43:16 +02:00
if ( ! isReactHelmet ) {
if ( child ? . type === 'title' ) {
console . warn (
2021-03-29 10:25:00 +02:00
"Warning: <title> should not be used in _document.js's <Head>. https://nextjs.org/docs/messages/no-document-title"
2020-07-07 07:43:16 +02:00
)
} else if (
child ? . type === 'meta' &&
child ? . props ? . name === 'viewport'
) {
console . warn (
2021-03-29 10:25:00 +02:00
"Warning: viewport meta tags should not be used in _document.js's <Head>. https://nextjs.org/docs/messages/no-document-viewport-meta"
2020-07-07 07:43:16 +02:00
)
}
2018-10-03 00:08:57 +02:00
}
return child
} )
2019-03-01 20:51:13 +01:00
if ( this . props . crossOrigin )
console . warn (
2021-03-29 10:25:00 +02:00
'Warning: `Head` attribute `crossOrigin` is deprecated. https://nextjs.org/docs/messages/doc-crossorigin-deprecated'
2019-03-01 20:51:13 +01:00
)
2018-10-03 00:08:57 +02:00
}
2019-05-04 18:46:50 +02:00
2021-11-30 18:14:28 +01:00
if ( process . env . NODE_ENV !== 'development' && optimizeFonts && ! inAmpMode ) {
2020-07-28 12:19:28 +02:00
children = this . makeStylesheetInert ( children )
}
2021-06-03 13:56:50 +02:00
children = this . handleDocumentScriptLoaderItems ( children )
2021-03-02 20:17:33 +01:00
2019-07-29 09:18:23 +02:00
let hasAmphtmlRel = false
let hasCanonicalRel = false
2019-03-18 12:29:31 +01:00
// show warning and remove conflicting amp head tags
2020-05-18 21:24:37 +02:00
head = React . Children . map ( head || [ ] , ( child ) = > {
2019-10-18 17:22:53 +02:00
if ( ! child ) return child
const { type , props } = child
if ( inAmpMode ) {
let badProp : string = ''
if ( type === 'meta' && props . name === 'viewport' ) {
badProp = 'name="viewport"'
} else if ( type === 'link' && props . rel === 'canonical' ) {
hasCanonicalRel = true
} else if ( type === 'script' ) {
// only block if
// 1. it has a src and isn't pointing to ampproject's CDN
// 2. it is using dangerouslySetInnerHTML without a type or
// a type of text/javascript
if (
( props . src && props . src . indexOf ( 'ampproject' ) < - 1 ) ||
( props . dangerouslySetInnerHTML &&
( ! props . type || props . type === 'text/javascript' ) )
) {
badProp = '<script'
2020-05-18 21:24:37 +02:00
Object . keys ( props ) . forEach ( ( prop ) = > {
2019-10-18 17:22:53 +02:00
badProp += ` ${ prop } =" ${ props [ prop ] } " `
} )
badProp += '/>'
2019-05-30 03:19:32 +02:00
}
2019-10-18 17:22:53 +02:00
}
2019-03-19 00:21:18 +01:00
2019-10-18 17:22:53 +02:00
if ( badProp ) {
console . warn (
2021-03-29 10:25:00 +02:00
` Found conflicting amp tag " ${ child . type } " with conflicting prop ${ badProp } in ${ __NEXT_DATA__ . page } . https://nextjs.org/docs/messages/conflicting-amp-tag `
2019-10-18 17:22:53 +02:00
)
return null
}
} else {
// non-amp mode
if ( type === 'link' && props . rel === 'amphtml' ) {
hasAmphtmlRel = true
}
}
return child
} )
2019-04-22 17:32:46 +02:00
2020-08-14 16:20:03 +02:00
const files : DocumentFiles = getDocumentFiles (
this . context . buildManifest ,
2020-12-22 04:18:01 +01:00
this . context . __NEXT_DATA__ . page ,
inAmpMode
2020-08-14 16:20:03 +02:00
)
2020-12-22 04:18:01 +01:00
2022-02-18 20:07:19 +01:00
return (
< head { ...this.props } >
2022-03-18 01:19:03 +01:00
{ this . context . isDevelopment && (
2019-03-01 20:51:13 +01:00
< >
2022-02-18 20:07:19 +01:00
< style
data - next - hide - fouc
data - ampdevmode = { inAmpMode ? 'true' : undefined }
dangerouslySetInnerHTML = { {
__html : ` body{display:none} ` ,
} }
2019-03-01 20:51:13 +01:00
/ >
2022-02-18 20:07:19 +01:00
< noscript
data - next - hide - fouc
data - ampdevmode = { inAmpMode ? 'true' : undefined }
>
< style
dangerouslySetInnerHTML = { {
__html : ` body{display:block} ` ,
} }
/ >
< / noscript >
2019-03-01 20:51:13 +01:00
< / >
2022-02-18 20:07:19 +01:00
) }
{ head }
< meta
name = "next-head-count"
content = { React . Children . count ( head || [ ] ) . toString ( ) }
/ >
{ children }
{ optimizeFonts && < meta name = "next-font-preconnect" / > }
2021-10-14 00:07:18 +02:00
2022-02-18 20:07:19 +01:00
{ inAmpMode && (
2019-03-01 20:51:13 +01:00
< >
2022-02-18 20:07:19 +01:00
< meta
name = "viewport"
content = "width=device-width,minimum-scale=1,initial-scale=1"
/ >
{ ! hasCanonicalRel && (
< link
rel = "canonical"
href = { canonicalBase + cleanAmpPath ( dangerousAsPath ) }
/ >
) }
{ /* https://www.ampproject.org/docs/fundamentals/optimize_amp#optimize-the-amp-runtime-loading */ }
< link
rel = "preload"
as = "script"
href = "https://cdn.ampproject.org/v0.js"
/ >
< AmpStyles styles = { styles } / >
< style
amp - boilerplate = ""
dangerouslySetInnerHTML = { {
__html : ` body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}} ` ,
} }
/ >
< noscript >
< style
amp - boilerplate = ""
dangerouslySetInnerHTML = { {
__html : ` body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none} ` ,
} }
/ >
< / noscript >
< script async src = "https://cdn.ampproject.org/v0.js" / >
< / >
) }
{ ! inAmpMode && (
< >
{ ! hasAmphtmlRel && hybridAmp && (
< link
rel = "amphtml"
href = { canonicalBase + getAmpPath ( ampPath , dangerousAsPath ) }
/ >
) }
{ ! optimizeCss && this . getCssLinks ( files ) }
{ ! optimizeCss && < noscript data-n-css = { this . props . nonce ? ? '' } / > }
2021-01-19 20:38:15 +01:00
{ ! disableRuntimeJS &&
! disableJsPreload &&
this . getPreloadDynamicChunks ( ) }
{ ! disableRuntimeJS &&
! disableJsPreload &&
this . getPreloadMainLinks ( files ) }
2021-10-14 00:07:18 +02:00
2022-02-18 20:07:19 +01:00
{ ! disableOptimizedLoading &&
! disableRuntimeJS &&
this . getPolyfillScripts ( ) }
2021-05-13 12:39:36 +02:00
{ ! disableOptimizedLoading &&
! disableRuntimeJS &&
this . getPreNextScripts ( ) }
{ ! disableOptimizedLoading &&
! disableRuntimeJS &&
this . getDynamicChunks ( files ) }
{ ! disableOptimizedLoading &&
! disableRuntimeJS &&
this . getScripts ( files ) }
2021-10-14 00:07:18 +02:00
2022-02-18 20:07:19 +01:00
{ optimizeCss && this . getCssLinks ( files ) }
{ optimizeCss && < noscript data-n-css = { this . props . nonce ? ? '' } / > }
{ this . context . isDevelopment && (
// this element is used to mount development styles so the
// ordering matches production
// (by default, style-loader injects at the bottom of <head />)
< noscript id = "__next_css__DO_NOT_USE__" / >
) }
{ styles || null }
2021-10-14 00:07:18 +02:00
< / >
2022-02-18 20:07:19 +01:00
) }
{ React . createElement ( React . Fragment , { } , . . . ( headTags || [ ] ) ) }
< / head >
)
2018-10-03 00:08:57 +02:00
}
}
2021-12-06 22:22:05 +01:00
export function Main() {
const { docComponentsRendered } = useContext ( HtmlContext )
2020-08-24 04:42:51 +02:00
docComponentsRendered . Main = true
2021-12-06 22:22:05 +01:00
// @ts-ignore
return < next - js - internal - body - render - target / >
2018-10-03 00:08:57 +02:00
}
2019-04-26 09:37:57 +02:00
export class NextScript extends Component < OriginProps > {
2021-08-13 05:36:54 +02:00
static contextType = HtmlContext
2018-10-03 00:08:57 +02:00
2021-08-13 05:36:54 +02:00
context ! : React . ContextType < typeof HtmlContext >
2019-04-22 19:55:03 +02:00
2019-07-25 04:16:32 +02:00
// Source: https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc
static safariNomoduleFix =
'!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();'
2020-08-14 16:20:03 +02:00
getDynamicChunks ( files : DocumentFiles ) {
2021-05-13 12:39:36 +02:00
return getDynamicChunks ( this . context , this . props , files )
2018-10-03 00:08:57 +02:00
}
2020-12-01 19:10:16 +01:00
getPreNextScripts() {
2021-05-13 12:39:36 +02:00
return getPreNextScripts ( this . context , this . props )
2020-12-01 19:10:16 +01:00
}
2020-08-14 16:20:03 +02:00
getScripts ( files : DocumentFiles ) {
2021-05-13 12:39:36 +02:00
return getScripts ( this . context , this . props , files )
2018-10-03 00:08:57 +02:00
}
2019-11-02 02:00:56 +01:00
getPolyfillScripts() {
2021-05-13 12:39:36 +02:00
return getPolyfillScripts ( this . context , this . props )
2019-11-02 02:00:56 +01:00
}
2021-08-13 05:36:54 +02:00
static getInlineScriptSource ( context : Readonly < HtmlProps > ) : string {
const { __NEXT_DATA__ } = context
2019-01-27 16:12:17 +01:00
try {
const data = JSON . stringify ( __NEXT_DATA__ )
2021-10-25 21:07:05 +02:00
if ( process . env . NODE_ENV === 'development' ) {
const bytes = Buffer . from ( data ) . byteLength
const prettyBytes = require ( '../lib/pretty-bytes' ) . default
if ( bytes > 128 * 1000 ) {
console . warn (
` Warning: data for page " ${ __NEXT_DATA__ . page } " is ${ prettyBytes (
bytes
) } , this amount of data can reduce performance . \ nSee more info here : https : //nextjs.org/docs/messages/large-page-data`
)
}
}
2019-01-27 16:12:17 +01:00
return htmlEscapeJsonString ( data )
2019-03-01 20:51:13 +01:00
} catch ( err ) {
2022-02-10 03:28:24 +01:00
if ( isError ( err ) && err . message . indexOf ( 'circular structure' ) !== - 1 ) {
2019-03-01 20:51:13 +01:00
throw new Error (
2021-03-29 10:25:00 +02:00
` Circular structure in "getInitialProps" result of page " ${ __NEXT_DATA__ . page } ". https://nextjs.org/docs/messages/circular-structure `
2019-03-01 20:51:13 +01:00
)
2019-01-27 16:12:17 +01:00
}
throw err
}
2018-10-03 00:08:57 +02:00
}
2019-03-01 20:51:13 +01:00
render() {
const {
assetPrefix ,
2019-06-27 16:22:24 +02:00
inAmpMode ,
2020-06-07 01:00:03 +02:00
buildManifest ,
2020-04-17 11:22:03 +02:00
unstable_runtimeJS ,
2020-08-24 04:42:51 +02:00
docComponentsRendered ,
2020-08-03 16:22:55 +02:00
devOnlyCacheBusterQueryString ,
2021-05-13 12:39:36 +02:00
disableOptimizedLoading ,
2021-11-30 18:14:28 +01:00
crossOrigin ,
2020-08-03 16:22:55 +02:00
} = this . context
2020-04-17 11:22:03 +02:00
const disableRuntimeJS = unstable_runtimeJS === false
2020-01-06 20:55:08 +01:00
2020-08-24 04:42:51 +02:00
docComponentsRendered . NextScript = true
2022-02-18 20:07:19 +01:00
if ( inAmpMode ) {
if ( process . env . NODE_ENV === 'production' ) {
return null
}
const ampDevFiles = [
. . . buildManifest . devFiles ,
. . . buildManifest . polyfillFiles ,
. . . buildManifest . ampDevFiles ,
]
2019-02-25 23:00:14 +01:00
2022-02-18 20:07:19 +01:00
return (
< >
{ disableRuntimeJS ? null : (
< script
id = "__NEXT_DATA__"
type = "application/json"
nonce = { this . props . nonce }
crossOrigin = { this . props . crossOrigin || crossOrigin }
dangerouslySetInnerHTML = { {
__html : NextScript.getInlineScriptSource ( this . context ) ,
} }
data - ampdevmode
/ >
) }
{ ampDevFiles . map ( ( file ) = > (
< script
key = { file }
src = { ` ${ assetPrefix } /_next/ ${ file } ${ devOnlyCacheBusterQueryString } ` }
nonce = { this . props . nonce }
crossOrigin = { this . props . crossOrigin || crossOrigin }
data - ampdevmode
/ >
) ) }
< / >
)
}
2019-02-14 16:22:57 +01:00
2022-02-18 20:07:19 +01:00
if ( process . env . NODE_ENV !== 'production' ) {
if ( this . props . crossOrigin )
console . warn (
'Warning: `NextScript` attribute `crossOrigin` is deprecated. https://nextjs.org/docs/messages/doc-crossorigin-deprecated'
2019-03-01 20:51:13 +01:00
)
2022-02-18 20:07:19 +01:00
}
2018-12-13 01:05:21 +01:00
2022-02-18 20:07:19 +01:00
const files : DocumentFiles = getDocumentFiles (
this . context . buildManifest ,
this . context . __NEXT_DATA__ . page ,
inAmpMode
)
return (
< >
{ ! disableRuntimeJS && buildManifest . devFiles
? buildManifest . devFiles . map ( ( file : string ) = > (
2020-06-08 20:11:00 +02:00
< script
2022-02-18 20:07:19 +01:00
key = { file }
src = { ` ${ assetPrefix } /_next/ ${ encodeURI (
file
) } $ { devOnlyCacheBusterQueryString } ` }
2020-06-08 20:11:00 +02:00
nonce = { this . props . nonce }
2021-11-30 18:14:28 +01:00
crossOrigin = { this . props . crossOrigin || crossOrigin }
2020-06-08 20:11:00 +02:00
/ >
2022-02-18 20:07:19 +01:00
) )
: null }
{ disableRuntimeJS ? null : (
< script
id = "__NEXT_DATA__"
type = "application/json"
nonce = { this . props . nonce }
crossOrigin = { this . props . crossOrigin || crossOrigin }
dangerouslySetInnerHTML = { {
__html : NextScript.getInlineScriptSource ( this . context ) ,
} }
/ >
) }
{ disableOptimizedLoading &&
! disableRuntimeJS &&
this . getPolyfillScripts ( ) }
{ disableOptimizedLoading &&
! disableRuntimeJS &&
this . getPreNextScripts ( ) }
{ disableOptimizedLoading &&
! disableRuntimeJS &&
this . getDynamicChunks ( files ) }
{ disableOptimizedLoading && ! disableRuntimeJS && this . getScripts ( files ) }
< / >
)
2018-10-03 00:08:57 +02:00
}
}
2020-05-10 23:52:30 +02:00
function getAmpPath ( ampPath : string , asPath : string ) : string {
2020-05-02 06:52:56 +02:00
return ampPath || ` ${ asPath } ${ asPath . includes ( '?' ) ? '&' : '?' } amp=1 `
2019-04-22 19:55:03 +02:00
}