import { parse , posix } from 'path' ; import kebabHash from 'kebab-hash' ; import { CACHING_HEADERS , COMMON_BUNDLES , ENV_VAR_HEADER_PREVIX , IMMUTABLE_CACHING_HEADER , INDEX_HTML , PAGE_DATA_DIR , PUBLIC_CACHING_HEADER , } from './constants' ; import { addHeaderDirective } from './nginx-generator' ; function preloadHeadersByPath ( pages , manifest , pathPrefix ) { return Object . fromEntries ( pages . map ( ( page ) => { const scripts = ( [ ] as string [ ] ) . concat ( ... COMMON_BUNDLES . map ( ( file ) => getScriptsPaths ( file , manifest ) ) ) ; scripts . push ( ... getScriptsPaths ( pathChunkName ( page . path ) , manifest ) ) ; scripts . push ( ... getScriptsPaths ( page . componentChunkName , manifest ) ) ; const jsons = [ posix . join ( PAGE_DATA_DIR , 'app-data.json' ) , getPageDataPath ( page . path ) , ] ; return [ normalizePath ( pathPrefix + page . path ) , [ ... scripts . filter ( Boolean ) . map ( ( script ) => ( { name : 'Link' , value : linkTemplate ( script , pathPrefix ) } ) ) , ... jsons . map ( ( json ) => ( { name : 'Link' , value : linkTemplate ( json , pathPrefix , 'fetch' ) } ) ) , ] , ] ; } ) ) ; } function cacheHeadersByPath ( pages , manifest ) { const chunks = pages . map ( ( page ) => page . componentChunkName ) ; chunks . push ( `pages-manifest` , `app` ) ; const files = ( [ ] as string [ ] ) . concat ( ... chunks . map ( ( chunk ) => manifest [ chunk ] || [ ] ) ) ; return Object . fromEntries ( files . map ( ( file ) => [ `/${ file }` , [ IMMUTABLE_CACHING_HEADER ] ] ) . concat ( CACHING_HEADERS ) ) ; } export function normalizePath ( path ) { if ( ! path . endsWith ( '/' ) || path === '/' ) { return path ; } return path . slice ( 0 , - 1 ) ; } function getScriptsPaths ( file , manifest ) { const chunks = manifest [ file ] ; if ( ! chunks ) { return [ ] ; } return chunks . filter ( ( script ) => parse ( script ) . ext === '.js' ) ; } function linkTemplate ( asset , pathPrefix , type = 'script' ) { return `<${ pathPrefix }/${ asset }>; rel=preload; as=${ type }${ type === 'fetch' ? '; crossorigin' : '' }` ; } function pathChunkName ( path ) { const name = path === '/' ? 'index' : kebabHash ( path ) ; return `path---${ name }` ; } function getPageDataPath ( path ) { const fixedPagePath = path === `/` ? `index` : path ; return posix . join ( PAGE_DATA_DIR , fixedPagePath , `page-data.json` ) ; } function applyUserHeadersTransform ( headersMap , transform ) { return Object . fromEntries ( Object . entries ( headersMap ) . map ( ( [ path , headers ] ) => { const headersAsStrings = headers . map ( ( { name , value } ) => `${ name }: ${ value }` ) ; return [ path , transform ( headersAsStrings , path ) . map ( headerFromString ) ] ; } ) ) ; } function addPublicCachingHeader ( headersMap ) { return Object . fromEntries ( Object . entries ( headersMap ) . map ( ( [ path , headers ] ) => { if ( headers . find ( ( h ) => h . name . toLowerCase ( ) === 'cache-control' ) ) { return [ path , headers ] ; } return [ path , [ ... headers , PUBLIC_CACHING_HEADER ] ] ; } ) ) ; } function addStaticCachingHeader ( headersMap ) { return Object . fromEntries ( Object . entries ( headersMap ) . map ( ( [ path , headers ] ) => { if ( ! path . startsWith ( '/static' ) ) { return [ path , headers ] ; } return [ path , [ ... headers , IMMUTABLE_CACHING_HEADER ] ] ; } ) ) ; } function headerFromString ( header ) { const [ name , ... rest ] = header . split ( ':' ) ; return { name , value : rest . join ( '' ) . trim ( ) } ; } function emptyHeadersMapForFiles ( files ) { return Object . fromEntries ( files . map ( ( file ) => [ normalizePath ( `/${ removeIndexSuffix ( file ) }` ) , [ ] ] ) ) ; } export function getGlobalHeaders ( customGlobalHeaders ) { const rawGlobalHeaders = { } ; Object . keys ( process . env ) . forEach ( ( envVar ) => { const isEnvVarHeader = envVar . startsWith ( ENV_VAR_HEADER_PREVIX ) && envVar . length > ENV_VAR_HEADER_PREVIX . length ; if ( ! isEnvVarHeader ) { return ; } const headerName = envVar . slice ( ENV_VAR_HEADER_PREVIX . length ) . replace ( /_/g , '-' ) ; rawGlobalHeaders [ headerName ] = process . env [ envVar ] ! ; } ) ; customGlobalHeaders . forEach ( ( header ) => { rawGlobalHeaders [ header . name ] = header . value ; } ) ; return Object . entries ( rawGlobalHeaders ) . map ( ( [ name , value ] ) => addHeaderDirective ( { name , value } ) ) ; } /** Any file ending with index.html might have a corresponding gatsby page.
 * Since we want these empty headers to be substituted by their page's headers
 * and to not create duplicate nginx directives for index.html files, we
 * remove the index.html suffix.
 * Any static, not-generated-by-gatsby, index.html file will still make its way to the
 * nginx configuration file, which when generated will try to match the header
 * entry with the list of files in the public directory. The only caveat is that
 * they will be served under a path without the 'index.html'.
 */ function removeIndexSuffix ( path ) { if ( ! path . endsWith ( INDEX_HTML ) ) { return path ; } return path . slice ( 0 , - INDEX_HTML . length ) ; } export { addPublicCachingHeader , addStaticCachingHeader , emptyHeadersMapForFiles , preloadHeadersByPath , cacheHeadersByPath , applyUserHeadersTransform , } ;