Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: generate edge middleware to run reroute #12296

Open
wants to merge 53 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
6d536c4
wip
eltigerchino Jun 4, 2024
58db3ce
fix types
eltigerchino Jun 4, 2024
12c1dc5
use import alias instead of builder.copy replace
eltigerchino Jun 5, 2024
5682120
add netlify support
eltigerchino Jun 5, 2024
91aeaaf
oops we only need one edge function for serverless split
eltigerchino Jun 5, 2024
06337fd
readability
eltigerchino Jun 5, 2024
7eeb691
Merge branch 'main' into fix-reroute-split
eltigerchino Jun 6, 2024
25c38a0
cleanup vercel implementation
eltigerchino Jun 6, 2024
28e7f39
this can be sync
eltigerchino Jun 6, 2024
766d4f7
make temp file a variable
eltigerchino Jun 7, 2024
2f40af2
fix node types with @vercel/edge
eltigerchino Jun 8, 2024
6ccef50
add separate tsconfig for edge files
eltigerchino Jun 8, 2024
322ba6c
prettier
eltigerchino Jun 8, 2024
2306806
cleanup netlify
eltigerchino Jun 9, 2024
5cfc8eb
docs
eltigerchino Jun 16, 2024
d8c29f3
changeset
eltigerchino Jun 16, 2024
f28e444
Merge branch 'main' into fix-reroute-split
eltigerchino Jul 24, 2024
58dfb9d
Update .changeset/hot-guests-enjoy.md
eltigerchino Oct 10, 2024
2302812
Merge branch 'main' into fix-reroute-split
eltigerchino Oct 10, 2024
2cfc6c6
Merge branch 'main' into fix-reroute-split
eltigerchino Oct 29, 2024
1f9d964
fix broken lockfile
eltigerchino Oct 29, 2024
ab4705e
Merge branch 'main' into fix-reroute-split
eltigerchino Dec 2, 2024
2d82cac
check if reroute hook exists before generating reroute middleware
eltigerchino Dec 2, 2024
2125115
bump @vercel/edge to 1.1.2
eltigerchino Dec 2, 2024
59b5f4b
copy over reroute.js
eltigerchino Dec 2, 2024
dc440c1
how do I get esbuild to bundle vercel/edge?
eltigerchino Dec 2, 2024
1d1a67d
add rollup to bundle @vercel/edge
eltigerchino Dec 3, 2024
c19a4ae
format
eltigerchino Dec 3, 2024
1a88cfa
Merge branch 'main' into fix-reroute-split
eltigerchino Jan 17, 2025
e75d042
Merge branch 'main' into fix-reroute-split
eltigerchino Jan 21, 2025
aa125d5
disable duplicate import eslint rule for line
eltigerchino Jan 21, 2025
014e5dd
bump @vercel/edge
eltigerchino Jan 21, 2025
11c4a70
strip sveltekit url internals before passing url to reroute
eltigerchino Jan 21, 2025
e9a2f85
revert
eltigerchino Jan 21, 2025
a589063
Update .changeset/hot-guests-enjoy.md
eltigerchino Jan 21, 2025
46ac4df
Update documentation/docs/25-build-and-deploy/90-adapter-vercel.md
eltigerchino Jan 21, 2025
f965bc0
Merge branch 'main' into fix-reroute-split
eltigerchino Jan 21, 2025
bb7418f
restore original path and export middleware reroute function
eltigerchino Jan 23, 2025
33c763f
Merge branch 'main' into fix-reroute-split
eltigerchino Jan 23, 2025
1da5a79
remove logs
eltigerchino Jan 23, 2025
d13fb42
bump adapter kit peer version
eltigerchino Jan 23, 2025
25a88d0
format
eltigerchino Jan 23, 2025
435e12e
reword changeset
eltigerchino Jan 23, 2025
8d667e6
fix incorrect merge
eltigerchino Jan 23, 2025
95d0d42
format
eltigerchino Jan 23, 2025
4fb32a5
apparently the edge middleware preserves the original url so we don't…
eltigerchino Jan 23, 2025
9934caa
format
eltigerchino Jan 23, 2025
705f2d1
fix merge discrepencies
eltigerchino Jan 23, 2025
a352d7c
Merge branch 'main' into fix-reroute-split
eltigerchino Jan 24, 2025
e38a660
fix endless loop on Netlify
eltigerchino Jan 24, 2025
5783e9b
restore original path
eltigerchino Jan 24, 2025
09a744b
format
eltigerchino Jan 24, 2025
9fcdfc1
Apply suggestions from code review
dummdidumm Jan 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions packages/adapter-vercel/files/reroute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { reroute } from 'HOOKS';
eltigerchino marked this conversation as resolved.
Show resolved Hide resolved

