Skip to content
This repository has been archived by the owner on May 22, 2024. It is now read-only.

Commit

Permalink
feat: add preferStatic to ISC (#1648)
Browse files Browse the repository at this point in the history
  • Loading branch information
eduardoboucas authored Nov 7, 2023
1 parent 97992ee commit d70673a
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 9 deletions.
7 changes: 6 additions & 1 deletion src/runtimes/node/in_source_config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,12 @@ export const parseSource = (
result.methods = normalizeMethods(configExport.method, functionName)
}

result.routes = getRoutes(configExport.path, functionName, result.methods ?? [])
result.routes = getRoutes({
functionName,
methods: result.methods ?? [],
path: configExport.path,
preferStatic: configExport.preferStatic === true,
})

return result
}
Expand Down
47 changes: 39 additions & 8 deletions src/utils/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { FunctionBundlingUserError } from './error.js'
import { nonNullable } from './non_nullable.js'
import { ExtendedURLPattern } from './urlpattern.js'

export type Route = { pattern: string; methods: string[] } & ({ literal: string } | { expression: string })
export type Route = { pattern: string; methods: string[]; prefer_static?: boolean } & (
| { literal: string }
| { expression: string }
)

// Based on https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API.
const isExpression = (part: string) =>
Expand All @@ -18,11 +21,18 @@ const isPathLiteral = (path: string) => {
return parts.every((part) => !isExpression(part))
}

interface GetRouteOption {
functionName: string
methods: string[]
path: unknown
preferStatic: boolean
}

/**
* Takes an element from a `path` declaration and returns a Route element that
* represents it.
*/
const getRoute = (path: unknown, functionName: string, methods: string[]): Route | undefined => {
const getRoute = ({ functionName, methods, path, preferStatic }: GetRouteOption): Route | undefined => {
if (typeof path !== 'string') {
throw new FunctionBundlingUserError(`'path' property must be a string, found '${JSON.stringify(path)}'`, {
functionName,
Expand All @@ -38,7 +48,7 @@ const getRoute = (path: unknown, functionName: string, methods: string[]): Route
}

if (isPathLiteral(path)) {
return { pattern: path, literal: path, methods }
return { pattern: path, literal: path, methods, prefer_static: preferStatic || undefined }
}

try {
Expand All @@ -53,7 +63,7 @@ const getRoute = (path: unknown, functionName: string, methods: string[]): Route
// for both `/foo` and `/foo/`.
const normalizedRegex = `^${regex}\\/?$`

return { pattern: path, expression: normalizedRegex, methods }
return { pattern: path, expression: normalizedRegex, methods, prefer_static: preferStatic || undefined }
} catch {
throw new FunctionBundlingUserError(`'${path}' is not a valid path according to the URLPattern specification`, {
functionName,
Expand All @@ -62,17 +72,38 @@ const getRoute = (path: unknown, functionName: string, methods: string[]): Route
}
}

interface GetRoutesOptions {
functionName: string
methods: string[]
path: unknown
preferStatic?: boolean
}

/**
* Takes a `path` declaration, normalizes it into an array, and processes the
* individual elements to obtain an array of `Route` expressions.
*/
export const getRoutes = (input: unknown, functionName: string, methods: string[]): Route[] => {
if (!input) {
export const getRoutes = ({
functionName,
methods,
path: pathOrPaths,
preferStatic = false,
}: GetRoutesOptions): Route[] => {
if (!pathOrPaths) {
return []
}

const paths = [...new Set(Array.isArray(input) ? input : [input])]
const routes = paths.map((path) => getRoute(path, functionName, methods ?? [])).filter(nonNullable)
const paths = [...new Set(Array.isArray(pathOrPaths) ? pathOrPaths : [pathOrPaths])]
const routes = paths
.map((path) =>
getRoute({
functionName,
methods,
path,
preferStatic,
}),
)
.filter(nonNullable)

return routes
}
64 changes: 64 additions & 0 deletions tests/unit/runtimes/node/in_source_config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -593,4 +593,68 @@ describe('V2 API', () => {
expect(routes).toEqual([{ pattern: '/products', literal: '/products', methods: [] }])
})
})

describe('`preferStatic` property', () => {
test('Sets a `prefer_static` property on a single route', () => {
const source = `export default async () => {
return new Response("Hello!")
}
export const config = {
path: "/products",
preferStatic: true
}`

const { routes } = parseSource(source, options)

expect(routes).toEqual([{ pattern: '/products', literal: '/products', methods: [], prefer_static: true }])
})

test('Sets a `prefer_static` property on all routes', () => {
const source = `export default async () => {
return new Response("Hello!")
}
export const config = {
path: ["/items", "/products"],
preferStatic: true
}`

const { routes } = parseSource(source, options)

expect(routes).toEqual([
{ pattern: '/items', literal: '/items', methods: [], prefer_static: true },
{ pattern: '/products', literal: '/products', methods: [], prefer_static: true },
])
})

test('Does not set a `prefer_static` property if `preferStatic` is not a boolean', () => {
const source = `export default async () => {
return new Response("Hello!")
}
export const config = {
path: "/products",
preferStatic: "yep"
}`

const { routes } = parseSource(source, options)

expect(routes).toEqual([{ pattern: '/products', literal: '/products', methods: [] }])
})

test('Does not set a `prefer_static` property if `preferStatic` is not set', () => {
const source = `export default async () => {
return new Response("Hello!")
}
export const config = {
path: "/products"
}`

const { routes } = parseSource(source, options)

expect(routes).toEqual([{ pattern: '/products', literal: '/products', methods: [] }])
})
})
})

1 comment on commit d70673a

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⏱ Benchmark results

  • largeDepsEsbuild: 1.5s
  • largeDepsNft: 5.5s
  • largeDepsZisi: 10.4s

Please sign in to comment.