Description
Is your feature request related to a problem? Please describe.
In SSR mode, onMount
, beforeUpdate
and afterUpdate
don't do anything. But build tools can't know that because they're not no-ops in the () => {}
sense — the callbacks get pushed to an array which is then discarded, which isn't something that can be optimised away with static analysis — and as a result the code remains in the generated bundle.
If onMount
etc were instead no-ops, the code could be removed, at least by tools like Rollup and Terser (the same isn't true for esbuild, sadly, but maybe one day).
This won't prevent Rollup from attempting to bundle (or create chunks for) dynamic imports inside those callbacks...
onMount(async () => {
const mod = await import('./client-only-module.js');
// client-only-module will appear in the SSR bundle even if `onMount` is correctly
// treated as a no-op. this feels like something that could change though
});
...but it's nonetheless a significant improvement on the status quo.
Describe the solution you'd like
The solution is a two-parter:
- Expose an equivalent module to
svelte
(e.g.svelte/ssr
) that re-exports most stuff fromsvelte
, but replaces the relevant functions with no-ops - Update Svelte bundler plugins like
rollup-plugin-svelte
and@sveltejs/vite-plugin-svelte
to intercept imports tosvelte
and replace them withsvelte/ssr
. (This needs to happen for all modules in the graph, not just .svelte files.)
Describe alternatives you've considered
One alternative we discussed is detecting calls to onMount
and similar functions, and simply removing them from the generated code. I'm opposed to this for Zalgo reasons: since this would only apply to onMount
calls (and only inside components, since Svelte plugins have no authority to mess around with non-components, especially since they may not have been transformed to standard JS by the time the plugin sees them), it would fail to remove code like the following:
// lifecycle.js
import { onMount } from 'svelte';
export function useSomeLibrary(fn) {
onMount(() => {
const mod = await import('some-library');
fn(mod);
});
}
<!-- App.svelte -->
<script>
import { useSomeLibrary } from './lifecycle.js';
let div;
useSomeLibrary(lib => {
lib.init(div);
});
</script>
<div bind:this={div}></div>
If that code is going to fail (because a build tool won't allow some-library
to be bundled for the server, for example), it's better that it fails the same way whether you write it inside onMount
in a component directly, or in the form shown above. Someone writing the onMount
version would be horribly confused if they refactored it into useSomeLibrary
to use in multiple components. Better to have more frequent failures than less frequent but unpredictable ones that cause people to lose faith in their understanding of the system as a whole.
How important is this feature to you?
It's come up in the context of SvelteKit, where building fails in some cases because of this issue.