From 71d182f336424e9e7e7f0a2138203c20663120b5 Mon Sep 17 00:00:00 2001 From: Emmett Hoolahan Date: Sat, 2 Nov 2024 16:00:22 +0800 Subject: [PATCH 1/5] fix(types): add TypeScript types for AST node paths in resolve-module-exports.ts --- .../gatsby/src/bootstrap/resolve-module-exports.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/gatsby/src/bootstrap/resolve-module-exports.ts b/packages/gatsby/src/bootstrap/resolve-module-exports.ts index e52b795dd4ee2..40a64573460b2 100644 --- a/packages/gatsby/src/bootstrap/resolve-module-exports.ts +++ b/packages/gatsby/src/bootstrap/resolve-module-exports.ts @@ -23,7 +23,7 @@ const staticallyAnalyzeExports = ( } const code = fs.readFileSync(absPath, `utf8`) // get file contents - let ast + let ast: t.File try { ast = babelParseToAst(code, absPath) } catch (err) { @@ -58,7 +58,7 @@ const staticallyAnalyzeExports = ( isES6 = true }, - ExportNamedDeclaration: function ExportNamedDeclaration(astPath) { + ExportNamedDeclaration: function ExportNamedDeclaration(astPath: t.NodePath) { const declaration = astPath.node.declaration // get foo from `export const foo = bar` @@ -82,7 +82,7 @@ const staticallyAnalyzeExports = ( // get foo from `export { foo } from 'bar'` // get foo from `export { foo }` - ExportSpecifier: function ExportSpecifier(astPath) { + ExportSpecifier: function ExportSpecifier(astPath: t.NodePath) { isES6 = true const exp = astPath?.node?.exported if (!exp) { @@ -100,7 +100,7 @@ const staticallyAnalyzeExports = ( // export default function() {} // export default function foo() {} // const foo = () => {}; export default foo - ExportDefaultDeclaration: function ExportDefaultDeclaration(astPath) { + ExportDefaultDeclaration: function ExportDefaultDeclaration(astPath: t.NodePath) { const declaration = astPath.node.declaration if ( !t.isIdentifier(declaration) && @@ -122,7 +122,7 @@ const staticallyAnalyzeExports = ( exportNames.push(exportName) }, - AssignmentExpression: function AssignmentExpression(astPath) { + AssignmentExpression: function AssignmentExpression(astPath: t.NodePath) { const nodeLeft = astPath.node.left if (!t.isMemberExpression(nodeLeft)) { From 5eb0d8ce71ac2bcddca3411e3dce7efb2a8e0a88 Mon Sep 17 00:00:00 2001 From: Emmett Hoolahan Date: Sat, 2 Nov 2024 16:06:22 +0800 Subject: [PATCH 2/5] fix(router): update imports and add matchPathParams function for better path handling --- .../src/bootstrap/resolve-module-exports.ts | 18 ++++++++++++++++++ packages/gatsby/src/utils/find-page-by-path.ts | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/gatsby/src/bootstrap/resolve-module-exports.ts b/packages/gatsby/src/bootstrap/resolve-module-exports.ts index 40a64573460b2..7e11a1256e8d1 100644 --- a/packages/gatsby/src/bootstrap/resolve-module-exports.ts +++ b/packages/gatsby/src/bootstrap/resolve-module-exports.ts @@ -8,6 +8,7 @@ import { testImportError } from "../utils/test-import-error" import { resolveModule, ModuleResolver } from "../utils/module-resolver" import { maybeAddFileProtocol, resolveJSFilepath } from "./resolve-js-file-path" import { preferDefault } from "./prefer-default" +import { match } from "@reach/router" const staticallyAnalyzeExports = ( modulePath: string, @@ -238,3 +239,20 @@ export async function resolveModuleExports( return [] } +function matchPathParams(path: string, matchPath?: string) { + // Try original path + let result = match(matchPath || path, { path }) + + // Production SSR with encoded URL + if (!result && path.includes('%')) { + try { + const decoded = decodeURIComponent(path) + result = match(matchPath || decoded, { path: decoded }) + } catch { + // Fallback to original on decode fail + } + } + + // Never return null in SSR to prevent TypeError + return result?.params || {} +} diff --git a/packages/gatsby/src/utils/find-page-by-path.ts b/packages/gatsby/src/utils/find-page-by-path.ts index 76a248d1c6daf..64ad676301d91 100644 --- a/packages/gatsby/src/utils/find-page-by-path.ts +++ b/packages/gatsby/src/utils/find-page-by-path.ts @@ -1,5 +1,5 @@ import { IGatsbyPage, IGatsbyState } from "../redux/types" -import { pick } from "@gatsbyjs/reach-router" +import { pick } from "@reach/router" // Ranks and picks the best page to match. Each segment gets the highest // amount of points, then the type of segment gets an additional amount of From cadf7c45c68bce0e11c03edd4522d69aaf7156af Mon Sep 17 00:00:00 2001 From: Emmett Hoolahan Date: Sat, 2 Nov 2024 16:14:20 +0800 Subject: [PATCH 3/5] fix(types): export matchPathParams function for improved path matching --- packages/gatsby/src/bootstrap/resolve-module-exports.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby/src/bootstrap/resolve-module-exports.ts b/packages/gatsby/src/bootstrap/resolve-module-exports.ts index 7e11a1256e8d1..002ba4c98bf7a 100644 --- a/packages/gatsby/src/bootstrap/resolve-module-exports.ts +++ b/packages/gatsby/src/bootstrap/resolve-module-exports.ts @@ -239,7 +239,7 @@ export async function resolveModuleExports( return [] } -function matchPathParams(path: string, matchPath?: string) { +export function matchPathParams(path: string, matchPath?: string) { // Try original path let result = match(matchPath || path, { path }) From 1b419954506621afa40c56a93f7fd40863c6affb Mon Sep 17 00:00:00 2001 From: Emmett Hoolahan Date: Sat, 2 Nov 2024 16:15:03 +0800 Subject: [PATCH 4/5] fix(types): change matchPathParams function to internal scope for better encapsulation --- packages/gatsby/src/bootstrap/resolve-module-exports.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby/src/bootstrap/resolve-module-exports.ts b/packages/gatsby/src/bootstrap/resolve-module-exports.ts index 002ba4c98bf7a..7e11a1256e8d1 100644 --- a/packages/gatsby/src/bootstrap/resolve-module-exports.ts +++ b/packages/gatsby/src/bootstrap/resolve-module-exports.ts @@ -239,7 +239,7 @@ export async function resolveModuleExports( return [] } -export function matchPathParams(path: string, matchPath?: string) { +function matchPathParams(path: string, matchPath?: string) { // Try original path let result = match(matchPath || path, { path }) From 6d7746031abffc03fd2f1ab800da75425e005b27 Mon Sep 17 00:00:00 2001 From: Emmett Hoolahan Date: Sat, 2 Nov 2024 22:36:07 +0800 Subject: [PATCH 5/5] issue fix --- .../src/bootstrap/resolve-module-exports.ts | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/packages/gatsby/src/bootstrap/resolve-module-exports.ts b/packages/gatsby/src/bootstrap/resolve-module-exports.ts index 7e11a1256e8d1..eaa010d9f056f 100644 --- a/packages/gatsby/src/bootstrap/resolve-module-exports.ts +++ b/packages/gatsby/src/bootstrap/resolve-module-exports.ts @@ -239,20 +239,38 @@ export async function resolveModuleExports( return [] } -function matchPathParams(path: string, matchPath?: string) { - // Try original path - let result = match(matchPath || path, { path }) +import { match } from "@reach/router" + +export function matchPath(rawPath: string, matchPath?: string): PathMatch { + // Normalize paths + const path = rawPath || `/` + + // First try original path + let result = reachMatch(matchPath || path, { path }) - // Production SSR with encoded URL + // If no match and path has encoded characters if (!result && path.includes('%')) { try { - const decoded = decodeURIComponent(path) - result = match(matchPath || decoded, { path: decoded }) - } catch { - // Fallback to original on decode fail + // Try matching decoded path + const decodedPath = decodeURIComponent(path) + result = reachMatch(matchPath || decodedPath, { path: decodedPath }) + + // Handle double-encoded paths + if (!result && decodedPath.includes('%')) { + const doubleDecodedPath = decodeURIComponent(decodedPath) + result = reachMatch(matchPath || doubleDecodedPath, { + path: doubleDecodedPath + }) + } + } catch (e) { + // Silently handle decode errors } } - // Never return null in SSR to prevent TypeError - return result?.params || {} + // Always return valid params object + return { + params: result?.params || {}, + uri: result?.uri || path, + path: result?.path || path + } }