Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(gatsby): Adapter header rules #38644

Merged
merged 11 commits into from
Oct 24, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ Array [
},
Object {
"functionId": "static-index-js",
"path": "/api/static/",
"path": "/api/static",
kathmbeck marked this conversation as resolved.
Show resolved Hide resolved
"type": "function",
},
Object {
Expand Down Expand Up @@ -233,7 +233,7 @@ Array [
Object {
"cache": true,
"functionId": "ssr-engine",
"path": "/dsg/",
"path": "/dsg",
"type": "function",
},
Object {
Expand Down Expand Up @@ -263,7 +263,7 @@ Array [
},
Object {
"functionId": "ssr-engine",
"path": "/ssr/",
"path": "/ssr",
"type": "function",
},
Object {
Expand Down
83 changes: 78 additions & 5 deletions packages/gatsby/src/utils/adapter/__tests__/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe(`getRoutesManifest`, () => {
process.chdir(fixturesDir)
setWebpackAssets(new Set([`app-123.js`]))

const routesManifest = getRoutesManifest()
const { routes: routesManifest } = getRoutesManifest()

expect(routesManifest).toMatchSnapshot()
})
Expand All @@ -62,7 +62,7 @@ describe(`getRoutesManifest`, () => {
process.chdir(fixturesDir)
setWebpackAssets(new Set([`app-123.js`]))

const routesManifest = getRoutesManifest()
const { routes: routesManifest } = getRoutesManifest()

expect(routesManifest).toEqual(
expect.arrayContaining([
Expand All @@ -81,7 +81,7 @@ describe(`getRoutesManifest`, () => {
process.chdir(fixturesDir)
setWebpackAssets(new Set([`app-123.js`]))

const routesManifest = getRoutesManifest()
const { routes: routesManifest } = getRoutesManifest()

expect(routesManifest).toEqual(
expect.arrayContaining([
Expand All @@ -98,14 +98,87 @@ describe(`getRoutesManifest`, () => {
process.chdir(fixturesDir)
setWebpackAssets(new Set([`app-123.js`]))

const routesManifest = getRoutesManifest()
expect(routesManifest).toEqual(
const { routes } = getRoutesManifest()
expect(routes).toEqual(
expect.arrayContaining([
expect.objectContaining({ path: `https://old-url` }),
expect.objectContaining({ path: `http://old-url` }),
])
)
})

it(`should return header rules`, () => {
mockStoreState(stateDefault, {
config: {
...stateDefault.config,
headers: [
{
source: `/ssr/*`,
headers: [
{
key: `x-ssr-header`,
value: `my custom header value from config`,
},
],
},
],
},
})
process.chdir(fixturesDir)
setWebpackAssets(new Set([`app-123.js`, `static/app-456.js`]))

const { headers } = getRoutesManifest()

expect(headers).toContainEqual({
headers: [
{ key: `x-xss-protection`, value: `1; mode=block` },
{ key: `x-content-type-options`, value: `nosniff` },
{ key: `referrer-policy`, value: `same-origin` },
{ key: `x-frame-options`, value: `DENY` },
],
path: `/*`,
})
expect(headers).toContainEqual({
headers: [
{
key: `cache-control`,
value: `public, max-age=31536000, immutable`,
},
],
path: `/static/*`,
})
expect(headers).toContainEqual({
headers: [
{
key: `cache-control`,
value: `public, max-age=0, must-revalidate`,
},
],
path: `/page-data/index/page-data.json`,
})
expect(headers).toContainEqual({
headers: [
{
key: `cache-control`,
value: `public, max-age=31536000, immutable`,
},
],
path: `/app-123.js`,
})
expect(headers).not.toContainEqual({
headers: [
{ key: `x-xss-protection`, value: `1; mode=block` },
{ key: `x-content-type-options`, value: `nosniff` },
{ key: `referrer-policy`, value: `same-origin` },
{ key: `x-frame-options`, value: `DENY` },
],
path: `/ssr/*`,
})

expect(headers).not.toContain(
expect.objectContaining({ path: `/static/app-456.js` })
)
})
})

describe(`getFunctionsManifest`, () => {
Expand Down
6 changes: 5 additions & 1 deletion packages/gatsby/src/utils/adapter/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ export const MUST_REVALIDATE_HEADERS: IHeader["headers"] = [
...BASE_HEADERS,
]

export const PERMAMENT_CACHING_HEADERS: IHeader["headers"] = [
export const PERMANENT_CACHE_CONTROL_HEADER: IHeader["headers"] = [
{
key: `cache-control`,
value: `public, max-age=31536000, immutable`,
},
]

export const PERMAMENT_CACHING_HEADERS: IHeader["headers"] = [
...PERMANENT_CACHE_CONTROL_HEADER,
...BASE_HEADERS,
]
104 changes: 82 additions & 22 deletions packages/gatsby/src/utils/adapter/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
IAdapter,
IAdapterFinalConfig,
IAdapterConfig,
HeaderRoutes,
} from "./types"
import { store, readState } from "../../redux"
import { getPageMode } from "../page-mode"
Expand All @@ -31,6 +32,7 @@ import {
BASE_HEADERS,
MUST_REVALIDATE_HEADERS,
PERMAMENT_CACHING_HEADERS,
PERMANENT_CACHE_CONTROL_HEADER,
} from "./constants"
import { createHeadersMatcher } from "./create-headers"
import { HTTP_STATUS_CODE } from "../../redux/types"
Expand Down Expand Up @@ -201,10 +203,13 @@ export async function initAdapterManager(): Promise<IAdapterManager> {

let _routesManifest: RoutesManifest | undefined = undefined
let _functionsManifest: FunctionsManifest | undefined = undefined
let _headerRoutes: HeaderRoutes | undefined = undefined
const adaptContext: IAdaptContext = {
get routesManifest(): RoutesManifest {
if (!_routesManifest) {
_routesManifest = getRoutesManifest()
const { routes, headers } = getRoutesManifest()
_routesManifest = routes
_headerRoutes = headers
}

return _routesManifest
Expand All @@ -216,6 +221,15 @@ export async function initAdapterManager(): Promise<IAdapterManager> {

return _functionsManifest
},
get headerRoutes(): HeaderRoutes {
if (!_headerRoutes) {
const { routes, headers } = getRoutesManifest()
_routesManifest = routes
_headerRoutes = headers
}

return _headerRoutes
},
reporter,
// Our internal Gatsby config allows this to be undefined but for the adapter we should always pass through the default values and correctly show this in the TypeScript types
trailingSlash: trailingSlash as TrailingSlash,
Expand Down Expand Up @@ -261,11 +275,48 @@ export function setWebpackAssets(assets: Set<string>): void {

type RouteWithScore = { score: number } & Route

function getRoutesManifest(): RoutesManifest {
const headersAreEqual = (a, b): boolean =>
a.key === b.key && a.value === b.value

const defaultHeaderRoutes: HeaderRoutes = [
{
path: `/*`,
headers: BASE_HEADERS,
},
{
path: `/static/*`,
headers: PERMANENT_CACHE_CONTROL_HEADER,
},
]

const customHeaderFilter =
(route: Route) =>
(h: IHeader["headers"][0]): boolean => {
for (const baseHeader of BASE_HEADERS) {
if (headersAreEqual(baseHeader, h)) {
return false
}
}
if (route.path.startsWith(`/static/`)) {
for (const cachingHeader of PERMAMENT_CACHING_HEADERS) {
if (headersAreEqual(cachingHeader, h)) {
return false
}
}
}
return true
}

function getRoutesManifest(): {
routes: RoutesManifest
headers: HeaderRoutes
} {
const routes: Array<RouteWithScore> = []
const state = store.getState()
const createHeaders = createHeadersMatcher(state.config.headers)

const headerRoutes: HeaderRoutes = [...defaultHeaderRoutes]

const fileAssets = new Set(
globSync(`**/**`, {
cwd: posix.join(process.cwd(), `public`),
Expand Down Expand Up @@ -293,11 +344,18 @@ function getRoutesManifest(): RoutesManifest {

if (route.type !== `function`) {
route.headers = createHeaders(route.path, route.headers)
const customHeaders = route.headers.filter(customHeaderFilter(route))
if (customHeaders.length > 0) {
headerRoutes.push({ path: route.path, headers: customHeaders })
}
}

;(route as RouteWithScore).score = rankRoute(route.path)
const routeWithScore: RouteWithScore = {
...route,
score: rankRoute(route.path),
}

routes.push(route as RouteWithScore)
routes.push(routeWithScore)
}

function addStaticRoute({
Expand Down Expand Up @@ -509,25 +567,27 @@ function getRoutesManifest(): RoutesManifest {
})
}

return (
routes
.sort((a, b) => {
// The higher the score, the higher the specificity of our path
const order = b.score - a.score
if (order !== 0) {
return order
}
const sortedRoutes = routes
.sort((a, b) => {
// The higher the score, the higher the specificity of our path
const order = b.score - a.score
if (order !== 0) {
return order
}

// if specificity is the same we do lexigraphic comparison of path to ensure
// deterministic order regardless of order pages where created
return a.path.localeCompare(b.path)
})
// The score should be internal only, so we remove it from the final manifest
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.map(({ score, ...rest }): Route => {
return { ...rest }
})
)
// if specificity is the same we do lexigraphic comparison of path to ensure
// deterministic order regardless of order pages where created
return a.path.localeCompare(b.path)
})
// The score should be internal only, so we remove it from the final manifest
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.map(({ score, ...rest }): Route => {
return { ...rest }
})
return {
routes: sortedRoutes,
headers: headerRoutes,
}
}

function getFunctionsManifest(): FunctionsManifest {
Expand Down
6 changes: 6 additions & 0 deletions packages/gatsby/src/utils/adapter/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ export type Route = IStaticRoute | IFunctionRoute | IRedirectRoute

export type RoutesManifest = Array<Route>

export interface IHeaderRoute extends IBaseRoute {
headers: IHeader["headers"]
}

export type HeaderRoutes = Array<IHeaderRoute>
export interface IFunctionDefinition {
/**
* Unique identifier of this function. Corresponds to the `functionId` inside the `routesManifest`.
Expand Down Expand Up @@ -99,6 +104,7 @@ interface IDefaultContext {
export interface IAdaptContext extends IDefaultContext {
routesManifest: RoutesManifest
functionsManifest: FunctionsManifest
headerRoutes: HeaderRoutes
/**
* @see https://www.gatsbyjs.com/docs/reference/config-files/gatsby-config/#pathprefix
*/
Expand Down