From 61a5a97f94dc6d9090af5e7147355d5d358091b7 Mon Sep 17 00:00:00 2001 From: Tobias Lins Date: Thu, 3 Apr 2025 16:31:08 +0200 Subject: [PATCH 1/7] Implement better build output using symlinks --- packages/adapter-vercel/index.js | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index d87f6946ed79..cfba13e2ce21 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -372,6 +372,47 @@ const plugin = function (defaults = {}) { }); } else if (!singular) { static_config.routes.push({ src: src + '(?:/__data.json)?$', dest: `/${name}` }); + } else { + // Create a symlink for each route to the main function + + // Use 'index' for the root route's filesystem representation + // Use an empty string ('') for the root route's destination name part in Vercel config + const isRoot = route.id === '/'; + const route_fs_name = isRoot ? 'index' : route.id.slice(1); + const route_dest_name = isRoot ? '' : route.id.slice(1); + + // Define paths using path.join for safety + const base_dir = path.join(dirs.functions, route_fs_name); // e.g., .vercel/output/functions/index + // The main symlink should be named based on the route, adjacent to its potential directory + const main_symlink_path = `${base_dir}.func`; // e.g., .vercel/output/functions/index.func + // The data symlink goes inside the directory + const data_symlink_path = path.join(base_dir, '__data.json.func'); // e.g., .vercel/output/functions/index/__data.json.func + + const target = path.join(dirs.functions, `${name}.func`); // The actual function directory e.g., .vercel/output/functions/fn.func + + // Ensure the directory for the data endpoint symlink exists (e.g., functions/index/) + builder.mkdirp(base_dir); + + // Calculate relative paths FROM the directory containing the symlink TO the target + const relative_for_main = path.relative( + path.dirname(main_symlink_path), + target, + ); + const relative_for_data = path.relative( + path.dirname(data_symlink_path), + target, + ); // This is path.relative(base_dir, target) + + // Create symlinks + fs.symlinkSync(relative_for_main, main_symlink_path); // Creates functions/index.func -> fn.func + fs.symlinkSync(relative_for_data, data_symlink_path); // Creates functions/index/__data.json.func -> ../fn.func + + // Add route to the config + static_config.routes.push({ + src: src + '(?:/__data.json)?$', // Matches the incoming request path + dest: `/${route_dest_name}`, // Maps to the function: '/' for root, '/about' for about, etc. + // Vercel uses this dest to find the corresponding .func dir/symlink + }); } } From 3d2f4e2d739d3a28cf2a6706659008a659a1ee9b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 4 Apr 2025 09:37:31 -0400 Subject: [PATCH 2/7] snake_case --- packages/adapter-vercel/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index cfba13e2ce21..fae961be96ef 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -377,9 +377,9 @@ const plugin = function (defaults = {}) { // Use 'index' for the root route's filesystem representation // Use an empty string ('') for the root route's destination name part in Vercel config - const isRoot = route.id === '/'; - const route_fs_name = isRoot ? 'index' : route.id.slice(1); - const route_dest_name = isRoot ? '' : route.id.slice(1); + const is_root = route.id === '/'; + const route_fs_name = is_root ? 'index' : route.id.slice(1); + const route_dest_name = is_root ? '' : route.id.slice(1); // Define paths using path.join for safety const base_dir = path.join(dirs.functions, route_fs_name); // e.g., .vercel/output/functions/index From 134a00628aa0b03f7797bb53aca882225b6b06d0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 4 Apr 2025 09:37:38 -0400 Subject: [PATCH 3/7] prettier --- packages/adapter-vercel/index.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index fae961be96ef..b63be093d025 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -394,14 +394,8 @@ const plugin = function (defaults = {}) { builder.mkdirp(base_dir); // Calculate relative paths FROM the directory containing the symlink TO the target - const relative_for_main = path.relative( - path.dirname(main_symlink_path), - target, - ); - const relative_for_data = path.relative( - path.dirname(data_symlink_path), - target, - ); // This is path.relative(base_dir, target) + const relative_for_main = path.relative(path.dirname(main_symlink_path), target); + const relative_for_data = path.relative(path.dirname(data_symlink_path), target); // This is path.relative(base_dir, target) // Create symlinks fs.symlinkSync(relative_for_main, main_symlink_path); // Creates functions/index.func -> fn.func @@ -410,7 +404,7 @@ const plugin = function (defaults = {}) { // Add route to the config static_config.routes.push({ src: src + '(?:/__data.json)?$', // Matches the incoming request path - dest: `/${route_dest_name}`, // Maps to the function: '/' for root, '/about' for about, etc. + dest: `/${route_dest_name}` // Maps to the function: '/' for root, '/about' for about, etc. // Vercel uses this dest to find the corresponding .func dir/symlink }); } From 75e2170be22608d76daf477276c7f86ad3a556b1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 4 Apr 2025 09:38:16 -0400 Subject: [PATCH 4/7] explanatory comment --- packages/adapter-vercel/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index b63be093d025..390f44c7aae4 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -373,7 +373,8 @@ const plugin = function (defaults = {}) { } else if (!singular) { static_config.routes.push({ src: src + '(?:/__data.json)?$', dest: `/${name}` }); } else { - // Create a symlink for each route to the main function + // Create a symlink for each route to the main function for better observability + // (without this, every request appears to go through `/fn` or `fn-{n}`) // Use 'index' for the root route's filesystem representation // Use an empty string ('') for the root route's destination name part in Vercel config From 122c67704de74019b325142fce0e9b0c089d9788 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 4 Apr 2025 10:21:00 -0400 Subject: [PATCH 5/7] generate symlinks when app uses function splitting --- packages/adapter-vercel/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index 390f44c7aae4..2a22d67821fc 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -370,8 +370,6 @@ const plugin = function (defaults = {}) { src: src + '/__data.json$', dest: `/${isr_name}/__data.json${q}` }); - } else if (!singular) { - static_config.routes.push({ src: src + '(?:/__data.json)?$', dest: `/${name}` }); } else { // Create a symlink for each route to the main function for better observability // (without this, every request appears to go through `/fn` or `fn-{n}`) From 280b6711dc415ad7db23128d9cbb945983bbcd7e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 4 Apr 2025 11:21:07 -0400 Subject: [PATCH 6/7] improve internal function naming and eliminate conflicts --- packages/adapter-vercel/index.js | 46 ++++++++++++++++---------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index 2a22d67821fc..257383302658 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -8,7 +8,7 @@ import { get_pathname, pattern_to_src } from './utils.js'; import { VERSION } from '@sveltejs/kit'; const name = '@sveltejs/adapter-vercel'; -const DEFAULT_FUNCTION_NAME = 'fn'; +const INTERNAL = '![-]'; // this name is guaranteed not to conflict with user routes const get_default_runtime = () => { const major = Number(process.version.slice(1).split('.')[0]); @@ -319,7 +319,7 @@ const plugin = function (defaults = {}) { group.config.runtime === 'edge' ? generate_edge_function : generate_serverless_function; // generate one function for the group - const name = singular ? DEFAULT_FUNCTION_NAME : `fn-${group.i}`; + const name = singular ? `${INTERNAL}/catchall` : `${INTERNAL}/${group.i}`; await generate_function( name, @@ -332,12 +332,27 @@ const plugin = function (defaults = {}) { } } + if (!singular) { + // we need to create a catch-all route so that 404s are handled + // by SvelteKit rather than Vercel + + const runtime = defaults.runtime ?? get_default_runtime(); + const generate_function = + runtime === 'edge' ? generate_edge_function : generate_serverless_function; + + await generate_function( + `${INTERNAL}/catchall`, + /** @type {any} */ ({ runtime, ...defaults }), + [] + ); + } + for (const route of builder.routes) { if (is_prerendered(route)) continue; const pattern = route.pattern.toString(); const src = pattern_to_src(pattern); - const name = functions.get(pattern) ?? 'fn-0'; + const name = functions.get(pattern); const isr = isr_config.get(route); if (isr) { @@ -372,7 +387,7 @@ const plugin = function (defaults = {}) { }); } else { // Create a symlink for each route to the main function for better observability - // (without this, every request appears to go through `/fn` or `fn-{n}`) + // (without this, every request appears to go through `/![-]`) // Use 'index' for the root route's filesystem representation // Use an empty string ('') for the root route's destination name part in Vercel config @@ -387,7 +402,7 @@ const plugin = function (defaults = {}) { // The data symlink goes inside the directory const data_symlink_path = path.join(base_dir, '__data.json.func'); // e.g., .vercel/output/functions/index/__data.json.func - const target = path.join(dirs.functions, `${name}.func`); // The actual function directory e.g., .vercel/output/functions/fn.func + const target = path.join(dirs.functions, `${name}.func`); // The actual function directory e.g., .vercel/output/functions/![-].func // Ensure the directory for the data endpoint symlink exists (e.g., functions/index/) builder.mkdirp(base_dir); @@ -397,8 +412,8 @@ const plugin = function (defaults = {}) { const relative_for_data = path.relative(path.dirname(data_symlink_path), target); // This is path.relative(base_dir, target) // Create symlinks - fs.symlinkSync(relative_for_main, main_symlink_path); // Creates functions/index.func -> fn.func - fs.symlinkSync(relative_for_data, data_symlink_path); // Creates functions/index/__data.json.func -> ../fn.func + fs.symlinkSync(relative_for_main, main_symlink_path); // Creates functions/index.func -> ![-].func + fs.symlinkSync(relative_for_data, data_symlink_path); // Creates functions/index/__data.json.func -> ../![-].func // Add route to the config static_config.routes.push({ @@ -409,21 +424,6 @@ const plugin = function (defaults = {}) { } } - if (!singular) { - // we need to create a catch-all route so that 404s are handled - // by SvelteKit rather than Vercel - - const runtime = defaults.runtime ?? get_default_runtime(); - const generate_function = - runtime === 'edge' ? generate_edge_function : generate_serverless_function; - - await generate_function( - DEFAULT_FUNCTION_NAME, - /** @type {any} */ ({ runtime, ...defaults }), - [] - ); - } - // optional chaining to support older versions that don't have this setting yet if (builder.config.kit.router?.resolution === 'server') { // Create a separate edge function just for server-side route resolution. @@ -446,7 +446,7 @@ const plugin = function (defaults = {}) { // Catch-all route must come at the end, otherwise it will swallow all other routes, // including ISR aliases if there is only one function - static_config.routes.push({ src: '/.*', dest: `/${DEFAULT_FUNCTION_NAME}` }); + static_config.routes.push({ src: '/.*', dest: `/${INTERNAL}/catchall` }); builder.log.minor('Writing routes...'); From 57064d8668808adb5540923869d223c8a02ff772 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 4 Apr 2025 11:59:04 -0400 Subject: [PATCH 7/7] changeset --- .changeset/sour-moles-heal.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sour-moles-heal.md diff --git a/.changeset/sour-moles-heal.md b/.changeset/sour-moles-heal.md new file mode 100644 index 000000000000..bee630913f01 --- /dev/null +++ b/.changeset/sour-moles-heal.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/adapter-vercel': minor +--- + +feat: create symlink functions for each route, for better observability