-
-
Notifications
You must be signed in to change notification settings - Fork 432
Feature request: Preload argument for context #751
Comments
Haven't heard anything, but it feels like it wouldn't be terribly difficult to implement so I think I will give it a try. Still, any guidance you can give would be great. Telling me "no" would also be appreciated before I put in the effort. :) One change I'd potentially make is to actually find a way to run the import { setContext } from 'svelte';
sapper.start({
context: () => {
setContext('apolloClient', createClient());
setContext('mixpanel', window.mixpanel);
setContext('honeycomb', honeycombProxy);
}
}) Is that a better API? Should it still be called |
…ent.js This is a preliminary commit for supporting root-level context on server and client. It currently does _too_ much and I need feedback on how to proceed. Here's what it currently does. In client.js, you can now do: ```js sapper.start({ target: ..., context: () => { setContext('key', new Blammo()); // set root-level context // can also return a Map to merge into the context. // I don't think both APIs should be supported, but they currently are. // Which one do you prefer? return new Map([['key2', new Foobar()]]); } }) ``` In `server.js` you can do: ``` middleware({ context: (req, res) => { setContext('key', new Blammo()); // set root-level context // can also return a Map to merge into the context. // I don't think both APIs should be supported, but they currently are. // Which one do you prefer? return new Map([['key2', new Foobar()]]); } }) ``` In `preload` there is both a new argument _and_ support for `getContext` and `setContext`. I don't think we should support both. ```js export async function preload(page, session, context) { const foobar = context.get('key2'); // can access the context initialized above. const blammo = getContext('key'); // again - we shouldn't support both of these. } ``` The code for supporting `getContext` and `setContext` is also a bit dirty for my liking, and there are TODOs in there about it. Is there a better way than calling `set_current_component`? Further works needs to be done before this is can be merged.
My solution, work with both side. //apollo.js
const clients = {}
export const getClient = (id) => {
if(id in clients) {
eturn clients[id]
}
return (clients[id] = createApolloClient())
};
export const removeClient = (id) => delete clients[id];
// server.js
import {removeClient} from './apollo';
const app = sapper.middleware({
session: (req, res) => {
const apolloID = uuidv4();
res.once('finish', function() {
removeClient(apolloID)
)
return { apolloID }
}
}) <script context="module">
import { getClient } from '../apollo'
export async function preload(_, session) {
const client = getClient(session.apolloID)
}
</script> But how about caching? Sapper don't have something like 'page template', so I can't inject apollo script below in sever-side. <script>
window.__APOLLO_STATE__=${JSON.stringify(cache.extract()).replace(/</g, '\\u003c')}
</script> Maybe add new feature request ? sapper.middleware({
session: () => {},
pageRender: (body /* template after replace sapper placeholder code */, context) => {
}
}) |
Can't we just put the output of cache.extract() into the session object? Seems like sapper will handle the |
This has been my biggest issue with Sapper so far |
Greetings from the future! I'm a time traveler from the year 2020. This is also the issue that I'm currently experiencing, specifically with Apollo Client. |
I've devoted a decent quantity of effort to working around this feature's absence. FWIW, using the clientId approach above seems to be the cleanest workaround so far. |
I've just tried the client ID approach and it works well. Thanks! |
@langbamit can you share what your I'm wondering if you use Sapper's |
On the topic of initializing per-request Apollo Clients, I found the below code to work best for me. No global map needed, directly set the apollo client in the session object. The trick here is to mutate the session during component rendering to make the session serializable, and then hydrate the client again in the client-side. This relies on the fact that Sapper serializes the session only after the app SSR-ed. server.js: // ...
sapper.middleware({
session: () => ({
// Instantiate client, but can't serialze? No problem, we'll fix this later
apollo: new ApolloClient({
// Make sure queries run once
ssrMode: true,
// Use SchemaLink to run queries on local schema (no round trips)
link: new SchemaLink(...),
// Collects cache for this session
cache: new InMemoryCache(),
})
})
})
// ... _layout.svelte: <script>
import { stores } from '@sapper/app'
import { onDestroy } from 'svelte'
import { apollo } from '../apollo' // imports client-side Apollo Client, undefined in server
const { session } = stores()
if (!process.browser) {
onDestroy(() => {
// Replace apollo client with its cache for serialization
$session.apollo = $session.apollo.extract()
})
} else {
// Restore the cache string back
apollo.restore($session.apollo)
// At client-side, the `$session.apollo` should refer to the client-side version
$session.apollo = apollo
}
</script> For clarity, apollo.js: export const apollo = process.browser
? new ApolloClient({
uri: '/graphql',
cache: new InMemoryCache(),
ssrForceFetchDelay: 100,
})
: undefined At this point, you could even return UPDATE: I've written two best solutions I've found so far in my blog, including the above, if anyone is interested. |
This is a nice approach! |
Closing as duplicate of sveltejs/kit#327 |
too bad that it is closed, i have given up on sapper and svelte-kit because of this |
It looks like the issue this has been marked as a duplicate of does not allow comments / replies, so I'll reply here. You can't wrap your fetch method (or otherwise augment the preload state) without actual sapper/sveltekit platform support. This is because (on the server-side) the information you need to use while wrapping is hidden in the request headers. So the minimum needed to "get around this" is:
in the same place. |
SvelteKit should be in beta shortly at which point you can comment on the issue. For now I've reopened the issue and left a comment there on your behalf |
thanks, sry for my short answer i was exhausted. my use case is that i am using cookie based authentication which renders all solutions above (and more that i found and even tried) not applicable or at least i was not able to make it work (this.fetch automatically has the cookie based authentication so it would be great to just use that and give it as a fetcher for graphql). so i would appreciate any improvements here. i tend to be annoyed by the complexity of graphql and understand that SSR makes it even harder. |
Problem
I've been trying to adopt Sapper the past couple weeks, and it's been mostly good. One topic seems to keep biting me though, and that has to do with "initializing" each request in various ways so that I can use my helpers in the client or on the server. For example, here are a couple things I wanted to do:
preload
and normal component lifecycle.context
to pass it because it's not available during preload.session
to pass it because the server-side version would be serialized to the client.req
object on the server so I can forward cookies.Workaround
I have so far been abusing
_layout.svelte
'spreload
to ensure something happens on the server, and all of my helpers have aprocess.browser
check to change behavior (and change which things arerequire()
'd) in browser and server. I have abusedsession
to share a property between all thepreload
s and all the ssr renders - in_layout.svelte
's preload I dosession.apolloClient = ....
and in_layout.svelte
's render I dosetContext(key, $session.apolloClient); $session.apolloClient = null;
. This is pretty ugly, but it's the best I've got at the moment.Solution?
I'd like to help implement something in Sapper to deal with this, if you have an guidance on what you want to see or whether this interests you.
My thinking was that a third parameter could be passed to
preload
which essentially acts like Svelte context - it doesn't persist between client and server, but is available during preload. E.g.This third parameter could be initialized similarly to
session
with server/browser-specific details, which avoids lots ofprocess.browser
switches throughout my code. Something like:(client.js)
(server.js)
Note I used an array-of-arrays to initialize because Context keys can be both objects and strings (so neither a Map nor an object literal are sufficient).EDIT: Just realized I was thinking of WeakMaps which can't accept string. AMap
is what's actually used, so returning aMap
from this function would be just fine! Perhaps some other API is better.Something like the above would solve my problem of overusing
process.browser
, and would let me pull helper logic out of_layout.svelte
that doesn't really belong there. Most importantly I could stop abusing session.I'm keen to hear what you think and again - I'm happy to put in work for this if you think it has legs.
Cheers!
The text was updated successfully, but these errors were encountered: