diff --git a/.circleci/config.yml b/.circleci/config.yml index d12ffd7e09754..7308ed2e7464c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,6 +15,7 @@ executors: environment: GATSBY_CPU_COUNT: 2 COMPILER_OPTIONS: GATSBY_MAJOR=<< parameters.gatsby_major >> + NODE_NO_WARNINGS: 1 aliases: e2e-executor-env: &e2e-executor-env diff --git a/e2e-tests/adapters/cypress/e2e/headers.cy.ts b/e2e-tests/adapters/cypress/e2e/headers.cy.ts index a2cd58cdecbb8..cc034a9afcd7d 100644 --- a/e2e-tests/adapters/cypress/e2e/headers.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/headers.cy.ts @@ -76,6 +76,10 @@ describe("Headers", () => { beforeEach(() => { cy.intercept(PATH_PREFIX + "/", WorkaroundCachedResponse).as("index") + cy.intercept( + PATH_PREFIX + "/routes/ssg/static", + WorkaroundCachedResponse + ).as("ssg") cy.intercept( PATH_PREFIX + "/routes/ssr/static", WorkaroundCachedResponse @@ -128,6 +132,22 @@ describe("Headers", () => { checkHeaders("@js") }) + it("should contain correct headers for ssg page", () => { + cy.visit("routes/ssg/static").waitForRouteChange() + + checkHeaders("@ssg", { + ...defaultHeaders, + "x-custom-header": "my custom header value", + "x-ssg-header": "my custom header value", + "cache-control": "public,max-age=0,must-revalidate", + }) + + checkHeaders("@app-data") + checkHeaders("@page-data") + checkHeaders("@slice-data") + checkHeaders("@static-query-result") + }) + it("should contain correct headers for ssr page", () => { cy.visit("routes/ssr/static").waitForRouteChange() diff --git a/e2e-tests/adapters/cypress/e2e/remote-file.cy.ts b/e2e-tests/adapters/cypress/e2e/remote-file.cy.ts index 56adae8cec9b1..7d2a4599203aa 100644 --- a/e2e-tests/adapters/cypress/e2e/remote-file.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/remote-file.cy.ts @@ -18,12 +18,12 @@ const PATH_PREFIX = Cypress.env(`PATH_PREFIX`) || `` const configs = [ { title: `remote-file (SSG, Page Query)`, - pagePath: `/routes/remote-file/`, + pagePath: `/routes/ssg/remote-file/`, placeholders: true, }, { title: `remote-file (SSG, Page Context)`, - pagePath: `/routes/remote-file-data-from-context/`, + pagePath: `/routes/ssg/remote-file-data-from-context/`, placeholders: true, }, { diff --git a/e2e-tests/adapters/cypress/e2e/trailing-slash.cy.ts b/e2e-tests/adapters/cypress/e2e/trailing-slash.cy.ts index c595f655907ce..0a4b1ce2de670 100644 --- a/e2e-tests/adapters/cypress/e2e/trailing-slash.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/trailing-slash.cy.ts @@ -1,7 +1,7 @@ import { assertPageVisits } from "../utils/assert-page-visits" import { applyTrailingSlashOption } from "../../utils" -Cypress.on("uncaught:exception", (err) => { +Cypress.on("uncaught:exception", err => { if (err.message.includes("Minified React error")) { return false } @@ -12,38 +12,62 @@ const TRAILING_SLASH = Cypress.env(`TRAILING_SLASH`) || `never` describe("trailingSlash", () => { describe(TRAILING_SLASH, () => { it("should work when using Gatsby Link (without slash)", () => { - cy.visit('/').waitForRouteChange() + cy.visit("/").waitForRouteChange() - cy.get(`[data-testid="static-without-slash"]`).click().waitForRouteChange().assertRoute(applyTrailingSlashOption(`/routes/static`, TRAILING_SLASH)) + cy.get(`[data-testid="static-without-slash"]`) + .click() + .waitForRouteChange() + .assertRoute( + applyTrailingSlashOption(`/routes/ssg/static`, TRAILING_SLASH) + ) }) it("should work when using Gatsby Link (with slash)", () => { - cy.visit('/').waitForRouteChange() + cy.visit("/").waitForRouteChange() - cy.get(`[data-testid="static-with-slash"]`).click().waitForRouteChange().assertRoute(applyTrailingSlashOption(`/routes/static`, TRAILING_SLASH)) + cy.get(`[data-testid="static-with-slash"]`) + .click() + .waitForRouteChange() + .assertRoute( + applyTrailingSlashOption(`/routes/ssg/static`, TRAILING_SLASH) + ) }) it("should work on direct visit (with other setting)", () => { - const destination = applyTrailingSlashOption("/routes/static", TRAILING_SLASH) - const inverse = TRAILING_SLASH === `always` ? "/routes/static" : "/routes/static/" + const destination = applyTrailingSlashOption( + "/routes/ssg/static", + TRAILING_SLASH + ) + const inverse = + TRAILING_SLASH === `always` + ? "/routes/ssg/static" + : "/routes/ssg/static/" assertPageVisits([ { path: destination, status: 200, }, - { path: inverse, status: 301, destinationPath: destination } + { path: inverse, status: 301, destinationPath: destination }, ]) - cy.visit(inverse).waitForRouteChange().assertRoute(applyTrailingSlashOption(`/routes/static`, TRAILING_SLASH)) + cy.visit(inverse) + .waitForRouteChange() + .assertRoute( + applyTrailingSlashOption(`/routes/ssg/static`, TRAILING_SLASH) + ) }) it("should work on direct visit (with current setting)", () => { assertPageVisits([ { - path: applyTrailingSlashOption("/routes/static", TRAILING_SLASH), + path: applyTrailingSlashOption("/routes/ssg/static", TRAILING_SLASH), status: 200, }, ]) - cy.visit(applyTrailingSlashOption("/routes/static", TRAILING_SLASH)).waitForRouteChange().assertRoute(applyTrailingSlashOption(`/routes/static`, TRAILING_SLASH)) + cy.visit(applyTrailingSlashOption("/routes/ssg/static", TRAILING_SLASH)) + .waitForRouteChange() + .assertRoute( + applyTrailingSlashOption(`/routes/ssg/static`, TRAILING_SLASH) + ) }) }) }) diff --git a/e2e-tests/adapters/gatsby-config.ts b/e2e-tests/adapters/gatsby-config.ts index 78c0361981ae9..803de68814634 100644 --- a/e2e-tests/adapters/gatsby-config.ts +++ b/e2e-tests/adapters/gatsby-config.ts @@ -77,6 +77,15 @@ const config: GatsbyConfig = { }, ], }, + { + source: `routes/ssg/*`, + headers: [ + { + key: "x-ssg-header", + value: "my custom header value", + }, + ], + }, ], ...configOverrides, } diff --git a/e2e-tests/adapters/gatsby-node.ts b/e2e-tests/adapters/gatsby-node.ts index 9342bb30fa191..f0c6697d1f932 100644 --- a/e2e-tests/adapters/gatsby-node.ts +++ b/e2e-tests/adapters/gatsby-node.ts @@ -55,7 +55,7 @@ export const createPages: GatsbyNode["createPages"] = async ({ createPage({ path: applyTrailingSlashOption( - `/routes/remote-file-data-from-context/`, + `/routes/ssg/remote-file-data-from-context/`, TRAILING_SLASH ), component: path.resolve(`./src/templates/remote-file-from-context.jsx`), diff --git a/e2e-tests/adapters/src/pages/index.jsx b/e2e-tests/adapters/src/pages/index.jsx index 0032374b21b5f..e847fbd2adab7 100644 --- a/e2e-tests/adapters/src/pages/index.jsx +++ b/e2e-tests/adapters/src/pages/index.jsx @@ -7,12 +7,12 @@ import "./index.css" const routes = [ { text: "Static", - url: "/routes/static", + url: "/routes/ssg/static", id: "static-without-slash", }, { text: "Static (With Slash)", - url: "/routes/static/", + url: "/routes/ssg/static/", id: "static-with-slash", }, { @@ -41,11 +41,11 @@ const routes = [ }, { text: "RemoteFile (ImageCDN and FileCDN) (SSG, Page Query)", - url: "/routes/remote-file", + url: "/routes/ssg/remote-file", }, { text: "RemoteFile (ImageCDN and FileCDN) (SSG, Page Context)", - url: "/routes/remote-file-data-from-context", + url: "/routes/ssg/remote-file-data-from-context", }, { text: "RemoteFile (ImageCDN and FileCDN) (SSR, Page Query)", diff --git a/e2e-tests/adapters/src/pages/routes/remote-file.jsx b/e2e-tests/adapters/src/pages/routes/ssg/remote-file.jsx similarity index 98% rename from e2e-tests/adapters/src/pages/routes/remote-file.jsx rename to e2e-tests/adapters/src/pages/routes/ssg/remote-file.jsx index 8f2ceba8a1756..8401dd5e948a1 100644 --- a/e2e-tests/adapters/src/pages/routes/remote-file.jsx +++ b/e2e-tests/adapters/src/pages/routes/ssg/remote-file.jsx @@ -2,7 +2,7 @@ import { graphql } from "gatsby" import React from "react" import { GatsbyImage } from "gatsby-plugin-image" -import Layout from "../../components/layout" +import Layout from "../../../components/layout" const RemoteFile = ({ data }) => { return ( diff --git a/e2e-tests/adapters/src/pages/routes/static.jsx b/e2e-tests/adapters/src/pages/routes/ssg/static.jsx similarity index 61% rename from e2e-tests/adapters/src/pages/routes/static.jsx rename to e2e-tests/adapters/src/pages/routes/ssg/static.jsx index 709b5b24559f1..e8d43fe501717 100644 --- a/e2e-tests/adapters/src/pages/routes/static.jsx +++ b/e2e-tests/adapters/src/pages/routes/ssg/static.jsx @@ -1,5 +1,5 @@ import * as React from "react" -import Layout from "../../components/layout" +import Layout from "../../../components/layout" const StaticPage = () => { return ( @@ -11,4 +11,4 @@ const StaticPage = () => { export default StaticPage -export const Head = () => Static \ No newline at end of file +export const Head = () => Static diff --git a/packages/gatsby-adapter-netlify/src/route-handler.ts b/packages/gatsby-adapter-netlify/src/route-handler.ts index f6320dc146c77..9f1aa67e36440 100644 --- a/packages/gatsby-adapter-netlify/src/route-handler.ts +++ b/packages/gatsby-adapter-netlify/src/route-handler.ts @@ -236,13 +236,13 @@ export function processRoutesManifest( _headers += buildHeaderString(route.path, route.headers) } } + } - if (headerRoutes) { - _headers = headerRoutes.reduce((acc, curr) => { - acc += buildHeaderString(curr.path, curr.headers) - return acc - }, ``) - } + if (headerRoutes) { + _headers = headerRoutes.reduce((acc, curr) => { + acc += buildHeaderString(curr.path, curr.headers) + return acc + }, ``) } return { diff --git a/packages/gatsby/src/utils/adapter/create-headers.ts b/packages/gatsby/src/utils/adapter/create-headers.ts index 65605dc3b0d97..f7f1f0643a999 100644 --- a/packages/gatsby/src/utils/adapter/create-headers.ts +++ b/packages/gatsby/src/utils/adapter/create-headers.ts @@ -12,8 +12,17 @@ const normalizePath = (input: string): string => input.endsWith(`/`) ? input : `${input}/` export const createHeadersMatcher = ( - headers: Array | undefined + headers: Array | undefined, + pathPrefix: string ): ((path: string, defaultHeaders: Headers) => Headers) => { + function stripPathPrefix(path: string): string { + if (pathPrefix && path.startsWith(pathPrefix)) { + path = path.slice(pathPrefix.length) + } + + return path + } + // Split the incoming user headers into two buckets: // - dynamicHeaders: Headers with dynamic paths (e.g. /* or /:tests) // - staticHeaders: Headers with fully static paths (e.g. /static/) @@ -27,13 +36,21 @@ export const createHeadersMatcher = ( } for (const header of headers) { - if (header.source.includes(`:`) || header.source.includes(`*`)) { + const source = stripPathPrefix(header.source) + if (source.includes(`:`) || source.includes(`*`)) { // rankRoute is the internal function that also "match" uses - const score = rankRoute(header.source) + const score = rankRoute(source) - dynamicHeaders.push({ ...header, score }) + dynamicHeaders.push({ + ...header, + score, + source, + }) } else { - staticHeaders.set(normalizePath(header.source), header) + staticHeaders.set(normalizePath(source), { + ...header, + source, + }) } } @@ -48,6 +65,8 @@ export const createHeadersMatcher = ( }) return (path: string, defaultHeaders: Headers): Headers => { + path = stripPathPrefix(path) + // Create a map of headers for the given path // The key will be the header key. Since a key may only appear once in a map, the last header with the same key will win const uniqueHeaders: Map = new Map() diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index a159fa1d4b5db..2cf30e595cf7c 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -342,10 +342,10 @@ function getRoutesManifest(): { } { const routes: Array = [] const state = store.getState() - const createHeaders = createHeadersMatcher(state.config.headers) const pathPrefix = state.program.prefixPaths ? state.config.pathPrefix ?? `` : `` + const createHeaders = createHeadersMatcher(state.config.headers, pathPrefix) const headerRoutes: HeaderRoutes = [...getDefaultHeaderRoutes(pathPrefix)] diff --git a/packages/gatsby/src/utils/page-ssr-module/bundle-webpack.ts b/packages/gatsby/src/utils/page-ssr-module/bundle-webpack.ts index ba42ba25e0961..70d6a411b170c 100644 --- a/packages/gatsby/src/utils/page-ssr-module/bundle-webpack.ts +++ b/packages/gatsby/src/utils/page-ssr-module/bundle-webpack.ts @@ -230,6 +230,7 @@ export async function createPageSSRBundle({ ) : `` ), + PATH_PREFIX: JSON.stringify(pathPrefix), // eslint-disable-next-line @typescript-eslint/naming-convention "process.env.GATSBY_LOGGER": JSON.stringify(`yurnalist`), // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/packages/gatsby/src/utils/page-ssr-module/entry.ts b/packages/gatsby/src/utils/page-ssr-module/entry.ts index 0d74eb0914f66..2f942bd088d2f 100644 --- a/packages/gatsby/src/utils/page-ssr-module/entry.ts +++ b/packages/gatsby/src/utils/page-ssr-module/entry.ts @@ -75,6 +75,7 @@ declare global { const WEBPACK_COMPILATION_HASH: string const GATSBY_SLICES_SCRIPT: string const GATSBY_PAGES: Array<[string, EnginePage]> + const PATH_PREFIX: string } const tracerReadyPromise = initTracer( @@ -85,7 +86,7 @@ type MaybePhantomActivity = | ReturnType | undefined -const createHeaders = createHeadersMatcher(INLINED_HEADERS_CONFIG) +const createHeaders = createHeadersMatcher(INLINED_HEADERS_CONFIG, PATH_PREFIX) interface IGetDataBaseArgs { pathName: string diff --git a/yarn.lock b/yarn.lock index eb07ce64dcf2b..9713d7ce306e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7473,9 +7473,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001503: - version "1.0.30001566" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz" - integrity sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA== + version "1.0.30001625" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001625.tgz" + integrity sha512-4KE9N2gcRH+HQhpeiRZXd+1niLB/XNLAhSy4z7fI8EzcbcPoAqjNInxVHTiTwWfTIV4w096XG8OtCOCQQKPv3w== capital-case@^1.0.4: version "1.0.4"