-
-
Notifications
You must be signed in to change notification settings - Fork 437
Extract CSS #388
Comments
This might be a little trickier than I thought initially. We can't discover which components are used by each route without bundling, we can't bundle without generating a manifest, and we can't generate a manifest that includes CSS information without knowing which components are used by each route. Will think further on this. |
A plan that might just work: we shove a bunch of placeholder strings into the manifest, like so: // app/manifest/client.js
const index = {
js: () => import('../../routes/index.html'),
css: '__REPLACE_ME_IN_A_TRANSFORMCHUNK_HOOK_XYZ123__';
};
const about = {
js: () => import('../../routes/about.html'),
css: '__REPLACE_ME_IN_A_TRANSFORMCHUNK_HOOK_XYZ234__';
};
const blog = {
js: () => import('../../routes/blog.html'),
css: '__REPLACE_ME_IN_A_TRANSFORMCHUNK_HOOK_XYZ345__';
};
const blog_ = {
js: () => import('../../routes/blog/[slug].html'),
css: '__REPLACE_ME_IN_A_TRANSFORMCHUNK_HOOK_XYZ456__';
};
export const manifest = {
ignore: [/^\/blog.json$/, /^\/blog\/([^\/]+?).json$/],
pages: [
{
// index.html
pattern: /^\/?$/,
parts: [
{ component: index }
]
},
... When bundling, we extract information about which components are contained in which chunk, and emit CSS files accordingly. We also note which routes depend on which chunks. Then we simply replace the strings in a // app/manifest/client.js
const index = {
js: () => import('../../routes/index.html'),
css: ['xyz123', 'xyz234'];
}; Then the runtime can load |
Argh. I got halfway through implementing this before I realised the fatal flaw: it's not enough to know which components and chunks each route depends on, because we can't actually generate the CSS for each chunk. We almost can — we know which components belong to each chunk, and could compile them easily enough — but Sapper can't account for any Not sure what the right solution here is. If the Rollup plugin is invoked with Back to the drawing board. |
Quick update: I'm making progress here. The solution I'm going with is a teensy bit hacky but it seems to work; we can always take steps to remove some of the hackiness later once it beds in. For now, I'm focusing on Rollup apps. I'm sure this is stuff is possible with webpack but we can come back to that. First step: add sourcemaps to the virtual CSS files generated by rollup-plugin-svelte. I've done this work locally but haven't PR'd it yet. It's fairly straightforward — for the sake of simplicity I'm doing inline sourcemaps (i.e. base64 data URIs). Second step: use Third step: Sapper, which is already adding a Fourth step: for each generated JS chunk, see which CSS files the chunk depends on, and (if Fifth step: some CSS may be left over — that which is referenced from the entry point rather than via a page component, or is dynamically imported. We concatenate them and compose sourcemaps using the same technique as before, hash the contents, and write out Sixth step: the middleware injects Seventh step: the runtime, upon navigation, checks to see if any needed styles haven't been loaded yet (by simply querying the DOM for links with the expected This might all sound a bit convoluted. And it is, but that's because it's solving what turns out to be a very hard problem. I think this is one of those times when a little bit of magic is okay, because trying to solve it in userland is prohibitively difficult. A nice thing about this solution is that it's not Svelte-specific — you can import CSS from any source (e.g. if you're using something like CodeMirror on one specific route, your CodeMirror component can import the CSS it needs directly from node_modules) and it'll still be baked out and lazily loaded as a code-split CSS chunk. I think this is pretty cool. I'm not aware of any other framework that can do this — Next and Nuxt, for example, include blobs of style in JavaScript, which is bad for performance on multiple fronts (adds to parse/eval time, reduces cacheability of assets, prevents you from loading styles and JS in parallel), so we might be setting a new benchmark here. |
Keep up the good work Rich, maybe some day we'll have it. |
Currently, Sapper doesn't do anything special with CSS — it expects your bundler to handle it. Which sort of works, in that if you don't do anything the CSS will be included as a string inside your JS, and if you configure webpack just right you can extract the CSS by emitting a separate .css asset and importing it from the JS, but it's still a little clunky.
It feels like it ought to be possible to extract the CSS for any components in the app, and make the client-side runtime aware of them. So that when it loads the JS for (say)
/about
, it loadsabout-xyz123.css
as well, and creates a link tag. We can avoid any duplication (as currently happens between the critical CSS and the initial route load) while also avoiding FOUC. If we were smart we could probably coalesce blobs of CSS into more optimized chunks.It would mean spelunking through the meta information generated by webpack/Rollup, pulling out things that look like components (based on
.html
extension, presumably?) and pre-compiling them (since this would have to happen prior to bundling) to get the CSS. If someone were using that extension for non-Svelte things, or if they were doing something wacky, there might be creative ways to break it. In a framework like this I guess it's okay to have a few constraints though, especially ones that most people will never become aware of?The text was updated successfully, but these errors were encountered: