-
-
Notifications
You must be signed in to change notification settings - Fork 886
feat(vercel): allow overriding function config by route #4124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
da1808e
24d92dc
ba05eb0
2758d9b
af5775a
0b8d4c5
a904f13
88c1972
ac313e1
1dd21d5
fba759c
90d8bb6
beb380e
54892f1
8c23741
5355a5b
c55c0e2
64492d9
0ad3564
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ import { defu } from "defu"; | |
| import { writeFile } from "../_utils/fs.ts"; | ||
| import type { Nitro, NitroRouteRules } from "nitro/types"; | ||
| import { dirname, relative, resolve } from "pathe"; | ||
| import { createRouter, addRoute, findRoute } from "rou3"; | ||
| import { joinURL, withLeadingSlash, withoutLeadingSlash } from "ufo"; | ||
| import type { | ||
| PrerenderFunctionConfig, | ||
|
|
@@ -48,15 +49,26 @@ export async function generateFunctionFiles(nitro: Nitro) { | |
| const buildConfig = generateBuildConfig(nitro, o11Routes); | ||
| await writeFile(buildConfigPath, JSON.stringify(buildConfig, null, 2)); | ||
|
|
||
| const functionConfigPath = resolve(nitro.options.output.serverDir, ".vc-config.json"); | ||
| const functionConfig: VercelServerlessFunctionConfig = { | ||
| const baseFunctionConfig: VercelServerlessFunctionConfig = { | ||
| handler: "index.mjs", | ||
| launcherType: "Nodejs", | ||
| shouldAddHelpers: false, | ||
| supportsResponseStreaming: true, | ||
| ...nitro.options.vercel?.functions, | ||
| }; | ||
| await writeFile(functionConfigPath, JSON.stringify(functionConfig, null, 2)); | ||
| const functionConfigPath = resolve(nitro.options.output.serverDir, ".vc-config.json"); | ||
| await writeFile(functionConfigPath, JSON.stringify(baseFunctionConfig, null, 2)); | ||
|
|
||
| // Build rou3 router for routeFunctionConfig matching | ||
| const routeFunctionConfig = nitro.options.vercel?.routeFunctionConfig; | ||
| const hasRouteFunctionConfig = routeFunctionConfig && Object.keys(routeFunctionConfig).length > 0; | ||
| let routeFuncRouter: ReturnType<typeof createRouter<VercelServerlessFunctionConfig>> | undefined; | ||
| if (hasRouteFunctionConfig) { | ||
| routeFuncRouter = createRouter<VercelServerlessFunctionConfig>(); | ||
| for (const [pattern, overrides] of Object.entries(routeFunctionConfig)) { | ||
| addRoute(routeFuncRouter, "", pattern, overrides); | ||
| } | ||
| } | ||
|
|
||
| // Write ISR functions | ||
| for (const [key, value] of Object.entries(nitro.options.routeRules)) { | ||
|
|
@@ -70,18 +82,49 @@ export async function generateFunctionFiles(nitro: Nitro) { | |
| normalizeRouteDest(key) + ISR_SUFFIX | ||
| ); | ||
| await fsp.mkdir(dirname(funcPrefix), { recursive: true }); | ||
| await fsp.symlink( | ||
| "./" + relative(dirname(funcPrefix), nitro.options.output.serverDir), | ||
| funcPrefix + ".func", | ||
| "junction" | ||
| ); | ||
|
|
||
| const match = routeFuncRouter && findRoute(routeFuncRouter, "", key); | ||
| if (match) { | ||
| await createFunctionDirWithCustomConfig( | ||
| funcPrefix + ".func", | ||
| nitro.options.output.serverDir, | ||
| baseFunctionConfig, | ||
| match.data | ||
| ); | ||
| } else { | ||
| await fsp.symlink( | ||
| "./" + relative(dirname(funcPrefix), nitro.options.output.serverDir), | ||
| funcPrefix + ".func", | ||
| "junction" | ||
| ); | ||
| } | ||
|
|
||
| await writePrerenderConfig( | ||
| funcPrefix + ".prerender-config.json", | ||
| value.isr, | ||
| nitro.options.vercel?.config?.bypassToken | ||
| ); | ||
| } | ||
|
|
||
| // Write routeFunctionConfig custom function directories | ||
| const createdFuncDirs = new Set<string>(); | ||
| if (hasRouteFunctionConfig) { | ||
| for (const [pattern, overrides] of Object.entries(routeFunctionConfig!)) { | ||
| const funcDir = resolve( | ||
| nitro.options.output.serverDir, | ||
| "..", | ||
| normalizeRouteDest(pattern) + ".func" | ||
| ); | ||
| await createFunctionDirWithCustomConfig( | ||
| funcDir, | ||
| nitro.options.output.serverDir, | ||
| baseFunctionConfig, | ||
| overrides | ||
| ); | ||
| createdFuncDirs.add(funcDir); | ||
| } | ||
| } | ||
|
|
||
| // Write observability routes | ||
| if (o11Routes.length === 0) { | ||
| return; | ||
|
|
@@ -94,12 +137,29 @@ export async function generateFunctionFiles(nitro: Nitro) { | |
| continue; // #3563 | ||
| } | ||
| const funcPrefix = resolve(nitro.options.output.serverDir, "..", route.dest); | ||
| await fsp.mkdir(dirname(funcPrefix), { recursive: true }); | ||
| await fsp.symlink( | ||
| "./" + relative(dirname(funcPrefix), nitro.options.output.serverDir), | ||
| funcPrefix + ".func", | ||
| "junction" | ||
| ); | ||
| const funcDir = funcPrefix + ".func"; | ||
|
|
||
| // Skip if already created by routeFunctionConfig | ||
| if (createdFuncDirs.has(funcDir)) { | ||
| continue; | ||
| } | ||
|
|
||
| const match = routeFuncRouter && findRoute(routeFuncRouter, "", route.src); | ||
| if (match) { | ||
| await createFunctionDirWithCustomConfig( | ||
| funcDir, | ||
| nitro.options.output.serverDir, | ||
| baseFunctionConfig, | ||
| match.data | ||
| ); | ||
| } else { | ||
| await fsp.mkdir(dirname(funcPrefix), { recursive: true }); | ||
| await fsp.symlink( | ||
| "./" + relative(dirname(funcPrefix), nitro.options.output.serverDir), | ||
| funcDir, | ||
| "junction" | ||
| ); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -273,6 +333,13 @@ function generateBuildConfig(nitro: Nitro, o11Routes?: ObservabilityRoute[]) { | |
| ), | ||
| }; | ||
| }), | ||
| // Route function config routes | ||
| ...(nitro.options.vercel?.routeFunctionConfig | ||
| ? Object.keys(nitro.options.vercel.routeFunctionConfig).map((pattern) => ({ | ||
| src: normalizeRouteSrc(pattern), | ||
| dest: withLeadingSlash(normalizeRouteDest(pattern)), | ||
| })) | ||
| : []), | ||
|
Comment on lines
+362
to
+368
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Route-function routes should be specificity-ordered and baseURL-aware. Current emission order depends on object key insertion, so wildcard patterns can shadow specific ones. Also, these π‘ Proposed fix+ const routeFunctionPatterns = nitro.options.vercel?.routeFunctionConfig
+ ? Object.keys(nitro.options.vercel.routeFunctionConfig).sort(
+ (a, b) => b.split(/\/(?!\*)/).length - a.split(/\/(?!\*)/).length
+ )
+ : [];
+
config.routes!.push(
@@
- ...(nitro.options.vercel?.routeFunctionConfig
- ? Object.keys(nitro.options.vercel.routeFunctionConfig).map((pattern) => ({
- src: normalizeRouteSrc(pattern),
+ ...routeFunctionPatterns.map((pattern) => ({
+ src: joinURL(nitro.options.baseURL, normalizeRouteSrc(pattern)),
dest: withLeadingSlash(normalizeRouteDest(pattern)),
- }))
- : []),
+ })),π€ Prompt for AI Agents |
||
| // Observability routes | ||
| ...(o11Routes || []).map((route) => ({ | ||
| src: joinURL(nitro.options.baseURL, route.src), | ||
|
|
@@ -512,6 +579,25 @@ function normalizeRouteDest(route: string) { | |
| ); | ||
| } | ||
|
|
||
| async function createFunctionDirWithCustomConfig( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you tried hardlinks?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Briefly but had complexities with symlinks in node_modules etc. since only files can be hard linked it seems. I don't think many functions would have overrides and copying reduces a lot of complexity. I may revisit getting symlinks working in Vercel CI later again though. |
||
| funcDir: string, | ||
| serverDir: string, | ||
| baseFunctionConfig: VercelServerlessFunctionConfig, | ||
| overrides: VercelServerlessFunctionConfig | ||
| ) { | ||
| await fsp.mkdir(funcDir, { recursive: true }); | ||
| const entries = await fsp.readdir(serverDir); | ||
| for (const entry of entries) { | ||
| if (entry === ".vc-config.json") { | ||
| continue; | ||
| } | ||
| const target = "./" + relative(funcDir, resolve(serverDir, entry)); | ||
| await fsp.symlink(target, resolve(funcDir, entry), "junction"); | ||
| } | ||
| const mergedConfig = defu(overrides, baseFunctionConfig); | ||
| await writeFile(resolve(funcDir, ".vc-config.json"), JSON.stringify(mergedConfig, null, 2)); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| async function writePrerenderConfig( | ||
| filename: string, | ||
| isrConfig: NitroRouteRules["isr"], | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.