Skip to content

Commit c9bb766

Browse files
GatsbyJS BotKyleAMathews
andauthored
feat(gatsby): support named splats in functions (#33301) (#33516)
Co-authored-by: Ward Peeters <[email protected]> (cherry picked from commit 018c041) Co-authored-by: Kyle Mathews <[email protected]>
1 parent 33a5ec7 commit c9bb766

File tree

7 files changed

+133
-137
lines changed

7 files changed

+133
-137
lines changed

integration-tests/functions/__tests__/__snapshots__/functions-dev.js.snap

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`develop dynamic routes param routes 1`] = `
4+
Object {
5+
"super": "additional",
6+
"userId": "23",
7+
}
8+
`;
9+
10+
exports[`develop dynamic routes unnamed wildcard routes 1`] = `
11+
Object {
12+
"*": "super",
13+
"0": "super",
14+
}
15+
`;
16+
317
exports[`develop functions can parse different ways of sending data file in multipart/form 1`] = `
418
Array [
519
Object {
@@ -90,35 +104,22 @@ Object {
90104
}
91105
`;
92106

93-
exports[`develop routing dynamic routes 1`] = `
94-
Object {
95-
"super": "additional",
96-
"userId": "23",
97-
}
98-
`;
99-
100-
exports[`develop routing dynamic routes 2`] = `
101-
Object {
102-
"0": "super",
103-
}
104-
`;
105-
106-
exports[`develop routing routes with special characters 1`] = `"I-Am-Capitalized.js"`;
107+
exports[`develop routes with special characters 1`] = `"I-Am-Capitalized.js"`;
107108

108-
exports[`develop routing routes with special characters 2`] = `"some whitespace.js"`;
109+
exports[`develop routes with special characters 2`] = `"some whitespace.js"`;
109110

110-
exports[`develop routing routes with special characters 3`] = `"with-äöü-umlaut.js"`;
111+
exports[`develop routes with special characters 3`] = `"with-äöü-umlaut.js"`;
111112

112-
exports[`develop routing routes with special characters 4`] = `"some-àè-french.js"`;
113+
exports[`develop routes with special characters 4`] = `"some-àè-french.js"`;
113114

114-
exports[`develop routing routes with special characters 5`] = `"some-אודות.js"`;
115+
exports[`develop routes with special characters 5`] = `"some-אודות.js"`;
115116

116-
exports[`develop routing secondary-level API 1`] = `"I am at a secondary-level"`;
117+
exports[`develop secondary-level API 1`] = `"I am at a secondary-level"`;
117118

118-
exports[`develop routing secondary-level API 2`] = `"I am another sub-directory function"`;
119+
exports[`develop secondary-level API 2`] = `"I am another sub-directory function"`;
119120

120-
exports[`develop routing secondary-level API with index.js 1`] = `"I am an index.js in a sub-directory!"`;
121+
exports[`develop secondary-level API with index.js 1`] = `"I am an index.js in a sub-directory!"`;
121122

122-
exports[`develop routing top-level API 1`] = `"I am at the top-level"`;
123+
exports[`develop top-level API 1`] = `"I am at the top-level"`;
123124

124125
exports[`develop typescript typescript functions work 1`] = `"I am typescript"`;

integration-tests/functions/__tests__/__snapshots__/functions-prod.js.snap

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`production dynamic routes param routes 1`] = `
4+
Object {
5+
"super": "additional",
6+
"userId": "23",
7+
}
8+
`;
9+
10+
exports[`production dynamic routes unnamed wildcard routes 1`] = `
11+
Object {
12+
"*": "super",
13+
"0": "super",
14+
}
15+
`;
16+
317
exports[`production functions can parse different ways of sending data file in multipart/form 1`] = `
418
Array [
519
Object {
@@ -86,35 +100,22 @@ Object {
86100
}
87101
`;
88102

89-
exports[`production routing dynamic routes 1`] = `
90-
Object {
91-
"super": "additional",
92-
"userId": "23",
93-
}
94-
`;
95-
96-
exports[`production routing dynamic routes 2`] = `
97-
Object {
98-
"0": "super",
99-
}
100-
`;
101-
102-
exports[`production routing routes with special characters 1`] = `"I-Am-Capitalized.js"`;
103+
exports[`production routes with special characters 1`] = `"I-Am-Capitalized.js"`;
103104

104-
exports[`production routing routes with special characters 2`] = `"some whitespace.js"`;
105+
exports[`production routes with special characters 2`] = `"some whitespace.js"`;
105106

106-
exports[`production routing routes with special characters 3`] = `"with-äöü-umlaut.js"`;
107+
exports[`production routes with special characters 3`] = `"with-äöü-umlaut.js"`;
107108

108-
exports[`production routing routes with special characters 4`] = `"some-àè-french.js"`;
109+
exports[`production routes with special characters 4`] = `"some-àè-french.js"`;
109110

110-
exports[`production routing routes with special characters 5`] = `"some-אודות.js"`;
111+
exports[`production routes with special characters 5`] = `"some-אודות.js"`;
111112

