Skip to content

Commit

Permalink
Merge branch 'stalkerg-server-fetch'
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich-Harris committed Jun 28, 2021
2 parents 7292bc7 + 064f848 commit 1b13416
Show file tree
Hide file tree
Showing 12 changed files with 114 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/pretty-beans-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

Implement serverFetch hook
25 changes: 25 additions & 0 deletions documentation/docs/04-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,28 @@ export function getSession(request) {
```
> `session` must be serializable, which means it must not contain things like functions or custom classes, just built-in JavaScript data types
### serverFetch
This function allows you to modify (or replace) a `fetch` request for an **external resource** that happens inside a `load` function that runs on the server (or during pre-rendering).
For example, your `load` function might make a request to a public URL like `https://api.yourapp.com` when the user performs a client-side navigation to the respective page, but during SSR it might make sense to hit the API directly (bypassing whatever proxies and load balancers sit between it and the public internet).

```ts
type ServerFetch = (req: Request) => Promise<Response>;
```

```js
/** @type {import('@sveltejs/kit').ServerFetch} */
export async function serverFetch(request) {
if (request.url.startsWith('https://api.yourapp.com/')) {
// clone the original request, but change the URL
request = new Request(
request.url.replace('https://api.yourapp.com/', 'http://localhost:9999/'),
request
);
}
return fetch(request);
}
```
3 changes: 2 additions & 1 deletion packages/kit/src/core/build/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,8 @@ async function build_server(
// named imports without triggering Rollup's missing import detection
const get_hooks = hooks => ({
getSession: hooks.getSession || (() => ({})),
handle: hooks.handle || (({ request, resolve }) => resolve(request))
handle: hooks.handle || (({ request, resolve }) => resolve(request)),
serverFetch: hooks.serverFetch || fetch
});
const module_lookup = {
Expand Down
3 changes: 2 additions & 1 deletion packages/kit/src/core/dev/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ class Watcher extends EventEmitter {
},
hooks: {
getSession: hooks.getSession || (() => ({})),
handle: hooks.handle || (({ request, resolve }) => resolve(request))
handle: hooks.handle || (({ request, resolve }) => resolve(request)),
serverFetch: hooks.serverFetch || fetch
},
hydrate: this.config.kit.hydrate,
paths: this.config.kit.paths,
Expand Down
3 changes: 2 additions & 1 deletion packages/kit/src/runtime/server/page/load_node.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ export async function load_node({

if (/^[a-zA-Z]+:/.test(url)) {
// external fetch
response = await fetch(url, /** @type {RequestInit} */ (opts));
const request = new Request(url, /** @type {RequestInit} */ (opts));
response = await options.hooks.serverFetch.call(null, request);
} else {
const [path, search] = url.split('?');

Expand Down
13 changes: 13 additions & 0 deletions packages/kit/test/apps/basics/src/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,16 @@ export async function handle({ request, resolve }) {
};
}
}

/** @type {import('@sveltejs/kit').ServerFetch} */
export async function serverFetch(request) {
let newRequest = request;
if (request.url.endsWith('/server-fetch-request.json')) {
newRequest = new Request(
request.url.replace('/server-fetch-request.json', '/server-fetch-request-modified.json'),
request
);
}

return fetch(newRequest);
}
37 changes: 37 additions & 0 deletions packages/kit/test/apps/basics/src/routes/load/_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,43 @@ export default function (test, is_dev) {
server.close();
});

test('handles external api', '/load', async ({ base, page }) => {
const port = await ports.find(4000);

/** @type {string[]} */
const requested_urls = [];

const server = http.createServer(async (req, res) => {
requested_urls.push(req.url);

if (req.url === '/server-fetch-request-modified.json') {
res.writeHead(200, {
'Access-Control-Allow-Origin': '*',
'content-type': 'application/json'
});

res.end(JSON.stringify({ answer: 42 }));
} else {
res.statusCode = 404;
res.end('not found');
}
});

await new Promise((fulfil) => {
server.listen(port, () => fulfil());
});

await page.goto(`${base}/load/server-fetch-request?port=${port}`);

assert.equal(requested_urls, [
'/server-fetch-request-modified.json'
]);

assert.equal(await page.textContent('h1'), 'the answer is 42');

server.close();
});

test(
'makes credentialed fetches to endpoints by default',
'/load',
Expand Down
1 change: 1 addition & 0 deletions packages/kit/test/apps/basics/src/routes/load/index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@
<a href="/load/fetch-credentialed">fetch credentialed</a>
<a href="/load/large-response">large response</a>
<a href="/load/raw-body">raw body</a>
<a href="/load/server-fetch-request">server fetch request</a>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<script context="module">
/** @type {import('@sveltejs/kit').Load} */
export async function load({ page, fetch }) {
const url = `http://localhost:${page.query.get('port')}/server-fetch-request.json`;
// @ts-ignore
const res = await fetch(url);
const { answer } = await res.json();
return {
props: { answer }
};
}
</script>

<script>
/** @type {number} */
export let answer;
</script>

<h1>the answer is {answer}</h1>
2 changes: 2 additions & 0 deletions packages/kit/types/hooks.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ export type Handle<Locals = Record<string, any>> = (input: {
request: ServerRequest<Locals>;
resolve: (request: ServerRequest<Locals>) => MaybePromise<ServerResponse>;
}) => MaybePromise<ServerResponse>;

export type ServerFetch = (req: Request) => Promise<Response>;
5 changes: 3 additions & 2 deletions packages/kit/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ export {
GetSession,
Handle,
ServerRequest as Request,
ServerResponse as Response
} from './hooks';
ServerResponse as Response,
ServerFetch
} from './hooks';
3 changes: 2 additions & 1 deletion packages/kit/types/internal.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Load } from './page';
import { Incoming, GetSession, Handle, ServerResponse } from './hooks';
import { Incoming, GetSession, Handle, ServerResponse, ServerFetch } from './hooks';
import { RequestHandler } from './endpoint';

type PageId = string;
Expand Down Expand Up @@ -111,6 +111,7 @@ export type SSRManifest = {
export type Hooks = {
getSession?: GetSession;
handle?: Handle;
serverFetch?: ServerFetch;
};

export type SSRNode = {
Expand Down

0 comments on commit 1b13416

Please sign in to comment.