Skip to content

Commit 9fc5ff3

Browse files
paoloricciutiRich-Harrisdummdidumm
authored
feat: server and client init hook (#13103)
* feat: server and client `init` hook * fix: provide client_hooks fallback even if universal hooks are present * chore: add tests * chore: cache server instance in `vite dev` * chore: remove only (duh) * chore: revert single server in dev * chore: revert weird did_it_run thing * chore: add additional inited check to prevent multiple init in case of race conditions * fix: remove `init` from client hooks * docs: write some documentation, use proper exported types * chore: regenerate types * simplify * chore: docs suggestions from review Co-authored-by: Rich Harris <[email protected]> * try this * chore: fix lint * chore: fix types * save a couple bytes * Apply suggestions from code review --------- Co-authored-by: Rich Harris <[email protected]> Co-authored-by: Rich Harris <[email protected]> Co-authored-by: Simon H <[email protected]>
1 parent 85b5716 commit 9fc5ff3

File tree

15 files changed

+126
-5
lines changed

15 files changed

+126
-5
lines changed

.changeset/eight-poems-learn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': minor
3+
---
4+
5+
feat: server and client `init` hook

documentation/docs/30-advanced/20-hooks.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,25 @@ During development, if an error occurs because of a syntax error in your Svelte
237237

238238
> [!NOTE] Make sure that `handleError` _never_ throws an error
239239
240+
### init
241+
242+
This function runs once, when the server is created or the app starts in the browser, and is a useful place to do asynchronous work such as initializing a database connection.
243+
244+
> [!NOTE] If your environment supports top-level await, the `init` function is really no different from writing your initialisation logic at the top level of the module, but some environments — most notably, Safari — don't.
245+
246+
```js
247+
/// file: src/hooks.server.js
248+
import * as db from '$lib/server/database';
249+
250+
/** @type {import('@sveltejs/kit').ServerInit} */
251+
export async function init() {
252+
await db.connect();
253+
}
254+
```
255+
256+
> [!NOTE]
257+
> In the browser, asynchronous work in `init` will delay hydration, so be mindful of what you put in there.
258+
240259
## Universal hooks
241260

242261
The following can be added to `src/hooks.js`. Universal hooks run on both server and client (not to be confused with shared hooks, which are environment-specific).

packages/kit/src/core/sync/write_client_manifest.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
151151
handleError: ${
152152
client_hooks_file ? 'client_hooks.handleError || ' : ''
153153
}(({ error }) => { console.error(error) }),
154+
${client_hooks_file ? 'init: client_hooks.init,' : ''}
154155
155156
reroute: ${universal_hooks_file ? 'universal_hooks.reroute || ' : ''}(() => {})
156157
};

packages/kit/src/exports/public.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,16 @@ export type HandleFetch = (input: {
724724
fetch: typeof fetch;
725725
}) => MaybePromise<Response>;
726726

727+
/**
728+
* The [`init`](https://svelte.dev/docs/kit/hooks#Shared-hooks-init) will be invoked before the server responds to its first request
729+
*/
730+
export type ServerInit = () => MaybePromise<void>;
731+
732+
/**
733+
* The [`init`](https://svelte.dev/docs/kit/hooks#Shared-hooks-init) will be invoked once the app starts in the browser
734+
*/
735+
export type ClientInit = () => MaybePromise<void>;
736+
727737
/**
728738
* The [`reroute`](https://svelte.dev/docs/kit/hooks#Universal-hooks-reroute) hook allows you to modify the URL before it is used to determine which route to render.
729739
* @since 2.3.0

packages/kit/src/runtime/client/client.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,9 @@ export async function start(_app, _target, hydrate) {
266266
}
267267

268268
app = _app;
269+
270+
await _app.hooks.init?.();
271+
269272
routes = parse(_app);
270273
container = __SVELTEKIT_EMBEDDED__ ? _target : document.documentElement;
271274
target = _target;

packages/kit/src/runtime/server/index.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ const prerender_env_handler = {
1515
}
1616
};
1717

18+
/** @type {Promise<any>} */
19+
let init_promise;
20+
1821
export class Server {
1922
/** @type {import('types').SSROptions} */
2023
#options;
@@ -63,7 +66,9 @@ export class Server {
6366
set_read_implementation(read);
6467
}
6568

66-
if (!this.#options.hooks) {
69+
// During DEV and for some adapters this function might be called in quick succession,
70+
// so we need to make sure we're not invoking this logic (most notably the init hook) multiple times
71+
await (init_promise ??= (async () => {
6772
try {
6873
const module = await get_hooks();
6974

@@ -73,6 +78,10 @@ export class Server {
7378
handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)),
7479
reroute: module.reroute || (() => {})
7580
};
81+
82+
if (module.init) {
83+
await module.init();
84+
}
7685
} catch (error) {
7786
if (DEV) {
7887
this.#options.hooks = {
@@ -87,7 +96,7 @@ export class Server {
8796
throw error;
8897
}
8998
}
90-
}
99+
})());
91100
}
92101

93102
/**

packages/kit/src/types/internal.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import {
1717
RequestEvent,
1818
SSRManifest,
1919
Emulator,
20-
Adapter
20+
Adapter,
21+
ServerInit,
22+
ClientInit
2123
} from '@sveltejs/kit';
2224
import {
2325
HttpMethod,
@@ -109,11 +111,13 @@ export interface ServerHooks {
109111
handle: Handle;
110112
handleError: HandleServerError;
111113
reroute: Reroute;
114+
init?: ServerInit;
112115
}
113116

114117
export interface ClientHooks {
115118
handleError: HandleClientError;
116119
reroute: Reroute;
120+
init?: ClientInit;
117121
}
118122

119123
export interface Env {

packages/kit/test/apps/basics/src/hooks.client.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@ export function handleError({ error, event, status, message }) {
88
? undefined
99
: { message: `${/** @type {Error} */ (error).message} (${status} ${message})` };
1010
}
11+
12+
export function init() {
13+
console.log('init hooks.client.js');
14+
}

packages/kit/test/apps/basics/src/hooks.server.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import fs from 'node:fs';
2-
import { sequence } from '@sveltejs/kit/hooks';
31
import { error, isHttpError, redirect } from '@sveltejs/kit';
2+
import { sequence } from '@sveltejs/kit/hooks';
3+
import fs from 'node:fs';
44
import { COOKIE_NAME } from './routes/cookies/shared';
5+
import { _set_from_init } from './routes/init-hooks/+page.server';
56

67
/**
78
* Transform an error into a POJO, by copying its `name`, `message`
@@ -154,3 +155,7 @@ export async function handleFetch({ request, fetch }) {
154155

155156
return fetch(request);
156157
}
158+
159+
export function init() {
160+
_set_from_init();
161+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
let did_init_run = 0;
2+
3+
export function _set_from_init() {
4+
did_init_run++;
5+
}
6+
7+
export function load() {
8+
return {
9+
did_init_run
10+
};
11+
}

0 commit comments

Comments
 (0)