Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 54 additions & 3 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 @@ -92,6 +92,57 @@ describe(`getRoutesManifest`, () => {
])
)
})

it(`should return header rules`, () => {
mockStoreState(stateDefault, {
config: { ...stateDefault.config, trailingSlash: `always` },
})
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.toContain(
expect.objectContaining({ path: "/static/app-456.js" })
)
})
})

describe(`getFunctionsManifest`, () => {
Expand Down
95 changes: 73 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 Down Expand Up @@ -201,10 +202,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 +220,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 +274,40 @@ export function setWebpackAssets(assets: Set<string>): void {

type RouteWithScore = { score: number } & Route

function getRoutesManifest(): RoutesManifest {
const defaultHeaderRoutes: HeaderRoutes = [
{
path: "/*",
headers: BASE_HEADERS,
},
{
path: "/static/*",
headers: PERMAMENT_CACHING_HEADERS.filter(h => !BASE_HEADERS.includes(h)),
},
]

const customHeaderFilter = (route: Route) => (h: IHeader["headers"][0]) => {
if (BASE_HEADERS.includes(h)) {
return false
}
if (
route.path.startsWith(`/static/`) &&
PERMAMENT_CACHING_HEADERS.includes(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 All @@ -290,11 +332,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 @@ -506,25 +555,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
7 changes: 7 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,12 @@ export type Route = IStaticRoute | IFunctionRoute | IRedirectRoute

export type RoutesManifest = Array<Route>

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

export type HeaderRoutes = HeaderRoute[]

export interface IFunctionDefinition {
/**
* Unique identifier of this function. Corresponds to the `functionId` inside the `routesManifest`.
Expand Down Expand Up @@ -99,6 +105,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