112-
exports[`production routing secondary-level API 1`] = `"I am at a secondary-level"`;
113+
exports[`production secondary-level API 1`] = `"I am at a secondary-level"`;
113114

114-
exports[`production routing secondary-level API 2`] = `"I am another sub-directory function"`;
115+
exports[`production secondary-level API 2`] = `"I am another sub-directory function"`;
115116

116-
exports[`production routing secondary-level API with index.js 1`] = `"I am an index.js in a sub-directory!"`;
117+
exports[`production secondary-level API with index.js 1`] = `"I am an index.js in a sub-directory!"`;
117118

118-
exports[`production routing top-level API 1`] = `"I am at the top-level"`;
119+
exports[`production top-level API 1`] = `"I am at the top-level"`;
119120

120121
exports[`production typescript typescript functions work 1`] = `"I am typescript"`;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function (req, res) {
2+
res.json(req.params)
3+
}

integration-tests/functions/test-helpers.js

Lines changed: 56 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,63 +7,79 @@ const FormData = require("form-data")
77

88
export function runTests(env, host) {
99
describe(env, () => {
10-
describe(`routing`, () => {
11-
test(`top-level API`, async () => {
12-
const result = await fetch(`${host}/api/top-level`).then(res =>
13-
res.text()
14-
)
10+
test(`top-level API`, async () => {
11+
const result = await fetch(`${host}/api/top-level`).then(res =>
12+
res.text()
13+
)
1514

16-
expect(result).toMatchSnapshot()
17-
})
18-
test(`secondary-level API`, async () => {
19-
const result = await fetch(
20-
`${host}/api/a-directory/function`
21-
).then(res => res.text())
15+
expect(result).toMatchSnapshot()
16+
})
17+
test(`secondary-level API`, async () => {
18+
const result = await fetch(`${host}/api/a-directory/function`).then(res =>
19+
res.text()
20+
)
2221

23-
expect(result).toMatchSnapshot()
24-
})
25-
test(`secondary-level API with index.js`, async () => {
26-
const result = await fetch(`${host}/api/a-directory`).then(res =>
27-
res.text()
28-
)
22+
expect(result).toMatchSnapshot()
23+
})
24+
test(`secondary-level API with index.js`, async () => {
25+
const result = await fetch(`${host}/api/a-directory`).then(res =>
26+
res.text()
27+
)
2928

30-
expect(result).toMatchSnapshot()
31-
})
32-
test(`secondary-level API`, async () => {
33-
const result = await fetch(`${host}/api/dir/function`).then(res =>
34-
res.text()
35-
)
29+
expect(result).toMatchSnapshot()
30+
})
31+
test(`secondary-level API`, async () => {
32+
const result = await fetch(`${host}/api/dir/function`).then(res =>
33+
res.text()
34+
)
35+
36+
expect(result).toMatchSnapshot()
37+
})
38+
39+
test(`routes with special characters`, async () => {
40+
const routes = [
41+
`${host}/api/I-Am-Capitalized`,
42+
`${host}/api/some whitespace`,
43+
`${host}/api/with-äöü-umlaut`,
44+
`${host}/api/some-àè-french`,
45+
encodeURI(`${host}/api/some-אודות`),
46+
]
47+
48+
for (const route of routes) {
49+
const result = await fetch(route).then(res => res.text())
3650

3751
expect(result).toMatchSnapshot()
38-
})
39-
test(`routes with special characters`, async () => {
40-
const routes = [
41-
`${host}/api/I-Am-Capitalized`,
42-
`${host}/api/some whitespace`,
43-
`${host}/api/with-äöü-umlaut`,
44-
`${host}/api/some-àè-french`,
45-
encodeURI(`${host}/api/some-אודות`),
46-
]
52+
}
53+
})
54+
55+
describe(`dynamic routes`, () => {
56+
test(`param routes`, async () => {
57+
const routes = [`${host}/api/users/23/additional`]
4758

4859
for (const route of routes) {
49-
const result = await fetch(route).then(res => res.text())
60+
const result = await fetch(route).then(res => res.json())
5061

5162
expect(result).toMatchSnapshot()
5263
}
5364
})
54-
55-
test(`dynamic routes`, async () => {
56-
const routes = [
57-
`${host}/api/users/23/additional`,
58-
`${host}/api/dir/super`,
59-
]
60-
65+
test(`unnamed wildcard routes`, async () => {
66+
const routes = [`${host}/api/dir/super`]
6167
for (const route of routes) {
6268
const result = await fetch(route).then(res => res.json())
6369

6470
expect(result).toMatchSnapshot()
6571
}
6672
})
73+
test(`named wildcard routes`, async () => {
74+
const route = `${host}/api/named-wildcard/super`
75+
const result = await fetch(route).then(res => res.json())
76+
77+
expect(result).toMatchInlineSnapshot(`
78+
Object {
79+
"foo": "super",
80+
}
81+
`)
82+
})
6783
})
6884

6985
describe(`environment variables`, () => {

packages/gatsby/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@
116116
"opentracing": "^0.14.4",
117117
"p-defer": "^3.0.0",
118118
"parseurl": "^1.3.3",
119-
"path-to-regexp": "0.1.7",
120119
"physical-cpu-count": "^2.0.0",
121120
"platform": "^1.3.6",
122121
"postcss": "^8.3.5",

packages/gatsby/src/commands/serve.ts

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { match as reachMatch } from "@gatsbyjs/reach-router/lib/utils"
88
import onExit from "signal-exit"
99
import report from "gatsby-cli/lib/reporter"
1010
import multer from "multer"
11-
import pathToRegexp from "path-to-regexp"
1211
import cookie from "cookie"
1312
import telemetry from "gatsby-telemetry"
1413

@@ -25,14 +24,6 @@ interface IMatchPath {
2524
matchPath: string
2625
}
2726

28-
interface IPathToRegexpKey {
29-
name: string | number
30-
prefix: string
31-
suffix: string
32-
pattern: string
33-
modifier: string
34-
}
35-
3627
interface IServeProgram extends IProgram {
3728
prefixPaths: boolean
3829
}
@@ -169,25 +160,22 @@ module.exports = async (program: IServeProgram): Promise<void> => {
169160
// Check if there's any matchPaths that match.
170161
// We loop until we find the first match.
171162
functions.some(f => {
172-
let exp
173-
const keys: Array<IPathToRegexpKey> = []
174163
if (f.matchPath) {
175-
exp = pathToRegexp(f.matchPath, keys)
176-
}
177-
if (exp && exp.exec(pathFragment) !== null) {
178-
functionObj = f
179-
// @ts-ignore - TS bug? https://stackoverflow.com/questions/50234481/typescript-2-8-3-type-must-have-a-symbol-iterator-method-that-returns-an-iterato
180-
const matches = [...pathFragment.match(exp)].slice(1)
181-
const newParams = {}
182-
matches.forEach(
183-
(match, index) => (newParams[keys[index].name] = match)
184-
)
185-
req.params = newParams
186-
187-
return true
188-
} else {
189-
return false
164+
const matchResult = reachMatch(f.matchPath, pathFragment)
165+
if (matchResult) {
166+
req.params = matchResult.params
167+
if (req.params[`*`]) {
168+
// Backwards compatability for v3
169+
// TODO remove in v5
170+
req.params[`0`] = req.params[`*`]
171+
}
172+
functionObj = f
173+
174+
return true
175+
}
190176
}
177+
178+
return false
191179
})
192180
}
193181

packages/gatsby/src/internal-plugins/functions/gatsby-node.ts

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ import { CreateDevServerArgs, ParentSpanPluginArgs } from "gatsby"
1010
import formatWebpackMessages from "react-dev-utils/formatWebpackMessages"
1111
import dotenv from "dotenv"
1212
import chokidar from "chokidar"
13-
// We use an ancient version of path-to-regexp as it has breaking changes to express v4
14-
// see: https://github.com/pillarjs/path-to-regexp/tree/77df63869075cfa5feda1988642080162c584427#compatibility-with-express--4x
15-
import pathToRegexp from "path-to-regexp"
13+
import { match } from "@gatsbyjs/reach-router/lib/utils"
1614
import cookie from "cookie"
1715
import { reportWebpackWarnings } from "../../utils/webpack-error-utils"
1816
import { internalActions } from "../../redux/actions"
@@ -29,14 +27,6 @@ interface IGlobPattern {
2927
globPattern: string
3028
}
3129

32-
interface IPathToRegexpKey {
33-
name: string | number
34-
prefix: string
35-
suffix: string
36-
pattern: string
37-
modifier: string
38-
}
39-
4030
// During development, we lazily compile functions only when they're requested.
4131
// Here we keep track of which functions have been requested so are "active"
4232
const activeDevelopmentFunctions = new Set<IGatsbyFunction>()
@@ -496,24 +486,22 @@ export async function onCreateDevServer({
496486
// Check if there's any matchPaths that match.
497487
// We loop until we find the first match.
498488
functions.some(f => {
499-
let exp
500-
const keys: Array<IPathToRegexpKey> = []
501489
if (f.matchPath) {
502-
exp = pathToRegexp(f.matchPath, keys)
490+
const matchResult = match(f.matchPath, pathFragment)
491+
if (matchResult) {
492+
req.params = matchResult.params
493+
if (req.params[`*`]) {
494+
// Backwards compatability for v3
495+
// TODO remove in v5
496+
req.params[`0`] = req.params[`*`]
497+
}
498+
functionObj = f
499+
500+
return true
501+
}
503502
}
504-
if (exp && exp.exec(pathFragment) !== null) {
505-
functionObj = f
506-
const matches = [...pathFragment.match(exp)].slice(1)
507-
const newParams = {}
508-
matches.forEach(
509-
(match, index) => (newParams[keys[index].name] = match)
510-
)
511-
req.params = newParams
512503

513-
return true
514-
} else {
515-
return false
516-
}
504+
return false
517505
})
518506
}
519507

0 commit comments

Comments
 (0)