fix: wait an extra microtask in dev before calling `$$_init_$$` (#14799)
authorPaolo Ricciuti <ricciutipaolo@gmail.com>
Wed, 5 Nov 2025 10:35:36 +0000 (11:35 +0100)
committerGitHub <noreply@github.com>
Wed, 5 Nov 2025 10:35:36 +0000 (18:35 +0800)
.changeset/empty-animals-retire.md [new file with mode: 0644]
packages/kit/src/exports/vite/index.js
packages/kit/test/apps/basics/src/routes/remote/dev/+page.svelte [new file with mode: 0644]
packages/kit/test/apps/basics/src/routes/remote/dev/preload/+page.server.js [new file with mode: 0644]
packages/kit/test/apps/basics/src/routes/remote/dev/preload/+page.svelte [new file with mode: 0644]
packages/kit/test/apps/basics/src/routes/remote/dev/preload/example.remote.js [new file with mode: 0644]
packages/kit/test/apps/basics/src/routes/remote/dev/preload/schema.js [new file with mode: 0644]
packages/kit/test/apps/basics/test/client.test.js

diff --git a/.changeset/empty-animals-retire.md b/.changeset/empty-animals-retire.md
new file mode 100644 (file)
index 0000000..e75b235
--- /dev/null
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix: wait an extra microtask in dev before calling `$$_init_$$`
index 7d6cf2a9589f963f9f9809510dba3489acea0ec1..14a1103f6a618aaa34947ca5cc51484ae343c26b 100644 (file)
@@ -698,6 +698,12 @@ async function kit({ svelte_config }) {
                        remotes.push(remote);
 
                        if (opts?.ssr) {
+                               // we need to add an `await Promise.resolve()` because if the user imports this function
+                               // on the client AND in a load function when loading the client module we will trigger
+                               // an ssrLoadModule during dev. During a link preload, the module can be mistakenly
+                               // loaded and transformed twice and the first time all its exports would be undefined
+                               // triggering a dev server error. By adding a microtask we ensure that the module is fully loaded
+
                                // Extra newlines to prevent syntax errors around missing semicolons or comments
                                code +=
                                        '\n\n' +
@@ -705,6 +711,8 @@ async function kit({ svelte_config }) {
                                        import * as $$_self_$$ from './${path.basename(id)}';
                                        import { init_remote_functions as $$_init_$$ } from '@sveltejs/kit/internal';
 
+                                       ${dev_server ? 'await Promise.resolve()' : ''}
+
                                        $$_init_$$($$_self_$$, ${s(file)}, ${s(remote.hash)});
 
                                        for (const [name, fn] of Object.entries($$_self_$$)) {
diff --git a/packages/kit/test/apps/basics/src/routes/remote/dev/+page.svelte b/packages/kit/test/apps/basics/src/routes/remote/dev/+page.svelte
new file mode 100644 (file)
index 0000000..ba83ae7
--- /dev/null
@@ -0,0 +1,5 @@
+<script lang="ts">
+       import { resolve } from '$app/paths';
+</script>
+
+<a href={resolve('/remote/dev/preload')} data-sveltekit-preload-data="hover">preload</a>
diff --git a/packages/kit/test/apps/basics/src/routes/remote/dev/preload/+page.server.js b/packages/kit/test/apps/basics/src/routes/remote/dev/preload/+page.server.js
new file mode 100644 (file)
index 0000000..102d173
--- /dev/null
@@ -0,0 +1,7 @@
+import { example } from './example.remote';
+
+export const load = async () => {
+       return {
+               example: await example('bar')
+       };
+};
diff --git a/packages/kit/test/apps/basics/src/routes/remote/dev/preload/+page.svelte b/packages/kit/test/apps/basics/src/routes/remote/dev/preload/+page.svelte
new file mode 100644 (file)
index 0000000..7f89d53
--- /dev/null
@@ -0,0 +1,8 @@
+<script>
+       import { example } from './example.remote';
+
+       export let data;
+</script>
+
+<p>{data.example}</p>
+<button onclick={async () => (data.example = await example('baz'))}>Refresh</button>
diff --git a/packages/kit/test/apps/basics/src/routes/remote/dev/preload/example.remote.js b/packages/kit/test/apps/basics/src/routes/remote/dev/preload/example.remote.js
new file mode 100644 (file)
index 0000000..d455c61
--- /dev/null
@@ -0,0 +1,7 @@
+import { query } from '$app/server';
+import { schema } from './schema';
+
+export const example = query(schema, async (param) => {
+       await new Promise((resolve) => setTimeout(resolve, 1000));
+       return 'foo' + param;
+});
diff --git a/packages/kit/test/apps/basics/src/routes/remote/dev/preload/schema.js b/packages/kit/test/apps/basics/src/routes/remote/dev/preload/schema.js
new file mode 100644 (file)
index 0000000..b057fd0
--- /dev/null
@@ -0,0 +1,3 @@
+import * as v from 'valibot';
+
+export const schema = v.string();
index df4c519275e70633d4960ad96d8ed01da3a7838b..059e3afed201d394d18dd060a769bc09fbb7be69 100644 (file)
@@ -1786,8 +1786,24 @@ test.describe('routing', () => {
        });
 });
 
-// have to run in serial because commands mutate in-memory data on the server
 test.describe('remote functions', () => {
+       test('preloading data works when the page component and server load both import a remote function', async ({
+               page
+       }) => {
+               test.skip(!process.env.DEV, 'remote functions are only analysed in dev mode');
+               await page.goto('/remote/dev');
+               await page.locator('a[href="/remote/dev/preload"]').hover();
+               await Promise.all([
+                       page.waitForTimeout(100), // wait for preloading to start
+                       page.waitForLoadState('networkidle') // wait for preloading to finish
+               ]);
+               await page.click('a[href="/remote/dev/preload"]');
+               await expect(page.locator('p')).toHaveText('foobar');
+       });
+});
+
+// have to run in serial because commands mutate in-memory data on the server
+test.describe('remote function mutations', () => {
        test.describe.configure({ mode: 'default' });
        test.afterEach(async ({ page }) => {
                if (page.url().endsWith('/remote')) {