-
-
Notifications
You must be signed in to change notification settings - Fork 460
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
RFC: svelte-urql: enable to query in SvelteKit load() function #1819
Comments
Question: is @urql/svelte usable with SvelteKit at all currently? I'm seeing <script>
// src/routes/index.svelte
import { onMount } from 'svelte';
import { getClient } from '@urql/svelte';
let client;
onMount(() => {
client = getClient();
});
</script> I saw in the docs this error might happen thanks to vite, but have added the recommended config change: // svelte.config.js
const config = {
kit: {
vite: {
optimizeDeps: {
exclude: ['@urql/svelte']
}
}
}
};
export default config; |
I'm using |
I'm using |
Seems it was due to calling |
I'm wondering what the current state of @urql/svelte + SvelteKit + SSR is? It would be fantastic to get a canonical example of how to do it, particularly around the SvelteKit I'm using a strategy based off of this gist: https://gist.github.com/sastan/85cf3d011b152a80d3a5933a09df21f6 The Then the route's This seems to work most of the time, but variable reactivity doesn't work. Specifically, changing the I've tried both Guessing it has something to do with the way the OperationStore is created in the loader, passed to the component, and Also the above solution seems a bit kludgy and I'm guessing (hoping) there's a more straightforward way to do this? |
I've been giving this some thought and well, it's a tough one. The issue being that the context doesn't work during ssr which means that methods behave differently in ssr vs csr. Secondly there isn't really a way to take the ssr-data and put it in an hydratable location for svelte-kit. An "ideal" workflow would be the following:
This would set us up for a seamless integration, as far as I can tell, no actual data-fetching library seems to have found a way to integrate with svelte-kit 😅 Changing the signature alone would not be off much use as this would tie too much into reactivity meaning there is no good heuristic to tell if all data has been loaded, hence why in Node Svelte opts for the async load helper rather than the normal render method. |
The It sounds similar to the gist I posted: the urql client executes the query in SSR, serializes the data, and the urql I'm doing this now and it's not 100% working with reactivity, but it does seem to use the serialized responses from the It's probably worthwhile for me to post my exact code: // __layout.svelte
<script context="module">
export const load = ({ fetch, stuff }) => {
const client = createClient({
fetch,
uri: '/graphql',
// ... other config
});
async function query(
queryDocument,
variables,
context = {}
) {
let store = operationStore(query, variables, context);
const result = await client.query(store.query, store.variables, store.context).toPromise();
return Object.assign(get(store), result);
}
return {
stuff: {
...stuff,
client,
query
},
props: { client }
};
};
</script>
<script>
import { setClient } from '@urql/svelte';
export let client: Client;
setClient(client);
</script>
<slot /> Possible problems with the above:
|
Yikes that's not great news. Someone from SvelteKit should probably be looped in on this. |
I think I found a solution @disbelief it's a bit hacky but shows the general idea 😅 JoviDeCroock/urql-svelte-kit@fe55295 someone with more svelte-knowledge might be able to clean this up 😅 |
Can we please contact someone from SvelteKit's team? |
@JoviDeCroock thanks this is a promising approach! It would take me some time to refactor my app like this, so I can't confirm it works for me just yet. |
@Rich-Harris wondering if you could provide any insight or know a good person on the SvelteKit team we could ask about this? |
I feel like @bluwy may know a lot about GraphQL and SvelteKit... the contributors over there seem super busy though |
Hey 👋 I've not used urql before but I can give some thoughts on this.
Those should be fixed since a couple months ago, we've fixed Vite's pre-bundling in vite-plugin-svelte side which resolved most of the quirks in the past.
I've actually written a blog about Apollo and Sapper before, but needs to be adapted to urql and SvelteKit. Looking back at the blog, I actually prefer Method 1 now and it seems to be closer to what most of the discussion here proposes. I'm not sure if the trick shown works in SvelteKit anymore, but if it does I'd be interested to know.
I think it makes sense to allow querying in |
Just throwing in my two cents here, since I have been facing this same issue with the Apollo client and are looking towards creating some samples using Urql instead to propose the switch in our app. Svelte kit actually caches fetch calls in the load function to make sure it does not make the call again on the client. What I did using Apollo client was simply instantiating the client using the fetch method provided in the load function, and then using the client to query the api both during ssr and csr svelte kit then automatically recognizes that the second fetch request on the client is identical to the one performed during ssr and serves the "cached" response. |
This makes sense: no need to do our own request/response serialization when SvelteKit's But for some reason things get messed up (for me) when working with urql Perhaps instead of creating the // src/routes/__layout.svelte
<script context="module">
import { createClient } from '@urql/svelte';
export async function load({ fetch, stuff }) {
const client = createClient({
// Pass in the fetch from sveltekit to have access to serialized requests
fetch,
});
return {
stuff: { ...stuff, client },
props: { client }
}
}
</script>
// src/routes/myRoute.svelte
<script context="module">
import { browser } from '$app/env'
export async function load({ stuff }) {
if (!browser) {
stuff.client.query(SOME_QUERY, SOME_VARIABLES);
}
return {
props: {}
};
}
</script>
<script>
import { query, operationStore } from '@urql/svelte';
const opStore = operationStore(SOME_QUERY, SOME_VARIABLES);
// should use the serialized result in the client if the query+variables are the same
query(opStore);
</script> |
Alright I believe I got the same idea working. In the layout file, you create the client, and return it as a prop, you can then set it on the context, so all child components can retrieve it, you also store it in "stuff" so your subsequent load functions has access to it, you create it using the svelte-kit provided fetch, so that it may use svelte-kit caching <!--__layout.svelte-->
<script lang="ts" context="module">
import type { Load } from '@sveltejs/kit';
import { createClient, setClient } from '@urql/svelte';
export const load: Load = async ({ fetch }) => {
const client = createClient({
url: '/graphql',
fetch
});
return {
stuff: {
client
},
props: {
client
}
};
};
</script>
<script lang="ts">
export let client;
setClient(client);
</script>
<slot /> On on routes you can now <!-- /someroute/index.svelte -->
<script lang="ts" context="module">
export const load: Load = async ({ stuff }) => {
const store = operationStore(`
query {
tasks {
name
}
}
`);
await stuff.client.query(store.query, store.variables, store.context).toPromise();
return {
props: {
store,
}
};
};
</script>
<script lang="ts">
import type { Load } from '@sveltejs/kit';
import { operationStore, query } from '@urql/svelte';
export let store;
query(store);
</script> Your store will then have the value server side rendered but not make multiple requests :) Edit: Ah just missed your solution also @disbelief |
@AndreasHald this is what I was doing but it runs into problems when modifying variables or refetching on the client side. Just realized after posting my above suggestion that it won't work because the urql |
Perhaps the ideal solution is to be able to provide a client (or |
@disbelief Yeah it seems you need to return it as a prop and call |
Interesting. Perhaps that's a red herring / bug coming from elsewhere in my code. |
@disbelief how are you reassigning variables? But I think that's a svelte thing, where the "reactivity" is not aware that variables has changed unless you actually reassign it, in the example that does not work the variable being reassigned is actually variables.index however I could be wrong. |
Yep, aware that the entire I tried these two ways: $opStore.variables = { ...variables, page: page + 1 }; and $opStore.set({ variables: { ...variables, page: page + 1 } }); |
My current solution is to issue a brand new operation store+query on the client to fetch the subsequent pages, vs reusing the operation store that the load function provides. Which obvs kinda sucks. |
I figured it out. Please post this official docs as it follows the same pattern as the other frameworks.
urql.ts export const ssr = ssrExchange({
isClient: !isServerSide,
initialState: !isServerSide ? window['__URQL_DATA__'] : undefined,
});
...
const client = createClient({
url: `YOUR URL`,
exchanges: [
dedupExchange,
cacheExchange,
ssr,
... YOUR EXCHANGES
fetchExchange
]
});
export const data = `
<script lang="ts">
window.__URQL_DATA__ = JSON.parse(JSON.stringify(__SSR__));
</script>
`;
export default client index.svelte <script context="module" lang="ts">
import { dev, browser } from '$app/env';
import { ssr, data, client } from '../modules/urql';
export async function load() {
if (browser) {
ssr.restoreData(window['__URQL_DATA__']);
}
let r = await client.query(....).toPromise();
const d = browser
? 'null'
: data.replace('__SSR__', JSON.stringify(ssr.extractData()));
return { props: { r, d } };
}
</script>
<script lang="ts">
export let d: any;
export let r: any[];
...
</script>
{@html d} Hope this helps someone, J |
Hey @jdgamble555, Regarding your example index.svelte, I think you need to add a
My next task is to get subscriptions working on the rehydrated client. |
While the above solution is appreciated and seems promising (thank you @jdgamble555), I'd argue that the goal of the |
@gmanfredi - Yes, you need @disbelief - @urql/svelte simply cannot work with sveltekit the way it currently stands, as it was not written to work with SSR (although it should have so it could work with Sapper). That being said, you can use @urql/core without problems. You can actually do this with Angular as well, although they don't claim to support it (I have written apps with urql/Angular). Since the I have not tested it, but it should be possible like I did here. Let me know if someone simplifies this! J |
@jdgamble555 yes the solutions myself and others posted above do what you describe -- passing the SvelteKit-provided |
Hi @disbelief. I was talking about using the svelte I think if you pass in the J |
Hi all — I was tagged earlier so have been dimly aware of this thread happening; sorry for not weighing in sooner. I will read the whole thing when I get a chance! In the meantime, I just wanted to make you aware of this: sveltejs/kit#2979. It's a proposal for (hopefully) making it easy for things like |
@jdgamble555 yes it's possible to provide Svelte's
The above should use the sveltekit-fetch-enabled client for all |
@Rich-Harris thanks for weighing in, I'm sure you're pretty busy so appreciate the reply. The linked proposal looks like a quite useful convenience helper. Generally what I was looking for is a well-defined convention for using an external client in SvelteKit's Perhaps the current "monstrosities" approach you outlined in that issue is the canonical way of doing this at the moment? |
It is the canonical approach right now, yes (or at least it's the approach I've been taking). Given the positive feedback on the proposal so far, I'm keen to make a start on it fairly soon, so hopefully it won't be canonical for long |
Will this solution allow proper prerendered routes using the query information? |
@rogueyoshi in my experience, yes it works. |
@disbelief - I was saying I cannot see how @urql/svelte would work in sveltekit, not whether or not passing the fetch would work. I was simply stating that what was mentioned above, was not the same thing. Either way, the problem is that the fetch function only exists in the load function. That suggests you need to repass the fetch to the load function for every single component, instead of doing it one time through out your app. That is a problem. Sharing a Anyone have any thoughts not being limited to the load function? J |
You can do what I said above just once in the This gist might provide a bit more clarity though it uses the old "context" naming instead of |
Is there any update on this issue? |
Obsolete and potentially resolved by: #2370 |
Summary
SvelteKit allows ServerSide rendering of the web application pages. One of the main benefit is to be able to load backend data from the server, have it rendered as HTML. The SvelteKit way of doing it is by adding the pre-rendering code in the
<script context="module">
part of the page.One ideal setup would be to:
However, Svelte Contexts are not available there. Which means the query(operationStore) cannot run, because it fetches the urql client through svelte contexts here.
Having a way to pass the urql client to the query function would allow such use case.
Proposed Solution
Change the query function signature from:
to
This would allow to either use query like now:
or to use specify a custom client
Having that in place, SvelteKit users can easily plug their code:
Please tell me what you think about this proposal.
The text was updated successfully, but these errors were encountered: