diff --git a/.changeset/hot-guests-enjoy.md b/.changeset/hot-guests-enjoy.md new file mode 100644 index 000000000000..97a4cf11457b --- /dev/null +++ b/.changeset/hot-guests-enjoy.md @@ -0,0 +1,6 @@ +--- +"@sveltejs/adapter-netlify": major +"@sveltejs/adapter-vercel": major +--- + +fix: run `reroute` in an edge middleware if the app has been split into multiple functions diff --git a/.changeset/modern-dogs-tie.md b/.changeset/modern-dogs-tie.md new file mode 100644 index 000000000000..e754970c0560 --- /dev/null +++ b/.changeset/modern-dogs-tie.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': minor +--- + +feat: add `applyReroute` and `builder.getReroutePath` helpers for running `reroute` in a middleware before the main handler diff --git a/documentation/docs/25-build-and-deploy/80-adapter-netlify.md b/documentation/docs/25-build-and-deploy/80-adapter-netlify.md index 78bdeaefa1fb..ac9fa5076ec6 100644 --- a/documentation/docs/25-build-and-deploy/80-adapter-netlify.md +++ b/documentation/docs/25-build-and-deploy/80-adapter-netlify.md @@ -120,6 +120,12 @@ Additionally, you can add your own Netlify functions by creating a directory for directory = "functions" ``` +## Notes + +### Individual functions and `reroute` + +If the `split` option is set to `true` in the adapter config, the [`reroute`](hooks#Universal-hooks-reroute) function will be deployed as an edge middleware that runs before any individual function. + ## Troubleshooting ### Accessing the file system diff --git a/documentation/docs/25-build-and-deploy/90-adapter-vercel.md b/documentation/docs/25-build-and-deploy/90-adapter-vercel.md index 50f25f4a9299..088005f40194 100644 --- a/documentation/docs/25-build-and-deploy/90-adapter-vercel.md +++ b/documentation/docs/25-build-and-deploy/90-adapter-vercel.md @@ -189,6 +189,10 @@ If you have Vercel functions contained in the `api` directory at the project's r Projects created before a certain date may default to using an older Node version than what SvelteKit currently requires. You can [change the Node version in your project settings](https://vercel.com/docs/concepts/functions/serverless-functions/runtimes/node-js#node.js-version). +### Individual functions and `reroute` + +If the `split` option is set to `true` for a route, or at the adapter level, the [`reroute`](hooks#Universal-hooks-reroute) function will be deployed as an edge middleware that runs before any individual function. + ## Troubleshooting ### Accessing the file system diff --git a/documentation/docs/98-reference/15-@sveltejs-kit-adapter.md b/documentation/docs/98-reference/15-@sveltejs-kit-adapter.md new file mode 100644 index 000000000000..8c3c72df9319 --- /dev/null +++ b/documentation/docs/98-reference/15-@sveltejs-kit-adapter.md @@ -0,0 +1,5 @@ +--- +title: @sveltejs/kit/adapter +--- + +> MODULE: @sveltejs/kit/adapter diff --git a/eslint.config.js b/eslint.config.js index f1754ae9d22a..dc2cf2594401 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -52,6 +52,7 @@ export default [ }, ignores: [ 'packages/adapter-cloudflare/test/apps/**/*', + 'packages/adapter-netlify/test/preview.js', 'packages/adapter-netlify/test/apps/**/*', 'packages/adapter-node/rollup.config.js', 'packages/adapter-node/tests/smoke.spec_disabled.js', @@ -60,8 +61,7 @@ export default [ 'packages/kit/test/apps/**/*', 'packages/kit/test/build-errors/**/*', 'packages/kit/test/prerendering/**/*', - 'packages/test-redirect-importer/index.js', - 'packages/adapter-netlify/test/preview.js' + 'packages/test-redirect-importer/index.js' ] } ]; diff --git a/package.json b/package.json index 84b61bf6bb57..52fb7660db1a 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "rolldown", "sharp", "svelte-preprocess", - "unix-dgram", "workerd" ] } diff --git a/packages/adapter-netlify/index.js b/packages/adapter-netlify/index.js index 955a323f5c21..6ee8f6a4d961 100644 --- a/packages/adapter-netlify/index.js +++ b/packages/adapter-netlify/index.js @@ -97,14 +97,24 @@ export default function ({ split = false, edge = edge_set_in_env_var } = {}) { `\n\n/${builder.getAppPath()}/immutable/*\n cache-control: public\n cache-control: immutable\n cache-control: max-age=31536000\n` ); + let reroute_middleware = false; + if (edge) { if (split) { throw new Error('Cannot use `split: true` alongside `edge: true`'); } - await generate_edge_functions({ builder }); + await generate_edge_functions({ builder, reroute_middleware }); } else { - generate_lambda_functions({ builder, split, publish }); + /** @type {string | void} */ + let reroute_path; + + if (split && (reroute_path = await builder.getReroutePath?.())) { + await generate_reroute_middleware({ builder, reroute_path }); + reroute_middleware = true; + } + + generate_lambda_functions({ builder, split, publish, reroute_middleware }); } }, @@ -123,11 +133,13 @@ export default function ({ split = false, edge = edge_set_in_env_var } = {}) { } }; } + /** - * @param { object } params + * @param {object} params * @param {Builder2_4_0} params.builder + * @param {boolean} params.reroute_middleware */ -async function generate_edge_functions({ builder }) { +async function generate_edge_functions({ builder, reroute_middleware }) { const tmp = builder.getBuildDirectory('netlify-tmp'); builder.rimraf(tmp); builder.mkdirp(tmp); @@ -135,6 +147,7 @@ async function generate_edge_functions({ builder }) { builder.mkdirp('.netlify/edge-functions'); builder.log.minor('Generating Edge Function...'); + const relativePath = posix.relative(tmp, builder.getServerDirectory()); builder.copy(`${files}/edge.js`, `${tmp}/entry.js`, { @@ -144,49 +157,48 @@ async function generate_edge_functions({ builder }) { } }); - const manifest = builder.generateManifest({ - relativePath - }); + await bundle_edge_function({ builder, name: 'render', reroute_middleware }); +} - writeFileSync(`${tmp}/manifest.js`, `export const manifest = ${manifest};\n`); +/** + * @param {object} params + * @param {Builder2_4_0} params.builder + * @param {string} params.reroute_path + */ +async function generate_reroute_middleware({ builder, reroute_path }) { + builder.log.minor('Generating edge middleware to run reroute before split functions...'); - /** @type {{ assets: Set }} */ - // we have to prepend the file:// protocol because Windows doesn't support absolute path imports - const { assets } = (await import(`file://${tmp}/manifest.js`)).manifest; + const tmp = builder.getBuildDirectory('netlify-tmp'); + builder.rimraf(tmp); + builder.mkdirp(tmp); - const path = '/*'; - // We only need to specify paths without the trailing slash because - // Netlify will handle the optional trailing slash for us - const excluded = [ - // Contains static files - `/${builder.getAppPath()}/immutable/*`, - `/${builder.getAppPath()}/version.json`, - ...builder.prerendered.paths, - ...Array.from(assets).flatMap((asset) => { - if (asset.endsWith('/index.html')) { - const dir = asset.replace(/\/index\.html$/, ''); - return [ - `${builder.config.kit.paths.base}/${asset}`, - `${builder.config.kit.paths.base}/${dir}` - ]; - } - return `${builder.config.kit.paths.base}/${asset}`; - }), - // Should not be served by SvelteKit at all - '/.netlify/*' - ]; + builder.mkdirp('.netlify/edge-functions'); - /** @type {import('@netlify/edge-functions').Manifest} */ - const edge_manifest = { - functions: [ - { - function: 'render', - path, - excludedPath: /** @type {`/${string}`[]} */ (excluded) - } - ], - version: 1 - }; + builder.copy(`${files}/reroute.js`, `${tmp}/entry.js`, { + replace: { + __HOOKS__: reroute_path + } + }); + + await bundle_edge_function({ builder, name: 'reroute', reroute_middleware: false }); +} + +/** + * + * @param {object} params + * @param {Builder2_4_0} params.builder + * @param {string} params.name + * @param {boolean} params.reroute_middleware + */ +async function bundle_edge_function({ builder, name, reroute_middleware }) { + const tmp = builder.getBuildDirectory('netlify-tmp'); + + const relativePath = posix.relative(tmp, builder.getServerDirectory()); + const manifest = builder.generateManifest({ + relativePath, + rerouteMiddleware: reroute_middleware + }); + writeFileSync(`${tmp}/manifest.js`, `export const manifest = ${manifest};\n`); /** @type {BuildOptions} */ const esbuild_config = { @@ -211,7 +223,7 @@ async function generate_edge_functions({ builder }) { await Promise.all([ esbuild.build({ entryPoints: [`${tmp}/entry.js`], - outfile: '.netlify/edge-functions/render.js', + outfile: `.netlify/edge-functions/${name}.js`, ...esbuild_config }), builder.hasServerInstrumentationFile?.() && @@ -230,15 +242,55 @@ async function generate_edge_functions({ builder }) { }); } + /** @type {{ assets: Set }} */ + // we have to prepend the file:// protocol because Windows doesn't support absolute path imports + const { assets } = (await import(`file://${tmp}/manifest.js`)).manifest; + + // We only need to specify paths without the trailing slash because + // Netlify will handle the optional trailing slash for us + const app_path = builder.getAppPath(); + const excluded = [ + // Contains static files + `/${app_path}/immutable/*`, + `/${app_path}/version.json`, + ...builder.prerendered.paths, + ...Array.from(assets).flatMap((asset) => { + if (asset.endsWith('/index.html')) { + const dir = asset.replace(/\/index\.html$/, ''); + return [ + `${builder.config.kit.paths.base}/${asset}`, + `${builder.config.kit.paths.base}/${dir}` + ]; + } + return `${builder.config.kit.paths.base}/${asset}`; + }), + // Should not be served by SvelteKit at all + '/.netlify/*' + ]; + + /** @type {import('@netlify/edge-functions').Manifest} */ + const edge_manifest = { + functions: [ + { + function: name, + path: '/*', + excludedPath: /** @type {`/${string}`[]} */ (excluded) + } + ], + version: 1 + }; + writeFileSync('.netlify/edge-functions/manifest.json', JSON.stringify(edge_manifest)); } + /** - * @param { object } params + * @param {object} params * @param {Builder2_4_0} params.builder - * @param { string } params.publish - * @param { boolean } params.split + * @param {string} params.publish + * @param {boolean} params.split + * @param {boolean} params.reroute_middleware */ -function generate_lambda_functions({ builder, publish, split }) { +function generate_lambda_functions({ builder, publish, split, reroute_middleware }) { builder.mkdirp('.netlify/functions-internal/.svelte-kit'); /** @type {string[]} */ @@ -299,7 +351,8 @@ function generate_lambda_functions({ builder, publish, split }) { const manifest = builder.generateManifest({ relativePath: '../server', - routes + routes, + rerouteMiddleware: reroute_middleware }); const fn = `import { init } from '../serverless.js';\n\nexport const handler = init(${manifest});\n`; @@ -323,7 +376,8 @@ function generate_lambda_functions({ builder, publish, split }) { } } else { const manifest = builder.generateManifest({ - relativePath: '../server' + relativePath: '../server', + rerouteMiddleware: reroute_middleware }); const fn = `import { init } from '../serverless.js';\n\nexport const handler = init(${manifest});\n`; diff --git a/packages/adapter-netlify/internal.d.ts b/packages/adapter-netlify/internal.d.ts index 55da8ba1fbf5..f0fed4f3b287 100644 --- a/packages/adapter-netlify/internal.d.ts +++ b/packages/adapter-netlify/internal.d.ts @@ -7,3 +7,10 @@ declare module 'MANIFEST' { export const manifest: SSRManifest; } + +declare module '__HOOKS__' { + // eslint-disable-next-line no-duplicate-imports + import { Reroute } from '@sveltejs/kit'; + + export const reroute: Reroute; +} diff --git a/packages/adapter-netlify/package.json b/packages/adapter-netlify/package.json index c53fda259f9f..f0f5b4d130af 100644 --- a/packages/adapter-netlify/package.json +++ b/packages/adapter-netlify/package.json @@ -60,11 +60,11 @@ "@sveltejs/vite-plugin-svelte": "catalog:", "@types/node": "catalog:", "@types/set-cookie-parser": "catalog:", - "rollup": "^4.14.2", + "rollup": "catalog:", "typescript": "^5.3.3", "vitest": "catalog:" }, "peerDependencies": { - "@sveltejs/kit": "^2.4.0" + "@sveltejs/kit": "^2.51.0" } } diff --git a/packages/adapter-netlify/rollup.config.js b/packages/adapter-netlify/rollup.config.js index 82d61b85d29d..e9f9528ccce0 100644 --- a/packages/adapter-netlify/rollup.config.js +++ b/packages/adapter-netlify/rollup.config.js @@ -3,6 +3,8 @@ import commonjs from '@rollup/plugin-commonjs'; import json from '@rollup/plugin-json'; import { rmSync } from 'node:fs'; +const EXTERNAL = new Set(['0SERVER', 'MANIFEST', '__HOOKS__']); + /** * @param {string} filepath * @returns {import('rollup').Plugin} @@ -25,7 +27,8 @@ const config = { input: { serverless: 'src/serverless.js', shims: 'src/shims.js', - edge: 'src/edge.js' + edge: 'src/edge.js', + reroute: 'src/reroute.js' }, output: { dir: 'files', @@ -33,7 +36,7 @@ const config = { }, // @ts-ignore https://github.com/rollup/plugins/issues/1329 plugins: [clearOutput('files'), nodeResolve({ preferBuiltins: true }), commonjs(), json()], - external: (id) => id === '0SERVER' || id === 'MANIFEST' || id.startsWith('node:'), + external: (id) => EXTERNAL.has(id) || id.startsWith('node:'), preserveEntrySignatures: 'exports-only' }; diff --git a/packages/adapter-netlify/src/reroute.js b/packages/adapter-netlify/src/reroute.js new file mode 100644 index 000000000000..145b27dc0852 --- /dev/null +++ b/packages/adapter-netlify/src/reroute.js @@ -0,0 +1,13 @@ +import { reroute } from '__HOOKS__'; +import { applyReroute } from '@sveltejs/kit/adapter'; + +/** @type {import('@netlify/edge-functions').EdgeFunction} */ +export default async function middleware(request, context) { + const resolved_url = await applyReroute(request.url, reroute); + + // Netlify rewrites can cause an endless loop because it will re-run this + // function with the rewritten URL. Therefore, we use `context.next` instead + // to specifically invoke the next function in the chain with the rewritten URL + const new_request = new Request(resolved_url, request); + return context.next(new_request); +} diff --git a/packages/adapter-netlify/src/serverless.js b/packages/adapter-netlify/src/serverless.js index 132de92b3295..3eb1d2ef07bd 100644 --- a/packages/adapter-netlify/src/serverless.js +++ b/packages/adapter-netlify/src/serverless.js @@ -57,7 +57,7 @@ export function init(manifest) { * @param {import('@netlify/functions').HandlerEvent} event * @returns {Request} */ -function to_request({ httpMethod, headers, rawUrl, body, isBase64Encoded }) { +function to_request({ httpMethod, headers, rawUrl, rawQuery, body, isBase64Encoded }) { /** @type {RequestInit} */ const init = { method: httpMethod, @@ -69,7 +69,7 @@ function to_request({ httpMethod, headers, rawUrl, body, isBase64Encoded }) { init.body = typeof body === 'string' ? Buffer.from(body, encoding) : body; } - return new Request(rawUrl, init); + return new Request(rawUrl + (rawQuery ? `?${rawQuery}` : ''), init); } const text_types = new Set([ diff --git a/packages/adapter-netlify/test/apps/basic/netlify.toml b/packages/adapter-netlify/test/apps/basic/netlify.toml index 75cbe2e5f61e..0183026741cd 100644 --- a/packages/adapter-netlify/test/apps/basic/netlify.toml +++ b/packages/adapter-netlify/test/apps/basic/netlify.toml @@ -1,2 +1,2 @@ -[dev] +[build] publish = "build" diff --git a/packages/adapter-netlify/test/apps/basic/netlify/functions/render.mjs b/packages/adapter-netlify/test/apps/basic/netlify/functions/render.mjs index 343db73977d2..fbad97b74960 100644 --- a/packages/adapter-netlify/test/apps/basic/netlify/functions/render.mjs +++ b/packages/adapter-netlify/test/apps/basic/netlify/functions/render.mjs @@ -35,5 +35,6 @@ export default async function (request, context) { } export const config = { - path: '/*' + path: '/*', + preferStatic: true }; diff --git a/packages/adapter-netlify/test/apps/edge/netlify.toml b/packages/adapter-netlify/test/apps/edge/netlify.toml index 0d9af7524b9d..522933727454 100644 --- a/packages/adapter-netlify/test/apps/edge/netlify.toml +++ b/packages/adapter-netlify/test/apps/edge/netlify.toml @@ -1,11 +1,9 @@ -[dev] -publish = "build" - # TODO: remove these once we overhaul the Netlify adapter to use the new edge declarations https://docs.netlify.com/build/edge-functions/declarations/#declare-edge-functions-inline [build] # defaults to "netlify/edge-functions" (without the . prefix) edge_functions = ".netlify/edge-functions" +publish = "build" # the dev server doesn't read the manifest.json in edge-functions so we need # to explicitly declare this here diff --git a/packages/adapter-netlify/test/apps/split/.gitignore b/packages/adapter-netlify/test/apps/split/.gitignore new file mode 100644 index 000000000000..69e22a8368e7 --- /dev/null +++ b/packages/adapter-netlify/test/apps/split/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +node_modules +/.svelte-kit +/.netlify +/build \ No newline at end of file diff --git a/packages/adapter-netlify/test/apps/split/netlify.toml b/packages/adapter-netlify/test/apps/split/netlify.toml new file mode 100644 index 000000000000..0cfa12126a53 --- /dev/null +++ b/packages/adapter-netlify/test/apps/split/netlify.toml @@ -0,0 +1,12 @@ +# TODO: remove these once we overhaul the Netlify adapter to use the new edge declarations https://docs.netlify.com/build/edge-functions/declarations/#declare-edge-functions-inline +[build] +# defaults to "netlify/edge-functions" (without the . prefix) +edge_functions = ".netlify/edge-functions" +publish = "build" + +# the dev server doesn't read the manifest.json in edge-functions so we need +# to explicitly declare this here +[[edge_functions]] +path = "/*" +function = "reroute" +excludedPath = ["/_app/immutable/*", "/_app/version.json", "/.netlify/*"] diff --git a/packages/adapter-netlify/test/apps/split/netlify/functions/render.mjs b/packages/adapter-netlify/test/apps/split/netlify/functions/render.mjs new file mode 100644 index 000000000000..67c76da93a3f --- /dev/null +++ b/packages/adapter-netlify/test/apps/split/netlify/functions/render.mjs @@ -0,0 +1,40 @@ +// This is a temporary workaround to be compatible with Netlify's new dev server +// TODO: remove this once we overhaul the Netlify adapter to use Netlify's new serverless function format https://docs.netlify.com/build/functions/get-started/?data-tab=TypeScript#write-a-function + +import { handler } from '../../.netlify/functions-internal/sveltekit-reroute.mjs'; + +/** + * @param {Request} request + * @param {import('@netlify/functions').HandlerContext} context + */ +export default async function (request, context) { + const [rawUrl, rawQuery] = request.url.split('?'); + /** @type {import('@netlify/functions').HandlerEvent} */ + const event = { + rawUrl, + rawQuery: rawQuery || '', + headers: Object.fromEntries(request.headers), + httpMethod: request.method, + isBase64Encoded: false, + path: new URL(request.url).pathname, + queryStringParameters: Object.fromEntries(new URL(request.url).searchParams), + body: request.body && (await request.text()), + multiValueHeaders: {}, + multiValueQueryStringParameters: null + }; + const result = await handler(event, context); + if (result) { + return new Response(result.body, { + status: result.statusCode, + // @ts-ignore + headers: result.headers + }); + } + + return new Response('Not Found', { status: 404 }); +} + +export const config = { + path: '/*', + preferStatic: true +}; diff --git a/packages/adapter-netlify/test/apps/split/package.json b/packages/adapter-netlify/test/apps/split/package.json new file mode 100644 index 000000000000..315548d99b52 --- /dev/null +++ b/packages/adapter-netlify/test/apps/split/package.json @@ -0,0 +1,19 @@ +{ + "name": "test-netlify-split", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "node ../../preview.js", + "prepare": "svelte-kit sync || echo ''", + "test": "playwright test" + }, + "devDependencies": { + "@sveltejs/kit": "workspace:^", + "@sveltejs/vite-plugin-svelte": "catalog:", + "svelte": "catalog:", + "vite": "catalog:" + }, + "type": "module" +} diff --git a/packages/adapter-netlify/test/apps/split/playwright.config.js b/packages/adapter-netlify/test/apps/split/playwright.config.js new file mode 100644 index 000000000000..33d36b651014 --- /dev/null +++ b/packages/adapter-netlify/test/apps/split/playwright.config.js @@ -0,0 +1 @@ +export { config as default } from '../../utils.js'; diff --git a/packages/adapter-netlify/test/apps/split/src/app.html b/packages/adapter-netlify/test/apps/split/src/app.html new file mode 100644 index 000000000000..d533c5e31716 --- /dev/null +++ b/packages/adapter-netlify/test/apps/split/src/app.html @@ -0,0 +1,11 @@ + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/packages/adapter-netlify/test/apps/split/src/hooks.js b/packages/adapter-netlify/test/apps/split/src/hooks.js new file mode 100644 index 000000000000..cb8d0e6e71e4 --- /dev/null +++ b/packages/adapter-netlify/test/apps/split/src/hooks.js @@ -0,0 +1,5 @@ +export function reroute({ url }) { + if (url.pathname.endsWith('/reroute')) { + return '/reroute'; + } +} diff --git a/packages/adapter-netlify/test/apps/split/src/routes/reroute/+page.svelte b/packages/adapter-netlify/test/apps/split/src/routes/reroute/+page.svelte new file mode 100644 index 000000000000..75f3f293db6d --- /dev/null +++ b/packages/adapter-netlify/test/apps/split/src/routes/reroute/+page.svelte @@ -0,0 +1,5 @@ + + +

{$page.url.pathname + $page.url.search}

diff --git a/packages/adapter-netlify/test/apps/split/svelte.config.js b/packages/adapter-netlify/test/apps/split/svelte.config.js new file mode 100644 index 000000000000..a4365e2ed6ad --- /dev/null +++ b/packages/adapter-netlify/test/apps/split/svelte.config.js @@ -0,0 +1,12 @@ +import adapter from '../../../index.js'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + adapter: adapter({ + split: true + }) + } +}; + +export default config; diff --git a/packages/adapter-netlify/test/apps/split/test/test.js b/packages/adapter-netlify/test/apps/split/test/test.js new file mode 100644 index 000000000000..4ef0b9433554 --- /dev/null +++ b/packages/adapter-netlify/test/apps/split/test/test.js @@ -0,0 +1,8 @@ +import { expect, test } from '@playwright/test'; + +test('edge middleware runs reroute before split function', async ({ page }) => { + await page.goto('/reroute'); + await expect(page.locator('p')).toContainText('/reroute'); + await page.goto('/en/reroute'); + await expect(page.locator('p')).toContainText('/en/reroute'); +}); diff --git a/packages/adapter-netlify/test/apps/split/tsconfig.json b/packages/adapter-netlify/test/apps/split/tsconfig.json new file mode 100644 index 000000000000..34380ebc986e --- /dev/null +++ b/packages/adapter-netlify/test/apps/split/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + }, + "extends": "./.svelte-kit/tsconfig.json" +} diff --git a/packages/adapter-netlify/test/apps/split/vite.config.js b/packages/adapter-netlify/test/apps/split/vite.config.js new file mode 100644 index 000000000000..29ad08debe6a --- /dev/null +++ b/packages/adapter-netlify/test/apps/split/vite.config.js @@ -0,0 +1,11 @@ +import { sveltekit } from '@sveltejs/kit/vite'; + +/** @type {import('vite').UserConfig} */ +const config = { + build: { + minify: false + }, + plugins: [sveltekit()] +}; + +export default config; diff --git a/packages/adapter-netlify/tsconfig.json b/packages/adapter-netlify/tsconfig.json index 80d2a47380f3..698181052278 100644 --- a/packages/adapter-netlify/tsconfig.json +++ b/packages/adapter-netlify/tsconfig.json @@ -14,5 +14,5 @@ "@sveltejs/kit": ["../kit/types/index"] } }, - "include": ["*.js", "src/**/*.js", "internal.d.ts", "test/utils.js"] + "include": ["index.js", "src/**/*.js", "internal.d.ts", "test/utils.js", "rollup.config.js"] } diff --git a/packages/adapter-vercel/.gitignore b/packages/adapter-vercel/.gitignore index 9daa8247da45..847a92e0c157 100644 --- a/packages/adapter-vercel/.gitignore +++ b/packages/adapter-vercel/.gitignore @@ -1,2 +1,3 @@ .DS_Store node_modules +/files diff --git a/packages/adapter-vercel/ambient.d.ts b/packages/adapter-vercel/ambient.d.ts deleted file mode 100644 index 67464d3365d9..000000000000 --- a/packages/adapter-vercel/ambient.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { RequestContext } from './index.js'; - -declare global { - namespace App { - export interface Platform { - /** - * `context` is only available in Edge Functions - * - * @deprecated Vercel's context is deprecated. Use [`@vercel/functions`](https://vercel.com/docs/functions/functions-api-reference/vercel-functions-package) instead. - */ - context?: RequestContext; - } - } -} diff --git a/packages/adapter-vercel/index.d.ts b/packages/adapter-vercel/index.d.ts index e3333d04a88e..fbd1a8478c3a 100644 --- a/packages/adapter-vercel/index.d.ts +++ b/packages/adapter-vercel/index.d.ts @@ -103,61 +103,3 @@ export type Config = (EdgeConfig | ServerlessConfig) & { */ images?: ImagesConfig; }; - -// we copy the RequestContext interface from `@vercel/edge` because that package can't co-exist with `@types/node`. -// see https://github.com/sveltejs/kit/pull/9280#issuecomment-1452110035 - -/** - * An extension to the standard `Request` object that is passed to every Edge Function. - * - * @deprecated - use [`@vercel/functions`](https://vercel.com/docs/functions/functions-api-reference/vercel-functions-package) instead. - * - * @example - * ```ts - * import type { RequestContext } from '@vercel/edge'; - * - * export default async function handler(request: Request, ctx: RequestContext): Promise { - * // ctx is the RequestContext - * } - * ``` - */ -export interface RequestContext { - /** - * A method that can be used to keep the function running after a response has been sent. - * This is useful when you have an async task that you want to keep running even after the - * response has been sent and the request has ended. - * - * @example - * - * Sending an internal error to an error tracking service - * - * ```ts - * import type { RequestContext } from '@vercel/edge'; - * - * export async function handleRequest(request: Request, ctx: RequestContext): Promise { - * try { - * return await myFunctionThatReturnsResponse(); - * } catch (e) { - * ctx.waitUntil((async () => { - * // report this error to your error tracking service - * await fetch('https://my-error-tracking-service.com', { - * method: 'POST', - * body: JSON.stringify({ - * stack: e.stack, - * message: e.message, - * name: e.name, - * url: request.url, - * }), - * }); - * })()); - * return new Response('Internal Server Error', { status: 500 }); - * } - * } - * ``` - */ - waitUntil( - /** - * A promise that will be kept alive until it resolves or rejects. - */ promise: Promise - ): void; -} diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index b2414fbea9dc..b6085e414cab 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -72,6 +72,8 @@ const plugin = function (defaults = {}) { builder.log.minor('Generating serverless function...'); + let reroute_middleware = false; + /** * @param {string} name * @param {import('./index.js').ServerlessConfig} config @@ -96,7 +98,7 @@ const plugin = function (defaults = {}) { write( `${tmp}/manifest.js`, - `export const manifest = ${builder.generateManifest({ relativePath, routes })};\n` + `export const manifest = ${builder.generateManifest({ relativePath, routes, rerouteMiddleware: reroute_middleware })};\n` ); await create_function_bundle(builder, `${tmp}/index.js`, dir, config); @@ -110,33 +112,11 @@ const plugin = function (defaults = {}) { let warned = false; /** + * @param {import('esbuild').BuildOptions & Required>} esbuild_options * @param {string} name - * @param {import('./index.js').EdgeConfig} config - * @param {import('@sveltejs/kit').RouteDefinition[]} routes + * @param {import('./index.js').Config} adapter_config */ - async function generate_edge_function(name, config, routes) { - if (!warned) { - warned = true; - builder.log.warn( - `The \`runtime: 'edge'\` option is deprecated, and will be removed in a future version of adapter-vercel` - ); - } - - const tmp = builder.getBuildDirectory(`vercel-tmp/${name}`); - const relativePath = path.posix.relative(tmp, builder.getServerDirectory()); - - builder.copy(`${files}/edge.js`, `${tmp}/edge.js`, { - replace: { - SERVER: `${relativePath}/index.js`, - MANIFEST: './manifest.js' - } - }); - - write( - `${tmp}/manifest.js`, - `export const manifest = ${builder.generateManifest({ relativePath, routes })};\n` - ); - + async function bundle_edge_function(esbuild_options, name, adapter_config) { try { const outdir = `${dirs.functions}/${name}.func`; /** @type {BuildOptions} */ @@ -158,7 +138,7 @@ const plugin = function (defaults = {}) { external: [ ...compatible_node_modules, ...compatible_node_modules.map((id) => `node:${id}`), - ...(config.external || []) + ...((adapter_config.runtime === 'edge' && adapter_config.external) || []) ], sourcemap: 'linked', banner: { js: 'globalThis.global = globalThis;' }, @@ -169,11 +149,12 @@ const plugin = function (defaults = {}) { '.ttf': 'copy', '.eot': 'copy', '.otf': 'copy' - } + }, + ...(esbuild_options || {}) }; + const result = await esbuild.build({ - entryPoints: [`${tmp}/edge.js`], - outfile: `${outdir}/index.js`, + outfile: `${dirs.functions}/${name}.func/index.js`, ...esbuild_config }); @@ -239,8 +220,8 @@ const plugin = function (defaults = {}) { `${dirs.functions}/${name}.func/.vc-config.json`, JSON.stringify( { - runtime: config.runtime, - regions: config.regions, + runtime: 'edge', + regions: adapter_config.regions, entrypoint: 'index.js', framework: { slug: 'sveltekit', @@ -253,6 +234,61 @@ const plugin = function (defaults = {}) { ); } + /** + * @param {string} name + * @param {import('./index.js').EdgeConfig} config + * @param {import('@sveltejs/kit').RouteDefinition[]} routes + */ + async function generate_edge_function(name, config, routes) { + if (!warned) { + warned = true; + builder.log.warn( + `The \`runtime: 'edge'\` option is deprecated, and will be removed in a future version of adapter-vercel` + ); + } + + const tmp = builder.getBuildDirectory(`vercel-tmp/${name}`); + const relativePath = path.posix.relative(tmp, builder.getServerDirectory()); + + const dest = `${tmp}/edge.js`; + + builder.copy(`${files}/edge/edge.js`, dest, { + replace: { + SERVER: `${relativePath}/index.js`, + MANIFEST: './manifest.js' + } + }); + + write( + `${tmp}/manifest.js`, + `export const manifest = ${builder.generateManifest({ relativePath, routes, rerouteMiddleware: reroute_middleware })};\n` + ); + + await bundle_edge_function({ entryPoints: [dest] }, name, config); + } + + /** + * @param {string} name + * @param {import('./index.js').Config} config + * @param {Record=} alias + */ + async function generate_edge_middleware(name, config, alias) { + const tmp = builder.getBuildDirectory('vercel-tmp'); + + const dest = `${tmp}/${name}.js`; + + builder.copy(`${files}/edge/${name}.js`, dest); + + await bundle_edge_function( + { + entryPoints: [dest], + alias + }, + name, + config + ); + } + /** @type {Map[] }>} */ const groups = new Map(); @@ -346,6 +382,25 @@ const plugin = function (defaults = {}) { const singular = groups.size === 1; + /** @type {string | void} */ + let reroute_path; + + if (!singular && (reroute_path = await builder.getReroutePath?.())) { + builder.log('Generating edge middleware to run reroute before split functions...'); + + static_config.routes.push({ + src: '/.*', + middlewarePath: 'reroute', + continue: true + }); + + await generate_edge_middleware('reroute', defaults, { + __HOOKS__: reroute_path + }); + + reroute_middleware = true; + } + for (const group of groups.values()) { const generate_function = group.config.runtime === 'edge' ? generate_edge_function : generate_serverless_function; diff --git a/packages/adapter-vercel/internal.d.ts b/packages/adapter-vercel/internal.d.ts index 537f7cc041d1..253d06ade4fa 100644 --- a/packages/adapter-vercel/internal.d.ts +++ b/packages/adapter-vercel/internal.d.ts @@ -6,3 +6,9 @@ declare module 'MANIFEST' { import { SSRManifest } from '@sveltejs/kit'; export const manifest: SSRManifest; } + +declare module '__HOOKS__' { + // eslint-disable-next-line no-duplicate-imports + import { Reroute } from '@sveltejs/kit'; + export const reroute: Reroute; +} diff --git a/packages/adapter-vercel/package.json b/packages/adapter-vercel/package.json index 8c61fcf09393..11521dd7b8f5 100644 --- a/packages/adapter-vercel/package.json +++ b/packages/adapter-vercel/package.json @@ -28,16 +28,19 @@ "types": "index.d.ts", "files": [ "files", + "!files/edge/tsconfig.json", "index.js", "utils.js", - "index.d.ts", - "ambient.d.ts" + "index.d.ts" ], "scripts": { + "dev": "rollup -cw", + "build": "rollup -c", "lint": "prettier --check .", "format": "pnpm lint --write", "check": "tsc", - "test": "vitest run" + "test": "vitest run", + "prepublishOnly": "pnpm build" }, "dependencies": { "@vercel/nft": "^1.0.0", @@ -46,12 +49,17 @@ "devDependencies": { "@sveltejs/kit": "workspace:^", "@sveltejs/vite-plugin-svelte": "catalog:", + "@rollup/plugin-commonjs": "catalog:", + "@rollup/plugin-json": "catalog:", + "@rollup/plugin-node-resolve": "catalog:", "@types/node": "catalog:", + "@vercel/functions": "^3.4.0", + "rollup": "catalog:", "typescript": "^5.3.3", "vitest": "catalog:" }, "peerDependencies": { - "@sveltejs/kit": "^2.4.0" + "@sveltejs/kit": "^2.51.0" }, "engines": { "node": ">=20.0" diff --git a/packages/adapter-vercel/rollup.config.js b/packages/adapter-vercel/rollup.config.js new file mode 100644 index 000000000000..3486541709bb --- /dev/null +++ b/packages/adapter-vercel/rollup.config.js @@ -0,0 +1,42 @@ +import { nodeResolve } from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import json from '@rollup/plugin-json'; +import { rmSync } from 'node:fs'; + +const EXTERNAL = new Set(['SERVER', 'MANIFEST', '__HOOKS__']); + +/** + * @param {string} filepath + * @returns {import('rollup').Plugin} + */ +function clearOutput(filepath) { + return { + name: 'clear-output', + buildStart: { + order: 'pre', + sequential: true, + handler() { + rmSync(filepath, { recursive: true, force: true }); + } + } + }; +} + +/** @type {import('rollup').RollupOptions} */ +const config = { + input: { + serverless: 'src/serverless.js', + edge: 'src/edge/edge.js', + reroute: 'src/reroute.js' + }, + output: { + dir: 'files', + format: 'esm' + }, + // @ts-ignore https://github.com/rollup/plugins/issues/1329 + plugins: [clearOutput('files'), nodeResolve({ preferBuiltins: true }), commonjs(), json()], + external: (id) => EXTERNAL.has(id) || id.startsWith('node:'), + preserveEntrySignatures: 'exports-only' +}; + +export default config; diff --git a/packages/adapter-vercel/files/edge.js b/packages/adapter-vercel/src/edge/edge.js similarity index 92% rename from packages/adapter-vercel/files/edge.js rename to packages/adapter-vercel/src/edge/edge.js index f87bb6e64a98..e35952d3cf0a 100644 --- a/packages/adapter-vercel/files/edge.js +++ b/packages/adapter-vercel/src/edge/edge.js @@ -49,9 +49,8 @@ const initialized = server.init({ /** * @param {Request} request - * @param {import('../index.js').RequestContext} context */ -export default async (request, context) => { +export default async (request) => { if (!origin) { origin = new URL(request.url).origin; await initialized; @@ -60,9 +59,6 @@ export default async (request, context) => { return server.respond(request, { getClientAddress() { return /** @type {string} */ (request.headers.get('x-forwarded-for')); - }, - platform: { - context } }); }; diff --git a/packages/adapter-vercel/src/edge/tsconfig.json b/packages/adapter-vercel/src/edge/tsconfig.json new file mode 100644 index 000000000000..4112238c3faa --- /dev/null +++ b/packages/adapter-vercel/src/edge/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "strict": true, + "noEmit": true, + "noImplicitAny": true, + "strictNullChecks": true, + "target": "es2022", + "module": "es2022", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "baseUrl": "." + }, + "include": ["*.js", "../../internal.d.ts"] +} diff --git a/packages/adapter-vercel/src/reroute.js b/packages/adapter-vercel/src/reroute.js new file mode 100644 index 000000000000..3273c3ae5b8b --- /dev/null +++ b/packages/adapter-vercel/src/reroute.js @@ -0,0 +1,12 @@ +import { reroute } from '__HOOKS__'; +import { applyReroute } from '@sveltejs/kit/adapter'; +import { rewrite } from '@vercel/functions/middleware'; + +/** + * @param {Request} request + * @returns {Promise} + */ +export default async function middleware(request) { + const resolved_url = await applyReroute(request.url, reroute); + return rewrite(resolved_url); +} diff --git a/packages/adapter-vercel/files/serverless.js b/packages/adapter-vercel/src/serverless.js similarity index 100% rename from packages/adapter-vercel/files/serverless.js rename to packages/adapter-vercel/src/serverless.js diff --git a/packages/adapter-vercel/tsconfig.json b/packages/adapter-vercel/tsconfig.json index 4432dae4102d..0e3783b47bea 100644 --- a/packages/adapter-vercel/tsconfig.json +++ b/packages/adapter-vercel/tsconfig.json @@ -15,5 +15,13 @@ "@sveltejs/kit": ["../kit/types/index"] } }, - "include": ["*.js", "files/**/*.js", "internal.d.ts", "test/**/*.js"] + "include": [ + "src/serverless.js", + "src/reroute.js", + "index.js", + "internal.d.ts", + "utils.js", + "test/**/*.js", + "rollup.config.js" + ] } diff --git a/packages/enhanced-img/package.json b/packages/enhanced-img/package.json index 3f5387858271..193afd02d903 100644 --- a/packages/enhanced-img/package.json +++ b/packages/enhanced-img/package.json @@ -48,7 +48,7 @@ "@sveltejs/vite-plugin-svelte": "catalog:", "@types/estree": "catalog:", "@types/node": "catalog:", - "rollup": "^4.27.4", + "rollup": "catalog:", "svelte": "catalog:", "typescript": "^5.6.3", "vite": "catalog:", diff --git a/packages/kit/package.json b/packages/kit/package.json index b0926a9b8a27..f028556ff444 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -40,7 +40,7 @@ "@types/node": "catalog:", "@types/set-cookie-parser": "catalog:", "dts-buddy": "catalog:", - "rollup": "^4.14.2", + "rollup": "catalog:", "svelte": "catalog:", "svelte-preprocess": "catalog:", "typescript": "^5.3.3", @@ -105,6 +105,14 @@ "types": "./types/index.d.ts", "import": "./src/exports/index.js" }, + "./adapter": { + "types": "./types/index.d.ts", + "import": "./src/exports/adapter/index.js" + }, + "./hooks": { + "types": "./types/index.d.ts", + "import": "./src/exports/hooks/index.js" + }, "./internal": { "types": "./types/index.d.ts", "import": "./src/exports/internal/index.js" @@ -121,10 +129,6 @@ "types": "./types/index.d.ts", "import": "./src/exports/node/polyfills.js" }, - "./hooks": { - "types": "./types/index.d.ts", - "import": "./src/exports/hooks/index.js" - }, "./vite": { "types": "./types/index.d.ts", "import": "./src/exports/vite/index.js" diff --git a/packages/kit/scripts/generate-dts.js b/packages/kit/scripts/generate-dts.js index b470d6d63494..7e51f6dfc474 100644 --- a/packages/kit/scripts/generate-dts.js +++ b/packages/kit/scripts/generate-dts.js @@ -5,6 +5,7 @@ await createBundle({ output: 'types/index.d.ts', modules: { '@sveltejs/kit': 'src/exports/public.d.ts', + '@sveltejs/kit/adapter': 'src/exports/adapter/index.js', '@sveltejs/kit/hooks': 'src/exports/hooks/index.js', '@sveltejs/kit/node': 'src/exports/node/index.js', '@sveltejs/kit/node/polyfills': 'src/exports/node/polyfills.js', diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index 20124e43e334..9f7033dba0b8 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -191,7 +191,7 @@ export function create_builder({ write(dest, `export const env=${JSON.stringify(env.public)}`); }, - generateManifest({ relativePath, routes: subset }) { + generateManifest({ relativePath, routes: subset, rerouteMiddleware }) { return generate_manifest({ build_data, prerendered: prerendered.paths, @@ -199,6 +199,7 @@ export function create_builder({ routes: subset ? subset.map((route) => /** @type {import('types').RouteData} */ (lookup.get(route))) : route_data.filter((route) => prerender_map.get(route.id) !== true), + reroute_middleware: rerouteMiddleware, remotes }); }, @@ -219,6 +220,17 @@ export function create_builder({ return build_data.app_path; }, + async getReroutePath() { + const hooks = build_data.manifest_data.hooks.universal; + if (!hooks) return; + + const hooks_path = `${config.kit.outDir}/output/server/${build_data.server_manifest[hooks].file}`; + const has_reroute_hook = existsSync(hooks_path) && !!(await import(hooks_path)).reroute; + if (has_reroute_hook) { + return hooks_path; + } + }, + writeClient(dest) { return copy(`${config.kit.outDir}/output/client`, dest, { // avoid making vite build artefacts public diff --git a/packages/kit/src/core/generate_manifest/index.js b/packages/kit/src/core/generate_manifest/index.js index 6b2445aa2107..b1bf1c91e379 100644 --- a/packages/kit/src/core/generate_manifest/index.js +++ b/packages/kit/src/core/generate_manifest/index.js @@ -20,9 +20,17 @@ import { uneval } from 'devalue'; * relative_path: string; * routes: import('types').RouteData[]; * remotes: RemoteChunk[]; + * reroute_middleware?: boolean; * }} opts */ -export function generate_manifest({ build_data, prerendered, relative_path, routes, remotes }) { +export function generate_manifest({ + build_data, + prerendered, + relative_path, + routes, + remotes, + reroute_middleware +}) { /** * @type {Map} The new index of each node in the filtered nodes array */ @@ -132,7 +140,8 @@ export function generate_manifest({ build_data, prerendered, relative_path, rout ).join('\n')} return { ${Array.from(matchers).join(', ')} }; }, - server_assets: ${s(files)} + server_assets: ${s(files)}, + reroute_middleware: ${reroute_middleware ?? false} } } `; diff --git a/packages/kit/src/exports/adapter/index.js b/packages/kit/src/exports/adapter/index.js new file mode 100644 index 000000000000..f9296ec299a4 --- /dev/null +++ b/packages/kit/src/exports/adapter/index.js @@ -0,0 +1,40 @@ +import { ORIGINAL_PATH_PARAM } from '../../runtime/shared.js'; +import { normalizeUrl } from '../index.js'; + +/** + * If your deployment platform supports splitting your app into multiple functions, + * you should run this in a middleware that runs before the main handler + * to reroute the request to the correct function and [generate a server-side manifest](https://svelte.dev/docs/kit/@sveltejs-kit#Builder) + * with the `rerouteMiddleware` option set to `true`. + * @example + * ```js + * import { applyReroute } from '@sveltejs/kit/adapter'; + * // replace __HOOKS__ with the path to the reroute hook obtained from `builder.getReroutePath()` + * import { reroute } from '__HOOKS__'; + * + * export default function middleware(request) { + * return applyReroute(request.url, reroute); + * } + * ``` + * @param {string} url + * @param {import("@sveltejs/kit").Reroute} reroute + * @returns {Promise} + * @since 2.51.0 + */ +export async function applyReroute(url, reroute) { + const url_copy = new URL(url); + url_copy.searchParams.set(ORIGINAL_PATH_PARAM, url_copy.pathname); + + const { url: normalized_url, denormalize } = normalizeUrl(url); + const resolved_path = await reroute({ url: normalized_url, fetch }); + + // bail out if there were no changes to the pathname + if (!resolved_path || resolved_path === url_copy.pathname) { + // we always return a URL with the x-sveltekit-original-path param set + // so that the requester can't fake it + return url_copy; + } + + url_copy.pathname = resolved_path; + return denormalize(url_copy); +} diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 200ae665893f..062ed3316214 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -146,9 +146,15 @@ export interface Builder { /** * Generate a server-side manifest to initialise the SvelteKit [server](https://svelte.dev/docs/kit/@sveltejs-kit#Server) with. - * @param opts a relative path to the base directory of the app and optionally in which format (esm or cjs) the manifest should be generated + * @param opts.relativePath a relative path to the base directory of the app + * @param opts.routes optional. In which format (esm or cjs) the manifest should be generated + * @param opts.rerouteMiddleware optional. True if the `reroute` hook will run in a middleware before the main handler using the [`applyReroute`](https://svelte.dev/docs/kit/@sveltejs-kit-adapter#applyReroute) function */ - generateManifest: (opts: { relativePath: string; routes?: RouteDefinition[] }) => string; + generateManifest: (opts: { + relativePath: string; + routes?: RouteDefinition[]; + rerouteMiddleware?: boolean; + }) => string; /** * Resolve a path to the `name` directory inside `outDir`, e.g. `/path/to/.svelte-kit/my-adapter`. @@ -161,6 +167,29 @@ export interface Builder { getServerDirectory: () => string; /** Get the application path including any configured `base` path, e.g. `my-base-path/_app`. */ getAppPath: () => string; + /** + * Get the fully resolved path to the file containing the `reroute` hook if it exists. + * @example + * ```js + * const reroutePath = builder.getReroutePath(); + * if (split && reroutePath) { + * // generate a server-side manifest with the `rerouteMiddleware` option set to `true` + * fs.writeFileSync( + * `${output}/manifest.js`, + * `export const manifest = ${builder.generateManifest({ relativePath, routes, rerouteMiddleware: true })};\n` + * ); + * + * // create a middleware that imports and runs the `reroute` hook + * builder.copy(`${files}/reroute.js`, `${output}/entry.js`, { + * replace: { + * __HOOKS__: reroutePath + * } + * }); + * } + * ``` + * @since 2.51.0 + */ + getReroutePath: () => Promise; /** * Write client assets to `dest`. @@ -1632,6 +1661,8 @@ export interface SSRManifest { matchers: () => Promise>; /** A `[file]: size` map of all assets imported by server code. */ server_assets: Record; + /** True if the `reroute` hook will run in a middleware before the main handler */ + reroute_middleware: boolean; }; } diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 31c13c1ead8b..3632dbb9b6c4 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -313,7 +313,8 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) { } return matchers; - } + }, + reroute_middleware: false } }; } diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index 8a7dfcc0d1f8..31173745901a 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -23,7 +23,7 @@ import { create_fetch } from './fetch.js'; import { PageNodes } from '../../utils/page_nodes.js'; import { validate_server_exports } from '../../utils/exports.js'; import { action_json_redirect, is_action_json_request } from './page/actions.js'; -import { INVALIDATED_PARAM, TRAILING_SLASH_PARAM } from '../shared.js'; +import { INVALIDATED_PARAM, TRAILING_SLASH_PARAM, ORIGINAL_PATH_PARAM } from '../shared.js'; import { get_public_env } from './env_module.js'; import { resolve_route } from './page/server_routing.js'; import { validateHeaders } from './validate-headers.js'; @@ -70,6 +70,15 @@ export async function internal_respond(request, options, manifest, state) { /** URL but stripped from the potential `/__data.json` suffix and its search param */ const url = new URL(request.url); + let resolved_path = url.pathname; + + // If reroute ran in an edge middleware, Vercel doesn't change the request URL but Netlify does. + // So, we always restore the original URL pathname to ensure that the correct route is invoked + if (manifest._.reroute_middleware && url.searchParams.has(ORIGINAL_PATH_PARAM)) { + url.pathname = /** @type {string} */ (url.searchParams.get(ORIGINAL_PATH_PARAM)); + url.searchParams.delete(ORIGINAL_PATH_PARAM); + } + const is_route_resolution_request = has_resolution_suffix(url.pathname); const is_data_request = has_data_suffix(url.pathname); const remote_id = get_remote_id(url); @@ -220,9 +229,7 @@ export async function internal_respond(request, options, manifest, state) { }); } - let resolved_path = url.pathname; - - if (!remote_id) { + if (!remote_id || !manifest._.reroute_middleware) { const prerendering_reroute_state = state.prerendering?.inside_reroute; try { // For the duration or a reroute, disable the prerendering state as reroute could call API endpoints diff --git a/packages/kit/src/runtime/shared.js b/packages/kit/src/runtime/shared.js index dd6895571f94..8c279eb710ae 100644 --- a/packages/kit/src/runtime/shared.js +++ b/packages/kit/src/runtime/shared.js @@ -19,6 +19,12 @@ export const INVALIDATED_PARAM = 'x-sveltekit-invalidated'; export const TRAILING_SLASH_PARAM = 'x-sveltekit-trailing-slash'; +/** + * If an adapter enables running reroute early in its own server, the original + * pathname is stored in this query parameter + */ +export const ORIGINAL_PATH_PARAM = 'x-sveltekit-original-path'; + /** * @param {any} data * @param {string} [location_description] diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index c41dd5ea556e..822126cf4595 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -122,9 +122,15 @@ declare module '@sveltejs/kit' { /** * Generate a server-side manifest to initialise the SvelteKit [server](https://svelte.dev/docs/kit/@sveltejs-kit#Server) with. - * @param opts a relative path to the base directory of the app and optionally in which format (esm or cjs) the manifest should be generated + * @param opts.relativePath a relative path to the base directory of the app + * @param opts.routes optional. In which format (esm or cjs) the manifest should be generated + * @param opts.rerouteMiddleware optional. True if the `reroute` hook will run in a middleware before the main handler using the [`applyReroute`](https://svelte.dev/docs/kit/@sveltejs-kit-adapter#applyReroute) function */ - generateManifest: (opts: { relativePath: string; routes?: RouteDefinition[] }) => string; + generateManifest: (opts: { + relativePath: string; + routes?: RouteDefinition[]; + rerouteMiddleware?: boolean; + }) => string; /** * Resolve a path to the `name` directory inside `outDir`, e.g. `/path/to/.svelte-kit/my-adapter`. @@ -137,6 +143,29 @@ declare module '@sveltejs/kit' { getServerDirectory: () => string; /** Get the application path including any configured `base` path, e.g. `my-base-path/_app`. */ getAppPath: () => string; + /** + * Get the fully resolved path to the file containing the `reroute` hook if it exists. + * @example + * ```js + * const reroutePath = builder.getReroutePath(); + * if (split && reroutePath) { + * // generate a server-side manifest with the `rerouteMiddleware` option set to `true` + * fs.writeFileSync( + * `${output}/manifest.js`, + * `export const manifest = ${builder.generateManifest({ relativePath, routes, rerouteMiddleware: true })};\n` + * ); + * + * // create a middleware that imports and runs the `reroute` hook + * builder.copy(`${files}/reroute.js`, `${output}/entry.js`, { + * replace: { + * __HOOKS__: reroutePath + * } + * }); + * } + * ``` + * @since 2.51.0 + */ + getReroutePath: () => Promise; /** * Write client assets to `dest`. @@ -1607,6 +1636,8 @@ declare module '@sveltejs/kit' { matchers: () => Promise>; /** A `[file]: size` map of all assets imported by server code. */ server_assets: Record; + /** True if the `reroute` hook will run in a middleware before the main handler */ + reroute_middleware: boolean; }; } @@ -2772,6 +2803,29 @@ declare module '@sveltejs/kit' { export {}; } +declare module '@sveltejs/kit/adapter' { + /** + * If your deployment platform supports splitting your app into multiple functions, + * you should run this in a middleware that runs before the main handler + * to reroute the request to the correct function and [generate a server-side manifest](https://svelte.dev/docs/kit/@sveltejs-kit#Builder) + * with the `rerouteMiddleware` option set to `true`. + * @example + * ```js + * import { applyReroute } from '@sveltejs/kit/adapter'; + * // replace __HOOKS__ with the path to the reroute hook obtained from `builder.getReroutePath()` + * import { reroute } from '__HOOKS__'; + * + * export default function middleware(request) { + * return applyReroute(request.url, reroute); + * } + * ``` + * @since 2.51.0 + */ + export function applyReroute(url: string, reroute: import("@sveltejs/kit").Reroute): Promise; + + export {}; +} + declare module '@sveltejs/kit/hooks' { import type { Handle } from '@sveltejs/kit'; /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3cca823e389c..f192a9d29a08 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -87,6 +87,9 @@ catalogs: publint: specifier: ^0.3.0 version: 0.3.0 + rollup: + specifier: ^4.57.1 + version: 4.57.1 sirv-cli: specifier: ^3.0.0 version: 3.0.0 @@ -249,7 +252,7 @@ importers: devDependencies: '@netlify/dev': specifier: 'catalog:' - version: 4.8.8(@netlify/api@14.0.12)(rollup@4.50.1) + version: 4.8.8(@netlify/api@14.0.12)(rollup@4.57.1) '@netlify/edge-functions': specifier: 'catalog:' version: 3.0.3 @@ -264,13 +267,13 @@ importers: version: 2.3.0 '@rollup/plugin-commonjs': specifier: 'catalog:' - version: 29.0.0(rollup@4.50.1) + version: 29.0.0(rollup@4.57.1) '@rollup/plugin-json': specifier: 'catalog:' - version: 6.1.0(rollup@4.50.1) + version: 6.1.0(rollup@4.57.1) '@rollup/plugin-node-resolve': specifier: 'catalog:' - version: 16.0.0(rollup@4.50.1) + version: 16.0.0(rollup@4.57.1) '@sveltejs/kit': specifier: workspace:^ version: link:../kit @@ -284,8 +287,8 @@ importers: specifier: 'catalog:' version: 2.4.7 rollup: - specifier: ^4.14.2 - version: 4.50.1 + specifier: 'catalog:' + version: 4.57.1 typescript: specifier: ^5.3.3 version: 5.8.3 @@ -323,6 +326,21 @@ importers: specifier: 'catalog:' version: 6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0) + packages/adapter-netlify/test/apps/split: + devDependencies: + '@sveltejs/kit': + specifier: workspace:^ + version: link:../../../../kit + '@sveltejs/vite-plugin-svelte': + specifier: 'catalog:' + version: 6.0.0-next.3(svelte@5.48.4)(vite@6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)) + svelte: + specifier: 'catalog:' + version: 5.48.4 + vite: + specifier: 'catalog:' + version: 6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0) + packages/adapter-node: dependencies: '@rollup/plugin-commonjs': @@ -430,11 +448,20 @@ importers: dependencies: '@vercel/nft': specifier: ^1.0.0 - version: 1.0.0(rollup@4.50.1) + version: 1.0.0(rollup@4.57.1) esbuild: specifier: ^0.25.4 version: 0.25.9 devDependencies: + '@rollup/plugin-commonjs': + specifier: 'catalog:' + version: 29.0.0(rollup@4.57.1) + '@rollup/plugin-json': + specifier: 'catalog:' + version: 6.1.0(rollup@4.57.1) + '@rollup/plugin-node-resolve': + specifier: 'catalog:' + version: 16.0.0(rollup@4.57.1) '@sveltejs/kit': specifier: workspace:^ version: link:../kit @@ -444,6 +471,12 @@ importers: '@types/node': specifier: 'catalog:' version: 18.19.119 + '@vercel/functions': + specifier: ^3.4.0 + version: 3.4.0 + rollup: + specifier: 'catalog:' + version: 4.57.1 typescript: specifier: ^5.3.3 version: 5.8.3 @@ -473,7 +506,7 @@ importers: version: 0.1.5(svelte@5.48.4) vite-imagetools: specifier: ^9.0.2 - version: 9.0.2(rollup@4.50.1) + version: 9.0.2(rollup@4.57.1) zimmerframe: specifier: ^1.1.2 version: 1.1.2 @@ -488,8 +521,8 @@ importers: specifier: 'catalog:' version: 18.19.119 rollup: - specifier: ^4.27.4 - version: 4.50.1 + specifier: 'catalog:' + version: 4.57.1 svelte: specifier: 'catalog:' version: 5.48.4 @@ -585,8 +618,8 @@ importers: specifier: 'catalog:' version: 0.6.2(typescript@5.8.3) rollup: - specifier: ^4.14.2 - version: 4.50.1 + specifier: 'catalog:' + version: 4.57.1 svelte: specifier: 'catalog:' version: 5.48.4 @@ -3004,55 +3037,115 @@ packages: cpu: [arm] os: [android] + '@rollup/rollup-android-arm-eabi@4.57.1': + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} + cpu: [arm] + os: [android] + '@rollup/rollup-android-arm64@4.50.1': resolution: {integrity: sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw==} cpu: [arm64] os: [android] + '@rollup/rollup-android-arm64@4.57.1': + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} + cpu: [arm64] + os: [android] + '@rollup/rollup-darwin-arm64@4.50.1': resolution: {integrity: sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw==} cpu: [arm64] os: [darwin] + '@rollup/rollup-darwin-arm64@4.57.1': + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} + cpu: [arm64] + os: [darwin] + '@rollup/rollup-darwin-x64@4.50.1': resolution: {integrity: sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw==} cpu: [x64] os: [darwin] + '@rollup/rollup-darwin-x64@4.57.1': + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} + cpu: [x64] + os: [darwin] + '@rollup/rollup-freebsd-arm64@4.50.1': resolution: {integrity: sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA==} cpu: [arm64] os: [freebsd] + '@rollup/rollup-freebsd-arm64@4.57.1': + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} + cpu: [arm64] + os: [freebsd] + '@rollup/rollup-freebsd-x64@4.50.1': resolution: {integrity: sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ==} cpu: [x64] os: [freebsd] + '@rollup/rollup-freebsd-x64@4.57.1': + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} + cpu: [x64] + os: [freebsd] + '@rollup/rollup-linux-arm-gnueabihf@4.50.1': resolution: {integrity: sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==} cpu: [arm] os: [linux] libc: [glibc] + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} + cpu: [arm] + os: [linux] + '@rollup/rollup-linux-arm-musleabihf@4.50.1': resolution: {integrity: sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==} cpu: [arm] os: [linux] libc: [musl] + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} + cpu: [arm] + os: [linux] + '@rollup/rollup-linux-arm64-gnu@4.50.1': resolution: {integrity: sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==} cpu: [arm64] os: [linux] libc: [glibc] + '@rollup/rollup-linux-arm64-gnu@4.57.1': + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} + cpu: [arm64] + os: [linux] + '@rollup/rollup-linux-arm64-musl@4.50.1': resolution: {integrity: sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==} cpu: [arm64] os: [linux] libc: [musl] + '@rollup/rollup-linux-arm64-musl@4.57.1': + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.57.1': + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} + cpu: [loong64] + os: [linux] + '@rollup/rollup-linux-loongarch64-gnu@4.50.1': resolution: {integrity: sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==} cpu: [loong64] @@ -3065,56 +3158,121 @@ packages: os: [linux] libc: [glibc] + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} + cpu: [ppc64] + os: [linux] + '@rollup/rollup-linux-riscv64-gnu@4.50.1': resolution: {integrity: sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==} cpu: [riscv64] os: [linux] libc: [glibc] + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} + cpu: [riscv64] + os: [linux] + '@rollup/rollup-linux-riscv64-musl@4.50.1': resolution: {integrity: sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==} cpu: [riscv64] os: [linux] libc: [musl] + '@rollup/rollup-linux-riscv64-musl@4.57.1': + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} + cpu: [riscv64] + os: [linux] + '@rollup/rollup-linux-s390x-gnu@4.50.1': resolution: {integrity: sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==} cpu: [s390x] os: [linux] libc: [glibc] + '@rollup/rollup-linux-s390x-gnu@4.57.1': + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} + cpu: [s390x] + os: [linux] + '@rollup/rollup-linux-x64-gnu@4.50.1': resolution: {integrity: sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==} cpu: [x64] os: [linux] libc: [glibc] + '@rollup/rollup-linux-x64-gnu@4.57.1': + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} + cpu: [x64] + os: [linux] + '@rollup/rollup-linux-x64-musl@4.50.1': resolution: {integrity: sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==} cpu: [x64] os: [linux] libc: [musl] + '@rollup/rollup-linux-x64-musl@4.57.1': + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.57.1': + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} + cpu: [x64] + os: [openbsd] + '@rollup/rollup-openharmony-arm64@4.50.1': resolution: {integrity: sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==} cpu: [arm64] os: [openharmony] + '@rollup/rollup-openharmony-arm64@4.57.1': + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} + cpu: [arm64] + os: [openharmony] + '@rollup/rollup-win32-arm64-msvc@4.50.1': resolution: {integrity: sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ==} cpu: [arm64] os: [win32] + '@rollup/rollup-win32-arm64-msvc@4.57.1': + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} + cpu: [arm64] + os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.50.1': resolution: {integrity: sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A==} cpu: [ia32] os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.57.1': + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.57.1': + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} + cpu: [x64] + os: [win32] + '@rollup/rollup-win32-x64-msvc@4.50.1': resolution: {integrity: sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA==} cpu: [x64] os: [win32] + '@rollup/rollup-win32-x64-msvc@4.57.1': + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} + cpu: [x64] + os: [win32] + '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} @@ -3315,6 +3473,15 @@ packages: resolution: {integrity: sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vercel/functions@3.4.0': + resolution: {integrity: sha512-lKWDWEhFcpt/zL3ueWqII8W5BY73MC5WUuZyudditbbya6mP8jCLQEuXBxJetpEKgnEn+5xCyK962rw4v1RSVA==} + engines: {node: '>= 20'} + peerDependencies: + '@aws-sdk/credential-provider-web-identity': '*' + peerDependenciesMeta: + '@aws-sdk/credential-provider-web-identity': + optional: true + '@vercel/nft@0.29.4': resolution: {integrity: sha512-6lLqMNX3TuycBPABycx7A9F1bHQR7kiQln6abjFbPrf5C/05qHM9M5E4PeTE59c7z8g6vHnx1Ioihb2AQl7BTA==} engines: {node: '>=18'} @@ -3325,6 +3492,10 @@ packages: engines: {node: '>=20'} hasBin: true + '@vercel/oidc@3.1.0': + resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==} + engines: {node: '>= 20'} + '@vitest/browser-playwright@4.0.16': resolution: {integrity: sha512-I2Fy/ANdphi1yI46d15o0M1M4M0UJrUiVKkH5oKeRZZCdPg0fw/cfTKZzv9Ge9eobtJYp4BGblMzXdXH0vcl5g==} peerDependencies: @@ -5329,6 +5500,11 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rollup@4.57.1: + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -6952,14 +7128,14 @@ snapshots: uuid: 13.0.0 write-file-atomic: 5.0.1 - '@netlify/dev@4.8.8(@netlify/api@14.0.12)(rollup@4.50.1)': + '@netlify/dev@4.8.8(@netlify/api@14.0.12)(rollup@4.57.1)': dependencies: '@netlify/ai': 0.3.5(@netlify/api@14.0.12) '@netlify/blobs': 10.5.0 '@netlify/config': 24.2.0 '@netlify/dev-utils': 4.3.3 '@netlify/edge-functions-dev': 1.0.8 - '@netlify/functions-dev': 1.1.8(rollup@4.50.1) + '@netlify/functions-dev': 1.1.8(rollup@4.57.1) '@netlify/headers': 2.1.3 '@netlify/images': 1.3.3(@netlify/blobs@10.5.0) '@netlify/redirects': 3.1.4 @@ -7027,12 +7203,12 @@ snapshots: dependencies: '@netlify/types': 2.3.0 - '@netlify/functions-dev@1.1.8(rollup@4.50.1)': + '@netlify/functions-dev@1.1.8(rollup@4.57.1)': dependencies: '@netlify/blobs': 10.5.0 '@netlify/dev-utils': 4.3.3 '@netlify/functions': 5.1.2 - '@netlify/zip-it-and-ship-it': 14.2.0(rollup@4.50.1) + '@netlify/zip-it-and-ship-it': 14.2.0(rollup@4.57.1) cron-parser: 4.9.0 decache: 4.6.2 extract-zip: 2.0.1 @@ -7135,13 +7311,13 @@ snapshots: '@netlify/types@2.3.0': {} - '@netlify/zip-it-and-ship-it@14.2.0(rollup@4.50.1)': + '@netlify/zip-it-and-ship-it@14.2.0(rollup@4.57.1)': dependencies: '@babel/parser': 7.27.5 '@babel/types': 7.28.6 '@netlify/binary-info': 1.0.0 '@netlify/serverless-functions-api': 2.8.3 - '@vercel/nft': 0.29.4(rollup@4.50.1) + '@vercel/nft': 0.29.4(rollup@4.57.1) archiver: 7.0.1 common-path-prefix: 3.0.0 copy-file: 11.0.0 @@ -7589,12 +7765,30 @@ snapshots: optionalDependencies: rollup: 4.50.1 + '@rollup/plugin-commonjs@29.0.0(rollup@4.57.1)': + dependencies: + '@rollup/pluginutils': 5.1.3(rollup@4.57.1) + commondir: 1.0.1 + estree-walker: 2.0.2 + fdir: 6.5.0(picomatch@4.0.3) + is-reference: 1.2.1 + magic-string: 0.30.21 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.57.1 + '@rollup/plugin-json@6.1.0(rollup@4.50.1)': dependencies: '@rollup/pluginutils': 5.1.3(rollup@4.50.1) optionalDependencies: rollup: 4.50.1 + '@rollup/plugin-json@6.1.0(rollup@4.57.1)': + dependencies: + '@rollup/pluginutils': 5.1.3(rollup@4.57.1) + optionalDependencies: + rollup: 4.57.1 + '@rollup/plugin-node-resolve@16.0.0(rollup@4.50.1)': dependencies: '@rollup/pluginutils': 5.1.3(rollup@4.50.1) @@ -7605,6 +7799,16 @@ snapshots: optionalDependencies: rollup: 4.50.1 + '@rollup/plugin-node-resolve@16.0.0(rollup@4.57.1)': + dependencies: + '@rollup/pluginutils': 5.1.3(rollup@4.57.1) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-module: 1.0.0 + resolve: 1.22.8 + optionalDependencies: + rollup: 4.57.1 + '@rollup/pluginutils@5.1.3(rollup@4.50.1)': dependencies: '@types/estree': 1.0.8 @@ -7613,69 +7817,152 @@ snapshots: optionalDependencies: rollup: 4.50.1 + '@rollup/pluginutils@5.1.3(rollup@4.57.1)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.57.1 + '@rollup/rollup-android-arm-eabi@4.50.1': optional: true + '@rollup/rollup-android-arm-eabi@4.57.1': + optional: true + '@rollup/rollup-android-arm64@4.50.1': optional: true + '@rollup/rollup-android-arm64@4.57.1': + optional: true + '@rollup/rollup-darwin-arm64@4.50.1': optional: true + '@rollup/rollup-darwin-arm64@4.57.1': + optional: true + '@rollup/rollup-darwin-x64@4.50.1': optional: true + '@rollup/rollup-darwin-x64@4.57.1': + optional: true + '@rollup/rollup-freebsd-arm64@4.50.1': optional: true + '@rollup/rollup-freebsd-arm64@4.57.1': + optional: true + '@rollup/rollup-freebsd-x64@4.50.1': optional: true + '@rollup/rollup-freebsd-x64@4.57.1': + optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.50.1': optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + optional: true + '@rollup/rollup-linux-arm-musleabihf@4.50.1': optional: true + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + optional: true + '@rollup/rollup-linux-arm64-gnu@4.50.1': optional: true + '@rollup/rollup-linux-arm64-gnu@4.57.1': + optional: true + '@rollup/rollup-linux-arm64-musl@4.50.1': optional: true + '@rollup/rollup-linux-arm64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.57.1': + optional: true + '@rollup/rollup-linux-loongarch64-gnu@4.50.1': optional: true '@rollup/rollup-linux-ppc64-gnu@4.50.1': optional: true + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + optional: true + '@rollup/rollup-linux-riscv64-gnu@4.50.1': optional: true + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + optional: true + '@rollup/rollup-linux-riscv64-musl@4.50.1': optional: true + '@rollup/rollup-linux-riscv64-musl@4.57.1': + optional: true + '@rollup/rollup-linux-s390x-gnu@4.50.1': optional: true + '@rollup/rollup-linux-s390x-gnu@4.57.1': + optional: true + '@rollup/rollup-linux-x64-gnu@4.50.1': optional: true + '@rollup/rollup-linux-x64-gnu@4.57.1': + optional: true + '@rollup/rollup-linux-x64-musl@4.50.1': optional: true + '@rollup/rollup-linux-x64-musl@4.57.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.57.1': + optional: true + '@rollup/rollup-openharmony-arm64@4.50.1': optional: true + '@rollup/rollup-openharmony-arm64@4.57.1': + optional: true + '@rollup/rollup-win32-arm64-msvc@4.50.1': optional: true + '@rollup/rollup-win32-arm64-msvc@4.57.1': + optional: true + '@rollup/rollup-win32-ia32-msvc@4.50.1': optional: true + '@rollup/rollup-win32-ia32-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.57.1': + optional: true + '@rollup/rollup-win32-x64-msvc@4.50.1': optional: true + '@rollup/rollup-win32-x64-msvc@4.57.1': + optional: true + '@standard-schema/spec@1.0.0': {} '@stylistic/eslint-plugin-js@2.1.0(eslint@9.34.0(jiti@2.4.2))': @@ -7932,10 +8219,14 @@ snapshots: '@typescript-eslint/types': 8.53.1 eslint-visitor-keys: 4.2.1 - '@vercel/nft@0.29.4(rollup@4.50.1)': + '@vercel/functions@3.4.0': + dependencies: + '@vercel/oidc': 3.1.0 + + '@vercel/nft@0.29.4(rollup@4.57.1)': dependencies: '@mapbox/node-pre-gyp': 2.0.0 - '@rollup/pluginutils': 5.1.3(rollup@4.50.1) + '@rollup/pluginutils': 5.1.3(rollup@4.57.1) acorn: 8.15.0 acorn-import-attributes: 1.9.5(acorn@8.15.0) async-sema: 3.1.1 @@ -7951,10 +8242,10 @@ snapshots: - rollup - supports-color - '@vercel/nft@1.0.0(rollup@4.50.1)': + '@vercel/nft@1.0.0(rollup@4.57.1)': dependencies: '@mapbox/node-pre-gyp': 2.0.0 - '@rollup/pluginutils': 5.1.3(rollup@4.50.1) + '@rollup/pluginutils': 5.1.3(rollup@4.57.1) acorn: 8.15.0 acorn-import-attributes: 1.9.5(acorn@8.15.0) async-sema: 3.1.1 @@ -7970,6 +8261,8 @@ snapshots: - rollup - supports-color + '@vercel/oidc@3.1.0': {} + '@vitest/browser-playwright@4.0.16(playwright@1.56.0)(vite@6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))(vitest@4.0.16)': dependencies: '@vitest/browser': 4.0.16(vite@6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))(vitest@4.0.16) @@ -10070,6 +10363,37 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.50.1 fsevents: 2.3.3 + rollup@4.57.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.57.1 + '@rollup/rollup-android-arm64': 4.57.1 + '@rollup/rollup-darwin-arm64': 4.57.1 + '@rollup/rollup-darwin-x64': 4.57.1 + '@rollup/rollup-freebsd-arm64': 4.57.1 + '@rollup/rollup-freebsd-x64': 4.57.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 + '@rollup/rollup-linux-arm64-musl': 4.57.1 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 + '@rollup/rollup-linux-loong64-musl': 4.57.1 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 + '@rollup/rollup-linux-x64-gnu': 4.57.1 + '@rollup/rollup-linux-x64-musl': 4.57.1 + '@rollup/rollup-openbsd-x64': 4.57.1 + '@rollup/rollup-openharmony-arm64': 4.57.1 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 + '@rollup/rollup-win32-x64-gnu': 4.57.1 + '@rollup/rollup-win32-x64-msvc': 4.57.1 + fsevents: 2.3.3 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -10548,9 +10872,9 @@ snapshots: validate-npm-package-name@5.0.1: {} - vite-imagetools@9.0.2(rollup@4.50.1): + vite-imagetools@9.0.2(rollup@4.57.1): dependencies: - '@rollup/pluginutils': 5.1.3(rollup@4.50.1) + '@rollup/pluginutils': 5.1.3(rollup@4.57.1) imagetools-core: 9.1.0 sharp: 0.34.4 transitivePeerDependencies: @@ -10562,7 +10886,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.50.1 + rollup: 4.57.1 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 18.19.119 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 73844e8cc37b..4e67f5e7bc78 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -10,9 +10,7 @@ packages: - packages/kit/test/build-errors/apps/* - '!.test-tmp/**' - playgrounds/* -# pnpm catalogs are currently only suitable for dependencies that -# wouldn't require publishing a new package version -# see https://github.com/changesets/changesets/issues/1707 + catalog: '@changesets/cli': ^2.29.6 '@fontsource/libre-barcode-128-text': ^5.1.0 @@ -41,6 +39,7 @@ catalog: eslint: ^9.34.0 polka: ^1.0.0-next.28 publint: ^0.3.0 + rollup: ^4.57.1 semver: ^7.5.4 sirv-cli: ^3.0.0 svelte: ^5.48.4