-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
feat: middleware for several adapters #13477
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
base: main
Are you sure you want to change the base?
Changes from 42 commits
8269fd8
9d0beac
fd8c296
b5ea739
ac0ca4b
9a38b48
2d406bd
253318a
05e9b62
8b8c4ac
344dd7b
cb2d693
db4ed04
b6e623f
1c9b512
fe1b82b
45b4706
c20d423
62289cb
9a3c6e2
e994082
05c941e
1d9839a
5d5eb26
70ad0f8
b159f21
4335dc5
b5f68cb
37bc902
5585df2
0b5396e
76f5a9b
679759f
e39683e
c1f7591
0876716
8524d6f
6e90adc
083b22d
bec3bed
4e948fa
b367434
1ee1d04
a2c5222
30a3680
a2bb531
cd116e9
663d59d
f9efcbd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@sveltejs/adapter-vercel': minor | ||
--- | ||
|
||
feat: support edge middleware |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@sveltejs/adapter-node': minor | ||
--- | ||
|
||
feat: add possibility for adding polka/express middleware | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@sveltejs/kit': minor | ||
--- | ||
|
||
feat: allow adapters to influence compilation entry points and to intercept requests at dev/preview time |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@sveltejs/adapter-vercel': minor | ||
--- | ||
|
||
feat: add possibility of defining edge middleware | ||
dummdidumm marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@sveltejs/adapter-cloudflare': minor | ||
--- | ||
|
||
feat: add pages-like middleware |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -107,6 +107,70 @@ Cloudflare Workers specific values in the `platform` property are emulated durin | |||||
|
||||||
For testing the build, you should use [Wrangler](https://developers.cloudflare.com/workers/wrangler/) **version 3**. Once you have built your site, run `wrangler pages dev .svelte-kit/cloudflare`. | ||||||
|
||||||
## Pages Middleware | ||||||
|
||||||
You can deploy one middleware function that closely follows the [Pages Middleware API](https://developers.cloudflare.com/pages/functions/middleware/). You can use it to intercept requests even for prerendered pages. Combined with using [server-side route resolution](configuration#router) you can make sure it runs prior to all navigations, no matter client- or server-side. This allows you to for example run A/B-tests on prerendered pages by rerouting a user to either variant A or B depending on a cookie. | ||||||
dummdidumm marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
||||||
> [!NOTE] It isn't really Pages Middleware because the adapter compiles to a [single `_worker.js` file](https://developers.cloudflare.com/pages/platform/functions/#advanced-mode) (also see the [Notes](#Notes) section), which ignores middleware, but it closely mirrors its capabilities. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Pages Middleware and Functions are all compiled to a single |
||||||
|
||||||
To get started, place a `cloudflare-middleware.js` file at the root of your project and export a `onRequest` function from it: | ||||||
|
||||||
```js | ||||||
/// file: cloudflare-middleware.js | ||||||
// @filename: ambient.d.ts | ||||||
declare module '@cloudflare/workers-types'; | ||||||
|
||||||
// @filename: index.js | ||||||
// ---cut--- | ||||||
import { normalizeUrl } from '@sveltejs/kit'; | ||||||
|
||||||
/** | ||||||
* @param {import('@cloudflare/workers-types'.EventContext)} context | ||||||
dummdidumm marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
*/ | ||||||
export function onRequest({ request, next }) { | ||||||
const url = new URL(request.url); | ||||||
|
||||||
if (url.pathname !== '/') return next(); | ||||||
|
||||||
// Retrieve cookies which contain the feature flags. | ||||||
let flag = split_cookies(request.headers.get('cookie') ?? '')?.['flags']; | ||||||
|
||||||
// Fall back to random value if this is a new visitor | ||||||
flag ||= Math.random() > 0.5 ? 'a' : 'b'; | ||||||
dummdidumm marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
// Get destination URL based on the feature flag | ||||||
request = new Request(new URL(flag === 'a' ? '/home-a' : '/home-b', url), request); | ||||||
|
||||||
const response = await next(request); | ||||||
|
||||||
// Set a cookie to remember the feature flags for this visitor | ||||||
response.headers.set('Set-Cookie', `flags=${flag}; Path=/`); | ||||||
|
||||||
return response; | ||||||
} | ||||||
|
||||||
/** @param {string} cookies */ | ||||||
function split_cookies(cookies) { | ||||||
dummdidumm marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
return cookies.split(';').reduce( | ||||||
(acc, cookie) => { | ||||||
const [name, value] = cookie.trim().split('='); | ||||||
acc[name] = value; | ||||||
return acc; | ||||||
}, | ||||||
{} as Record<string, string> | ||||||
); | ||||||
} | ||||||
|
||||||
``` | ||||||
|
||||||
The `context` parameter closely follows the [EventContext](https://developers.cloudflare.com/pages/functions/api-reference/#eventcontext) object but is missing some Pages-specific parameters such as `data`, `params` and `functionPath`. | ||||||
|
||||||
The middleware runs on all requests that your worker is invoked for, which is dependent on the [`include/exlcude` options](#Options-routes). | ||||||
dummdidumm marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
||||||
> [!NOTE] Locally during dev and preview this only approximates the capabilities of middleware. Notably, you cannot read the request or response body, and middleware runs on all requests except those that would end up in `_app/immutable`. | ||||||
|
> [!NOTE] Locally during dev and preview this only approximates the capabilities of middleware. Notably, you cannot read the request or response body, and middleware runs on all requests except those that would end up in `_app/immutable`. | |
> [!NOTE] During `dev` and `preview` you cannot read the request or response body. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For dev I realise that it's not running on files that are being transformed by Vite, though IIUC that's not adapter-specific, and I'm not totally sure I understand what we're excluding and why
It's excluding everything that looks like unbundled files that will not ever be requested like that during production (because it's bundled and the path will be totally different). In other words everything that would end up in _app/immutable
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this pairs with a separate concern I have, which is that someone might expect to be able to do this sort of thing...
const response = await next();
return new Response(response.body!.pipeThrough(transform), response);
...which is currently prohibited in these middlewares. If it wasn't, then I'd expect to be able to transform source code at runtime whether in dev or prod. (I feel like it has to be possible, I have distinct memories of monkey-patching res.write
and res.end
before calling next()
in Express apps of yore.)
Perhaps that's an unreasonable expectation, though if we're not going to expose the full capabilities of the platform then I find myself wondering anew about lowest-common-denominator cross-platform APIs...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, we are exposing them, but not at dev/preview time.
Is it realistic that someone would do these things in a SvelteKit app. Feels like something you'd do in the handle hook.
So how to proceed here? Investigate if this is possible to replicate? And go with the lowest common denominator after all if not? Or just do that right away? I get the feeling that you're not really happy with this uncanny valley after looking more closely.
Uh oh!
There was an error while loading. Please reload this page.