From c3c347cb992fc0be8b20f7fc2af02d9940d15c68 Mon Sep 17 00:00:00 2001 From: PR Replica Date: Sat, 1 Jan 2000 00:00:00 +0000 Subject: [PATCH] fix: support optional params in Netlify split mode Source PR: https://github.com/sveltejs/kit/pull/17020 Source head: 1a9f6b8b3d34e9eb97d49c783e82b9d1fc057245 --- .changeset/fresh-pandas-smile.md | 5 +++++ packages/adapter-netlify/index.js | 11 +++++++---- .../[[optional]]/article/+page.server.js | 4 ++++ .../[[optional]]/article/+page.svelte | 6 ++++++ .../test/apps/split/test/test.js | 18 +++++++++++++++++- 5 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 .changeset/fresh-pandas-smile.md create mode 100644 packages/adapter-netlify/test/apps/split/src/routes/collection/[[optional]]/article/+page.server.js create mode 100644 packages/adapter-netlify/test/apps/split/src/routes/collection/[[optional]]/article/+page.svelte diff --git a/.changeset/fresh-pandas-smile.md b/.changeset/fresh-pandas-smile.md new file mode 100644 index 000000000000..044c9dc6e98c --- /dev/null +++ b/.changeset/fresh-pandas-smile.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/adapter-netlify': patch +--- + +fix: route requests with omitted optional parameters to split serverless functions diff --git a/packages/adapter-netlify/index.js b/packages/adapter-netlify/index.js index 6d38a543e20c..5dae8c85f644 100644 --- a/packages/adapter-netlify/index.js +++ b/packages/adapter-netlify/index.js @@ -145,12 +145,13 @@ function generate_serverless_functions({ builder, publish, split }) { // The parts should conform to URLPattern syntax // https://docs.netlify.com/build/functions/get-started/?fn-language=ts&data-tab=TypeScript#route-requests - for (const segment of route.segments) { + for (const [i, segment] of route.segments.entries()) { if (segment.rest) { parts.push('*'); } else if (segment.dynamic) { // URLPattern requires params to start with letters - parts.push(`:param${parts.length}`); + const optional = /^\[\[.+\]\]$/.test(segment.content) ? '?' : ''; + parts.push(`:param${i}${optional}`); } else { parts.push(segment.content); } @@ -159,13 +160,15 @@ function generate_serverless_functions({ builder, publish, split }) { // Netlify handles trailing slashes for us, so we don't need to include them in the pattern const pattern = `/${parts.join('/')}`; const name = - FUNCTION_PREFIX + (parts.join('-').replace(/[:.]/g, '_').replace('*', '__rest') || 'index'); + FUNCTION_PREFIX + + (parts.join('-').replace(/[:.]/g, '_').replace(/\?/g, '').replace(/\*/g, '__rest') || + 'index'); // skip routes with identical patterns, they were already folded into another function if (seen.has(pattern)) continue; const patterns = [pattern, `${pattern === '/' ? '' : pattern}/__data.json`]; - patterns.forEach((p) => seen.add(p)); + patterns.forEach((pattern) => seen.add(pattern)); // figure out which lower priority routes should be considered fallbacks for (let j = i + 1; j < builder.routes.length; j += 1) { diff --git a/packages/adapter-netlify/test/apps/split/src/routes/collection/[[optional]]/article/+page.server.js b/packages/adapter-netlify/test/apps/split/src/routes/collection/[[optional]]/article/+page.server.js new file mode 100644 index 000000000000..0e38a0aa6b4a --- /dev/null +++ b/packages/adapter-netlify/test/apps/split/src/routes/collection/[[optional]]/article/+page.server.js @@ -0,0 +1,4 @@ +/** @type {import('./$types').PageServerLoad} */ +export function load({ params }) { + return { optional: params.optional ?? null }; +} diff --git a/packages/adapter-netlify/test/apps/split/src/routes/collection/[[optional]]/article/+page.svelte b/packages/adapter-netlify/test/apps/split/src/routes/collection/[[optional]]/article/+page.svelte new file mode 100644 index 000000000000..cab0e751c806 --- /dev/null +++ b/packages/adapter-netlify/test/apps/split/src/routes/collection/[[optional]]/article/+page.svelte @@ -0,0 +1,6 @@ + + + +

optional: {data.optional ?? 'none'}

diff --git a/packages/adapter-netlify/test/apps/split/test/test.js b/packages/adapter-netlify/test/apps/split/test/test.js index d272573b03c8..52a61f05f67b 100644 --- a/packages/adapter-netlify/test/apps/split/test/test.js +++ b/packages/adapter-netlify/test/apps/split/test/test.js @@ -2,11 +2,19 @@ import fs from 'node:fs'; import path from 'node:path'; import { expect, test } from '@playwright/test'; -test('routes to routes with dynamic params', async ({ page }) => { +test('routes with dynamic params', async ({ page }) => { await page.goto('/dynamic/123'); await expect(page.locator('p')).toHaveText('id: 123'); }); +test('routes with optional params', async ({ page }) => { + await page.goto('/collection/article'); + await expect(page.locator('p')).toHaveText('optional: none'); + + await page.goto('/collection/value/article'); + await expect(page.locator('p')).toHaveText('optional: value'); +}); + test('client-side navigation fetches server load function data', async ({ page }) => { await page.goto('/dynamic'); await page.click('a'); @@ -27,6 +35,14 @@ test('split generates multiple function files', () => { const functions_dir = path.resolve(import.meta.dirname, '../.netlify/v1/functions'); const files = fs.readdirSync(functions_dir).filter((f) => f.startsWith('sveltekit-')); expect(files.length).toBeGreaterThan(1); + + const optional_route = fs.readFileSync( + path.join(functions_dir, 'sveltekit-collection-_param1-article.mjs'), + 'utf-8' + ); + expect(optional_route).toContain( + 'path: ["/collection/:param1?/article", "/collection/:param1?/article/__data.json"]' + ); }); test('_redirects are copied to publish directory', () => {