-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Hook for initialization? #1538
Comments
Possibly related to #1530 |
That issue is about client initialization. But yeah, the two issues should probably be considered en masse to ensure an intuitive API. |
Another way to solve this problem is that svelte kit offers a central place to initialize such clients, and pass the clients as an argument to endpoints and hooks: export async function get({ params, svcs }) {
const { slug } = params;
const article = await svcs.db.get(slug);
// ...
} This seems to be more conventional than the approach that a module is directly usable as an external service. |
You seem to have missed the kit/packages/create-svelte/templates/default/src/hooks.ts Lines 1 to 7 in 6372690
There's no rule that you have to re-do work every request (or that the information involved has to be personalized to the user that made the request). Let me demonstrate: // src/hooks.js
import { stuff } from "db-library";
async function setupDatabaseOrAnythingReally() {
const dbClient = await stuff({ ...options });
return dbClient;
}
// Initialize the database *once*
const dbPromise = setupDatabaseOrAnythingReally();
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ request, render }) {
// Wait for the db to initialize the first time
// or instantly get the result of when it was initialized
const db = await dbPromise;
// Make the database available to the endpoint answering the request
request.locals = { db };
return await render(request);
}
// src/routes/blog/[article].json.js
export async function get({ params, locals }) {
const { slug } = params;
const article = await locals.db.get(slug);
// ...
} |
Another maintainer can re-open this if I'm mistaken about what this feature request was about. |
Thanks for the tip. However I think this means that clients are actually initialized at the first connection, potentially delaying it (and other connections) if the initialization is not trivial. |
I believe that promises automatically "start executing" the next tick after being created, so this shouldn't be an issue. You can verify by putting a console log at the beginning of the initialization function and see when it shows up. |
Oh you are correct. I forgot how promise works lol. Thanks. This works. |
Two minor issues I encountered while working with real code:
For |
Wrap the stuff you don't want to happen during |
I've just spent an unnecessarily long time with a certain problem that might be simpler if this was a thing, I was thinking to post this in #1530, but "hook for initialization" might be more appropriate. Can this be reconsidered as it would be more straightforward than the solution/workaround below? The problem I was having is with an external library that provides an Now, I feel it's reasonable to assume that Inlining the options in the I even tried to block off the whole kit/documentation/docs/04-hooks.md Line 11 in eae1b1d
|
@babichjacob Great tip! I totally forgot about build flags. @ignatiusmb I believe hooks are server side only, "pages" in the referenced doc refers to their SSR step. I do think |
Actually, I think I should ask before writing the doc: Will the maintainers accept an PR that add a chapter about (server side) initialization to the doc? Should it be in its own chapter? Putting it in a subsection of the hooks chapter seems to assume it's obvious that initialization should happen in the hooks file, which I don't think is the case. |
Reopening to reconsider a single file approach and would probably resolve #1530 as well |
I think it would be confusing if most hooks ran only on the server-side, but one ran on the client-side, so I wouldn't put something that runs on both in There are certain things you want to do only on the server-side and I think
This is a Node feature and you need to be running at least Node 14.8 for it to be present Running code in |
I just want to add that what I'm looking for in an initialization hook is the ability to guarantee certain code finishes running before any of the rest of the server begins listening, since I don't want to wait until I receive a request to know that some of my initialization hasn't finished yet.
Also, it seems much more like a hack than something that SvelteKit was made to support. |
Let's say I'm using If I use that method both in
it initializes my Is there a workaround today? I'm using it like this because in |
Here's an approach I just thought of. Create a store in a separate module, say // urqlclient.ts
import { initClient } from 'svelte-urql';
const noop = () => {};
let client = null;
export const urqlclient = readable(null, (set) => {
if (!client) {
client = initClient('whatever params you need');
}
set(client);
return noop;
}); Then when you need an URQL client, get it from the store via This will allow the rest of your code to just do |
While the workaround (above) seems to clearly work - it requires a huge amount of typescript code for something that could be solved with one or three lines in other stacks like Vue3. Or am I getting something wrong in this TS version import { firebaseConfig } from '../env'
import { initializeApp } from 'firebase/app'
import type { FirebaseApp } from 'firebase/app'
import { readable } from 'svelte/store'
import type { Readable } from 'svelte/store'
// SvelteKit init for Firebase at the _Client side_
// eslint-disable-next-line @typescript-eslint/no-empty-function
const noop = () => {}
let app:FirebaseApp|null = null
export const firebaseApp:Readable<FirebaseApp> = readable(null, (set) => {
if (!app) {
app = initializeApp(firebaseConfig)
}
set(app)
return noop
}) Almost the same block of readable init is required for other firebase modules, normally initiated with simple Obviously, the question here is, how important the statically rendered SPA's are? After all, this can be done easily and in a lot more sensible manner at the server-side, if a static app bundle is not a requirement. |
Top-level await was broken in Vite at the time of #1538 (comment), but is since fixed, which means this problem is very easily solved — you can do your setup work in const db = await createConnection();
export function handle({ request, resolve }) {
request.locals.db = db;
return resolve(request);
} ...or if that feels weird you can do it in a module that's imported by any endpoints that need it: // src/lib/db.js
export const db = await createConnection(); // src/routes/my-endpoint.js
import { db } from '$lib/db';
export async function get() {
return {
body: await db.get(...)
}
} The latter technique also applies to clients that are needed by pages, like Urql. Demo here. Given all this, is a dedicated initialization hook necessary? |
@Rich-Harris if there is some complicated logic at init, that takes some time, it would be better to do init immediately when server start instead when first request come, as it slows first request. But actually it doesn't need to be init hook, just execute hooks.js file at server start. |
Alternately, you can start your server and immediately do a request to trigger the init logic: |
@Mlocik97 An ICMP Echo Request is not even visible to a web server. |
oh right, I meant to do GET command, somehow wrote bad one.. but it can be done same way: exec(`GET ${process.env.server_url}`) or even better, use "poststart" in package.json with GET command... You can create endpoint, for that, that will return nothing, just status, so You don't burden server. |
@Rich-Harris
Is there a workaround? |
* default to target: node16 - fixes #1538 * Update packages/kit/src/core/build/build_server.js * Update .changeset/hungry-coats-sort.md Co-authored-by: Conduitry <git@chor.date> Co-authored-by: Conduitry <git@chor.date>
yes |
Is your feature request related to a problem? Please describe.
It should be pretty common for a svelt kit app to utilize external services that you need to initialize clients to talk to them. The doc demonstrates such use case:
One issue with this code is that it assumes
db
can be initialized in the module itself either synchronously (which is not always the case) or via top-levelawait
(which svelte kit doesn't seem to support).Describe the solution you'd like
Offer a hook like
async func init() {}
that only runs once when it starts?Describe alternatives you've considered
How important is this feature to you?
Very, since it's pretty awkward to workaround when the initialization is async (e.g., force initialization to be synchronous and only call the async part when actually talk to external services).
Additional context
The text was updated successfully, but these errors were encountered: