-
Notifications
You must be signed in to change notification settings - Fork 82
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
Server preload #31
Server preload #31
Conversation
Cool! A couple of quick comments:
This 2nd point is somewhat where my mind was going when you mentioned this "auto-generate DOM stuff" idea in our last meeting. However, seeing this RFC sparked some better ideas/details that may be more possible than what I was originally thinking. Will pass-through again tomorrow for more feedback on what's presented. |
Something I mentioned on Discord but should probably have gone here: The Would we be able to make the URL of the implicit endpoint an implementation detail, so we're free to adjust it as we see fit? If someone really wants to be able to access it in a more public way, they can just create a server route like they do now. Separately, does this really need the multiple |
yeah, that was needlessly confusing. Changed to
I'm a little confused by this part — it looks like the
Yeah, this is... a good point. In which case...
...perhaps the default is to tack on the
Partly that. It side-steps the (real) confusion people experience when they try to use packages in But it also means the bundler isn't creating vestigial chunks for things that will never be dynamically imported in the browser — just because <script context="module">
export async function serverPreload(page) {
const sql = await import('postgres');
// ...
}
</script> ...doesn't mean Rollup can skip resolving/analysing/emitting a chunk for |
I suppose I should mention that this is possible to do in userland with a preprocessor. Quick hacky version: Component.svelte<script context="module" ssr>
export async function load(page) {
const article = await get_post_somehow(page.params.slug);
if (article) {
return { article };
} else {
this.error(404, {
message: 'Not found'
});
}
}
</script>
<script>
export let article;
</script>
<h1>{article.title}</h1> transform.jsconst fs = require('fs');
const { preprocess } = require('svelte/compiler');
const source = fs.readFileSync('Component.svelte', 'utf-8');
const get_preprocess = dom => {
return {
markup: ({ content }) => {
const scripts = [];
const re = /<script([^>]*?)>([\s\S]+?)<\/script>/gm;
let match;
while (match = re.exec(content)) {
const attrs = match[1].split(' ')
.map(str => str.trim())
.filter(Boolean)
.map(str => {
const [name, quoted_value] = str.split('=');
const value = quoted_value
? quoted_value.replace(/^['"]/, '').replace(/['"]$/, '')
: true;
return { name, value };
})
.reduce((attrs, { name, value }) => {
attrs[name] = value;
return attrs;
}, {});
scripts.push({ attrs, content: match[2] });
}
const ssr_module_script = scripts.find(s => s.attrs.context === 'module' && s.attrs.ssr);
const dom_module_script = scripts.find(s => s.attrs.context === 'module' && s.attrs.dom);
console.log(scripts);
let i = 0;
if (dom) {
const implicit_dom_module_script = dom_module_script ? '' : `
<script context="module">
export { preload } from '@sapper/internal';
</script>`.replace(/^\t{4}/gm, '');
return {
code: content.replace(re, (_, attrs, content) => {
const script = scripts[i++];
if (script === ssr_module_script) {
console.log('!!!!', script)
return _.replace(/\S/g, ' ');
}
return _;
}) + implicit_dom_module_script
}
} else {
return {
code: content.replace(re, (_, attrs, content) => {
const script = scripts[i++];
if (script === dom_module_script) {
return _.replace(/\S/g, ' ');
}
return _;
})
};
}
}
};
};
async function main() {
const ssr_processed = await preprocess(source, get_preprocess(false));
const dom_processed = await preprocess(source, get_preprocess(true));
fs.writeFileSync('Component.ssr.svelte', ssr_processed.code);
fs.writeFileSync('Component.dom.svelte', dom_processed.code);
}
main(); Component.dom.svelte
<script>
export let article;
</script>
<h1>{article.title}</h1>
<script context="module">
export { preload } from '@sapper/internal';
</script> Component.ssr.svelte<script context="module" ssr>
export async function load(page) {
const article = await get_post_somehow(page.params.slug);
if (article) {
return { article };
} else {
this.error(404, {
message: 'Not found'
});
}
}
</script>
<script>
export let article;
</script>
<h1>{article.title}</h1> |
There's a |
An alternative approach to reducing the boilerplate: If the filesystem looks like this:
And the The request format is standarized: The The json response matches the props of the component. {
"id": 135,
"title": "What's new in Sapper"
} <script>
export let id;
export let title;
</script> When the response is not an Pro's
Con's
Some of the cons could be solved by exposing hooks and middleware into the generic preload function: Setting headers, redirect behavior on 403, etc. But that gets complex fast, and at that point you'd be better off writing your own createPreload function that generates preload functions. Adding a |
An additional unresolved question here is: how should updates to the session store interact with server preloads? Today in sapper the preload function is called every time the session store is updated. The current behavior, when combined with more explicit server-side preload machinery, would probably be extra surprising to developers using sapper. I don't want to advocate for broadening the scope of this RFC unnecessarily, but I am curious to understand how this RFC is intended to interact with the existing preload/session store dynamic. |
Hey! I'm not part of the dev team and just a happy user of Svelte, but I thought I'd share for reference a little framework I independently made for myself to use in a real system that feels pretty nice and is related to this RFC. It's designed for use with Node. Sorry if this comment isn't appropriate! Basically I hacked together the Svelte compiler to make files like this work: <!-- routes/index.svelte -->
<script context="node">
import db from '../db';
export async function get(req, abort, redirect) {
const user = await db.User.findOne({where: {id: req.query.user_id}});
return {user}
}
</script>
<script>
export let user;
</script>
<h1>Hi {user.name}!</h1> The part in the first So far it's great — I love the tighter coupling between server and UI (just like Svelte takes previously-decoupled HTML/CSS/JS and puts it together). It makes creating and tracing the relationship between the client and server much more straightforward for me. I also like how it's very easy to tell what code and data will be shown to the client and what won't (being paranoid about data exposure in my small business). It's a little tedious-feeling to have to define variables multiple times ( |
Here's a probably out-of-date version: https://github.com/Glench/full-stack-svelte-prototype/ |
Is this still under consideration in the context of sveltekit? I think it's an essential feature, for all the reasons outlined in the RFC. Without it a simple CMS-backed website becomes pretty cumbersome, and it's not intuitive for people coming from a NextJS environment that |
running into a few issues with prismic right now building a related articles component. looking forward to this. |
I'm going to close this as we recently added page endpoints to SvelteKit, which solve this problem more elegantly |
Rendered