Skip to content

Commit

Permalink
feat(gatsby): support named splats in functions (#33301)
Browse files Browse the repository at this point in the history
Co-authored-by: Ward Peeters <[email protected]>
  • Loading branch information
KyleAMathews and wardpeet authored Oct 12, 2021
1 parent c70227b commit 018c041
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 136 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`develop dynamic routes param routes 1`] = `
Object {
"super": "additional",
"userId": "23",
}
`;

exports[`develop dynamic routes unnamed wildcard routes 1`] = `
Object {
"*": "super",
"0": "super",
}
`;

exports[`develop functions can parse different ways of sending data file in multipart/form 1`] = `
Array [
Object {
Expand Down Expand Up @@ -90,35 +104,22 @@ Object {
}
`;

exports[`develop routing dynamic routes 1`] = `
Object {
"super": "additional",
"userId": "23",
}
`;

exports[`develop routing dynamic routes 2`] = `
Object {
"0": "super",
}
`;

exports[`develop routing routes with special characters 1`] = `"I-Am-Capitalized.js"`;
exports[`develop routes with special characters 1`] = `"I-Am-Capitalized.js"`;

exports[`develop routing routes with special characters 2`] = `"some whitespace.js"`;
exports[`develop routes with special characters 2`] = `"some whitespace.js"`;

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

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

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

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

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

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

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

exports[`develop typescript typescript functions work 1`] = `"I am typescript"`;
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`production dynamic routes param routes 1`] = `
Object {
"super": "additional",
"userId": "23",
}
`;

exports[`production dynamic routes unnamed wildcard routes 1`] = `
Object {
"*": "super",
"0": "super",
}
`;

exports[`production functions can parse different ways of sending data file in multipart/form 1`] = `
Array [
Object {
Expand Down Expand Up @@ -86,35 +100,22 @@ Object {
}
`;

exports[`production routing dynamic routes 1`] = `
Object {
"super": "additional",
"userId": "23",
}
`;

exports[`production routing dynamic routes 2`] = `
Object {
"0": "super",
}
`;

exports[`production routing routes with special characters 1`] = `"I-Am-Capitalized.js"`;
exports[`production routes with special characters 1`] = `"I-Am-Capitalized.js"`;

exports[`production routing routes with special characters 2`] = `"some whitespace.js"`;
exports[`production routes with special characters 2`] = `"some whitespace.js"`;

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

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

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

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

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

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

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

exports[`production typescript typescript functions work 1`] = `"I am typescript"`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function (req, res) {
res.json(req.params)
}
96 changes: 56 additions & 40 deletions integration-tests/functions/test-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,63 +7,79 @@ const FormData = require("form-data")

