2020-06-12 00:09:06 +02:00
import React , { useContext } from 'react'
import Effect from './side-effect'
2019-06-27 16:22:24 +02:00
import { AmpStateContext } from './amp-context'
2019-05-30 03:19:32 +02:00
import { HeadManagerContext } from './head-manager-context'
2019-06-27 16:22:24 +02:00
import { isInAmpMode } from './amp'
2022-02-28 23:39:51 +01:00
import { warnOnce } from './utils'
2019-01-25 16:43:12 +01:00
2019-06-27 16:22:24 +02:00
type WithInAmpMode = {
inAmpMode? : boolean
2019-03-27 17:46:44 +01:00
}
2020-05-25 00:44:05 +02:00
export function defaultHead ( inAmpMode = false ) : JSX . Element [ ] {
2019-11-26 18:27:33 +01:00
const head = [ < meta charSet = "utf-8" / > ]
2019-06-27 16:22:24 +02:00
if ( ! inAmpMode ) {
2020-03-05 11:03:15 +01:00
head . push ( < meta name = "viewport" content = "width=device-width" / > )
2019-03-27 17:46:44 +01:00
}
2019-05-30 03:19:32 +02:00
return head
2019-01-25 16:43:12 +01:00
}
function onlyReactElement (
list : Array < React.ReactElement < any > > ,
2019-05-30 03:19:32 +02:00
child : React.ReactChild
2019-01-25 16:43:12 +01:00
) : Array < React.ReactElement < any > > {
// React children can be "string" or "number" in this case we ignore them for backwards compat
2019-05-30 03:19:32 +02:00
if ( typeof child === 'string' || typeof child === 'number' ) {
return list
2019-01-25 16:43:12 +01:00
}
// Adds support for React.Fragment
if ( child . type === React . Fragment ) {
return list . concat (
2019-05-30 03:19:32 +02:00
React . Children . toArray ( child . props . children ) . reduce (
(
fragmentList : Array < React.ReactElement < any > > ,
fragmentChild : React.ReactChild
) : Array < React.ReactElement < any > > = > {
if (
typeof fragmentChild === 'string' ||
typeof fragmentChild === 'number'
) {
return fragmentList
}
return fragmentList . concat ( fragmentChild )
} ,
[ ]
)
)
2019-01-25 16:43:12 +01:00
}
2019-05-30 03:19:32 +02:00
return list . concat ( child )
2019-01-25 16:43:12 +01:00
}
2019-06-10 18:41:43 +02:00
const METATYPES = [ 'name' , 'httpEquiv' , 'charSet' , 'itemProp' ]
2019-01-25 16:43:12 +01:00
/ *
returns a function for filtering head child elements
which shouldn ' t be duplicated , like < title / >
Also adds support for deduplicated ` key ` properties
* /
function unique ( ) {
2019-05-30 03:19:32 +02:00
const keys = new Set ( )
const tags = new Set ( )
const metaTypes = new Set ( )
const metaCategories : { [ metatype : string ] : Set < string > } = { }
2019-01-25 16:43:12 +01:00
return ( h : React.ReactElement < any > ) = > {
2020-06-01 23:00:22 +02:00
let isUnique = true
2021-01-03 11:35:51 +01:00
let hasKey = false
2019-10-18 07:40:54 +02:00
2019-11-26 18:27:33 +01:00
if ( h . key && typeof h . key !== 'number' && h . key . indexOf ( '$' ) > 0 ) {
2021-01-03 11:35:51 +01:00
hasKey = true
2019-11-26 18:27:33 +01:00
const key = h . key . slice ( h . key . indexOf ( '$' ) + 1 )
if ( keys . has ( key ) ) {
2020-06-01 23:00:22 +02:00
isUnique = false
2019-11-26 18:27:33 +01:00
} else {
keys . add ( key )
}
}
2019-10-18 07:40:54 +02:00
2019-11-11 04:24:53 +01:00
// eslint-disable-next-line default-case
2019-01-25 16:43:12 +01:00
switch ( h . type ) {
2019-05-30 03:19:32 +02:00
case 'title' :
case 'base' :
2019-11-26 18:27:33 +01:00
if ( tags . has ( h . type ) ) {
2020-06-01 23:00:22 +02:00
isUnique = false
2019-11-26 18:27:33 +01:00
} else {
tags . add ( h . type )
}
2019-05-30 03:19:32 +02:00
break
case 'meta' :
2019-01-25 16:43:12 +01:00
for ( let i = 0 , len = METATYPES . length ; i < len ; i ++ ) {
2019-05-30 03:19:32 +02:00
const metatype = METATYPES [ i ]
if ( ! h . props . hasOwnProperty ( metatype ) ) continue
2019-01-25 16:43:12 +01:00
2019-06-10 18:41:43 +02:00
if ( metatype === 'charSet' ) {
2019-11-26 18:27:33 +01:00
if ( metaTypes . has ( metatype ) ) {
2020-06-01 23:00:22 +02:00
isUnique = false
2019-11-26 18:27:33 +01:00
} else {
metaTypes . add ( metatype )
}
2019-01-25 16:43:12 +01:00
} else {
2019-05-30 03:19:32 +02:00
const category = h . props [ metatype ]
const categories = metaCategories [ metatype ] || new Set ( )
2021-01-03 11:35:51 +01:00
if ( ( metatype !== 'name' || ! hasKey ) && categories . has ( category ) ) {
2020-06-01 23:00:22 +02:00
isUnique = false
2019-11-26 18:27:33 +01:00
} else {
categories . add ( category )
metaCategories [ metatype ] = categories
}
2019-01-25 16:43:12 +01:00
}
}
2019-05-30 03:19:32 +02:00
break
2019-01-25 16:43:12 +01:00
}
2019-11-26 18:27:33 +01:00
2020-06-01 23:00:22 +02:00
return isUnique
2019-05-30 03:19:32 +02:00
}
2019-01-25 16:43:12 +01:00
}
/ * *
*
2020-03-25 13:26:53 +01:00
* @param headElements List of multiple < Head > instances
2019-01-25 16:43:12 +01:00
* /
2019-05-30 03:19:32 +02:00
function reduceComponents (
headElements : Array < React.ReactElement < any > > ,
2019-06-27 16:22:24 +02:00
props : WithInAmpMode
2019-05-30 03:19:32 +02:00
) {
2019-01-25 16:43:12 +01:00
return headElements
. reduce (
( list : React.ReactChild [ ] , headElement : React.ReactElement < any > ) = > {
const headElementChildren = React . Children . toArray (
2019-05-30 03:19:32 +02:00
headElement . props . children
)
return list . concat ( headElementChildren )
2019-01-25 16:43:12 +01:00
} ,
2019-05-30 03:19:32 +02:00
[ ]
2019-01-25 16:43:12 +01:00
)
. reduce ( onlyReactElement , [ ] )
. reverse ( )
2019-07-25 18:39:09 +02:00
. concat ( defaultHead ( props . inAmpMode ) )
2019-01-25 16:43:12 +01:00
. filter ( unique ( ) )
. reverse ( )
. map ( ( c : React.ReactElement < any > , i : number ) = > {
2019-05-30 03:19:32 +02:00
const key = c . key || i
2020-12-21 20:26:00 +01:00
if (
process . env . NODE_ENV !== 'development' &&
process . env . __NEXT_OPTIMIZE_FONTS &&
! props . inAmpMode
) {
2020-07-28 12:19:28 +02:00
if (
c . type === 'link' &&
c . props [ 'href' ] &&
// TODO(prateekbh@): Replace this with const from `constants` when the tree shaking works.
2021-08-17 09:18:08 +02:00
[ 'https://fonts.googleapis.com/css' , 'https://use.typekit.net/' ] . some (
( url ) = > c . props [ 'href' ] . startsWith ( url )
)
2020-07-28 12:19:28 +02:00
) {
const newProps = { . . . ( c . props || { } ) }
newProps [ 'data-href' ] = newProps [ 'href' ]
newProps [ 'href' ] = undefined
2021-05-12 13:39:26 +02:00
// Add this attribute to make it easy to identify optimized tags
newProps [ 'data-optimized-fonts' ] = true
2020-07-28 12:19:28 +02:00
return React . cloneElement ( c , newProps )
}
}
2022-02-28 23:39:51 +01:00
if (
process . env . NODE_ENV === 'development' &&
2022-04-28 21:17:23 +02:00
process . env . __NEXT_REACT_ROOT
2022-02-28 23:39:51 +01:00
) {
2022-02-05 21:45:02 +01:00
// omit JSON-LD structured data snippets from the warning
if ( c . type === 'script' && c . props [ 'type' ] !== 'application/ld+json' ) {
2022-02-04 23:48:30 +01:00
const srcMessage = c . props [ 'src' ]
? ` <script> tag with src=" ${ c . props [ 'src' ] } " `
: ` inline <script> `
2022-02-28 23:39:51 +01:00
warnOnce (
2022-02-04 23:48:30 +01:00
` Do not add <script> tags using next/head (see ${ srcMessage } ). Use next/script instead. \ nSee more info here: https://nextjs.org/docs/messages/no-script-tags-in-head-component `
)
} else if ( c . type === 'link' && c . props [ 'rel' ] === 'stylesheet' ) {
2022-02-28 23:39:51 +01:00
warnOnce (
2022-02-04 23:48:30 +01:00
` Do not add stylesheets using next/head (see <link rel="stylesheet"> tag with href=" ${ c . props [ 'href' ] } "). Use Document instead. \ nSee more info here: https://nextjs.org/docs/messages/no-stylesheets-in-head-component `
)
}
2022-02-04 14:06:55 +01:00
}
2019-07-25 18:39:09 +02:00
return React . cloneElement ( c , { key } )
2019-05-30 03:19:32 +02:00
} )
2019-01-25 16:43:12 +01:00
}
2019-05-23 21:31:22 +02:00
/ * *
* This component injects elements to ` <head> ` of your page .
* To avoid duplicated ` tags ` in ` <head> ` you can use the ` key ` property , which will make sure every tag is only rendered once .
* /
2019-04-24 16:47:50 +02:00
function Head ( { children } : { children : React.ReactNode } ) {
2020-06-12 00:09:06 +02:00
const ampState = useContext ( AmpStateContext )
const headManager = useContext ( HeadManagerContext )
2019-01-25 16:43:12 +01:00
return (
2020-06-12 00:09:06 +02:00
< Effect
reduceComponentsToState = { reduceComponents }
headManager = { headManager }
inAmpMode = { isInAmpMode ( ampState ) }
>
{ children }
< / Effect >
2019-05-30 03:19:32 +02:00
)
2019-01-25 16:43:12 +01:00
}
2019-04-24 16:47:50 +02:00
export default Head