From 09f58defb6c3b4b26a3efbc8151fbf129e5cb7fd Mon Sep 17 00:00:00 2001 From: Sidhartha Chatterjee Date: Thu, 25 Feb 2021 16:35:00 +0530 Subject: [PATCH] =?UTF-8?q?feat(gatsby):=20Add=20ignoreCase=20option=20for?= =?UTF-8?q?=20createRedirect=20and=20support=20it=20in=20client=20sid?= =?UTF-8?q?=E2=80=A6=20(#29714)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michal Piechowiak Co-authored-by: Ward Peeters --- .../cypress/integration/redirects.js | 12 +++++ e2e-tests/production-runtime/gatsby-node.js | 18 ++++++- packages/gatsby/cache-dir/navigation.js | 25 ++++++--- packages/gatsby/index.d.ts | 1 + .../gatsby/src/bootstrap/redirects-writer.ts | 10 +++- packages/gatsby/src/query/redirects-writer.ts | 52 ------------------- .../__tests__/__snapshots__/redirects.ts.snap | 26 ++++++++++ packages/gatsby/src/redux/actions/public.js | 3 ++ packages/gatsby/src/redux/types.ts | 1 + 9 files changed, 88 insertions(+), 60 deletions(-) create mode 100644 e2e-tests/production-runtime/cypress/integration/redirects.js delete mode 100644 packages/gatsby/src/query/redirects-writer.ts diff --git a/e2e-tests/production-runtime/cypress/integration/redirects.js b/e2e-tests/production-runtime/cypress/integration/redirects.js new file mode 100644 index 0000000000000..ef702da80942a --- /dev/null +++ b/e2e-tests/production-runtime/cypress/integration/redirects.js @@ -0,0 +1,12 @@ +describe(`Redirects`, () => { + it(`are case insensitive when ignoreCase is set to true`, () => { + cy.visit(`/Longue-PAGE`, { failOnStatusCode: false }).waitForRouteChange() + + cy.get(`h1`).invoke(`text`).should(`contain`, `Hi from the long page`) + }) + it(`are case sensitive when ignoreCase is set to false`, () => { + cy.visit(`/PAGINA-larga`, { failOnStatusCode: false }).waitForRouteChange() + + cy.get(`h1`).invoke(`text`).should(`contain`, `NOT FOUND`) + }) +}) diff --git a/e2e-tests/production-runtime/gatsby-node.js b/e2e-tests/production-runtime/gatsby-node.js index ab92251e58fd0..1dcd0aa7f2d97 100644 --- a/e2e-tests/production-runtime/gatsby-node.js +++ b/e2e-tests/production-runtime/gatsby-node.js @@ -36,7 +36,7 @@ exports.sourceNodes = ({ actions, createNodeId }) => { }) } -exports.createPages = ({ actions: { createPage } }) => { +exports.createPages = ({ actions: { createPage, createRedirect } }) => { createPage({ path: `/안녕`, component: path.resolve(`src/pages/page-2.js`), @@ -124,6 +124,22 @@ exports.createPages = ({ actions: { createPage } }) => { path: `/page-from-cache/`, component: path.resolve(`./.cache/static-page-from-cache.js`), }) + + createRedirect({ + fromPath: "/pagina-larga", + toPath: "/long-page", + isPermanent: true, + redirectInBrowser: true, + ignoreCase: false, + }) + + createRedirect({ + fromPath: "/Longue-Page", + toPath: "/long-page", + isPermanent: true, + redirectInBrowser: true, + ignoreCase: true, + }) } exports.onCreatePage = ({ page, actions }) => { diff --git a/packages/gatsby/cache-dir/navigation.js b/packages/gatsby/cache-dir/navigation.js index d2944fd1cc6ad..62e1832f2477a 100644 --- a/packages/gatsby/cache-dir/navigation.js +++ b/packages/gatsby/cache-dir/navigation.js @@ -10,13 +10,23 @@ import { globalHistory } from "@reach/router/lib/history" import { parsePath } from "gatsby-link" // Convert to a map for faster lookup in maybeRedirect() -const redirectMap = redirects.reduce((map, redirect) => { - map[redirect.fromPath] = redirect - return map -}, {}) + +const redirectMap = new Map() +const redirectIgnoreCaseMap = new Map() + +redirects.forEach(redirect => { + if (redirect.ignoreCase) { + redirectIgnoreCaseMap.set(redirect.fromPath, redirect) + } else { + redirectMap.set(redirect.fromPath, redirect) + } +}) function maybeRedirect(pathname) { - const redirect = redirectMap[pathname] + let redirect = redirectMap.get(pathname) + if (!redirect) { + redirect = redirectIgnoreCaseMap.get(pathname.toLowerCase()) + } if (redirect != null) { if (process.env.NODE_ENV !== `production`) { @@ -62,7 +72,10 @@ const navigate = (to, options = {}) => { } let { pathname } = parsePath(to) - const redirect = redirectMap[pathname] + let redirect = redirectMap.get(pathname) + if (!redirect) { + redirect = redirectIgnoreCaseMap.get(pathname.toLowerCase()) + } // If we're redirecting, just replace the passed in pathname // to the one we want to redirect to. diff --git a/packages/gatsby/index.d.ts b/packages/gatsby/index.d.ts index 5394220014ad5..eb1bb9dcf0b64 100644 --- a/packages/gatsby/index.d.ts +++ b/packages/gatsby/index.d.ts @@ -1205,6 +1205,7 @@ export interface Actions { redirectInBrowser?: boolean force?: boolean statusCode?: number + ignoreCase?: boolean [key: string]: unknown }, plugin?: ActionPlugin diff --git a/packages/gatsby/src/bootstrap/redirects-writer.ts b/packages/gatsby/src/bootstrap/redirects-writer.ts index 975d813f1cd10..f8d9584a7058d 100644 --- a/packages/gatsby/src/bootstrap/redirects-writer.ts +++ b/packages/gatsby/src/bootstrap/redirects-writer.ts @@ -16,7 +16,15 @@ export const writeRedirects = async (): Promise => { const browserRedirects = redirects .filter(r => r.redirectInBrowser) // eslint-disable-next-line @typescript-eslint/no-unused-vars - .map(({ redirectInBrowser, isPermanent, ...rest }) => rest) + .map( + ({ redirectInBrowser, isPermanent, ignoreCase, fromPath, ...rest }) => { + return { + fromPath: ignoreCase ? fromPath.toLowerCase() : fromPath, + ignoreCase, + ...rest, + } + } + ) const newHash = crypto .createHash(`md5`) diff --git a/packages/gatsby/src/query/redirects-writer.ts b/packages/gatsby/src/query/redirects-writer.ts deleted file mode 100644 index 86ef266e40917..0000000000000 --- a/packages/gatsby/src/query/redirects-writer.ts +++ /dev/null @@ -1,52 +0,0 @@ -import _ from "lodash" -import crypto from "crypto" -import fs from "fs-extra" -import { store, emitter } from "../redux" -import { joinPath } from "gatsby-core-utils" - -let lastHash: string | null = null -let bootstrapFinished = false - -export const writeRedirects = async (): Promise => { - bootstrapFinished = true - - const { program, redirects } = store.getState() - - // Filter for redirects that are meant for the browser. - const browserRedirects = redirects - .filter(r => r.redirectInBrowser) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .map(({ redirectInBrowser, isPermanent, ...rest }) => rest) - - const newHash = crypto - .createHash(`md5`) - .update(JSON.stringify(browserRedirects)) - .digest(`hex`) - - if (newHash === lastHash) { - return Promise.resolve() - } - - lastHash = newHash - - return await fs.writeFile( - joinPath(program.directory, `.cache/redirects.json`), - JSON.stringify(browserRedirects, null, 2) - ) -} - -let oldRedirects -const debouncedWriteRedirects = _.debounce(() => { - // Don't write redirects again until bootstrap has finished. - if ( - bootstrapFinished && - !_.isEqual(oldRedirects, store.getState().redirects) - ) { - writeRedirects() - oldRedirects = store.getState().redirects - } -}, 250) - -emitter.on(`CREATE_REDIRECT`, () => { - debouncedWriteRedirects() -}) diff --git a/packages/gatsby/src/redux/__tests__/__snapshots__/redirects.ts.snap b/packages/gatsby/src/redux/__tests__/__snapshots__/redirects.ts.snap index ab045bba78589..49aadc4602f99 100644 --- a/packages/gatsby/src/redux/__tests__/__snapshots__/redirects.ts.snap +++ b/packages/gatsby/src/redux/__tests__/__snapshots__/redirects.ts.snap @@ -4,6 +4,7 @@ exports[`Add redirects allows you to add redirects 1`] = ` Object { "payload": Object { "fromPath": "/old/hello-world", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "/new/hello-world", @@ -16,6 +17,7 @@ exports[`Add redirects create redirects as permanent 1`] = ` Object { "payload": Object { "fromPath": "/old/hello-world", + "ignoreCase": true, "isPermanent": true, "redirectInBrowser": false, "toPath": "/new/hello-world", @@ -28,6 +30,7 @@ exports[`Add redirects creates redirects from the URL starts with // 1`] = ` Object { "payload": Object { "fromPath": "//example.com", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "/new/hello-world-2", @@ -40,6 +43,7 @@ exports[`Add redirects creates redirects from the URL starts with ftp 1`] = ` Object { "payload": Object { "fromPath": "ftp://example.com", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "/new/hello-world-3", @@ -52,6 +56,7 @@ exports[`Add redirects creates redirects from the URL starts with http 1`] = ` Object { "payload": Object { "fromPath": "http://example.com", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "/new/hello-world-1", @@ -64,6 +69,7 @@ exports[`Add redirects creates redirects from the URL starts with https 1`] = ` Object { "payload": Object { "fromPath": "https://example.com", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "/new/hello-world-0", @@ -76,6 +82,7 @@ exports[`Add redirects creates redirects from the URL starts with mailto 1`] = ` Object { "payload": Object { "fromPath": "mailto:example@email.com", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "/new/hello-world-4", @@ -88,6 +95,7 @@ exports[`Add redirects creates redirects to the URL starts with // 1`] = ` Object { "payload": Object { "fromPath": "/old/hello-world-2", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "//example.com", @@ -100,6 +108,7 @@ exports[`Add redirects creates redirects to the URL starts with ftp 1`] = ` Object { "payload": Object { "fromPath": "/old/hello-world-3", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "ftp://example.com", @@ -112,6 +121,7 @@ exports[`Add redirects creates redirects to the URL starts with http 1`] = ` Object { "payload": Object { "fromPath": "/old/hello-world-1", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "http://example.com", @@ -124,6 +134,7 @@ exports[`Add redirects creates redirects to the URL starts with https 1`] = ` Object { "payload": Object { "fromPath": "/old/hello-world-0", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "https://example.com", @@ -136,6 +147,7 @@ exports[`Add redirects creates redirects to the URL starts with mailto 1`] = ` Object { "payload": Object { "fromPath": "/old/hello-world-4", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "mailto:example@email.com", @@ -148,6 +160,7 @@ exports[`Add redirects creates redirects with in-browser redirect option 1`] = ` Object { "payload": Object { "fromPath": "/old/hello-world", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": true, "toPath": "/new/hello-world", @@ -160,6 +173,7 @@ exports[`Add redirects with path prefixs allows you to add redirects 1`] = ` Object { "payload": Object { "fromPath": "/blog/old/hello-world", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "/blog/new/hello-world", @@ -172,6 +186,7 @@ exports[`Add redirects with path prefixs create redirects as permanent 1`] = ` Object { "payload": Object { "fromPath": "/blog/old/hello-world", + "ignoreCase": true, "isPermanent": true, "redirectInBrowser": false, "toPath": "/blog/new/hello-world", @@ -184,6 +199,7 @@ exports[`Add redirects with path prefixs creates redirects from the URL starts w Object { "payload": Object { "fromPath": "//example.com", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "/blog/new/hello-world-2", @@ -196,6 +212,7 @@ exports[`Add redirects with path prefixs creates redirects from the URL starts w Object { "payload": Object { "fromPath": "ftp://example.com", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "/blog/new/hello-world-3", @@ -208,6 +225,7 @@ exports[`Add redirects with path prefixs creates redirects from the URL starts w Object { "payload": Object { "fromPath": "http://example.com", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "/blog/new/hello-world-1", @@ -220,6 +238,7 @@ exports[`Add redirects with path prefixs creates redirects from the URL starts w Object { "payload": Object { "fromPath": "https://example.com", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "/blog/new/hello-world-0", @@ -232,6 +251,7 @@ exports[`Add redirects with path prefixs creates redirects from the URL starts w Object { "payload": Object { "fromPath": "mailto:example@email.com", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "/blog/new/hello-world-4", @@ -244,6 +264,7 @@ exports[`Add redirects with path prefixs creates redirects to the URL starts wit Object { "payload": Object { "fromPath": "/blog/old/hello-world-2", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "//example.com", @@ -256,6 +277,7 @@ exports[`Add redirects with path prefixs creates redirects to the URL starts wit Object { "payload": Object { "fromPath": "/blog/old/hello-world-3", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "ftp://example.com", @@ -268,6 +290,7 @@ exports[`Add redirects with path prefixs creates redirects to the URL starts wit Object { "payload": Object { "fromPath": "/blog/old/hello-world-1", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "http://example.com", @@ -280,6 +303,7 @@ exports[`Add redirects with path prefixs creates redirects to the URL starts wit Object { "payload": Object { "fromPath": "/blog/old/hello-world-0", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "https://example.com", @@ -292,6 +316,7 @@ exports[`Add redirects with path prefixs creates redirects to the URL starts wit Object { "payload": Object { "fromPath": "/blog/old/hello-world-4", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": false, "toPath": "mailto:example@email.com", @@ -304,6 +329,7 @@ exports[`Add redirects with path prefixs creates redirects with in-browser redir Object { "payload": Object { "fromPath": "/blog/old/hello-world", + "ignoreCase": true, "isPermanent": false, "redirectInBrowser": true, "toPath": "/blog/new/hello-world", diff --git a/packages/gatsby/src/redux/actions/public.js b/packages/gatsby/src/redux/actions/public.js index 9aa3c016985b0..513ff3caf132e 100644 --- a/packages/gatsby/src/redux/actions/public.js +++ b/packages/gatsby/src/redux/actions/public.js @@ -1286,6 +1286,7 @@ const maybeAddPathPrefix = (path, pathPrefix) => { * @param {boolean} redirect.redirectInBrowser Redirects are generally for redirecting legacy URLs to their new configuration. If you can't update your UI for some reason, set `redirectInBrowser` to true and Gatsby will handle redirecting in the client as well. * @param {boolean} redirect.force (Plugin-specific) Will trigger the redirect even if the `fromPath` matches a piece of content. This is not part of the Gatsby API, but implemented by (some) plugins that configure hosting provider redirects * @param {number} redirect.statusCode (Plugin-specific) Manually set the HTTP status code. This allows you to create a rewrite (status code 200) or custom error page (status code 404). Note that this will override the `isPermanent` option which also sets the status code. This is not part of the Gatsby API, but implemented by (some) plugins that configure hosting provider redirects + * @param {boolean} redirect.ignoreCase (Plugin-specific) Ignore case when looking for redirects * @example * // Generally you create redirects while creating pages. * exports.createPages = ({ graphql, actions }) => { @@ -1301,6 +1302,7 @@ actions.createRedirect = ({ isPermanent = false, redirectInBrowser = false, toPath, + ignoreCase = true, ...rest }) => { let pathPrefix = `` @@ -1313,6 +1315,7 @@ actions.createRedirect = ({ payload: { fromPath: maybeAddPathPrefix(fromPath, pathPrefix), isPermanent, + ignoreCase, redirectInBrowser, toPath: maybeAddPathPrefix(toPath, pathPrefix), ...rest, diff --git a/packages/gatsby/src/redux/types.ts b/packages/gatsby/src/redux/types.ts index 7f5c87b890855..f1219daed0a4e 100644 --- a/packages/gatsby/src/redux/types.ts +++ b/packages/gatsby/src/redux/types.ts @@ -14,6 +14,7 @@ export interface IRedirect { toPath: string isPermanent?: boolean redirectInBrowser?: boolean + ignoreCase: boolean // Users can add anything to this createRedirect API [key: string]: any }