// we copy the rewrite function from `@vercel/edge` because that package can't co-exist with `@types/node`.
// see https://github.com/sveltejs/kit/pull/9280#issuecomment-1452110035
Copy link
Member

Choose a reason for hiding this comment

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

Gah this is a real nuisance. The types were harmless, this one feels a bit more brittle. Is there a secret third option we haven't discovered yet? Like having an ambient d.ts file that somehow trumps both definitions by just making it : any?

Copy link
Member Author

@eltigerchino eltigerchino Jun 8, 2024

Choose a reason for hiding this comment

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

can you verify if this solution is the right one? 2f40af2 (#12296)

EDIT: oops guess not. lint failed on CI but not on my machine

Copy link
Member Author

@eltigerchino eltigerchino Jun 8, 2024

Choose a reason for hiding this comment

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

tried another hack: move the edge files to their own folder with their own tsconfig so that the adapter never includes files importing from @vercel/edge, hence the process variable declaration never clashes with node 6ccef50 (#12296)


/**
* https://github.com/vercel/vercel/blob/4337ea0654c4ee2c91c4464540f879d43da6696f/packages/edge/src/middleware-helpers.ts#L38-L55
* @param {import('index.js').ExtraResponseInit | undefined} init
* @param {Headers} headers
*/
function handleMiddlewareField(init, headers) {
if (init?.request?.headers) {
if (!(init.request.headers instanceof Headers)) {
throw new Error('request.headers must be an instance of Headers');
}

const keys = [];
for (const [key, value] of init.request.headers) {
headers.set('x-middleware-request-' + key, value);
keys.push(key);
}

headers.set('x-middleware-override-headers', keys.join(','));
}
}

/**
* https://github.com/vercel/vercel/blob/4337ea0654c4ee2c91c4464540f879d43da6696f/packages/edge/src/middleware-helpers.ts#L101-L114
* @param {string | URL} destination
* @returns {Response}
*/
export function rewrite(destination) {
const headers = new Headers({});
headers.set('x-middleware-rewrite', String(destination));

handleMiddlewareField(undefined, headers);

return new Response(null, {
headers
});
}

/**
*
* @param {import('index.js').ExtraResponseInit=} init
* @returns {Response}
*/
export function next(init) {
const headers = new Headers(init?.headers ?? {});
headers.set('x-middleware-next', '1');

handleMiddlewareField(init, headers);

return new Response(null, {
...init,
headers
});
}

/**
* @param {Request} request
* @returns {Response}
*/
export default function middleware(request) {
const pathname = reroute({ url: new URL(request.url) });
return pathname ? rewrite(pathname) : next(request);
}
37 changes: 37 additions & 0 deletions packages/adapter-vercel/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,40 @@ export interface RequestContext {
*/ promise: Promise<unknown>
): void;
}

export interface ModifiedRequest {
/**
* If set, overwrites the incoming headers to the origin request.
*
* This is useful when you want to pass data between a Middleware and a
* Serverless or Edge Function.
*
* @example
* <caption>Add a `x-user-id` header and remove the `Authorization` header</caption>
*
* ```ts
* import { rewrite } from '@vercel/edge';
* export default async function middleware(request: Request): Promise<Response> {
* const newHeaders = new Headers(request.headers);
* newHeaders.set('x-user-id', 'user_123');
* newHeaders.delete('authorization');
* return rewrite(request.url, {
* request: { headers: newHeaders }
* })
* }
* ```
*/
headers?: Headers;
}

export interface ExtraResponseInit extends Omit<ResponseInit, 'headers'> {
/**
* These headers will be sent to the user response
* along with the response headers from the origin.
*/
headers?: HeadersInit;
/**
* Fields to rewrite for the upstream request.
*/
request?: ModifiedRequest;
}
103 changes: 102 additions & 1 deletion packages/adapter-vercel/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ const plugin = function (defaults = {}) {
}

const node_runtime = /nodejs([0-9]+)\.x/.exec(runtime);
if (runtime !== 'edge' && (!node_runtime || node_runtime[1] < 18)) {
if (runtime !== 'edge' && (!node_runtime || +node_runtime[1] < 18)) {
throw new Error(
`Invalid runtime '${runtime}' for route ${route.id}. Valid runtimes are 'edge' and 'nodejs18.x' or higher ` +
'(see the Node.js Version section in your Vercel project settings for info on the currently supported versions).'
Expand Down Expand Up @@ -293,6 +293,107 @@ const plugin = function (defaults = {}) {

const singular = groups.size === 1;

const hooks_filename = builder.config.kit.files.hooks.universal.split('/').at(-1);
const hooks_output_path = `${builder.getServerDirectory()}/chunks/${hooks_filename}.js`;

if (!singular && hooks_output_path) {
/**
* @param {string} name
* @param {import('.').EdgeConfig} config
*/
async function generate_middleware(name, config) {
const tmp = builder.getBuildDirectory(`vercel-tmp/${name}`);
const relativePath = path.posix.relative(tmp, hooks_output_path);

builder.copy(`${files}/${name}.js`, `${tmp}/${name}.js`, {
replace: {
HOOKS: relativePath
}
});

try {
const result = await esbuild.build({
entryPoints: [`${tmp}/${name}.js`],
outfile: `${dirs.functions}/${name}.func/index.js`,
target: 'es2020', // TODO verify what the edge runtime supports
bundle: true,
platform: 'browser',
format: 'esm',
external: [
...compatible_node_modules,
...compatible_node_modules.map((id) => `node:${id}`),
...(config.external || [])
],
sourcemap: 'linked',
banner: { js: 'globalThis.global = globalThis;' },
loader: {
'.wasm': 'copy'
}
});

if (result.warnings.length > 0) {
const formatted = await esbuild.formatMessages(result.warnings, {
kind: 'warning',
color: true
});

console.error(formatted.join('\n'));
}
} catch (error) {
for (const e of error.errors) {
for (const node of e.notes) {
const match =
/The package "(.+)" wasn't found on the file system but is built into node/.exec(
node.text
);

if (match) {
node.text = `Cannot use "${match[1]}" when deploying to Vercel Edge Functions.`;
}
}
}

const formatted = await esbuild.formatMessages(error.errors, {
kind: 'error',
color: true
});

console.error(formatted.join('\n'));

throw new Error(
`Bundling with esbuild failed with ${error.errors.length} ${
error.errors.length === 1 ? 'error' : 'errors'
}`
);
}

write(
`${dirs.functions}/${name}.func/.vc-config.json`,
JSON.stringify(
{
runtime: 'edge',
regions: config.regions,
entrypoint: 'index.js',
framework: {
slug: 'sveltekit',
version: VERSION
}
},
null,
'\t'
)
);
}

static_config.routes.push({
src: '/.*',
middlewarePath: 'reroute',
continue: true
});

await generate_middleware('reroute', { external: defaults?.external });
}

for (const group of groups.values()) {
const generate_function =
group.config.runtime === 'edge' ? generate_edge_function : generate_serverless_function;
Expand Down
5 changes: 5 additions & 0 deletions packages/adapter-vercel/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ declare module 'MANIFEST' {
import { SSRManifest } from '@sveltejs/kit';
export const manifest: SSRManifest;
}

declare module 'HOOKS' {
import { Reroute } from '@sveltejs/kit';
export const reroute: Reroute;
}
Loading