export function runTests(env, host) {
describe(env, () => {
describe(`routing`, () => {
test(`top-level API`, async () => {
const result = await fetch(`${host}/api/top-level`).then(res =>
res.text()
)
test(`top-level API`, async () => {
const result = await fetch(`${host}/api/top-level`).then(res =>
res.text()
)

expect(result).toMatchSnapshot()
})
test(`secondary-level API`, async () => {
const result = await fetch(
`${host}/api/a-directory/function`
).then(res => res.text())
expect(result).toMatchSnapshot()
})
test(`secondary-level API`, async () => {
const result = await fetch(`${host}/api/a-directory/function`).then(res =>
res.text()
)

expect(result).toMatchSnapshot()
})
test(`secondary-level API with index.js`, async () => {
const result = await fetch(`${host}/api/a-directory`).then(res =>
res.text()
)
expect(result).toMatchSnapshot()
})
test(`secondary-level API with index.js`, async () => {
const result = await fetch(`${host}/api/a-directory`).then(res =>
res.text()
)

expect(result).toMatchSnapshot()
})
test(`secondary-level API`, async () => {
const result = await fetch(`${host}/api/dir/function`).then(res =>
res.text()
)
expect(result).toMatchSnapshot()
})
test(`secondary-level API`, async () => {
const result = await fetch(`${host}/api/dir/function`).then(res =>
res.text()
)

expect(result).toMatchSnapshot()
})

test(`routes with special characters`, async () => {
const routes = [
`${host}/api/I-Am-Capitalized`,
`${host}/api/some whitespace`,
`${host}/api/with-äöü-umlaut`,
`${host}/api/some-àè-french`,
encodeURI(`${host}/api/some-אודות`),
]

for (const route of routes) {
const result = await fetch(route).then(res => res.text())

expect(result).toMatchSnapshot()
})
test(`routes with special characters`, async () => {
const routes = [
`${host}/api/I-Am-Capitalized`,
`${host}/api/some whitespace`,
`${host}/api/with-äöü-umlaut`,
`${host}/api/some-àè-french`,
encodeURI(`${host}/api/some-אודות`),
]
}
})

describe(`dynamic routes`, () => {
test(`param routes`, async () => {
const routes = [`${host}/api/users/23/additional`]

for (const route of routes) {
const result = await fetch(route).then(res => res.text())
const result = await fetch(route).then(res => res.json())

expect(result).toMatchSnapshot()
}
})

test(`dynamic routes`, async () => {
const routes = [
`${host}/api/users/23/additional`,
`${host}/api/dir/super`,
]

test(`unnamed wildcard routes`, async () => {
const routes = [`${host}/api/dir/super`]
for (const route of routes) {
const result = await fetch(route).then(res => res.json())

expect(result).toMatchSnapshot()
}
})
test(`named wildcard routes`, async () => {
const route = `${host}/api/named-wildcard/super`
const result = await fetch(route).then(res => res.json())

expect(result).toMatchInlineSnapshot(`
Object {
"foo": "super",
}
`)
})
})

describe(`environment variables`, () => {
Expand Down
1 change: 0 additions & 1 deletion packages/gatsby/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@
"opentracing": "^0.14.4",
"p-defer": "^3.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "0.1.7",
"physical-cpu-count": "^2.0.0",
"platform": "^1.3.6",
"postcss": "^8.3.5",
Expand Down
38 changes: 13 additions & 25 deletions packages/gatsby/src/commands/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { match as reachMatch } from "@gatsbyjs/reach-router/lib/utils"
import onExit from "signal-exit"
import report from "gatsby-cli/lib/reporter"
import multer from "multer"
import pathToRegexp from "path-to-regexp"
import cookie from "cookie"
import telemetry from "gatsby-telemetry"

Expand All @@ -25,14 +24,6 @@ interface IMatchPath {
matchPath: string
}

interface IPathToRegexpKey {
name: string | number
prefix: string
suffix: string
pattern: string
modifier: string
}

interface IServeProgram extends IProgram {
prefixPaths: boolean
}
Expand Down Expand Up @@ -169,25 +160,22 @@ module.exports = async (program: IServeProgram): Promise<void> => {
// Check if there's any matchPaths that match.
// We loop until we find the first match.
functions.some(f => {
let exp
const keys: Array<IPathToRegexpKey> = []
if (f.matchPath) {
exp = pathToRegexp(f.matchPath, keys)
}
if (exp && exp.exec(pathFragment) !== null) {
functionObj = f
// @ts-ignore - TS bug? https://stackoverflow.com/questions/50234481/typescript-2-8-3-type-must-have-a-symbol-iterator-method-that-returns-an-iterato
const matches = [...pathFragment.match(exp)].slice(1)
const newParams = {}
matches.forEach(
(match, index) => (newParams[keys[index].name] = match)
)
req.params = newParams
const matchResult = reachMatch(f.matchPath, pathFragment)
if (matchResult) {
req.params = matchResult.params
if (req.params[`*`]) {
// Backwards compatability for v3
// TODO remove in v5
req.params[`0`] = req.params[`*`]
}
functionObj = f

return true
} else {
return false
return true
}
}

return false
})
}

Expand Down
40 changes: 14 additions & 26 deletions packages/gatsby/src/internal-plugins/functions/gatsby-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import { CreateDevServerArgs, ParentSpanPluginArgs } from "gatsby"
import formatWebpackMessages from "react-dev-utils/formatWebpackMessages"
import dotenv from "dotenv"
import chokidar from "chokidar"
// We use an ancient version of path-to-regexp as it has breaking changes to express v4
// see: https://github.com/pillarjs/path-to-regexp/tree/77df63869075cfa5feda1988642080162c584427#compatibility-with-express--4x
import pathToRegexp from "path-to-regexp"
import { match } from "@gatsbyjs/reach-router/lib/utils"
import cookie from "cookie"
import { reportWebpackWarnings } from "../../utils/webpack-error-utils"
import { internalActions } from "../../redux/actions"
Expand All @@ -29,14 +27,6 @@ interface IGlobPattern {
globPattern: string
}

interface IPathToRegexpKey {
name: string | number
prefix: string
suffix: string
pattern: string
modifier: string
}

// During development, we lazily compile functions only when they're requested.
// Here we keep track of which functions have been requested so are "active"
const activeDevelopmentFunctions = new Set<IGatsbyFunction>()
Expand Down Expand Up @@ -496,24 +486,22 @@ export async function onCreateDevServer({
// Check if there's any matchPaths that match.
// We loop until we find the first match.
functions.some(f => {
let exp
const keys: Array<IPathToRegexpKey> = []
if (f.matchPath) {
exp = pathToRegexp(f.matchPath, keys)
const matchResult = match(f.matchPath, pathFragment)
if (matchResult) {
req.params = matchResult.params
if (req.params[`*`]) {
// Backwards compatability for v3
// TODO remove in v5
req.params[`0`] = req.params[`*`]
}
functionObj = f

return true
}
}
if (exp && exp.exec(pathFragment) !== null) {
functionObj = f
const matches = [...pathFragment.match(exp)].slice(1)
const newParams = {}
matches.forEach(
(match, index) => (newParams[keys[index].name] = match)
)
req.params = newParams

return true
} else {
return false
}
return false
})
}

Expand Down

0 comments on commit 018c041

Please sign in to comment.