diff --git a/.changeset/empty-animals-retire.md b/.changeset/empty-animals-retire.md new file mode 100644 index 000000000000..e75b235ac854 --- /dev/null +++ b/.changeset/empty-animals-retire.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: wait an extra microtask in dev before calling `$$_init_$$` diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 7d6cf2a9589f..14a1103f6a61 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -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 index 000000000000..ba83ae74c565 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/remote/dev/+page.svelte @@ -0,0 +1,5 @@ + + +preload 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 index 000000000000..102d1732b736 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/remote/dev/preload/+page.server.js @@ -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 index 000000000000..7f89d533c468 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/remote/dev/preload/+page.svelte @@ -0,0 +1,8 @@ + + +

{data.example}

+ 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 index 000000000000..d455c61cc022 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/remote/dev/preload/example.remote.js @@ -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 index 000000000000..b057fd097443 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/remote/dev/preload/schema.js @@ -0,0 +1,3 @@ +import * as v from 'valibot'; + +export const schema = v.string(); diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js index df4c519275e7..059e3afed201 100644 --- a/packages/kit/test/apps/basics/test/client.test.js +++ b/packages/kit/test/apps/basics/test/client.test.js @@ -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')) {